add image scaler (box sampling)
This commit is contained in:
@@ -53,6 +53,8 @@ target_sources(tomato PUBLIC
|
|||||||
./image_loader_qoi.cpp
|
./image_loader_qoi.cpp
|
||||||
./image_loader_sdl_image.hpp
|
./image_loader_sdl_image.hpp
|
||||||
./image_loader_sdl_image.cpp
|
./image_loader_sdl_image.cpp
|
||||||
|
./image_scaler.hpp
|
||||||
|
./image_scaler.cpp
|
||||||
|
|
||||||
./texture_uploader.hpp
|
./texture_uploader.hpp
|
||||||
./sdlrenderer_texture_uploader.hpp
|
./sdlrenderer_texture_uploader.hpp
|
||||||
|
|||||||
144
src/image_scaler.cpp
Normal file
144
src/image_scaler.cpp
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
#include "./image_scaler.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
// 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<typename ColorCanvas, typename ColorTmp>
|
||||||
|
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<uint8_t*>(dst->pixels)};
|
||||||
|
const ColorCanvas8888 src_c{reinterpret_cast<uint8_t*>(src->pixels)};
|
||||||
|
|
||||||
|
image_scale<ColorCanvas8888, ColorFloat4>(
|
||||||
|
dst_c,
|
||||||
|
dst->w, dst->h,
|
||||||
|
src_c,
|
||||||
|
src->w, src->h
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
6
src/image_scaler.hpp
Normal file
6
src/image_scaler.hpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
bool image_scale(SDL_Surface* dst, SDL_Surface* src);
|
||||||
|
|
||||||
Reference in New Issue
Block a user