From bd7ee1c167fb30e44ce1c98ec153117569ccb57c Mon Sep 17 00:00:00 2001 From: Green Sky Date: Mon, 12 May 2025 22:07:15 +0200 Subject: [PATCH] add lossy qoi encoding using rdo --- external/CMakeLists.txt | 1 + external/libqoirdo/CMakeLists.txt | 2 + src/CMakeLists.txt | 1 + src/chat_gui/send_image_popup.cpp | 15 +++-- src/image_loader_qoi.cpp | 93 +++++++++++++++++++++++-------- 5 files changed, 83 insertions(+), 29 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a528b8d..b968fd9 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -25,6 +25,7 @@ add_subdirectory(./implot) add_subdirectory(./stb) add_subdirectory(./libwebp) add_subdirectory(./qoi) +add_subdirectory(./libqoirdo) add_subdirectory(./sdl_image) if (TOMATO_BREAKPAD) diff --git a/external/libqoirdo/CMakeLists.txt b/external/libqoirdo/CMakeLists.txt index 70406e4..d7f45f4 100644 --- a/external/libqoirdo/CMakeLists.txt +++ b/external/libqoirdo/CMakeLists.txt @@ -15,6 +15,8 @@ add_library(qoirdo target_compile_features(qoirdo PUBLIC cxx_std_11) +target_include_directories(qoirdo SYSTEM INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}") + #if (NOT MSVC) # target_link_libraries(rdopng m pthread) #endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86330a2..2b6a13d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -208,6 +208,7 @@ target_link_libraries(tomato PUBLIC WebP::webpdemux WebP::libwebpmux # the f why (needed for anim encode) qoi + qoirdo SDL3_image::SDL3_image ) diff --git a/src/chat_gui/send_image_popup.cpp b/src/chat_gui/send_image_popup.cpp index 680c49e..3f5ee1e 100644 --- a/src/chat_gui/send_image_popup.cpp +++ b/src/chat_gui/send_image_popup.cpp @@ -473,12 +473,12 @@ void SendImagePopup::render(float time_delta) { if (compress) { ImGui::SameLine(); - ImGui::Combo("##compression_type", ¤t_compressor, "webp\0webp lossless\0jpeg\0png\0qoi\0"); + ImGui::Combo("##compression_type", ¤t_compressor, "webp\0webp lossless\0jpeg\0png\0qoi\0qoi lossy\0"); ImGui::Indent(); // combo "webp""webp-lossless""png""jpg?" // if lossy quality slider (1-100) default 80 - if (current_compressor == 0 || current_compressor == 2) { + if (current_compressor == 0 || current_compressor == 2 || current_compressor == 5) { const uint32_t qmin = 1; const uint32_t qmax = 100; recalc_size |= ImGui::SliderScalar("quality", ImGuiDataType_U32, &quality, &qmin, &qmax); @@ -534,17 +534,22 @@ void SendImagePopup::render(float time_delta) { _on_send(new_data, ".webp"); } } else if (current_compressor == 2) { - new_data = ImageEncoderSTBJpeg{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}});; + new_data = ImageEncoderSTBJpeg{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}}); if (!new_data.empty()) { _on_send(new_data, ".jpg"); } } else if (current_compressor == 3) { - new_data = ImageEncoderSTBPNG{}.encodeToMemoryRGBA(tmp_img, {{"png_compression_level", compression_level}});; + new_data = ImageEncoderSTBPNG{}.encodeToMemoryRGBA(tmp_img, {{"png_compression_level", compression_level}}); if (!new_data.empty()) { _on_send(new_data, ".png"); } } else if (current_compressor == 4) { - new_data = ImageEncoderQOI{}.encodeToMemoryRGBA(tmp_img, {});; + new_data = ImageEncoderQOI{}.encodeToMemoryRGBA(tmp_img, {}); + if (!new_data.empty()) { + _on_send(new_data, ".qoi"); + } + } else if (current_compressor == 5) { + new_data = ImageEncoderQOI{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}}); if (!new_data.empty()) { _on_send(new_data, ".qoi"); } diff --git a/src/image_loader_qoi.cpp b/src/image_loader_qoi.cpp index 5c3eae7..0fe923b 100644 --- a/src/image_loader_qoi.cpp +++ b/src/image_loader_qoi.cpp @@ -1,8 +1,10 @@ #include "./image_loader_qoi.hpp" #include +#include #include +#include #include #include @@ -131,7 +133,7 @@ ImageLoaderQOI::ImageResult ImageLoaderQOI::loadFromMemoryRGBA(const uint8_t* da return res; } -std::vector ImageEncoderQOI::encodeToMemoryRGBA(const ImageResult& input_image, const std::map&) { +std::vector ImageEncoderQOI::encodeToMemoryRGBA(const ImageResult& input_image, const std::map& extra_options) { if (input_image.frames.empty()) { std::cerr << "IEQOI error: empty image\n"; return {}; @@ -143,34 +145,77 @@ std::vector ImageEncoderQOI::encodeToMemoryRGBA(const ImageResult& inpu //png_compression_level = extra_options.at("png_compression_level"); //} - qoi_desc desc; - desc.width = input_image.width; - desc.height = input_image.height; - desc.channels = 4; - desc.colorspace = QOI_SRGB; // TODO: decide + bool lossless = true; + int quality = 90; + if (extra_options.count("quality")) { + lossless = false; + quality = extra_options.at("quality"); + } std::vector new_data; - for (const auto& frame : input_image.frames) { - int out_len {0}; - uint8_t* enc_data = static_cast(qoi_encode( - frame.data.data(), - &desc, - &out_len - )); + if (lossless) { + qoi_desc desc; + desc.width = input_image.width; + desc.height = input_image.height; + desc.channels = 4; + desc.colorspace = QOI_SRGB; // TODO: decide - if (enc_data == nullptr) { - std::cerr << "IEQOI error: qoi_encode failed!\n"; - break; + for (const auto& frame : input_image.frames) { + int out_len {0}; + uint8_t* enc_data = static_cast(qoi_encode( + frame.data.data(), + &desc, + &out_len + )); + + if (enc_data == nullptr) { + std::cerr << "IEQOI error: qoi_encode failed!\n"; + break; + } + + // qoi_pipe like animation support. simple concatination of images + if (new_data.empty()) { + new_data = std::vector(enc_data, enc_data+out_len); + } else { + new_data.insert(new_data.cend(), enc_data, enc_data+out_len); + } + + free(enc_data); // TODO: a streaming encoder would be better + } + } else { // rdo + // current rdo is not thread safe + static std::mutex rdo_mutex{}; + std::lock_guard lg{rdo_mutex}; + init_qoi_rdo(); + + // TODO: merge with qoi? + qoi_rdo_desc desc; + desc.width = input_image.width; + desc.height = input_image.height; + desc.channels = 4; + desc.colorspace = QOI_SRGB; // TODO: decide + + for (const auto& frame : input_image.frames) { + auto enc_data = encode_qoi_rdo_simple( + frame.data.data(), + desc, + quality + ); + + if (enc_data.empty()) { + std::cerr << "IEQOI error: encode_qoi_rdo_simple failed!\n"; + break; + } + + // qoi_pipe like animation support. simple concatination of images + if (new_data.empty()) { + new_data = enc_data; + } else { + new_data.insert(new_data.cend(), enc_data.cbegin(), enc_data.cend()); + } } - // qoi_pipe like animation support. simple concatination of images - if (new_data.empty()) { - new_data = std::vector(enc_data, enc_data+out_len); - } else { - new_data.insert(new_data.cend(), enc_data, enc_data+out_len); - } - - free(enc_data); // TODO: a streaming encoder would be better + quit_qoi_rdo(); } return new_data;