diff --git a/Tests/LibGfx/CMakeLists.txt b/Tests/LibGfx/CMakeLists.txt index 05c38ceb459..b8a36c8c489 100644 --- a/Tests/LibGfx/CMakeLists.txt +++ b/Tests/LibGfx/CMakeLists.txt @@ -3,14 +3,12 @@ set(TEST_SOURCES BenchmarkJPEGLoader.cpp TestColor.cpp TestDeltaE.cpp - TestGfxBitmap.cpp TestICCProfile.cpp TestImageDecoder.cpp TestImageWriter.cpp TestMedianCut.cpp TestPainter.cpp TestRect.cpp - TestScalingFunctions.cpp TestWOFF.cpp TestWOFF2.cpp ) diff --git a/Tests/LibGfx/TestGfxBitmap.cpp b/Tests/LibGfx/TestGfxBitmap.cpp deleted file mode 100644 index 271b7e7f8a4..00000000000 --- a/Tests/LibGfx/TestGfxBitmap.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2022, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -TEST_CASE(0001_bitmap_upscaling_width1_height1) -{ - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 1, 1 }); - EXPECT_EQ(bitmap.is_error(), false); - bitmap.value()->fill(Gfx::Color::White); - auto scaledBitmap = bitmap.value()->scaled(5.5f, 5.5f); - EXPECT_EQ(scaledBitmap.is_error(), false); - EXPECT_EQ(scaledBitmap.value()->size(), Gfx::IntSize(6, 6)); - for (auto x = 0; x < scaledBitmap.value()->width(); x++) { - for (auto y = 0; y < scaledBitmap.value()->height(); y++) { - EXPECT_EQ(scaledBitmap.value()->get_pixel(x, y), bitmap.value()->get_pixel(0, 0)); - } - } -} - -TEST_CASE(0002_bitmap_upscaling_width1) -{ - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 1, 10 }); - EXPECT_EQ(bitmap.is_error(), false); - bitmap.value()->fill(Gfx::Color::White); - auto scaledBitmap = bitmap.value()->scaled(5.5f, 5.5f); - EXPECT_EQ(scaledBitmap.is_error(), false); - EXPECT_EQ(scaledBitmap.value()->size(), Gfx::IntSize(6, 55)); - for (auto x = 0; x < scaledBitmap.value()->width(); x++) { - for (auto y = 0; y < scaledBitmap.value()->height(); y++) { - EXPECT_EQ(scaledBitmap.value()->get_pixel(x, y), bitmap.value()->get_pixel(0, 0)); - } - } -} - -TEST_CASE(0003_bitmap_upscaling_height1) -{ - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 10, 1 }); - EXPECT_EQ(bitmap.is_error(), false); - bitmap.value()->fill(Gfx::Color::White); - auto scaledBitmap = bitmap.value()->scaled(5.5f, 5.5f); - EXPECT_EQ(scaledBitmap.is_error(), false); - EXPECT_EQ(scaledBitmap.value()->size(), Gfx::IntSize(55, 6)); - for (auto x = 0; x < scaledBitmap.value()->width(); x++) { - for (auto y = 0; y < scaledBitmap.value()->height(); y++) { - EXPECT_EQ(scaledBitmap.value()->get_pixel(x, y), bitmap.value()->get_pixel(0, 0)); - } - } -} - -TEST_CASE(0004_bitmap_upscaling_keep_width) -{ - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 1, 10 }); - EXPECT_EQ(bitmap.is_error(), false); - bitmap.value()->fill(Gfx::Color::White); - auto scaledBitmap = bitmap.value()->scaled(1.f, 5.5f); - EXPECT_EQ(scaledBitmap.is_error(), false); - EXPECT_EQ(scaledBitmap.value()->size(), Gfx::IntSize(1, 55)); - for (auto x = 0; x < scaledBitmap.value()->width(); x++) { - for (auto y = 0; y < scaledBitmap.value()->height(); y++) { - EXPECT_EQ(scaledBitmap.value()->get_pixel(x, y), bitmap.value()->get_pixel(0, 0)); - } - } -} - -TEST_CASE(0005_bitmap_upscaling_keep_height) -{ - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 10, 1 }); - EXPECT_EQ(bitmap.is_error(), false); - bitmap.value()->fill(Gfx::Color::White); - auto scaledBitmap = bitmap.value()->scaled(5.5f, 1.f); - EXPECT_EQ(scaledBitmap.is_error(), false); - EXPECT_EQ(scaledBitmap.value()->size(), Gfx::IntSize(55, 1)); - for (auto x = 0; x < scaledBitmap.value()->width(); x++) { - for (auto y = 0; y < scaledBitmap.value()->height(); y++) { - EXPECT_EQ(scaledBitmap.value()->get_pixel(x, y), bitmap.value()->get_pixel(0, 0)); - } - } -} - -TEST_CASE(0006_bitmap_downscaling_width1_height1) -{ - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 10, 10 }); - EXPECT_EQ(bitmap.is_error(), false); - bitmap.value()->fill(Gfx::Color::White); - auto scaledBitmap = bitmap.value()->scaled(0.099f, 0.099f); - EXPECT_EQ(scaledBitmap.is_error(), false); - EXPECT_EQ(scaledBitmap.value()->size(), Gfx::IntSize(1, 1)); - for (auto x = 0; x < scaledBitmap.value()->width(); x++) { - for (auto y = 0; y < scaledBitmap.value()->height(); y++) { - EXPECT_EQ(scaledBitmap.value()->get_pixel(x, y), bitmap.value()->get_pixel(0, 0)); - } - } -} - -TEST_CASE(0007_bitmap_downscaling_width1) -{ - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 10, 10 }); - EXPECT_EQ(bitmap.is_error(), false); - bitmap.value()->fill(Gfx::Color::White); - auto scaledBitmap = bitmap.value()->scaled(1.f, 0.099f); - EXPECT_EQ(scaledBitmap.is_error(), false); - EXPECT_EQ(scaledBitmap.value()->size(), Gfx::IntSize(10, 1)); - for (auto x = 0; x < scaledBitmap.value()->width(); x++) { - for (auto y = 0; y < scaledBitmap.value()->height(); y++) { - EXPECT_EQ(scaledBitmap.value()->get_pixel(x, y), bitmap.value()->get_pixel(0, 0)); - } - } -} - -TEST_CASE(0008_bitmap_downscaling_height1) -{ - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 10, 10 }); - EXPECT_EQ(bitmap.is_error(), false); - bitmap.value()->fill(Gfx::Color::White); - auto scaledBitmap = bitmap.value()->scaled(0.099f, 1.f); - EXPECT_EQ(scaledBitmap.is_error(), false); - EXPECT_EQ(scaledBitmap.value()->size(), Gfx::IntSize(1, 10)); - for (auto x = 0; x < scaledBitmap.value()->width(); x++) { - for (auto y = 0; y < scaledBitmap.value()->height(); y++) { - EXPECT_EQ(scaledBitmap.value()->get_pixel(x, y), bitmap.value()->get_pixel(0, 0)); - } - } -} diff --git a/Tests/LibGfx/TestScalingFunctions.cpp b/Tests/LibGfx/TestScalingFunctions.cpp deleted file mode 100644 index f074c024f49..00000000000 --- a/Tests/LibGfx/TestScalingFunctions.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2023, Tim Ledbetter - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -// Scaling modes which use linear interpolation should use premultiplied alpha. -// This prevents colors from changing hue unexpectedly when there is a change in opacity. -// This test uses an image that transitions from a completely opaque pixel in the top left to a completely transparent background. -// We ensure that premultipled alpha is used by checking that the RGB values of the interpolated pixels do not change, just the alpha values. -TEST_CASE(test_painter_scaling_uses_premultiplied_alpha) -{ - auto test_scaling_mode = [](auto scaling_mode) { - auto src_bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { 2, 2 })); - src_bitmap->fill(Color::Transparent); - src_bitmap->set_pixel({ 0, 0 }, Color::White); - - auto scaled_bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { 5, 5 })); - scaled_bitmap->fill(Color::Transparent); - - Gfx::DeprecatedPainter painter(scaled_bitmap); - painter.draw_scaled_bitmap(scaled_bitmap->rect(), src_bitmap, src_bitmap->rect(), 1.0f, scaling_mode); - - auto top_left_pixel = scaled_bitmap->get_pixel(0, 0); - EXPECT_EQ(top_left_pixel, Color::White); - - auto center_pixel = scaled_bitmap->get_pixel(scaled_bitmap->rect().center()); - EXPECT(center_pixel.alpha() > 0); - EXPECT(center_pixel.alpha() < 255); - EXPECT_EQ(center_pixel.with_alpha(0), Color(Color::White).with_alpha(0)); - - auto bottom_right_pixel = scaled_bitmap->get_pixel(scaled_bitmap->rect().bottom_right().translated(-1)); - EXPECT_EQ(bottom_right_pixel, Color::Transparent); - }; - - test_scaling_mode(Gfx::ScalingMode::BilinearBlend); - // FIXME: Include ScalingMode::SmoothPixels as part of this test - // This mode does not currently pass this test, as it behave according to the spec - // defined here: https://drafts.csswg.org/css-images/#valdef-image-rendering-pixelated - // test_scaling_mode(Gfx::ScalingMode::SmoothPixels); -} - -TEST_CASE(test_bitmap_scaling_uses_premultiplied_alpha) -{ - auto src_bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { 2, 2 })); - src_bitmap->fill(Color::Transparent); - src_bitmap->set_pixel({ 0, 0 }, Color::White); - - auto scaled_bitmap = MUST(src_bitmap->scaled(2.5f, 2.5f)); - EXPECT_EQ(scaled_bitmap->width(), 5); - EXPECT_EQ(scaled_bitmap->height(), 5); - auto top_left_pixel = scaled_bitmap->get_pixel(0, 0); - EXPECT_EQ(top_left_pixel, Color::White); - - auto center_pixel = scaled_bitmap->get_pixel(scaled_bitmap->rect().center()); - EXPECT(center_pixel.alpha() > 0); - EXPECT(center_pixel.alpha() < 255); - EXPECT_EQ(center_pixel.with_alpha(0), Color(Color::White).with_alpha(0)); - - auto bottom_right_pixel = scaled_bitmap->get_pixel(scaled_bitmap->rect().bottom_right().translated(-1)); - EXPECT_EQ(bottom_right_pixel, Color::Transparent); -} diff --git a/Userland/Libraries/LibGfx/Bitmap.cpp b/Userland/Libraries/LibGfx/Bitmap.cpp index 0a3e3926d66..2a371dc901a 100644 --- a/Userland/Libraries/LibGfx/Bitmap.cpp +++ b/Userland/Libraries/LibGfx/Bitmap.cpp @@ -192,166 +192,6 @@ void Bitmap::apply_mask(Gfx::Bitmap const& mask, MaskKind mask_kind) } } -ErrorOr> Bitmap::scaled(int sx, int sy) const -{ - VERIFY(sx >= 0 && sy >= 0); - if (sx == 1 && sy == 1) - return clone(); - - auto new_bitmap = TRY(Gfx::Bitmap::create(format(), alpha_type(), { width() * sx, height() * sy })); - - auto old_width = width(); - auto old_height = 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; -} - -ErrorOr> 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)); - - int scaled_width = (int)ceilf(sx * (float)width()); - int scaled_height = (int)ceilf(sy * (float)height()); - return scaled_to_size({ scaled_width, scaled_height }); -} - -// http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html -ErrorOr> Bitmap::scaled_to_size(Gfx::IntSize size) const -{ - auto new_bitmap = TRY(Gfx::Bitmap::create(format(), alpha_type(), size)); - - auto old_width = width(); - auto old_height = height(); - auto new_width = new_bitmap->width(); - auto new_height = new_bitmap->height(); - - if (old_width == 1 && old_height == 1) { - new_bitmap->fill(get_pixel(0, 0)); - return new_bitmap; - } - - if (old_width > 1 && old_height > 1) { - // 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 = floorf(p); - int j = floorf(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.mixed_with(b, u); - auto f = c.mixed_with(d, u); - auto color = e.mixed_with(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 = floorf(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.mixed_with(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 = floorf(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.mixed_with(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(width() - 1, height() - 1)); - return new_bitmap; - } else if (old_height == 1) { - // Copy horizontal strip multiple times (excluding last pixel to out of bounds). - auto old_bottom_y = old_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 = floorf(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.mixed_with(b, u); - for (int new_bottom_y = 0; new_bottom_y < new_height; new_bottom_y++) { - // Interpolate color only once and then copy into all columns. - new_bitmap->set_pixel(x, new_bottom_y, color); - } - } - for (int new_bottom_y = 0; new_bottom_y < new_height; new_bottom_y++) { - // Copy last pixel of horizontal strip - new_bitmap->set_pixel(new_width - 1, new_bottom_y, get_pixel(width() - 1, old_bottom_y)); - } - return new_bitmap; - } else if (old_width == 1) { - // Copy vertical strip multiple times (excluding last pixel to avoid out of bounds). - auto old_right_x = old_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 = floorf(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.mixed_with(d, v); - for (int new_right_x = 0; new_right_x < new_width; new_right_x++) { - // Interpolate color only once and copy into all rows. - new_bitmap->set_pixel(new_right_x, y, color); - } - } - for (int new_right_x = 0; new_right_x < new_width; new_right_x++) { - // Copy last pixel of vertical strip - new_bitmap->set_pixel(new_right_x, new_height - 1, get_pixel(old_right_x, height() - 1)); - } - } - return new_bitmap; -} - ErrorOr> Bitmap::cropped(Gfx::IntRect crop, Optional new_bitmap_format) const { auto new_bitmap = TRY(Gfx::Bitmap::create(new_bitmap_format.value_or(format()), alpha_type(), { crop.width(), crop.height() })); diff --git a/Userland/Libraries/LibGfx/Bitmap.h b/Userland/Libraries/LibGfx/Bitmap.h index 7a7915ca71c..b3751b562d0 100644 --- a/Userland/Libraries/LibGfx/Bitmap.h +++ b/Userland/Libraries/LibGfx/Bitmap.h @@ -73,9 +73,6 @@ public: ErrorOr> clone() const; - ErrorOr> scaled(int sx, int sy) const; - ErrorOr> scaled(float sx, float sy) const; - ErrorOr> scaled_to_size(Gfx::IntSize) const; ErrorOr> cropped(Gfx::IntRect, Optional new_bitmap_format = {}) const; ErrorOr> to_bitmap_backed_by_anonymous_buffer() const;