diff --git a/Userland/Libraries/LibGfx/Bitmap.cpp b/Userland/Libraries/LibGfx/Bitmap.cpp index 201e334a0b..b3be385944 100644 --- a/Userland/Libraries/LibGfx/Bitmap.cpp +++ b/Userland/Libraries/LibGfx/Bitmap.cpp @@ -389,6 +389,115 @@ RefPtr Bitmap::flipped(Gfx::Orientation orientation) const return new_bitmap; } +RefPtr Bitmap::scaled(int sx, int sy) const +{ + VERIFY(sx >= 0 && sy >= 0); + if (sx == 1 && sy == 1) + return this; + + auto new_bitmap = Gfx::Bitmap::create(format(), { width() * sx, height() * sy }, scale()); + if (!new_bitmap) + return nullptr; + + auto old_width = physical_width(); + auto old_height = physical_height(); + + for (int y = 0; y < old_height; y++) { + for (int x = 0; x < old_width; x++) { + auto color = get_pixel(x, y); + + auto base_x = x * sx; + auto base_y = y * sy; + for (int new_y = base_y; new_y < base_y + sy; new_y++) { + for (int new_x = base_x; new_x < base_x + sx; new_x++) { + new_bitmap->set_pixel(new_x, new_y, color); + } + } + } + } + + return new_bitmap; +} + +// http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html +RefPtr Bitmap::scaled(float sx, float sy) const +{ + VERIFY(sx >= 0.0f && sy >= 0.0f); + if (floorf(sx) == sx && floorf(sy) == sy) + return scaled(static_cast(sx), static_cast(sy)); + + auto new_bitmap = Gfx::Bitmap::create(format(), { width() * sx, height() * sy }, scale()); + if (!new_bitmap) + return nullptr; + + auto old_width = physical_width(); + auto old_height = physical_height(); + auto new_width = new_bitmap->physical_width(); + auto new_height = new_bitmap->physical_height(); + + // The interpolation goes out of bounds on the bottom- and right-most edges. + // We handle those in two specialized loops not only to make them faster, but + // also to avoid four branch checks for every pixel. + + for (int y = 0; y < new_height - 1; y++) { + for (int x = 0; x < new_width - 1; x++) { + auto p = static_cast(x) * static_cast(old_width - 1) / static_cast(new_width - 1); + auto q = static_cast(y) * static_cast(old_height - 1) / static_cast(new_height - 1); + + int i = floor(p); + int j = floor(q); + float u = p - static_cast(i); + float v = q - static_cast(j); + + auto a = get_pixel(i, j); + auto b = get_pixel(i + 1, j); + auto c = get_pixel(i, j + 1); + auto d = get_pixel(i + 1, j + 1); + + auto e = a.interpolate(b, u); + auto f = c.interpolate(d, u); + auto color = e.interpolate(f, v); + new_bitmap->set_pixel(x, y, color); + } + } + + // Bottom strip (excluding last pixel) + auto old_bottom_y = old_height - 1; + auto new_bottom_y = new_height - 1; + for (int x = 0; x < new_width - 1; x++) { + auto p = static_cast(x) * static_cast(old_width - 1) / static_cast(new_width - 1); + + int i = floor(p); + float u = p - static_cast(i); + + auto a = get_pixel(i, old_bottom_y); + auto b = get_pixel(i + 1, old_bottom_y); + auto color = a.interpolate(b, u); + new_bitmap->set_pixel(x, new_bottom_y, color); + } + + // Right strip (excluding last pixel) + auto old_right_x = old_width - 1; + auto new_right_x = new_width - 1; + for (int y = 0; y < new_height - 1; y++) { + auto q = static_cast(y) * static_cast(old_height - 1) / static_cast(new_height - 1); + + int j = floor(q); + float v = q - static_cast(j); + + auto c = get_pixel(old_right_x, j); + auto d = get_pixel(old_right_x, j + 1); + + auto color = c.interpolate(d, v); + new_bitmap->set_pixel(new_right_x, y, color); + } + + // Bottom-right pixel + new_bitmap->set_pixel(new_width - 1, new_height - 1, get_pixel(physical_width() - 1, physical_height() - 1)); + + return new_bitmap; +} + #ifdef __serenity__ RefPtr Bitmap::to_bitmap_backed_by_anon_fd() const { diff --git a/Userland/Libraries/LibGfx/Bitmap.h b/Userland/Libraries/LibGfx/Bitmap.h index 572a526cc2..be920a64ee 100644 --- a/Userland/Libraries/LibGfx/Bitmap.h +++ b/Userland/Libraries/LibGfx/Bitmap.h @@ -115,6 +115,8 @@ public: RefPtr rotated(Gfx::RotationDirection) const; RefPtr flipped(Gfx::Orientation) const; + RefPtr scaled(int sx, int sy) const; + RefPtr scaled(float sx, float sy) const; RefPtr to_bitmap_backed_by_anon_fd() const; ByteBuffer serialize_to_byte_buffer() const;