From e8a15a58dd266db6328fcf3fd9d980fc34d57d5d Mon Sep 17 00:00:00 2001 From: Green Sky Date: Wed, 5 Nov 2025 00:01:42 +0100 Subject: [PATCH] add image scaler (box sampling) --- src/CMakeLists.txt | 2 + src/image_scaler.cpp | 144 +++++++++++++++++++++++++++++++++++++++++++ src/image_scaler.hpp | 6 ++ 3 files changed, 152 insertions(+) create mode 100644 src/image_scaler.cpp create mode 100644 src/image_scaler.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e04557..41cea53 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,8 @@ target_sources(tomato PUBLIC ./image_loader_qoi.cpp ./image_loader_sdl_image.hpp ./image_loader_sdl_image.cpp + ./image_scaler.hpp + ./image_scaler.cpp ./texture_uploader.hpp ./sdlrenderer_texture_uploader.hpp diff --git a/src/image_scaler.cpp b/src/image_scaler.cpp new file mode 100644 index 0000000..4a984c8 --- /dev/null +++ b/src/image_scaler.cpp @@ -0,0 +1,144 @@ +#include "./image_scaler.hpp" + +#include +#include + +// requires ColorTmp to have * and + operators + +struct ColorCanvas8888; + +struct ColorFloat4 { + float v[4]{}; + + ColorFloat4& operator*=(const float scalar) { + v[0] *= scalar; + v[1] *= scalar; + v[2] *= scalar; + v[3] *= scalar; + return *this; + } + + ColorFloat4 operator*(const float scalar) const { + ColorFloat4 newcf = *this; + newcf.v[0] *= scalar; + newcf.v[1] *= scalar; + newcf.v[2] *= scalar; + newcf.v[3] *= scalar; + return newcf; + } + + ColorFloat4& operator/=(const float scalar) { + v[0] /= scalar; + v[1] /= scalar; + v[2] /= scalar; + v[3] /= scalar; + return *this; + } + + ColorFloat4& operator+=(const ColorFloat4& color) { + v[0] += color.v[0]; + v[1] += color.v[1]; + v[2] += color.v[2]; + v[3] += color.v[3]; + return *this; + } +}; + +struct ColorCanvas8888 { + uint8_t* ptr {nullptr}; + + ColorFloat4 operator[](size_t i) const { + return { + { + float(ptr[i*4+0])/255.f, + float(ptr[i*4+1])/255.f, + float(ptr[i*4+2])/255.f, + float(ptr[i*4+3])/255.f, + } + }; + } + + void set(size_t i, const ColorFloat4& color) { + ptr[i*4+0] = std::round(color.v[0]*255.f); + ptr[i*4+1] = std::round(color.v[1]*255.f); + ptr[i*4+2] = std::round(color.v[2]*255.f); + ptr[i*4+3] = std::round(color.v[3]*255.f); + } +}; + +template +void image_scale(ColorCanvas& dst, const int dst_w, const int dst_h, const ColorCanvas& src, const int src_w, const int src_h) { + // Box sampling - Imagine projecting the new, smaller pixels onto the larger source, covering multiple pixel. + for (int y = 0; y < dst_h; y++) { + for (int x = 0; x < dst_w; x++) { + // We perform a weighted mean. + ColorTmp color; + float weight_sum = 0.f; + + // Walk from upper edge to bottom edge (vertical) + const float edge_up = ((float)y * src_h) / dst_h; + const float edge_down = ((y + 1.f) * src_h) / dst_h; + for (float frac_pos_y = edge_up; frac_pos_y < edge_down;) { + const int src_y = (int)std::floor(frac_pos_y); assert(src_y < src_h); + const float frac_y = 1.f - (frac_pos_y - src_y); + + // Walk from left edge to right edge (horizontal) + const float edge_left = ((float)x * src_w) / dst_w; + const float edge_right = ((x + 1.f) * src_w) / dst_w; + for (float frac_pos_x = edge_left; frac_pos_x < edge_right;) { + const int src_x = (int)std::floor(frac_pos_x); assert(src_x < src_w); + const float frac_x = 1.f - (frac_pos_x - src_x); + + const float src_pixel_weight = frac_x * frac_y; + + //const ColorTmp pixel_color = ImGui::ColorConvertU32ToFloat4(src[src_y * src_w + src_x]); + const ColorTmp pixel_color = src[src_y * src_w + src_x]; + color += pixel_color * src_pixel_weight; + weight_sum += src_pixel_weight; + + frac_pos_x += frac_x; + } + + frac_pos_y += frac_y; + } + + color /= weight_sum; + + dst.set(y * dst_w + x, color); + } + } +} + +bool image_scale(SDL_Surface* dst, SDL_Surface* src) { + if (dst->format != src->format) { + return false; + } + + // TODO: handle other numbers of components beside 4 + + if ( + src->format != SDL_PIXELFORMAT_RGBA8888 && + src->format != SDL_PIXELFORMAT_ARGB8888 && + src->format != SDL_PIXELFORMAT_BGRA8888 && + src->format != SDL_PIXELFORMAT_ABGR8888 && + src->format != SDL_PIXELFORMAT_RGBX8888 && + src->format != SDL_PIXELFORMAT_XRGB8888 && + src->format != SDL_PIXELFORMAT_BGRX8888 && + src->format != SDL_PIXELFORMAT_XBGR8888 + ) { + return false; + } + + ColorCanvas8888 dst_c{reinterpret_cast(dst->pixels)}; + const ColorCanvas8888 src_c{reinterpret_cast(src->pixels)}; + + image_scale( + dst_c, + dst->w, dst->h, + src_c, + src->w, src->h + ); + + return true; +} + diff --git a/src/image_scaler.hpp b/src/image_scaler.hpp new file mode 100644 index 0000000..755a94b --- /dev/null +++ b/src/image_scaler.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include + +bool image_scale(SDL_Surface* dst, SDL_Surface* src); +