summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Tests/LibGfx/CMakeLists.txt1
-rw-r--r--Tests/LibGfx/TestGfxBitmap.cpp128
-rw-r--r--Userland/Libraries/LibGfx/Bitmap.cpp136
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;
}