diff --git a/external/solanaceae_tox b/external/solanaceae_tox index f12e609c..0e6556cd 160000 --- a/external/solanaceae_tox +++ b/external/solanaceae_tox @@ -1 +1 @@ -Subproject commit f12e609c21e84b77f5252e6b00e5fdb933b168ab +Subproject commit 0e6556cd86c558c86dfde60d82891a46bf32b64f diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e582d76b..216083ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,6 +71,7 @@ target_link_libraries(tomato PUBLIC imgui_backend_sdlrenderer3 stb_image + stb_image_write webpdemux libwebpmux # the f why (needed for anim encode) ) diff --git a/src/image_loader.hpp b/src/image_loader.hpp index 013b6b4b..2c5d12c6 100644 --- a/src/image_loader.hpp +++ b/src/image_loader.hpp @@ -33,7 +33,6 @@ struct ImageEncoderI { using ImageResult = ImageLoaderI::ImageResult; - virtual std::vector encodeToMemoryRGBA(const ImageResult& input_image) = 0; - virtual std::vector encodeToMemoryRGBAExt(const ImageResult& input_image, const std::map& extra_options) = 0; + virtual std::vector encodeToMemoryRGBA(const ImageResult& input_image, const std::map& extra_options = {}) = 0; }; diff --git a/src/image_loader_stb.cpp b/src/image_loader_stb.cpp index 55210324..77303a25 100644 --- a/src/image_loader_stb.cpp +++ b/src/image_loader_stb.cpp @@ -1,6 +1,10 @@ #include "./image_loader_stb.hpp" +#include #include +#include + +#include ImageLoaderSTB::ImageInfo ImageLoaderSTB::loadInfoFromMemory(const uint8_t* data, uint64_t data_size) { ImageInfo res; @@ -64,3 +68,68 @@ ImageLoaderSTB::ImageResult ImageLoaderSTB::loadFromMemoryRGBA(const uint8_t* da return res; } +std::vector ImageEncoderSTBPNG::encodeToMemoryRGBA(const ImageResult& input_image, const std::map& extra_options) { + if (input_image.frames.empty()) { + std::cerr << "IESTBPNG error: empty image\n"; + return {}; + } + + if (input_image.frames.size() > 1) { + std::cerr << "IESTBPNG warning: image with animation, only first frame will be encoded!\n"; + return {}; + } + + int png_compression_level = 8; + if (extra_options.count("png_compression_level")) { + png_compression_level = extra_options.at("png_compression_level"); + } + // TODO: + //int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + struct Context { + std::vector new_data; + } context; + auto write_f = +[](void* context, void* data, int size) -> void { + Context* ctx = reinterpret_cast(context); + uint8_t* d = reinterpret_cast(data); + ctx->new_data.insert(ctx->new_data.cend(), d, d + size); + }; + + stbi_write_png_compression_level = png_compression_level; + + if (!stbi_write_png_to_func(write_f, &context, input_image.width, input_image.height, 4, input_image.frames.front().data.data(), 4*input_image.width)) { + std::cerr << "IESTBPNG error: stbi_write_png failed!\n"; + return {}; + } + + return context.new_data; +} + +std::vector ImageEncoderSTBJpeg::encodeToMemoryRGBA(const ImageResult& input_image, const std::map&) { + if (input_image.frames.empty()) { + std::cerr << "IESTBJpeg error: empty image\n"; + return {}; + } + + if (input_image.frames.size() > 1) { + std::cerr << "IESTBJpeg warning: image with animation, only first frame will be encoded!\n"; + return {}; + } + + struct Context { + std::vector new_data; + } context; + auto write_f = +[](void* context, void* data, int size) -> void { + Context* ctx = reinterpret_cast(context); + uint8_t* d = reinterpret_cast(data); + ctx->new_data.insert(ctx->new_data.cend(), d, d + size); + }; + + if (!stbi_write_jpg_to_func(write_f, &context, input_image.width, input_image.height, 4, input_image.frames.front().data.data(), 4*input_image.width)) { + std::cerr << "IESTBJpeg error: stbi_write_jpg failed!\n"; + return {}; + } + + return context.new_data; +} + diff --git a/src/image_loader_stb.hpp b/src/image_loader_stb.hpp index 1ba44ab6..b15c9aff 100644 --- a/src/image_loader_stb.hpp +++ b/src/image_loader_stb.hpp @@ -7,3 +7,11 @@ struct ImageLoaderSTB : public ImageLoaderI { ImageResult loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) override; }; +struct ImageEncoderSTBPNG : public ImageEncoderI { + std::vector encodeToMemoryRGBA(const ImageResult& input_image, const std::map& extra_options = {}) override; +}; + +struct ImageEncoderSTBJpeg : public ImageEncoderI { + std::vector encodeToMemoryRGBA(const ImageResult& input_image, const std::map& extra_options = {}) override; +}; + diff --git a/src/image_loader_webp.cpp b/src/image_loader_webp.cpp index 15cd3c66..1748a4fe 100644 --- a/src/image_loader_webp.cpp +++ b/src/image_loader_webp.cpp @@ -81,11 +81,7 @@ ImageLoaderWebP::ImageResult ImageLoaderWebP::loadFromMemoryRGBA(const uint8_t* return res; } -std::vector ImageEncoderWebP::encodeToMemoryRGBA(const ImageResult& input_image) { - return encodeToMemoryRGBAExt(input_image, {{"quality", 80.f}}); -} - -std::vector ImageEncoderWebP::encodeToMemoryRGBAExt(const ImageResult& input_image, const std::map& extra_options) { +std::vector ImageEncoderWebP::encodeToMemoryRGBA(const ImageResult& input_image, const std::map& extra_options) { // setup options float quality = 80.f; if (extra_options.count("quality")) { diff --git a/src/image_loader_webp.hpp b/src/image_loader_webp.hpp index 812e78ba..cfc775bc 100644 --- a/src/image_loader_webp.hpp +++ b/src/image_loader_webp.hpp @@ -8,7 +8,6 @@ struct ImageLoaderWebP : public ImageLoaderI { }; struct ImageEncoderWebP : public ImageEncoderI { - std::vector encodeToMemoryRGBA(const ImageResult& input_image) override; - std::vector encodeToMemoryRGBAExt(const ImageResult& input_image, const std::map& extra_options) override; + std::vector encodeToMemoryRGBA(const ImageResult& input_image, const std::map& extra_options = {}) override; }; diff --git a/src/send_image_popup.cpp b/src/send_image_popup.cpp index b41ed3de..6aa5ec48 100644 --- a/src/send_image_popup.cpp +++ b/src/send_image_popup.cpp @@ -103,7 +103,7 @@ bool SendImagePopup::load(void) { std::vector SendImagePopup::compressWebp(const ImageLoaderI::ImageResult& input_image, uint32_t quality) { // HACK: generic list - return ImageEncoderWebP{}.encodeToMemoryRGBAExt(input_image, {{"quality", quality}}); + return ImageEncoderWebP{}.encodeToMemoryRGBA(input_image, {{"quality", quality}}); } ImageLoaderI::ImageResult SendImagePopup::crop(const ImageLoaderI::ImageResult& input_image, const Rect& crop_rect) {