diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index c35bf908..b58e7e2a 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -201,6 +201,14 @@ Here, *color* can be specified in the following ways: | 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 | +| data:encode(format: str) | encodes image to specified format and returns bytearray | + +To decode a byte array into a Canvas, use the static method: +```lua +Canvas.decode(data: Bytearray, format: str) -> Canvas +``` + +Currently, only png is supported. ## Inline frame (iframe) diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index a6d6006d..e593dc4a 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -186,21 +186,29 @@ document["worlds-panel"]:clear() - r: int, g: int, b: int - r: int, g: int, b: int, a: int -| Метод | Описание | -|----------------------------------------------------------|------------------------------------------------------| -| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | -| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | -| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | -| data:clear() | очищает холст | -| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | -| data:update() | применяет изменения и загружает холст в видеопамять | -| data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | -| data:create_texture(name: str) | создаёт и делится текстурой с рендерером | -| data:unbind_texture() | отвязывает текстуру от холста | -| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст | -| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету | -| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету | +| Метод | Описание | +|----------------------------------------------------------|-----------------------------------------------------------------| +| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | +| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | +| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | +| data:clear() | очищает холст | +| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | +| data:update() | применяет изменения и загружает холст в видеопамять | +| data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | +| data:create_texture(name: str) | создаёт и делится текстурой с рендерером | +| data:unbind_texture() | отвязывает текстуру от холста | +| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст | +| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету | +| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету | +| data:encode(format: str) | кодирует изображение в указанный формат и возращает массив байт | + +Для декодирования массива байт в Canvas используйте статический метод: +```lua +Canvas.decode(data: Bytearray, format: str) -> Canvas +``` + +На данный момент, из форматов поддерживается только png. ## Рамка встраивания (iframe) diff --git a/src/coders/imageio.cpp b/src/coders/imageio.cpp index 36e1f76d..b63c65fc 100644 --- a/src/coders/imageio.cpp +++ b/src/coders/imageio.cpp @@ -1,3 +1,4 @@ +#define VC_ENABLE_REFLECTION #include "imageio.hpp" #include @@ -7,28 +8,34 @@ #include "io/io.hpp" #include "png.hpp" +using namespace imageio; + using image_reader = std::function(const ubyte*, size_t)>; using image_writer = std::function; -static std::unordered_map readers { - {".png", png::load_image}, +static std::unordered_map readers { + {ImageFileFormat::PNG, png::load_image}, }; -static std::unordered_map writers { - {".png", png::write_image}, +static std::unordered_map writers { + {ImageFileFormat::PNG, png::write_image}, }; bool imageio::is_read_supported(const std::string& extension) { - return readers.find(extension) != readers.end(); + return extension == ".png"; } bool imageio::is_write_supported(const std::string& extension) { - return writers.find(extension) != writers.end(); + return extension == ".png"; } std::unique_ptr imageio::read(const io::path& file) { - auto found = readers.find(file.extension()); + ImageFileFormat format; + if (!ImageFileFormatMeta.getItem(file.extension().substr(1), format)) { + throw std::runtime_error("unsupported image format"); + } + auto found = readers.find(format); if (found == readers.end()) { throw std::runtime_error( "file format is not supported (read): " + file.string() @@ -44,8 +51,25 @@ std::unique_ptr imageio::read(const io::path& file) { } } +std::unique_ptr imageio::decode( + ImageFileFormat format, util::span src +) { + auto found = readers.find(format); + try { + return std::unique_ptr(found->second(src.data(), src.size())); + } catch (const std::runtime_error& err) { + throw std::runtime_error( + "could not to decode image: " + std::string(err.what()) + ); + } +} + void imageio::write(const io::path& file, const ImageData* image) { - auto found = writers.find(file.extension()); + ImageFileFormat format; + if (!ImageFileFormatMeta.getItem(file.extension().substr(1), format)) { + throw std::runtime_error("unsupported image format"); + } + auto found = writers.find(format); if (found == writers.end()) { throw std::runtime_error( "file format is not supported (write): " + file.string() @@ -53,3 +77,14 @@ void imageio::write(const io::path& file, const ImageData* image) { } return found->second(io::resolve(file).u8string(), image); } + +util::Buffer imageio::encode( + ImageFileFormat format, const ImageData& image +) { + switch (format) { + case ImageFileFormat::PNG: + return png::encode_image(image); + default: + throw std::runtime_error("file format is not supported for encoding"); + } +} diff --git a/src/coders/imageio.hpp b/src/coders/imageio.hpp index 0e59ae37..782d51f5 100644 --- a/src/coders/imageio.hpp +++ b/src/coders/imageio.hpp @@ -4,10 +4,22 @@ #include #include "io/fwd.hpp" +#include "util/Buffer.hpp" +#include "util/EnumMetadata.hpp" +#include "util/span.hpp" +#include "typedefs.hpp" class ImageData; namespace imageio { + enum class ImageFileFormat { + PNG + }; + + VC_ENUM_METADATA(ImageFileFormat) + {"png", ImageFileFormat::PNG}, + VC_ENUM_END + inline const std::string PNG = ".png"; bool is_read_supported(const std::string& extension); @@ -15,4 +27,6 @@ namespace imageio { std::unique_ptr read(const io::path& file); void write(const io::path& file, const ImageData* image); + std::unique_ptr decode(ImageFileFormat format, util::span src); + util::Buffer encode(ImageFileFormat format, const ImageData& image); } diff --git a/src/coders/png.cpp b/src/coders/png.cpp index 4121b052..a6c3f9e6 100644 --- a/src/coders/png.cpp +++ b/src/coders/png.cpp @@ -11,6 +11,60 @@ static debug::Logger logger("png-coder"); +static util::Buffer write_to_memory(uint width, uint height, const ubyte* data, bool alpha) { + uint pixsize = alpha ? 4 : 3; + + std::vector buffer; + png_structp png_ptr = png_create_write_struct( + PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr + ); + png_infop info_ptr = png_create_info_struct(png_ptr); + + png_set_write_fn( + png_ptr, + &buffer, + [](png_structp pngPtr, png_bytep data, png_size_t length) { + auto& buf = *reinterpret_cast*>(png_get_io_ptr(pngPtr)); + buf.insert( + buf.end(), + reinterpret_cast(data), + reinterpret_cast(data) + length + ); + }, + nullptr + ); + + png_set_IHDR( + png_ptr, + info_ptr, + width, + height, + 8, + alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE + ); + + png_write_info(png_ptr, info_ptr); + + auto row = std::make_unique(pixsize * width); + for (uint y = 0; y < height; y++) { + for (uint x = 0; x < width; x++) { + for (uint i = 0; i < pixsize; i++) { + row[x * pixsize + i] = + (png_byte)data[(y * width + x) * pixsize + i]; + } + } + png_write_row(png_ptr, row.get()); + } + + png_write_end(png_ptr, nullptr); + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + png_destroy_write_struct(&png_ptr, &info_ptr); + return util::Buffer(buffer.data(), buffer.size()); +} + // returns 0 if all-right, 1 otherwise static int png_write( const char* filename, uint width, uint height, const ubyte* data, bool alpha @@ -230,3 +284,13 @@ void png::write_image(const std::string& filename, const ImageData* image) { image->getFormat() == ImageFormat::rgba8888 ); } + +util::Buffer png::encode_image(const ImageData& image) { + auto format = image.getFormat(); + return write_to_memory( + image.getWidth(), + image.getHeight(), + image.getData(), + format == ImageFormat::rgba8888 + ); +} diff --git a/src/coders/png.hpp b/src/coders/png.hpp index 9ba2eda9..468efc4b 100644 --- a/src/coders/png.hpp +++ b/src/coders/png.hpp @@ -4,6 +4,7 @@ #include #include "typedefs.hpp" +#include "util/Buffer.hpp" class Texture; class ImageData; @@ -11,6 +12,7 @@ class ImageData; namespace png { std::unique_ptr load_image(const ubyte* bytes, size_t size); void write_image(const std::string& filename, const ImageData* image); + util::Buffer encode_image(const ImageData& image); std::unique_ptr load_texture(const ubyte* bytes, size_t size); std::unique_ptr load_texture(const std::string& filename); } diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index de8021de..f89c9d5e 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -1,8 +1,10 @@ +#define VC_ENABLE_REFLECTION #include "lua_type_canvas.hpp" #include "graphics/core/ImageData.hpp" #include "graphics/core/Texture.hpp" #include "logic/scripting/lua/lua_util.hpp" +#include "coders/imageio.hpp" #include "engine/Engine.hpp" #include "assets/Assets.hpp" @@ -284,6 +286,23 @@ static int l_sub(State* L) { return 0; } +static int l_encode(State* L) { + auto canvas = touserdata(L, 1); + if (canvas == nullptr) { + return 0; + } + auto format = imageio::ImageFileFormat::PNG; + if (lua::isstring(L, 2)) { + auto name = lua::require_string(L, 2); + if (!imageio::ImageFileFormatMeta.getItem(name, format)) { + throw std::runtime_error("unsupported image file format"); + } + } + + auto buffer = imageio::encode(format, canvas->getData()); + return lua::create_bytearray(L, buffer.data(), buffer.size()); +} + static std::unordered_map methods { {"at", lua::wrap}, {"set", lua::wrap}, @@ -296,6 +315,7 @@ static std::unordered_map methods { {"mul", lua::wrap}, {"add", lua::wrap}, {"sub", lua::wrap}, + {"encode", lua::wrap}, {"_set_data", lua::wrap}, }; @@ -354,6 +374,23 @@ static int l_meta_meta_call(lua::State* L) { ); } +static int l_canvas_decode(lua::State* L) { + auto bytes = bytearray_as_string(L, 1); + auto formatName = require_lstring(L, 2); + imageio::ImageFileFormat format; + if (!imageio::ImageFileFormatMeta.getItem(formatName, format)) { + throw std::runtime_error("unsupported image format"); + } + return newuserdata( + L, + nullptr, + imageio::decode( + format, + {reinterpret_cast(bytes.data()), bytes.size()} + ) + ); +} + int LuaCanvas::createMetatable(State* L) { createtable(L, 0, 3); pushcfunction(L, lua::wrap); @@ -365,5 +402,8 @@ int LuaCanvas::createMetatable(State* L) { pushcfunction(L, lua::wrap); setfield(L, "__call"); setmetatable(L); + + pushcfunction(L, lua::wrap); + setfield(L, "decode"); return 1; }