add lossy qoi encoding using rdo
Some checks are pending
ContinuousDelivery / linux-ubuntu (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android-23]) (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android-23]) (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android-23]) (push) Waiting to run
ContinuousDelivery / windows (push) Waiting to run
ContinuousDelivery / windows-asan (push) Waiting to run
ContinuousDelivery / dumpsyms (push) Blocked by required conditions
ContinuousDelivery / release (push) Blocked by required conditions
ContinuousIntegration / linux (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android-23]) (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android-23]) (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android-23]) (push) Waiting to run
ContinuousIntegration / macos (push) Waiting to run
ContinuousIntegration / windows (push) Waiting to run

This commit is contained in:
Green Sky 2025-05-12 22:07:15 +02:00
parent 9878cabc9c
commit bd7ee1c167
No known key found for this signature in database
GPG Key ID: DBE05085D874AB4A
5 changed files with 83 additions and 29 deletions

View File

@ -25,6 +25,7 @@ add_subdirectory(./implot)
add_subdirectory(./stb) add_subdirectory(./stb)
add_subdirectory(./libwebp) add_subdirectory(./libwebp)
add_subdirectory(./qoi) add_subdirectory(./qoi)
add_subdirectory(./libqoirdo)
add_subdirectory(./sdl_image) add_subdirectory(./sdl_image)
if (TOMATO_BREAKPAD) if (TOMATO_BREAKPAD)

View File

@ -15,6 +15,8 @@ add_library(qoirdo
target_compile_features(qoirdo PUBLIC cxx_std_11) target_compile_features(qoirdo PUBLIC cxx_std_11)
target_include_directories(qoirdo SYSTEM INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
#if (NOT MSVC) #if (NOT MSVC)
# target_link_libraries(rdopng m pthread) # target_link_libraries(rdopng m pthread)
#endif() #endif()

View File

@ -208,6 +208,7 @@ target_link_libraries(tomato PUBLIC
WebP::webpdemux WebP::webpdemux
WebP::libwebpmux # the f why (needed for anim encode) WebP::libwebpmux # the f why (needed for anim encode)
qoi qoi
qoirdo
SDL3_image::SDL3_image SDL3_image::SDL3_image
) )

View File

@ -473,12 +473,12 @@ void SendImagePopup::render(float time_delta) {
if (compress) { if (compress) {
ImGui::SameLine(); ImGui::SameLine();
ImGui::Combo("##compression_type", &current_compressor, "webp\0webp lossless\0jpeg\0png\0qoi\0"); ImGui::Combo("##compression_type", &current_compressor, "webp\0webp lossless\0jpeg\0png\0qoi\0qoi lossy\0");
ImGui::Indent(); ImGui::Indent();
// combo "webp""webp-lossless""png""jpg?" // combo "webp""webp-lossless""png""jpg?"
// if lossy quality slider (1-100) default 80 // 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 qmin = 1;
const uint32_t qmax = 100; const uint32_t qmax = 100;
recalc_size |= ImGui::SliderScalar("quality", ImGuiDataType_U32, &quality, &qmin, &qmax); 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"); _on_send(new_data, ".webp");
} }
} else if (current_compressor == 2) { } 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()) { if (!new_data.empty()) {
_on_send(new_data, ".jpg"); _on_send(new_data, ".jpg");
} }
} else if (current_compressor == 3) { } 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()) { if (!new_data.empty()) {
_on_send(new_data, ".png"); _on_send(new_data, ".png");
} }
} else if (current_compressor == 4) { } 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()) { if (!new_data.empty()) {
_on_send(new_data, ".qoi"); _on_send(new_data, ".qoi");
} }

View File

@ -1,8 +1,10 @@
#include "./image_loader_qoi.hpp" #include "./image_loader_qoi.hpp"
#include <qoi/qoi.h> #include <qoi/qoi.h>
#include <qoirdo.hpp>
#include <cstdint> #include <cstdint>
#include <mutex>
#include <cassert> #include <cassert>
#include <iostream> #include <iostream>
@ -131,7 +133,7 @@ ImageLoaderQOI::ImageResult ImageLoaderQOI::loadFromMemoryRGBA(const uint8_t* da
return res; return res;
} }
std::vector<uint8_t> ImageEncoderQOI::encodeToMemoryRGBA(const ImageResult& input_image, const std::map<std::string, float>&) { std::vector<uint8_t> ImageEncoderQOI::encodeToMemoryRGBA(const ImageResult& input_image, const std::map<std::string, float>& extra_options) {
if (input_image.frames.empty()) { if (input_image.frames.empty()) {
std::cerr << "IEQOI error: empty image\n"; std::cerr << "IEQOI error: empty image\n";
return {}; return {};
@ -143,13 +145,21 @@ std::vector<uint8_t> ImageEncoderQOI::encodeToMemoryRGBA(const ImageResult& inpu
//png_compression_level = extra_options.at("png_compression_level"); //png_compression_level = extra_options.at("png_compression_level");
//} //}
bool lossless = true;
int quality = 90;
if (extra_options.count("quality")) {
lossless = false;
quality = extra_options.at("quality");
}
std::vector<uint8_t> new_data;
if (lossless) {
qoi_desc desc; qoi_desc desc;
desc.width = input_image.width; desc.width = input_image.width;
desc.height = input_image.height; desc.height = input_image.height;
desc.channels = 4; desc.channels = 4;
desc.colorspace = QOI_SRGB; // TODO: decide desc.colorspace = QOI_SRGB; // TODO: decide
std::vector<uint8_t> new_data;
for (const auto& frame : input_image.frames) { for (const auto& frame : input_image.frames) {
int out_len {0}; int out_len {0};
uint8_t* enc_data = static_cast<uint8_t*>(qoi_encode( uint8_t* enc_data = static_cast<uint8_t*>(qoi_encode(
@ -172,6 +182,41 @@ std::vector<uint8_t> ImageEncoderQOI::encodeToMemoryRGBA(const ImageResult& inpu
free(enc_data); // TODO: a streaming encoder would be better 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());
}
}
quit_qoi_rdo();
}
return new_data; return new_data;
} }