diff options
-rw-r--r-- | Tests/LibGfx/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Tests/LibGfx/TestGfxBitmap.cpp | 128 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Bitmap.cpp | 136 |
3 files changed, 221 insertions, 44 deletions
diff --git a/Tests/LibGfx/CMakeLists.txt b/Tests/LibGfx/CMakeLists.txt index d528778d03..027aed5d86 100644 --- a/Tests/LibGfx/CMakeLists.txt +++ b/Tests/LibGfx/CMakeLists.txt @@ -3,6 +3,7 @@ set(TEST_SOURCES BenchmarkJPEGLoader.cpp TestDeltaE.cpp TestFontHandling.cpp + TestGfxBitmap.cpp TestICCProfile.cpp TestImageDecoder.cpp TestRect.cpp diff --git a/Tests/LibGfx/TestGfxBitmap.cpp b/Tests/LibGfx/TestGfxBitmap.cpp new file mode 100644 index 0000000000..271b7e7f8a --- /dev/null +++ b/Tests/LibGfx/TestGfxBitmap.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibGfx/Bitmap.h> +#include <LibTest/TestCase.h> + +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/Userland/Libraries/LibGfx/Bitmap.cpp b/Userland/Libraries/LibGfx/Bitmap.cpp index e98a200cff..7133e3ff50 100644 --- a/Userland/Libraries/LibGfx/Bitmap.cpp +++ b/Userland/Libraries/LibGfx/Bitmap.cpp @@ -384,66 +384,114 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled(float sx, float sy) const 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. + 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<float>(x) * static_cast<float>(old_width - 1) / static_cast<float>(new_width - 1); + auto q = static_cast<float>(y) * static_cast<float>(old_height - 1) / static_cast<float>(new_height - 1); + + int i = floorf(p); + int j = floorf(q); + float u = p - static_cast<float>(i); + float v = q - static_cast<float>(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); + } + } - for (int y = 0; y < new_height - 1; y++) { + // 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<float>(x) * static_cast<float>(old_width - 1) / static_cast<float>(new_width - 1); - auto q = static_cast<float>(y) * static_cast<float>(old_height - 1) / static_cast<float>(new_height - 1); int i = floorf(p); - int j = floorf(q); float u = p - static_cast<float>(i); - float v = q - static_cast<float>(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); + 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); } - } - - // 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<float>(x) * static_cast<float>(old_width - 1) / static_cast<float>(new_width - 1); - int i = floorf(p); - float u = p - static_cast<float>(i); + // 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<float>(y) * static_cast<float>(old_height - 1) / static_cast<float>(new_height - 1); - 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); - } + int j = floorf(q); + float v = q - static_cast<float>(j); - // 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<float>(y) * static_cast<float>(old_height - 1) / static_cast<float>(new_height - 1); + auto c = get_pixel(old_right_x, j); + auto d = get_pixel(old_right_x, j + 1); - int j = floorf(q); - float v = q - static_cast<float>(j); + auto color = c.mixed_with(d, v); + new_bitmap->set_pixel(new_right_x, y, color); + } - auto c = get_pixel(old_right_x, j); - auto d = get_pixel(old_right_x, j + 1); + // Bottom-right pixel + new_bitmap->set_pixel(new_width - 1, new_height - 1, get_pixel(physical_width() - 1, physical_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<float>(x) * static_cast<float>(old_width - 1) / static_cast<float>(new_width - 1); + int i = floorf(p); + float u = p - static_cast<float>(i); - auto color = c.mixed_with(d, v); - new_bitmap->set_pixel(new_right_x, y, color); - } + 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(physical_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<float>(y) * static_cast<float>(old_height - 1) / static_cast<float>(new_height - 1); + int j = floorf(q); + float v = q - static_cast<float>(j); - // Bottom-right pixel - new_bitmap->set_pixel(new_width - 1, new_height - 1, get_pixel(physical_width() - 1, physical_height() - 1)); + 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, physical_height() - 1)); + } + } return new_bitmap; } |