1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2015-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/copy_rect.h"
13#include "app/cmd/copy_region.h"
14#include "app/commands/new_params.h" // Used for enum <-> Lua conversions
15#include "app/context.h"
16#include "app/doc.h"
17#include "app/file/file.h"
18#include "app/script/docobj.h"
19#include "app/script/engine.h"
20#include "app/script/luacpp.h"
21#include "app/script/security.h"
22#include "app/site.h"
23#include "app/tx.h"
24#include "app/util/autocrop.h"
25#include "app/util/resize_image.h"
26#include "base/fs.h"
27#include "doc/algorithm/shrink_bounds.h"
28#include "doc/cel.h"
29#include "doc/image.h"
30#include "doc/image_ref.h"
31#include "doc/primitives.h"
32#include "doc/sprite.h"
33#include "render/render.h"
34
35#include <algorithm>
36#include <cstring>
37#include <memory>
38
39namespace app {
40namespace script {
41
42namespace {
43
44struct ImageObj {
45 doc::ObjectId imageId = 0;
46 doc::ObjectId celId = 0;
47 doc::ObjectId tilesetId = 0;
48 ImageObj(doc::Image* image)
49 : imageId(image->id()) {
50 }
51 ImageObj(doc::Cel* cel)
52 : imageId(cel->image()->id())
53 , celId(cel->id()) {
54 }
55 ImageObj(doc::Tileset* tileset, doc::Image* image)
56 : imageId(image->id())
57 , tilesetId(tileset->id()) {
58 }
59 ImageObj(const ImageObj&) = delete;
60 ImageObj& operator=(const ImageObj&) = delete;
61
62 ~ImageObj() {
63 ASSERT(!imageId);
64 }
65
66 void gc(lua_State* L) {
67 if (!celId && !tilesetId)
68 delete this->image(L);
69 imageId = 0;
70 }
71
72 doc::Image* image(lua_State* L) {
73 return check_docobj(L, doc::get<doc::Image>(imageId));
74 }
75
76 doc::Cel* cel(lua_State* L) {
77 if (celId)
78 return check_docobj(L, doc::get<doc::Cel>(celId));
79 else
80 return nullptr;
81 }
82};
83
84void render_sprite(Image* dst,
85 const Sprite* sprite,
86 const frame_t frame,
87 const int x, const int y)
88{
89 render::Render render;
90 render.setNewBlend(true);
91 render.renderSprite(
92 dst, sprite, frame,
93 gfx::Clip(x, y,
94 0, 0,
95 sprite->width(),
96 sprite->height()));
97}
98
99int Image_clone(lua_State* L);
100
101int Image_new(lua_State* L)
102{
103 doc::Image* image = nullptr;
104 doc::ImageSpec spec(doc::ColorMode::RGB, 1, 1, 0);
105 if (auto spec2 = may_get_obj<doc::ImageSpec>(L, 1)) {
106 spec = *spec2;
107 }
108 else if (auto imgObj = may_get_obj<ImageObj>(L, 1)) {
109 // Copy a region of the image
110 if (auto rc = may_get_obj<gfx::Rect>(L, 2)) {
111 doc::Image* crop = nullptr;
112 try {
113 auto docImg = imgObj->image(L);
114 crop = doc::crop_image(docImg, *rc, docImg->maskColor());
115 }
116 catch (const std::invalid_argument&) {
117 // Do nothing (will return nil)
118 }
119 if (crop) {
120 push_new<ImageObj>(L, crop);
121 return 1;
122 }
123 else {
124 return 0;
125 }
126 }
127 // Copy the whole image
128 else {
129 return Image_clone(L);
130 }
131 }
132 else if (auto spr = may_get_docobj<doc::Sprite>(L, 1)) {
133 image = doc::Image::create(spr->spec());
134 if (!image)
135 return 0;
136
137 render_sprite(image, spr, 0, 0, 0);
138 }
139 else if (lua_istable(L, 1)) {
140 // Image{ fromFile }
141 int type = lua_getfield(L, 1, "fromFile");
142 if (type != LUA_TNIL) {
143 if (const char* fromFile = lua_tostring(L, -1)) {
144 std::string fn = fromFile;
145 lua_pop(L, 1);
146 return load_sprite_from_file(
147 L, fn.c_str(),
148 LoadSpriteFromFileParam::OneFrameAsImage);
149 }
150 }
151 lua_pop(L, 1);
152
153 // In case that there is no "fromFile" field
154 if (type == LUA_TNIL) {
155 // Image{ width, height, colorMode }
156 lua_getfield(L, 1, "width");
157 lua_getfield(L, 1, "height");
158 spec.setWidth(lua_tointeger(L, -2));
159 spec.setHeight(lua_tointeger(L, -1));
160 lua_pop(L, 2);
161
162 type = lua_getfield(L, 1, "colorMode");
163 if (type != LUA_TNIL)
164 spec.setColorMode((doc::ColorMode)lua_tointeger(L, -1));
165 lua_pop(L, 1);
166 }
167 }
168 else {
169 const int w = lua_tointeger(L, 1);
170 const int h = lua_tointeger(L, 2);
171 const int colorMode = (lua_isnone(L, 3) ? doc::IMAGE_RGB:
172 lua_tointeger(L, 3));
173 spec.setWidth(w);
174 spec.setHeight(h);
175 spec.setColorMode((doc::ColorMode)colorMode);
176 }
177 if (!image) {
178 if (spec.width() < 1) spec.setWidth(1);
179 if (spec.height() < 1) spec.setHeight(1);
180 image = doc::Image::create(spec);
181 if (!image) {
182 // Invalid spec (e.g. width=0, height=0, etc.)
183 return 0;
184 }
185 doc::clear_image(image, spec.maskColor());
186 }
187 push_new<ImageObj>(L, image);
188 return 1;
189}
190
191int Image_clone(lua_State* L)
192{
193 auto obj = get_obj<ImageObj>(L, 1);
194 doc::Image* cloned = doc::Image::createCopy(obj->image(L));
195 push_new<ImageObj>(L, cloned);
196 return 1;
197}
198
199int Image_gc(lua_State* L)
200{
201 auto obj = get_obj<ImageObj>(L, 1);
202 obj->gc(L);
203 obj->~ImageObj();
204 return 0;
205}
206
207int Image_eq(lua_State* L)
208{
209 const auto a = get_obj<ImageObj>(L, 1);
210 const auto b = get_obj<ImageObj>(L, 2);
211 lua_pushboolean(L, a->imageId == b->imageId);
212 return 1;
213}
214
215int Image_clear(lua_State* L)
216{
217 auto obj = get_obj<ImageObj>(L, 1);
218 auto img = obj->image(L);
219 doc::color_t color;
220 if (lua_isnone(L, 2))
221 color = img->maskColor();
222 else if (lua_isinteger(L, 2))
223 color = lua_tointeger(L, 2);
224 else
225 color = convert_args_into_pixel_color(L, 2, img->pixelFormat());
226 doc::clear_image(img, color);
227 return 0;
228}
229
230int Image_drawPixel(lua_State* L)
231{
232 auto obj = get_obj<ImageObj>(L, 1);
233 auto img = obj->image(L);
234 const int x = lua_tointeger(L, 2);
235 const int y = lua_tointeger(L, 3);
236 doc::color_t color;
237 if (lua_isinteger(L, 4))
238 color = lua_tointeger(L, 4);
239 else
240 color = convert_args_into_pixel_color(L, 4, img->pixelFormat());
241 doc::put_pixel(img, x, y, color);
242 return 0;
243}
244
245int Image_drawImage(lua_State* L)
246{
247 auto obj = get_obj<ImageObj>(L, 1);
248 auto sprite = get_obj<ImageObj>(L, 2);
249 gfx::Point pos = convert_args_into_point(L, 3);
250 Image* dst = obj->image(L);
251 const Image* src = sprite->image(L);
252
253 // If the destination image is not related to a sprite, we just draw
254 // the source image without undo information.
255 if (obj->cel(L) == nullptr) {
256 doc::copy_image(dst, src, pos.x, pos.y);
257 }
258 else {
259 gfx::Rect bounds(0, 0, src->size().w, src->size().h);
260
261 // TODO Use something similar to doc::algorithm::shrink_bounds2()
262 // but we need something that does the render and compares
263 // the minimal modified area.
264 Tx tx;
265 tx(new cmd::CopyRegion(
266 dst, src, gfx::Region(bounds),
267 gfx::Point(pos.x + bounds.x, pos.y + bounds.y)));
268 tx.commit();
269 }
270 return 0;
271}
272
273int Image_drawSprite(lua_State* L)
274{
275 auto obj = get_obj<ImageObj>(L, 1);
276 const auto sprite = get_docobj<Sprite>(L, 2);
277 doc::frame_t frame = get_frame_number_from_arg(L, 3);
278 gfx::Point pos = convert_args_into_point(L, 4);
279 doc::Image* dst = obj->image(L);
280
281 ASSERT(dst);
282 ASSERT(sprite);
283
284 // If the destination image is not related to a sprite, we just draw
285 // the source image without undo information.
286 if (obj->cel(L) == nullptr) {
287 render_sprite(dst, sprite, frame, pos.x, pos.y);
288 }
289 else {
290 Tx tx;
291
292 ImageRef tmp(Image::createCopy(dst));
293 render_sprite(tmp.get(), sprite, frame, pos.x, pos.y);
294
295 int x1, y1, x2, y2;
296 if (get_shrink_rect2(&x1, &y1, &x2, &y2, dst, tmp.get())) {
297 tx(new cmd::CopyRect(
298 dst, tmp.get(),
299 gfx::Clip(x1, y1, x1, y1, x2-x1+1, y2-y1+1)));
300 }
301
302 tx.commit();
303 }
304 return 0;
305}
306
307int Image_pixels(lua_State* L)
308{
309 auto obj = get_obj<ImageObj>(L, 1);
310 push_image_iterator_function(L, obj->image(L), 2);
311 return 1;
312}
313
314int Image_getPixel(lua_State* L)
315{
316 const auto obj = get_obj<ImageObj>(L, 1);
317 const int x = lua_tointeger(L, 2);
318 const int y = lua_tointeger(L, 3);
319 const doc::color_t color = doc::get_pixel(obj->image(L), x, y);
320 lua_pushinteger(L, color);
321 return 1;
322}
323
324int Image_isEqual(lua_State* L)
325{
326 auto objA = get_obj<ImageObj>(L, 1);
327 auto objB = get_obj<ImageObj>(L, 2);
328 bool res = doc::is_same_image(objA->image(L),
329 objB->image(L));
330 lua_pushboolean(L, res);
331 return 1;
332}
333
334int Image_isEmpty(lua_State* L)
335{
336 auto obj = get_obj<ImageObj>(L, 1);
337 auto img = obj->image(L);
338 bool res = doc::is_empty_image(img);
339 lua_pushboolean(L, res);
340 return 1;
341}
342
343int Image_isPlain(lua_State* L)
344{
345 auto obj = get_obj<ImageObj>(L, 1);
346 auto img = obj->image(L);
347 doc::color_t color;
348 if (lua_isnone(L, 2))
349 color = img->maskColor();
350 else if (lua_isinteger(L, 2))
351 color = lua_tointeger(L, 2);
352 else
353 color = convert_args_into_pixel_color(L, 2, img->pixelFormat());
354
355 bool res = doc::is_plain_image(img, color);
356 lua_pushboolean(L, res);
357 return 1;
358}
359
360int Image_saveAs(lua_State* L)
361{
362 auto obj = get_obj<ImageObj>(L, 1);
363 Image* img = obj->image(L);
364 Cel* cel = obj->cel(L);
365 Palette* pal = (cel ? cel->sprite()->palette(cel->frame()): nullptr);
366 std::string fn;
367 bool result = false;
368
369 if (lua_istable(L, 2)) {
370 // Image:saveAs{ filename }
371 int type = lua_getfield(L, 2, "filename");
372 if (type != LUA_TNIL) {
373 if (const char* fn0 = lua_tostring(L, -1))
374 fn = fn0;
375 }
376 lua_pop(L, 1);
377
378 // Image:saveAs{ palette }
379 lua_getfield(L, 2, "palette");
380 if (type != LUA_TNIL) {
381 if (auto pal0 = get_palette_from_arg(L, -1))
382 pal = pal0;
383 }
384 lua_pop(L, 1);
385 }
386 else {
387 // Image:saveAs(filename)
388 const char* fn0 = luaL_checkstring(L, 2);
389 if (fn0)
390 fn = fn0;
391 }
392
393 if (fn.empty())
394 return luaL_error(L, "missing filename in Image:saveAs()");
395
396 std::string absFn = base::get_absolute_path(fn);
397 if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, ResourceType::File))
398 return luaL_error(L, "script doesn't have access to write file %s",
399 absFn.c_str());
400
401 std::unique_ptr<Sprite> sprite(Sprite::MakeStdSprite(img->spec(), 256));
402
403 std::vector<ImageRef> oneImage;
404 sprite->getImages(oneImage);
405 ASSERT(oneImage.size() == 1);
406 if (!oneImage.empty())
407 copy_image(oneImage.front().get(), img);
408
409 if (pal)
410 sprite->setPalette(pal, false);
411
412 std::unique_ptr<Doc> doc(new Doc(sprite.get()));
413 sprite.release();
414 doc->setFilename(absFn);
415
416 app::Context* ctx = App::instance()->context();
417 result = (save_document(ctx, doc.get()) >= 0);
418
419 lua_pushboolean(L, result);
420 return 1;
421}
422
423int Image_resize(lua_State* L)
424{
425 auto obj = get_obj<ImageObj>(L, 1);
426 doc::Image* img = obj->image(L);
427 Cel* cel = obj->cel(L);
428 ASSERT(img);
429 gfx::Size newSize = img->size();
430 auto method = doc::algorithm::ResizeMethod::RESIZE_METHOD_NEAREST_NEIGHBOR;
431 gfx::Point pivot(0, 0);
432
433 if (lua_istable(L, 2)) {
434 // Image:resize{ (size | width, height),
435 // method [, pivot] }
436
437 int type = lua_getfield(L, 2, "size");
438 if (VALID_LUATYPE(type)) {
439 newSize = convert_args_into_size(L, -1);
440 lua_pop(L, 1);
441 }
442 else {
443 lua_pop(L, 1);
444
445 type = lua_getfield(L, 2, "width");
446 if (VALID_LUATYPE(type))
447 newSize.w = lua_tointeger(L, -1);
448 lua_pop(L, 1);
449
450 type = lua_getfield(L, 2, "height");
451 if (VALID_LUATYPE(type))
452 newSize.h = lua_tointeger(L, -1);
453 lua_pop(L, 1);
454 }
455
456 type = lua_getfield(L, 2, "method");
457 if (VALID_LUATYPE(type)) {
458 // TODO improve these lua <-> enum conversions, a lot of useless
459 // work is done to create this dummy NewParams, etc.
460 NewParams dummyParams;
461 Param<doc::algorithm::ResizeMethod> param(&dummyParams, method, "method");
462 param.fromLua(L, -1);
463 method = param();
464 }
465 lua_pop(L, 1);
466
467 type = lua_getfield(L, 2, "pivot");
468 if (VALID_LUATYPE(type))
469 pivot = convert_args_into_point(L, -1);
470 lua_pop(L, 1);
471 }
472 else {
473 newSize.w = lua_tointeger(L, 2);
474 newSize.h = lua_tointeger(L, 3);
475 }
476
477 newSize.w = std::max(1, newSize.w);
478 newSize.h = std::max(1, newSize.h);
479
480 const gfx::SizeF scale(
481 double(newSize.w) / double(img->width()),
482 double(newSize.h) / double(img->height()));
483
484 // If the destination image is not related to a sprite, we just draw
485 // the source image without undo information.
486 if (cel) {
487 Tx tx;
488 resize_cel_image(tx, cel, scale, method,
489 gfx::PointF(pivot));
490 tx.commit();
491 obj->imageId = cel->image()->id();
492 }
493 else {
494 Context* ctx = App::instance()->context();
495 ASSERT(ctx);
496 Site site = ctx->activeSite();
497 const doc::Palette* pal = site.palette();
498 const doc::RgbMap* rgbmap = site.rgbMap();
499
500 std::unique_ptr<doc::Image> newImg(
501 resize_image(img, scale, method,
502 pal, rgbmap));
503 // Delete old image, and we put the same ID of the old image into
504 // the new image so this userdata references the resized image.
505 delete img;
506 newImg->setId(obj->imageId);
507 // Release the image from the smart pointer because now it's owned
508 // by the ImageObj userdata.
509 newImg.release();
510 }
511 return 0;
512}
513
514int Image_get_rowStride(lua_State* L)
515{
516 const auto obj = get_obj<ImageObj>(L, 1);
517 lua_pushinteger(L, obj->image(L)->getRowStrideSize());
518 return 1;
519}
520
521int Image_get_bytes(lua_State* L)
522{
523 const auto img = get_obj<ImageObj>(L, 1)->image(L);
524 lua_pushlstring(L, (const char*)img->getPixelAddress(0, 0), img->getRowStrideSize() * img->height());
525 return 1;
526}
527
528int Image_set_bytes(lua_State* L)
529{
530 const auto img = get_obj<ImageObj>(L, 1)->image(L);
531 size_t bytes_size, bytes_needed = img->getRowStrideSize() * img->height();
532 const char* bytes = lua_tolstring(L, 2, &bytes_size);
533
534 if (bytes_size == bytes_needed) {
535 std::memcpy(img->getPixelAddress(0, 0), bytes, bytes_size);
536 }
537 else {
538 lua_pushfstring(L, "Data size does not match: given %d, needed %d.", bytes_size, bytes_needed);
539 lua_error(L);
540 }
541
542 return 0;
543}
544
545int Image_get_width(lua_State* L)
546{
547 const auto obj = get_obj<ImageObj>(L, 1);
548 lua_pushinteger(L, obj->image(L)->width());
549 return 1;
550}
551
552int Image_get_height(lua_State* L)
553{
554 const auto obj = get_obj<ImageObj>(L, 1);
555 lua_pushinteger(L, obj->image(L)->height());
556 return 1;
557}
558
559int Image_get_colorMode(lua_State* L)
560{
561 const auto obj = get_obj<ImageObj>(L, 1);
562 lua_pushinteger(L, obj->image(L)->pixelFormat());
563 return 1;
564}
565
566int Image_get_spec(lua_State* L)
567{
568 const auto obj = get_obj<ImageObj>(L, 1);
569 push_obj(L, obj->image(L)->spec());
570 return 1;
571}
572
573int Image_get_cel(lua_State* L)
574{
575 const auto obj = get_obj<ImageObj>(L, 1);
576 push_docobj<Cel>(L, obj->celId);
577 return 1;
578}
579
580const luaL_Reg Image_methods[] = {
581 { "clone", Image_clone },
582 { "clear", Image_clear },
583 { "getPixel", Image_getPixel },
584 { "drawPixel", Image_drawPixel }, { "putPixel", Image_drawPixel },
585 { "drawImage", Image_drawImage }, { "putImage", Image_drawImage }, // TODO putImage is deprecated
586 { "drawSprite", Image_drawSprite }, { "putSprite", Image_drawSprite }, // TODO putSprite is deprecated
587 { "pixels", Image_pixels },
588 { "isEqual", Image_isEqual },
589 { "isEmpty", Image_isEmpty },
590 { "isPlain", Image_isPlain },
591 { "saveAs", Image_saveAs },
592 { "resize", Image_resize },
593 { "__gc", Image_gc },
594 { "__eq", Image_eq },
595 { nullptr, nullptr }
596};
597
598const Property Image_properties[] = {
599 { "rowStride", Image_get_rowStride, nullptr },
600 { "bytes", Image_get_bytes, Image_set_bytes },
601 { "width", Image_get_width, nullptr },
602 { "height", Image_get_height, nullptr },
603 { "colorMode", Image_get_colorMode, nullptr },
604 { "spec", Image_get_spec, nullptr },
605 { "cel", Image_get_cel, nullptr },
606 { nullptr, nullptr, nullptr }
607};
608
609} // anonymous namespace
610
611DEF_MTNAME(ImageObj);
612DEF_MTNAME_ALIAS(ImageObj, Image);
613
614void register_image_class(lua_State* L)
615{
616 using Image = ImageObj;
617 REG_CLASS(L, Image);
618 REG_CLASS_NEW(L, Image);
619 REG_CLASS_PROPERTIES(L, Image);
620}
621
622void push_cel_image(lua_State* L, doc::Cel* cel)
623{
624 push_new<ImageObj>(L, cel);
625}
626
627void push_image(lua_State* L, doc::Image* image)
628{
629 push_new<ImageObj>(L, image);
630}
631
632void push_tileset_image(lua_State* L, doc::Tileset* tileset, doc::Image* image)
633{
634 push_new<ImageObj>(L, tileset, image);
635}
636
637doc::Image* may_get_image_from_arg(lua_State* L, int index)
638{
639 auto obj = may_get_obj<ImageObj>(L, index);
640 if (obj)
641 return obj->image(L);
642 else
643 return nullptr;
644}
645
646doc::Image* get_image_from_arg(lua_State* L, int index)
647{
648 return get_obj<ImageObj>(L, index)->image(L);
649}
650
651doc::Cel* get_image_cel_from_arg(lua_State* L, int index)
652{
653 return get_obj<ImageObj>(L, index)->cel(L);
654}
655
656} // namespace script
657} // namespace app
658