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 | |
39 | namespace app { |
40 | namespace script { |
41 | |
42 | namespace { |
43 | |
44 | struct 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 | |
84 | void 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 | |
99 | int Image_clone(lua_State* L); |
100 | |
101 | int 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 | |
191 | int 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 | |
199 | int 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 | |
207 | int 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 | |
215 | int 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 | |
230 | int 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 | |
245 | int 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 | |
273 | int 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 | |
307 | int 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 | |
314 | int 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 | |
324 | int 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 | |
334 | int 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 | |
343 | int 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 | |
360 | int 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 | |
423 | int 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 | |
514 | int 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 | |
521 | int 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 | |
528 | int 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 | |
545 | int 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 | |
552 | int 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 | |
559 | int 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 | |
566 | int 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 | |
573 | int 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 | |
580 | const 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 | |
598 | const 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 | |
611 | DEF_MTNAME(ImageObj); |
612 | DEF_MTNAME_ALIAS(ImageObj, Image); |
613 | |
614 | void 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 | |
622 | void push_cel_image(lua_State* L, doc::Cel* cel) |
623 | { |
624 | push_new<ImageObj>(L, cel); |
625 | } |
626 | |
627 | void push_image(lua_State* L, doc::Image* image) |
628 | { |
629 | push_new<ImageObj>(L, image); |
630 | } |
631 | |
632 | void push_tileset_image(lua_State* L, doc::Tileset* tileset, doc::Image* image) |
633 | { |
634 | push_new<ImageObj>(L, tileset, image); |
635 | } |
636 | |
637 | doc::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 | |
646 | doc::Image* get_image_from_arg(lua_State* L, int index) |
647 | { |
648 | return get_obj<ImageObj>(L, index)->image(L); |
649 | } |
650 | |
651 | doc::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 | |