From 2fca2f4395a35ebb1879ad4dad9c317913c562f8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Apr 2023 02:59:48 -0700 Subject: [PATCH] Scripting: Export current image API --- include/mgba/script/base.h | 4 ++ src/script/CMakeLists.txt | 2 + src/script/docgen.c | 8 +-- src/script/image.c | 73 ++++++++++++++++++++++++ src/script/test/image.c | 111 +++++++++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 src/script/image.c create mode 100644 src/script/test/image.c diff --git a/include/mgba/script/base.h b/include/mgba/script/base.h index de8eef033..4e8bbf168 100644 --- a/include/mgba/script/base.h +++ b/include/mgba/script/base.h @@ -11,8 +11,12 @@ CXX_GUARD_START #include +#include + +mSCRIPT_DECLARE_STRUCT(mImage) struct mScriptContext; +void mScriptContextAttachImage(struct mScriptContext* context); void mScriptContextAttachStdlib(struct mScriptContext* context); void mScriptContextAttachSocket(struct mScriptContext* context); diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index f741b9c05..a451ff6d8 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -2,6 +2,7 @@ include(ExportDirectory) set(SOURCE_FILES context.c input.c + image.c socket.c stdlib.c types.c) @@ -18,6 +19,7 @@ if(USE_LUA) list(APPEND SOURCE_FILES engines/lua.c) list(APPEND TEST_FILES test/context.c + test/image.c test/input.c test/lua.c test/stdlib.c) diff --git a/src/script/docgen.c b/src/script/docgen.c index a81e59422..7e35adbbd 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -7,10 +7,7 @@ #include #include #include -#include -#include -#include -#include +#include #include struct mScriptContext context; @@ -473,9 +470,10 @@ int main(int argc, char* argv[]) { mScriptContextInit(&context); mScriptContextAttachStdlib(&context); + mScriptContextAttachImage(&context); + mScriptContextAttachInput(&context); mScriptContextAttachSocket(&context); mScriptContextAttachStorage(&context); - mScriptContextAttachInput(&context); mScriptContextSetTextBufferFactory(&context, NULL, NULL); initTypes(); diff --git a/src/script/image.c b/src/script/image.c new file mode 100644 index 000000000..35575b9ae --- /dev/null +++ b/src/script/image.c @@ -0,0 +1,73 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +static struct mScriptValue* _mImageNew(unsigned width, unsigned height) { + // For various reasons, it's probably a good idea to limit the maximum image size scripts can make + if (width >= 10000 || height >= 10000) { + return NULL; + } + struct mImage* image = mImageCreate(width, height, mCOLOR_ABGR8); + if (!image) { + return NULL; + } + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage)); + result->value.opaque = image; + result->flags = mSCRIPT_VALUE_FLAG_DEINIT; + return result; +} + +static struct mScriptValue* _mImageLoad(const char* path) { + struct mImage* image = mImageLoad(path); + if (!image) { + return NULL; + } + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage)); + result->value.opaque = image; + result->flags = mSCRIPT_VALUE_FLAG_DEINIT; + return result; +} + +mSCRIPT_DECLARE_STRUCT_C_METHOD(mImage, U32, getPixel, mImageGetPixel, 2, U32, x, U32, y); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, setPixel, mImageSetPixel, 3, U32, x, U32, y, U32, color); +mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(mImage, BOOL, save, mImageSave, 2, CHARP, path, CHARP, format); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, _deinit, mImageDestroy, 0); + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mImage, save) + mSCRIPT_NO_DEFAULT, + mSCRIPT_CHARP("PNG") +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_STRUCT(mImage) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "A single, static image." + ) + mSCRIPT_DEFINE_STRUCT_DEINIT(mImage) + mSCRIPT_DEFINE_DOCSTRING("Save the image to a file. Currently, only `PNG` format is supported") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, save) + mSCRIPT_DEFINE_DOCSTRING("Get the ARGB value of the pixel at a given coordinate") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, getPixel) + mSCRIPT_DEFINE_DOCSTRING("Set the ARGB value of the pixel at a given coordinate") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, setPixel) + mSCRIPT_DEFINE_DOCSTRING("The width of the image, in pixels") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, width) + mSCRIPT_DEFINE_DOCSTRING("The height of the image, in pixels") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, height) +mSCRIPT_DEFINE_END; + +mSCRIPT_BIND_FUNCTION(mImageNew_Binding, W(mImage), _mImageNew, 2, U32, width, U32, height); +mSCRIPT_BIND_FUNCTION(mImageLoad_Binding, W(mImage), _mImageLoad, 1, CHARP, path); + +void mScriptContextAttachImage(struct mScriptContext* context) { + mScriptContextExportNamespace(context, "image", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(new, &mImageNew_Binding), + mSCRIPT_KV_PAIR(load, &mImageLoad_Binding), + mSCRIPT_KV_SENTINEL + }); + mScriptContextSetDocstring(context, "image", "Methods for creating struct::mImage instances"); + mScriptContextSetDocstring(context, "image.new", "Create a new image with the given dimensions"); + mScriptContextSetDocstring(context, "image.load", "Load an image from a path. Currently, only `PNG` format is supported"); +} diff --git a/src/script/test/image.c b/src/script/test/image.c new file mode 100644 index 000000000..541f7a210 --- /dev/null +++ b/src/script/test/image.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include + +#include "script/test.h" + +#define SETUP_LUA \ + struct mScriptContext context; \ + mScriptContextInit(&context); \ + struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ + mScriptContextAttachStdlib(&context); \ + mScriptContextAttachImage(&context) + +M_TEST_SUITE_SETUP(mScriptImage) { + if (mSCRIPT_ENGINE_LUA->init) { + mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_SUITE_TEARDOWN(mScriptImage) { + if (mSCRIPT_ENGINE_LUA->deinit) { + mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_DEFINE(members) { + SETUP_LUA; + + TEST_PROGRAM("assert(image)"); + TEST_PROGRAM("assert(image.new)"); + TEST_PROGRAM("assert(image.load)"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im)"); + TEST_PROGRAM("assert(im.width == 1)"); + TEST_PROGRAM("assert(im.height == 1)"); + TEST_PROGRAM("assert(im.save)"); + TEST_PROGRAM("assert(im.save)"); + TEST_PROGRAM("assert(im.getPixel)"); + TEST_PROGRAM("assert(im.setPixel)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(zeroDim) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(0, 0)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(1, 0)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(0, 1)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(pixelColorDefault) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(pixelColorRoundTrip) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("im:setPixel(0, 0, 0xFF123456)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0xFF123456)"); + + mScriptContextDeinit(&context); +} + +#ifdef USE_PNG +M_TEST_DEFINE(saveLoadRoundTrip) { + SETUP_LUA; + + unlink("tmp.png"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("im:setPixel(0, 0, 0xFF123456)"); + TEST_PROGRAM("assert(im:save('tmp.png'))"); + TEST_PROGRAM("im = image.load('tmp.png')"); + TEST_PROGRAM("assert(im)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0xFF123456)"); + unlink("tmp.png"); + + mScriptContextDeinit(&context); +} +#endif + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptImage, + cmocka_unit_test(members), + cmocka_unit_test(zeroDim), + cmocka_unit_test(pixelColorDefault), + cmocka_unit_test(pixelColorRoundTrip), +#ifdef USE_PNG + cmocka_unit_test(saveLoadRoundTrip), +#endif +)