diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 216083b..8c070f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(tomato ./theme.hpp ./image_loader.hpp + ./image_loader.cpp ./image_loader_sdl_bmp.hpp ./image_loader_sdl_bmp.cpp ./image_loader_stb.hpp diff --git a/src/image_loader.cpp b/src/image_loader.cpp new file mode 100644 index 0000000..5fe0863 --- /dev/null +++ b/src/image_loader.cpp @@ -0,0 +1,32 @@ +#include "./image_loader.hpp" + +#include + +ImageLoaderI::ImageResult ImageLoaderI::ImageResult::crop(int32_t c_x, int32_t c_y, int32_t c_w, int32_t c_h) const { + // TODO: proper error handling + assert(c_x+c_w <= width); + assert(c_y+c_h <= height); + + ImageLoaderI::ImageResult new_image; + new_image.width = c_w; + new_image.height = c_h; + new_image.file_ext = file_ext; + + for (const auto& input_frame : frames) { + auto& new_frame = new_image.frames.emplace_back(); + new_frame.ms = input_frame.ms; + + // TODO: improve this, this is super inefficent + for (int64_t y = c_y; y < c_y + c_h; y++) { + for (int64_t x = c_x; x < c_x + c_w; x++) { + new_frame.data.push_back(input_frame.data.at(y*width*4+x*4+0)); + new_frame.data.push_back(input_frame.data.at(y*width*4+x*4+1)); + new_frame.data.push_back(input_frame.data.at(y*width*4+x*4+2)); + new_frame.data.push_back(input_frame.data.at(y*width*4+x*4+3)); + } + } + } + + return new_image; +} + diff --git a/src/image_loader.hpp b/src/image_loader.hpp index 2c5d12c..78f2885 100644 --- a/src/image_loader.hpp +++ b/src/image_loader.hpp @@ -8,14 +8,14 @@ struct ImageLoaderI { virtual ~ImageLoaderI(void) {} - struct ImageInfo { + struct ImageInfo final { uint32_t width {0}; uint32_t height {0}; const char* file_ext {nullptr}; }; virtual ImageInfo loadInfoFromMemory(const uint8_t* data, uint64_t data_size) = 0; - struct ImageResult { + struct ImageResult final { uint32_t width {0}; uint32_t height {0}; struct Frame { @@ -24,6 +24,9 @@ struct ImageLoaderI { }; std::vector frames; const char* file_ext {nullptr}; + + // only positive values are valid + ImageResult crop(int32_t c_x, int32_t c_y, int32_t c_w, int32_t c_h) const; }; virtual ImageResult loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) = 0; }; diff --git a/src/image_loader_stb.cpp b/src/image_loader_stb.cpp index 77303a2..d983f63 100644 --- a/src/image_loader_stb.cpp +++ b/src/image_loader_stb.cpp @@ -105,7 +105,7 @@ std::vector ImageEncoderSTBPNG::encodeToMemoryRGBA(const ImageResult& i return context.new_data; } -std::vector ImageEncoderSTBJpeg::encodeToMemoryRGBA(const ImageResult& input_image, const std::map&) { +std::vector ImageEncoderSTBJpeg::encodeToMemoryRGBA(const ImageResult& input_image, const std::map& extra_options) { if (input_image.frames.empty()) { std::cerr << "IESTBJpeg error: empty image\n"; return {}; @@ -116,6 +116,13 @@ std::vector ImageEncoderSTBJpeg::encodeToMemoryRGBA(const ImageResult& return {}; } + // setup options + float quality = 80.f; + if (extra_options.count("quality")) { + quality = extra_options.at("quality"); + } + + struct Context { std::vector new_data; } context; @@ -125,7 +132,7 @@ std::vector ImageEncoderSTBJpeg::encodeToMemoryRGBA(const ImageResult& 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)) { + if (!stbi_write_jpg_to_func(write_f, &context, input_image.width, input_image.height, 4, input_image.frames.front().data.data(), quality)) { std::cerr << "IESTBJpeg error: stbi_write_jpg failed!\n"; return {}; } diff --git a/src/send_image_popup.cpp b/src/send_image_popup.cpp index 6aa5ec4..1706957 100644 --- a/src/send_image_popup.cpp +++ b/src/send_image_popup.cpp @@ -100,40 +100,6 @@ bool SendImagePopup::load(void) { return false; } -std::vector SendImagePopup::compressWebp(const ImageLoaderI::ImageResult& input_image, uint32_t quality) { - // HACK: generic list - - return ImageEncoderWebP{}.encodeToMemoryRGBA(input_image, {{"quality", quality}}); -} - -ImageLoaderI::ImageResult SendImagePopup::crop(const ImageLoaderI::ImageResult& input_image, const Rect& crop_rect) { - // TODO: proper error handling - assert(crop_rect.x+crop_rect.w <= input_image.width); - assert(crop_rect.y+crop_rect.h <= input_image.height); - - ImageLoaderI::ImageResult new_image; - new_image.width = crop_rect.w; - new_image.height = crop_rect.h; - new_image.file_ext = input_image.file_ext; - - for (const auto& input_frame : input_image.frames) { - auto& new_frame = new_image.frames.emplace_back(); - new_frame.ms = input_frame.ms; - - // TODO: improve this, this is super inefficent - for (int64_t y = crop_rect.y; y < crop_rect.y + crop_rect.h; y++) { - for (int64_t x = crop_rect.x; x < crop_rect.x + crop_rect.w; x++) { - new_frame.data.push_back(input_frame.data.at(y*input_image.width*4+x*4+0)); - new_frame.data.push_back(input_frame.data.at(y*input_image.width*4+x*4+1)); - new_frame.data.push_back(input_frame.data.at(y*input_image.width*4+x*4+2)); - new_frame.data.push_back(input_frame.data.at(y*input_image.width*4+x*4+3)); - } - } - } - - return new_image; -} - SendImagePopup::Rect SendImagePopup::sanitizeCrop(Rect crop_rect, int32_t image_width, int32_t image_height) { // w and h min is 1 -> x/y need to be smaller so the img is atleast 1px in any dim if (crop_rect.x >= image_width-1) { @@ -441,18 +407,30 @@ void SendImagePopup::render(void) { compress = true; } - recalc_size |= ImGui::Checkbox("reencode", &compress); + recalc_size |= ImGui::Checkbox("compress", &compress); if (cropped && ImGui::IsItemHovered()) { ImGui::SetTooltip("required since cropped!"); } + static int current_compressor = 0; + if (compress) { + ImGui::SameLine(); + ImGui::Combo("##compression_type", ¤t_compressor, "webp\0jpeg\0png\n"); + ImGui::Indent(); // combo "webp""webp-lossless""png""jpg?" // if lossy quality slider (1-100) default 80 - const uint32_t qmin = 1; - const uint32_t qmax = 100; - recalc_size |= ImGui::SliderScalar("quality", ImGuiDataType_U32, &quality, &qmin, &qmax); + if (current_compressor == 0 || current_compressor == 1) { + const uint32_t qmin = 1; + const uint32_t qmax = 100; + recalc_size |= ImGui::SliderScalar("quality", ImGuiDataType_U32, &quality, &qmin, &qmax); + } + if (current_compressor == 2) { + const uint32_t qmin = 0; + const uint32_t qmax = 9; + recalc_size |= ImGui::SliderScalar("compression_level", ImGuiDataType_U32, &compression_level, &qmin, &qmax); + } if (recalc_size) { // compress and save temp? cooldown? async? save size only? @@ -470,17 +448,41 @@ void SendImagePopup::render(void) { ImGui::SameLine(); if (ImGui::Button("send ->", {-FLT_MIN, TEXT_BASE_HEIGHT*2})) { if (compress || cropped) { - std::vector new_data; + // TODO: copy bad + ImageLoaderI::ImageResult tmp_img; if (cropped) { std::cout << "SIP: CROP!!!!!\n"; - new_data = compressWebp(crop(original_image, crop_rect), quality); + tmp_img = original_image.crop( + crop_rect.x, + crop_rect.y, + crop_rect.w, + crop_rect.h + ); } else { - new_data = compressWebp(original_image, quality); + tmp_img = original_image; } - if (!new_data.empty()) { - _on_send(new_data, ".webp"); + std::vector new_data; + + // HACK: generic list + if (current_compressor == 0) { + new_data = ImageEncoderWebP{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}}); + if (!new_data.empty()) { + _on_send(new_data, ".webp"); + } + } else if (current_compressor == 1) { + new_data = ImageEncoderSTBJpeg{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}});; + if (!new_data.empty()) { + _on_send(new_data, ".jpg"); + } + } else if (current_compressor == 2) { + new_data = ImageEncoderSTBPNG{}.encodeToMemoryRGBA(tmp_img, {{"png_compression_level", compression_level}});; + if (!new_data.empty()) { + _on_send(new_data, ".png"); + } } + // error + } else { _on_send(original_data, original_file_ext); } diff --git a/src/send_image_popup.hpp b/src/send_image_popup.hpp index 20a0494..d334d0b 100644 --- a/src/send_image_popup.hpp +++ b/src/send_image_popup.hpp @@ -13,6 +13,7 @@ struct SendImagePopup { // private std::vector> _image_loaders; + std::vector> _image_encoders; // copy of the original data, dont touch! std::vector original_data; @@ -40,6 +41,7 @@ struct SendImagePopup { bool compress {false}; uint32_t quality {80u}; + uint32_t compression_level {8u}; float time {0.f}; // cycling form 0 to 1 over time @@ -55,8 +57,6 @@ struct SendImagePopup { // returns if loaded successfully bool load(void); - static std::vector compressWebp(const ImageLoaderI::ImageResult& input_image, uint32_t quality = 80u); - static ImageLoaderI::ImageResult crop(const ImageLoaderI::ImageResult& input_image, const Rect& crop_rect); static Rect sanitizeCrop(Rect crop_rect, int32_t image_width, int32_t image_height); public: