Browse Source

LibGfx: Fix off-by-some in Painter::draw_scaled_bitmap_with_transform()

Before this, drawing a 1x1 bitmap scaled up to MxN would only fill
M/2 x N/2 pixel, due to source_point going outside (0, 0).
Nico Weber 1 year ago
parent
commit
7fb32b6682
3 changed files with 53 additions and 1 deletions
  1. 1 0
      Tests/LibGfx/CMakeLists.txt
  2. 48 0
      Tests/LibGfx/TestPainter.cpp
  3. 4 1
      Userland/Libraries/LibGfx/Painter.cpp

+ 1 - 0
Tests/LibGfx/CMakeLists.txt

@@ -6,6 +6,7 @@ set(TEST_SOURCES
     TestGfxBitmap.cpp
     TestICCProfile.cpp
     TestImageDecoder.cpp
+    TestPainter.cpp
     TestParseISOBMFF.cpp
     TestRect.cpp
     TestScalingFunctions.cpp

+ 48 - 0
Tests/LibGfx/TestPainter.cpp

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024, Nico Weber <thakis@chromium.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibTest/TestCase.h>
+
+#include <LibGfx/Painter.h>
+
+TEST_CASE(draw_scaled_bitmap_with_transform)
+{
+    auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { 40, 30 }));
+    bitmap->fill(Gfx::Color::White);
+    Gfx::Painter painter(bitmap);
+
+    auto source_bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { 1, 1 }));
+    source_bitmap->fill(Gfx::Color::Black);
+
+    auto dest_rect = source_bitmap->rect();
+    auto source_rect = source_bitmap->rect().to_rounded<float>();
+
+    // Identity transform: Lower left pixel is black, rest stays white.
+    Gfx::AffineTransform transform;
+    painter.draw_scaled_bitmap_with_transform(dest_rect, source_bitmap, source_rect, transform);
+    for (int y = 0; y < bitmap->height(); ++y) {
+        for (int x = 0; x < bitmap->width(); ++x) {
+            if (x == 0 && y == 0)
+                EXPECT_EQ(bitmap->get_pixel(x, y), Color::Black);
+            else
+                EXPECT_EQ(bitmap->get_pixel(x, y), Color::White);
+        }
+    }
+
+    // Scale up 1x1 source bitmap 10x in x and 5x in y and paint at 10, 20. Should fill that rect:
+    bitmap->fill(Gfx::Color::White);
+    transform = transform.translate(10, 20).scale(10, 5);
+    painter.draw_scaled_bitmap_with_transform(dest_rect, source_bitmap, source_rect, transform);
+    for (int y = 0; y < bitmap->height(); ++y) {
+        for (int x = 0; x < bitmap->width(); ++x) {
+            dbgln("{} {}: {}", x, y, bitmap->get_pixel(x, y));
+            if (x >= 10 && x < 10 + 10 && y >= 20 && y < 20 + 5)
+                EXPECT_EQ(bitmap->get_pixel(x, y), Color::Black);
+            else
+                EXPECT_EQ(bitmap->get_pixel(x, y), Color::White);
+        }
+    }
+}

+ 4 - 1
Userland/Libraries/LibGfx/Painter.cpp

@@ -2489,7 +2489,10 @@ void Painter::draw_scaled_bitmap_with_transform(IntRect const& dst_rect, Bitmap
             for (int x = 0; x < clipped_bounding_rect.width(); ++x) {
                 auto point = Gfx::IntPoint { x, y };
                 auto sample_point = point + start_offset;
-                auto source_point = sample_transform.map(sample_point).to_rounded<int>();
+
+                // AffineTransform::map(IntPoint) rounds internally, which is wrong here. So explicitly call the FloatPoint version, and then truncate the result.
+                auto source_point = Gfx::IntPoint { sample_transform.map(Gfx::FloatPoint { sample_point }) };
+
                 if (!source_rect.contains(source_point))
                     continue;
                 auto source_color = bitmap.get_pixel(source_point);