1// Aseprite
2// Copyright (C) 2018-2020 Igara Studio S.A.
3// Copyright (C) 2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/cmd/set_palette.h"
13#include "app/color.h"
14#include "app/file/palette_file.h"
15#include "app/res/palettes_loader_delegate.h"
16#include "app/script/docobj.h"
17#include "app/script/engine.h"
18#include "app/script/luacpp.h"
19#include "app/script/security.h"
20#include "app/tx.h"
21#include "base/fs.h"
22#include "doc/palette.h"
23#include "doc/sprite.h"
24
25namespace app {
26namespace script {
27
28using namespace doc;
29
30namespace {
31
32struct PaletteObj {
33 ObjectId spriteId;
34 ObjectId paletteId;
35
36 PaletteObj(Sprite* sprite, Palette* palette)
37 : spriteId(sprite ? sprite->id(): 0),
38 paletteId(palette ? palette->id(): 0) {
39 }
40
41 ~PaletteObj() {
42 ASSERT(!paletteId);
43 }
44
45 void gc(lua_State* L) {
46 if (!spriteId)
47 delete this->palette(L);
48 paletteId = 0;
49 }
50
51 PaletteObj(const PaletteObj&) = delete;
52 PaletteObj& operator=(const PaletteObj&) = delete;
53
54 Sprite* sprite(lua_State* L) {
55 if (spriteId)
56 return check_docobj(L, doc::get<Sprite>(spriteId));
57 else
58 return nullptr;
59 }
60
61 Palette* palette(lua_State* L) {
62 return check_docobj(L, doc::get<Palette>(paletteId));
63 }
64};
65
66int Palette_new(lua_State* L)
67{
68 if (auto pal2 = may_get_obj<PaletteObj>(L, 1)) {
69 push_new<PaletteObj>(L, nullptr, new Palette(*pal2->palette(L)));
70 }
71 else if (lua_istable(L, 1)) {
72 // Palette{ fromFile }
73 int type = lua_getfield(L, 1, "fromFile");
74 if (type != LUA_TNIL) {
75 if (const char* fromFile = lua_tostring(L, -1)) {
76 std::string absFn = base::get_absolute_path(fromFile);
77 lua_pop(L, 1);
78
79 if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, ResourceType::File))
80 return luaL_error(L, "script doesn't have access to open file %s",
81 absFn.c_str());
82
83 auto pal = load_palette(absFn.c_str());
84 if (pal)
85 push_new<PaletteObj>(L, nullptr, pal.release());
86 else
87 lua_pushnil(L);
88 return 1;
89 }
90 }
91 lua_pop(L, 1);
92
93 // Palette{ fromResource }
94 type = lua_getfield(L, 1, "fromResource");
95 if (type != LUA_TNIL) {
96 if (const char* _id = lua_tostring(L, -1)) {
97 std::string id = _id;
98 lua_pop(L, 1);
99
100 // TODO Improve this, maybe using an existent set of palettes
101 // TODO Centralize the resources management/loading process on-demand (#2059)
102 std::map<std::string, std::string> idAndPaths;
103 PalettesLoaderDelegate().getResourcesPaths(idAndPaths);
104
105 if (!idAndPaths[id].empty()) {
106 std::string absFn = base::get_absolute_path(idAndPaths[id]);
107
108 if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, ResourceType::File))
109 return luaL_error(L, "script doesn't have access to open file %s",
110 absFn.c_str());
111
112 auto pal = load_palette(absFn.c_str());
113 if (pal)
114 push_new<PaletteObj>(L, nullptr, pal.release());
115 else
116 lua_pushnil(L);
117 return 1;
118 }
119 else {
120 return luaL_error(L, "palette resource with ID %s not found", _id);
121 }
122 }
123 }
124 lua_pop(L, 1);
125 }
126 else {
127 int ncolors = lua_tointeger(L, 1);
128 push_new<PaletteObj>(L, nullptr,
129 new Palette(0, ncolors > 0 ? ncolors: 256));
130 }
131 return 1;
132}
133
134int Palette_gc(lua_State* L)
135{
136 auto obj = get_obj<PaletteObj>(L, 1);
137 obj->gc(L);
138 obj->~PaletteObj();
139 return 0;
140}
141
142int Palette_len(lua_State* L)
143{
144 auto obj = get_obj<PaletteObj>(L, 1);
145 auto pal = obj->palette(L);
146 lua_pushinteger(L, pal->size());
147 return 1;
148}
149
150int Palette_eq(lua_State* L)
151{
152 const auto a = get_obj<PaletteObj>(L, 1);
153 const auto b = get_obj<PaletteObj>(L, 2);
154 lua_pushboolean(L, *a->palette(L) == *b->palette(L));
155 return 1;
156}
157
158int Palette_resize(lua_State* L)
159{
160 auto obj = get_obj<PaletteObj>(L, 1);
161 auto pal = obj->palette(L);
162 int ncolors = lua_tointeger(L, 2);
163 if (auto sprite = obj->sprite(L)) {
164 Palette newPal(*pal);
165 newPal.resize(ncolors);
166
167 if (*pal != newPal) {
168 Tx tx;
169 tx(new cmd::SetPalette(sprite, pal->frame(), &newPal));
170 tx.commit();
171 }
172 }
173 else
174 pal->resize(ncolors);
175 return 1;
176}
177
178int Palette_getColor(lua_State* L)
179{
180 auto obj = get_obj<PaletteObj>(L, 1);
181 auto pal = obj->palette(L);
182 int i = lua_tointeger(L, 2);
183 if (i < 0 || i >= int(pal->size()))
184 return luaL_error(L, "index out of bounds %d", i);
185
186 doc::color_t docColor = pal->getEntry(i);
187 app::Color appColor = app::Color::fromRgb(doc::rgba_getr(docColor),
188 doc::rgba_getg(docColor),
189 doc::rgba_getb(docColor),
190 doc::rgba_geta(docColor));
191
192 push_obj<app::Color>(L, appColor);
193 return 1;
194}
195
196int Palette_setColor(lua_State* L)
197{
198 auto obj = get_obj<PaletteObj>(L, 1);
199 auto pal = obj->palette(L);
200 int i = lua_tointeger(L, 2);
201 if (i < 0 || i >= int(pal->size()))
202 return luaL_error(L, "index out of bounds %d", i);
203
204 doc::color_t docColor = convert_args_into_pixel_color(
205 L, 3, doc::IMAGE_RGB);
206
207 if (auto sprite = obj->sprite(L)) {
208 // Nothing to do
209 if (pal->getEntry(i) == docColor)
210 return 0;
211
212 Palette newPal(*pal);
213 newPal.setEntry(i, docColor);
214
215 Tx tx;
216 tx(new cmd::SetPalette(sprite, pal->frame(), &newPal));
217 tx.commit();
218 }
219 else {
220 pal->setEntry(i, docColor);
221 }
222 return 0;
223}
224
225int Palette_get_frame(lua_State* L)
226{
227 auto obj = get_obj<PaletteObj>(L, 1);
228 auto pal = obj->palette(L);
229 if (auto sprite = obj->sprite(L))
230 push_sprite_frame(L, sprite, pal->frame());
231 else
232 lua_pushnil(L);
233 return 1;
234}
235
236int Palette_saveAs(lua_State* L)
237{
238 auto obj = get_obj<PaletteObj>(L, 1);
239 auto pal = obj->palette(L);
240 const char* fn = luaL_checkstring(L, 2);
241 Sprite* sprite = obj->sprite(L);
242
243 if (fn) {
244 std::string absFn = base::get_absolute_path(fn);
245 if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, ResourceType::File))
246 return luaL_error(L, "script doesn't have access to write file %s",
247 absFn.c_str());
248 save_palette(absFn.c_str(),
249 pal,
250 pal->size(),
251 (sprite ? sprite->colorSpace(): nullptr));
252 }
253 return 0;
254}
255
256int Palette_get_frameNumber(lua_State* L)
257{
258 auto obj = get_obj<PaletteObj>(L, 1);
259 auto pal = obj->palette(L);
260 lua_pushinteger(L, pal->frame()+1);
261 return 1;
262}
263
264const luaL_Reg Palette_methods[] = {
265 { "__gc", Palette_gc },
266 { "__len", Palette_len },
267 { "__eq", Palette_eq },
268 { "resize", Palette_resize },
269 { "getColor", Palette_getColor },
270 { "setColor", Palette_setColor },
271 { "saveAs", Palette_saveAs },
272 { nullptr, nullptr }
273};
274
275const Property Palette_properties[] = {
276 { "frame", Palette_get_frame, nullptr },
277 { "frameNumber", Palette_get_frameNumber, nullptr },
278 { nullptr, nullptr, nullptr }
279};
280
281} // anonymous namespace
282
283DEF_MTNAME(PaletteObj);
284DEF_MTNAME_ALIAS(PaletteObj, Palette);
285
286void register_palette_class(lua_State* L)
287{
288 using Palette = PaletteObj;
289 REG_CLASS(L, Palette);
290 REG_CLASS_NEW(L, Palette);
291 REG_CLASS_PROPERTIES(L, Palette);
292}
293
294void push_sprite_palette(lua_State* L, doc::Sprite* sprite, doc::Palette* palette)
295{
296 ASSERT(sprite);
297 push_new<PaletteObj>(L, sprite, palette);
298}
299
300void push_palette(lua_State* L, doc::Palette* palette)
301{
302 push_new<PaletteObj>(L, nullptr, palette);
303}
304
305doc::Palette* get_palette_from_arg(lua_State* L, int index)
306{
307 auto obj = get_obj<PaletteObj>(L, index);
308 return obj->palette(L);
309}
310
311} // namespace script
312} // namespace app
313