From b9777cc682f8aa698223bb4890315b4f373124e1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 11 Nov 2025 17:46:38 +0300 Subject: [PATCH 1/3] add canvas:unbind_texture method --- doc/en/scripting/ui.md | 1 + doc/ru/scripting/ui.md | 1 + src/logic/scripting/lua/usertypes/lua_type_canvas.cpp | 11 +++++++++++ src/logic/scripting/lua/usertypes/lua_type_canvas.hpp | 1 + 4 files changed, 14 insertions(+) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 2da6843c..091d7ff8 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -196,6 +196,7 @@ Here, *color* can be specified in the following ways: | data:update() | applies changes to the canvas and uploads it to the GPU | | data:set_data(data: table) | replaces pixel data (width * height * 4 numbers) | | data:create_texture(name: str) | creates and shares texture to renderer | +| data:unbind_texture() | unbinds the texture from the canvas | ## Inline frame (iframe) diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 19f1f8ab..10514edb 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -196,6 +196,7 @@ document["worlds-panel"]:clear() | data:update() | применяет изменения и загружает холст в видеопамять | | data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | | data:create_texture(name: str) | создаёт и делится текстурой с рендерером | +| data:unbind_texture() | отвязывает текстуру от холста | ## Рамка встраивания (iframe) diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index 325f916f..40af6e81 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -66,6 +66,10 @@ void LuaCanvas::createTexture() { texture->setMipMapping(false, true); } +void LuaCanvas::unbindTexture() { + texture.reset(); +} + union RGBA { struct { uint8_t r, g, b, a; @@ -99,7 +103,13 @@ static int l_at(State* L) { if (auto pixel = get_at(L, x, y)) { return pushinteger(L, pixel->rgba); } + return 0; +} +static int l_unbind_texture(State* L) { + if (auto canvas = touserdata(L, 1)) { + canvas->unbindTexture(); + } return 0; } @@ -238,6 +248,7 @@ static std::unordered_map methods { {"clear", lua::wrap}, {"update", lua::wrap}, {"create_texture", lua::wrap}, + {"unbind_texture", lua::wrap}, {"_set_data", lua::wrap}, }; diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.hpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.hpp index 198313cb..854443d1 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.hpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.hpp @@ -40,6 +40,7 @@ namespace lua { void update(int extrusion = ATLAS_EXTRUSION); void createTexture(); + void unbindTexture(); static int createMetatable(lua::State*); inline static std::string TYPENAME = "Canvas"; From 83f7bf80c434cb4b877b1ca0b81e71e7a56091e4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 11 Nov 2025 19:30:27 +0300 Subject: [PATCH 2/3] add canvas:mul and canvas:add methods --- doc/en/scripting/ui.md | 2 + doc/ru/scripting/ui.md | 2 + src/graphics/core/ImageData.cpp | 93 +++++++++++++++++++ src/graphics/core/ImageData.hpp | 4 + .../lua/usertypes/lua_type_canvas.cpp | 30 ++++++ 5 files changed, 131 insertions(+) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 091d7ff8..37c316fd 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -197,6 +197,8 @@ Here, *color* can be specified in the following ways: | data:set_data(data: table) | replaces pixel data (width * height * 4 numbers) | | data:create_texture(name: str) | creates and shares texture to renderer | | data:unbind_texture() | unbinds the texture from the canvas | +| data:mul(*color* or Canvas) | multiplies a color by the specified color or canvas | +| data:add(*color* or Canvas) | adds a color or another canvas to a color | ## Inline frame (iframe) diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 10514edb..741c8cf3 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -197,6 +197,8 @@ document["worlds-panel"]:clear() | data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | | data:create_texture(name: str) | создаёт и делится текстурой с рендерером | | data:unbind_texture() | отвязывает текстуру от холста | +| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст | +| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету | ## Рамка встраивания (iframe) diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index 3a5e3cb6..b69b5f4a 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -420,6 +420,99 @@ void ImageData::fixAlphaColor() { } } +static void check_matching(const ImageData& a, const ImageData& b) { + if (b.getWidth() != a.getWidth() || + b.getHeight() != a.getHeight() || + b.getFormat() != a.getFormat()) { + throw std::runtime_error("image sizes or formats do not match"); + } +} + +void ImageData::mulColor(const glm::ivec4& color) { + uint comps; + switch (format) { + case ImageFormat::rgb888: comps = 3; break; + case ImageFormat::rgba8888: comps = 4; break; + default: + throw std::runtime_error("only unsigned byte formats supported"); + } + for (uint y = 0; y < height; y++) { + for (uint x = 0; x < width; x++) { + uint idx = (y * width + x) * comps; + for (uint c = 0; c < comps; c++) { + float val = static_cast(data[idx + c]) * color[c] / 255.0f; + data[idx + c] = + static_cast(std::min(std::max(val, 0.0f), 255.0f)); + } + } + } +} + +void ImageData::addColor(const ImageData& other) { + check_matching(*this, other); + + uint comps; + switch (format) { + case ImageFormat::rgb888: comps = 3; break; + case ImageFormat::rgba8888: comps = 4; break; + default: + throw std::runtime_error("only unsigned byte formats supported"); + } + for (uint y = 0; y < height; y++) { + for (uint x = 0; x < width; x++) { + uint idx = (y * width + x) * comps; + for (uint c = 0; c < comps; c++) { + int val = data[idx + c] + other.data[idx + c]; + data[idx + c] = + static_cast(std::min(std::max(val, 0), 255)); + } + } + } +} + +void ImageData::addColor(const glm::ivec4& color) { + uint comps; + switch (format) { + case ImageFormat::rgb888: comps = 3; break; + case ImageFormat::rgba8888: comps = 4; break; + default: + throw std::runtime_error("only unsigned byte formats supported"); + } + for (uint y = 0; y < height; y++) { + for (uint x = 0; x < width; x++) { + uint idx = (y * width + x) * comps; + for (uint c = 0; c < comps; c++) { + int val = data[idx + c] + color[c]; + data[idx + c] = + static_cast(std::min(std::max(val, 0), 255)); + } + } + } +} + +void ImageData::mulColor(const ImageData& other) { + check_matching(*this, other); + + uint comps; + switch (format) { + case ImageFormat::rgb888: comps = 3; break; + case ImageFormat::rgba8888: comps = 4; break; + default: + throw std::runtime_error("only unsigned byte formats supported"); + } + for (uint y = 0; y < height; y++) { + for (uint x = 0; x < width; x++) { + uint idx = (y * width + x) * comps; + for (uint c = 0; c < comps; c++) { + float val = static_cast(data[idx + c]) * + static_cast(other.data[idx + c]) / 255.0f; + data[idx + c] = + static_cast(std::min(std::max(val, 0.0f), 255.0f)); + } + } + } +} + std::unique_ptr add_atlas_margins(ImageData* image, int grid_size) { // RGBA is only supported assert(image->getFormat() == ImageFormat::rgba8888); diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index 86396568..a0e694e3 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -32,6 +32,10 @@ public: void blit(const ImageData& image, int x, int y); void extrude(int x, int y, int w, int h); void fixAlphaColor(); + void mulColor(const glm::ivec4& color); + void mulColor(const ImageData& other); + void addColor(const glm::ivec4& color); + void addColor(const ImageData& other); std::unique_ptr cropped(int x, int y, int width, int height) const; diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index 40af6e81..8adb558c 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -240,6 +240,34 @@ static int l_create_texture(State* L) { return 0; } +static int l_mul(State* L) { + auto canvas = touserdata(L, 1); + if (canvas == nullptr) { + return 0; + } + if (lua::isnumber(L, 2)) { + RGBA rgba = get_rgba(L, 2); + canvas->getData().mulColor(glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a}); + } else if (auto other = touserdata(L, 2)) { + canvas->getData().mulColor(other->getData()); + } + return 0; +} + +static int l_add(State* L) { + auto canvas = touserdata(L, 1); + if (canvas == nullptr) { + return 0; + } + if (lua::istable(L, 2)) { + RGBA rgba = get_rgba(L, 2); + canvas->getData().addColor(glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a}); + } else if (auto other = touserdata(L, 2)) { + canvas->getData().addColor(other->getData()); + } + return 0; +} + static std::unordered_map methods { {"at", lua::wrap}, {"set", lua::wrap}, @@ -249,6 +277,8 @@ static std::unordered_map methods { {"update", lua::wrap}, {"create_texture", lua::wrap}, {"unbind_texture", lua::wrap}, + {"mul", lua::wrap}, + {"add", lua::wrap}, {"_set_data", lua::wrap}, }; From 34d13442b614b76a7794bd7a2fd909ddf7931a2a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 11 Nov 2025 19:34:40 +0300 Subject: [PATCH 3/3] add canvas:sub method --- doc/en/scripting/ui.md | 1 + doc/ru/scripting/ui.md | 1 + src/graphics/core/ImageData.cpp | 8 ++++---- src/graphics/core/ImageData.hpp | 4 ++-- .../lua/usertypes/lua_type_canvas.cpp | 19 +++++++++++++++++-- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 37c316fd..80750501 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -199,6 +199,7 @@ Here, *color* can be specified in the following ways: | data:unbind_texture() | unbinds the texture from the canvas | | data:mul(*color* or Canvas) | multiplies a color by the specified color or canvas | | data:add(*color* or Canvas) | adds a color or another canvas to a color | +| data:sub(*color* or Canvas) | subtracts a color or another canvas to a color | ## Inline frame (iframe) diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 741c8cf3..c6c769ac 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -199,6 +199,7 @@ document["worlds-panel"]:clear() | data:unbind_texture() | отвязывает текстуру от холста | | data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст | | data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету | +| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету | ## Рамка встраивания (iframe) diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index b69b5f4a..bd243832 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -448,7 +448,7 @@ void ImageData::mulColor(const glm::ivec4& color) { } } -void ImageData::addColor(const ImageData& other) { +void ImageData::addColor(const ImageData& other, int multiplier) { check_matching(*this, other); uint comps; @@ -462,7 +462,7 @@ void ImageData::addColor(const ImageData& other) { for (uint x = 0; x < width; x++) { uint idx = (y * width + x) * comps; for (uint c = 0; c < comps; c++) { - int val = data[idx + c] + other.data[idx + c]; + int val = data[idx + c] + other.data[idx + c] * multiplier; data[idx + c] = static_cast(std::min(std::max(val, 0), 255)); } @@ -470,7 +470,7 @@ void ImageData::addColor(const ImageData& other) { } } -void ImageData::addColor(const glm::ivec4& color) { +void ImageData::addColor(const glm::ivec4& color, int multiplier) { uint comps; switch (format) { case ImageFormat::rgb888: comps = 3; break; @@ -482,7 +482,7 @@ void ImageData::addColor(const glm::ivec4& color) { for (uint x = 0; x < width; x++) { uint idx = (y * width + x) * comps; for (uint c = 0; c < comps; c++) { - int val = data[idx + c] + color[c]; + int val = data[idx + c] + color[c] * multiplier; data[idx + c] = static_cast(std::min(std::max(val, 0), 255)); } diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index a0e694e3..a1597a98 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -34,8 +34,8 @@ public: void fixAlphaColor(); void mulColor(const glm::ivec4& color); void mulColor(const ImageData& other); - void addColor(const glm::ivec4& color); - void addColor(const ImageData& other); + void addColor(const glm::ivec4& color, int multiplier); + void addColor(const ImageData& other, int multiplier); std::unique_ptr cropped(int x, int y, int width, int height) const; diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index 8adb558c..b93b52c6 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -261,9 +261,23 @@ static int l_add(State* L) { } if (lua::istable(L, 2)) { RGBA rgba = get_rgba(L, 2); - canvas->getData().addColor(glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a}); + canvas->getData().addColor(glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a}, 1); } else if (auto other = touserdata(L, 2)) { - canvas->getData().addColor(other->getData()); + canvas->getData().addColor(other->getData(), 1); + } + return 0; +} + +static int l_sub(State* L) { + auto canvas = touserdata(L, 1); + if (canvas == nullptr) { + return 0; + } + if (lua::istable(L, 2)) { + RGBA rgba = get_rgba(L, 2); + canvas->getData().addColor(glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a}, -1); + } else if (auto other = touserdata(L, 2)) { + canvas->getData().addColor(other->getData(), -1); } return 0; } @@ -279,6 +293,7 @@ static std::unordered_map methods { {"unbind_texture", lua::wrap}, {"mul", lua::wrap}, {"add", lua::wrap}, + {"sub", lua::wrap}, {"_set_data", lua::wrap}, };