From eb418bec32dbd2371d2f6ead973ff7e93b16c919 Mon Sep 17 00:00:00 2001 From: Jelle Raaijmakers Date: Fri, 19 May 2023 00:26:34 +0200 Subject: [PATCH] LibGfx: Implement box sampling image scaling Box sampling is a scaling algorithm that averages all the pixels that form the source for the target pixel. For example, if you would resize a 9x9 image to 3x3, each target pixel would encompass a 3x3 pixel area in the source image. Box sampling is a near perfect scaling algorithm for downscaling. When upscaling with this algorithm, the result is similar to nearest neighbor or smooth pixels. --- Userland/Libraries/LibGfx/Painter.cpp | 59 +++++++++++++++++++++++++++ Userland/Libraries/LibGfx/Painter.h | 1 + 2 files changed, 60 insertions(+) diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index a4dd0a2544..5771b6f6d6 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -1180,6 +1180,59 @@ ALWAYS_INLINE static void do_draw_integer_scaled_bitmap(Gfx::Bitmap& target, Int } } +template +ALWAYS_INLINE static void do_draw_box_sampled_scaled_bitmap(Gfx::Bitmap& target, IntRect const& dst_rect, IntRect const& clipped_rect, Gfx::Bitmap const& source, FloatRect const& src_rect, GetPixel get_pixel, float opacity) +{ + float source_pixel_width = src_rect.width() / dst_rect.width(); + float source_pixel_height = src_rect.height() / dst_rect.height(); + float source_pixel_area = source_pixel_width * source_pixel_height; + + for (int y = clipped_rect.top(); y <= clipped_rect.bottom(); ++y) { + auto* scanline = reinterpret_cast(target.scanline(y)); + for (int x = clipped_rect.left(); x <= clipped_rect.right(); ++x) { + // Project the destination pixel in the source image + FloatRect source_box = { + src_rect.left() + (x - dst_rect.x()) * source_pixel_width, + src_rect.top() + (y - dst_rect.y()) * source_pixel_height, + source_pixel_width, + source_pixel_height, + }; + IntRect enclosing_source_box = enclosing_int_rect(source_box).intersected(source.rect()); + + // Sum the contribution of all source pixels inside the projected pixel + float red_accumulator = 0.f; + float green_accumulator = 0.f; + float blue_accumulator = 0.f; + float total_area = 0.f; + for (int sy = enclosing_source_box.y(); sy <= enclosing_source_box.bottom(); ++sy) { + for (int sx = enclosing_source_box.x(); sx <= enclosing_source_box.right(); ++sx) { + float area = source_box.intersected({ static_cast(sx), static_cast(sy), 1.f, 1.f }).size().area(); + + auto pixel = get_pixel(source, sx, sy); + area *= pixel.alpha() / 255.f; + + red_accumulator += pixel.red() * area; + green_accumulator += pixel.green() * area; + blue_accumulator += pixel.blue() * area; + total_area += area; + } + } + + Color src_pixel = { + round_to(min(red_accumulator / total_area, 255.f)), + round_to(min(green_accumulator / total_area, 255.f)), + round_to(min(blue_accumulator / total_area, 255.f)), + round_to(min(total_area * 255.f / source_pixel_area * opacity, 255.f)), + }; + + if constexpr (has_alpha_channel) + scanline[x] = scanline[x].blend(src_pixel); + else + scanline[x] = src_pixel; + } + } +} + template ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, IntRect const& dst_rect, IntRect const& clipped_rect, Gfx::Bitmap const& source, FloatRect const& src_rect, GetPixel get_pixel, float opacity) { @@ -1208,6 +1261,9 @@ ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, IntRect con } } + if constexpr (scaling_mode == Painter::ScalingMode::BoxSampling) + return do_draw_box_sampled_scaled_bitmap(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity); + bool has_opacity = opacity != 1.f; i64 shift = (i64)1 << 32; i64 fractional_mask = shift - (u64)1; @@ -1307,6 +1363,9 @@ ALWAYS_INLINE static void do_draw_scaled_bitmap(Gfx::Bitmap& target, IntRect con case Painter::ScalingMode::BilinearBlend: do_draw_scaled_bitmap(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity); break; + case Painter::ScalingMode::BoxSampling: + do_draw_scaled_bitmap(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity); + break; case Painter::ScalingMode::None: do_draw_scaled_bitmap(target, dst_rect, clipped_rect, source, src_rect, get_pixel, opacity); break; diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h index 508bcac814..07f9eb6d5a 100644 --- a/Userland/Libraries/LibGfx/Painter.h +++ b/Userland/Libraries/LibGfx/Painter.h @@ -45,6 +45,7 @@ public: NearestNeighbor, SmoothPixels, BilinearBlend, + BoxSampling, None, };