From 56cad36ef259332e603df8494cfaee4f34915beb Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 12 Jan 2021 20:22:15 -0500 Subject: [PATCH] LibGfx: Make Painter::draw_rect() scale-aware Needed for the window server minimize animation. draw_rect() can't just call draw_line() because that isn't draw_op()-aware. The draw_op()-awareness in Painter looks a bit ad-hoc, but that's for another day. --- Userland/Demos/LibGfxScaleDemo/main.cpp | 4 ++ Userland/Libraries/LibGfx/Painter.cpp | 56 +++++++++++++++---------- Userland/Libraries/LibGfx/Painter.h | 4 +- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/Userland/Demos/LibGfxScaleDemo/main.cpp b/Userland/Demos/LibGfxScaleDemo/main.cpp index 83eb6a23ab..9a8bb034a9 100644 --- a/Userland/Demos/LibGfxScaleDemo/main.cpp +++ b/Userland/Demos/LibGfxScaleDemo/main.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include const int WIDTH = 300; @@ -86,6 +87,9 @@ void Canvas::draw(Gfx::Painter& painter) { auto active_window_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png"); Gfx::WindowTheme::current().paint_normal_frame(painter, Gfx::WindowTheme::WindowState::Active, { 4, 18, WIDTH - 8, HEIGHT - 29 }, "Well hello friends", *active_window_icon, palette(), { WIDTH - 20, 6, 16, 16 }); + + painter.draw_rect({ 20, 34, WIDTH - 40, HEIGHT - 45 }, palette().color(Gfx::ColorRole::Selection), true); + painter.draw_rect({ 24, 38, WIDTH - 48, HEIGHT - 53 }, palette().color(Gfx::ColorRole::Selection)); } int main(int argc, char** argv) diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index 4244a3b42d..decdc8d70c 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -95,7 +95,7 @@ void Painter::fill_rect_with_draw_op(const IntRect& a_rect, Color color) for (int i = rect.height() - 1; i >= 0; --i) { for (int j = 0; j < rect.width(); ++j) - set_pixel_with_draw_op(dst[j], color); + set_physical_pixel_with_draw_op(dst[j], color); dst += dst_skip; } } @@ -319,9 +319,7 @@ void Painter::draw_focus_rect(const IntRect& rect, Color color) void Painter::draw_rect(const IntRect& a_rect, Color color, bool rough) { - ASSERT(scale() == 1); // FIXME: Add scaling support. - - IntRect rect = a_rect.translated(translation()); + IntRect rect = to_physical(a_rect); auto clipped_rect = rect.intersected(clip_rect()); if (clipped_rect.is_empty()) return; @@ -329,17 +327,27 @@ void Painter::draw_rect(const IntRect& a_rect, Color color, bool rough) int min_y = clipped_rect.top(); int max_y = clipped_rect.bottom(); + // Don't use rect.bottom() / right() when dealing with physical rects: They will be off by scale()-1 physical pixels. + // (It's fine to use them when comparing bottom() / right() to other physical rects, since then both rects are off by the same amount. + // But don't use them for pixel access.) + int max_y_rounded_to_logical_increment = clipped_rect.top() + clipped_rect.height() - scale(); + int max_x_rounded_to_logical_increment = clipped_rect.left() + clipped_rect.width() - scale(); + if (rect.top() >= clipped_rect.top() && rect.top() <= clipped_rect.bottom()) { - int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x(); - int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width(); - fill_scanline_with_draw_op(rect.top(), start_x, width, color); - ++min_y; + int start_x = rough ? max(rect.x() + scale(), clipped_rect.x()) : clipped_rect.x(); + int width = rough ? min(rect.width() - 2 * scale(), clipped_rect.width()) : clipped_rect.width(); + for (int i = 0; i < scale(); ++i) { + fill_physical_scanline_with_draw_op(rect.top() + i, start_x, width, color); + ++min_y; + } } if (rect.bottom() >= clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) { - int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x(); - int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width(); - fill_scanline_with_draw_op(rect.bottom(), start_x, width, color); - --max_y; + int start_x = rough ? max(rect.x() + scale(), clipped_rect.x()) : clipped_rect.x(); + int width = rough ? min(rect.width() - 2 * scale(), clipped_rect.width()) : clipped_rect.width(); + for (int i = 0; i < scale(); ++i) { + fill_physical_scanline_with_draw_op(max_y_rounded_to_logical_increment + i, start_x, width, color); + --max_y; + } } bool draw_left_side = rect.left() >= clipped_rect.left(); @@ -349,16 +357,20 @@ void Painter::draw_rect(const IntRect& a_rect, Color color, bool rough) // Specialized loop when drawing both sides. for (int y = min_y; y <= max_y; ++y) { auto* bits = m_target->scanline(y); - set_pixel_with_draw_op(bits[rect.left()], color); - set_pixel_with_draw_op(bits[rect.right()], color); + for (int i = 0; i < scale(); ++i) + set_physical_pixel_with_draw_op(bits[rect.left() + i], color); + for (int i = 0; i < scale(); ++i) + set_physical_pixel_with_draw_op(bits[max_x_rounded_to_logical_increment + i], color); } } else { for (int y = min_y; y <= max_y; ++y) { auto* bits = m_target->scanline(y); if (draw_left_side) - set_pixel_with_draw_op(bits[rect.left()], color); + for (int i = 0; i < scale(); ++i) + set_physical_pixel_with_draw_op(bits[rect.left() + i], color); if (draw_right_side) - set_pixel_with_draw_op(bits[rect.right()], color); + for (int i = 0; i < scale(); ++i) + set_physical_pixel_with_draw_op(bits[max_x_rounded_to_logical_increment + i], color); } } } @@ -1169,9 +1181,10 @@ void Painter::set_pixel(const IntPoint& p, Color color) m_target->scanline(point.y())[point.x()] = color.value(); } -ALWAYS_INLINE void Painter::set_pixel_with_draw_op(u32& pixel, const Color& color) +ALWAYS_INLINE void Painter::set_physical_pixel_with_draw_op(u32& pixel, const Color& color) { - ASSERT(scale() == 1); // FIXME: Add scaling support. + // This always sets a single physical pixel, independent of scale(). + // This should only be called by routines that already handle scale. switch (draw_op()) { case DrawOp::Copy: @@ -1186,9 +1199,10 @@ ALWAYS_INLINE void Painter::set_pixel_with_draw_op(u32& pixel, const Color& colo } } -ALWAYS_INLINE void Painter::fill_scanline_with_draw_op(int y, int x, int width, const Color& color) +ALWAYS_INLINE void Painter::fill_physical_scanline_with_draw_op(int y, int x, int width, const Color& color) { - ASSERT(scale() == 1); // FIXME: Add scaling support. + // This always draws a single physical scanline, independent of scale(). + // This should only be called by routines that already handle scale. switch (draw_op()) { case DrawOp::Copy: @@ -1224,7 +1238,7 @@ void Painter::draw_physical_pixel(const IntPoint& position, Color color, int thi if (thickness == 1) { // Implies scale() == 1. auto& pixel = m_target->scanline(position.y())[position.x()]; - return set_pixel_with_draw_op(pixel, Color::from_rgba(pixel).blend(color)); + return set_physical_pixel_with_draw_op(pixel, Color::from_rgba(pixel).blend(color)); } IntRect rect { position, { thickness, thickness } }; diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h index d8bb2f7cb2..2bb510a34a 100644 --- a/Userland/Libraries/LibGfx/Painter.h +++ b/Userland/Libraries/LibGfx/Painter.h @@ -135,8 +135,8 @@ protected: IntRect to_physical(const IntRect& r) const { return (r * scale()).translated(translation()); } IntPoint to_physical(const IntPoint& p) const { return (p * scale()).translated(translation()); } int scale() const { return state().scale; } - void set_pixel_with_draw_op(u32& pixel, const Color&); - void fill_scanline_with_draw_op(int y, int x, int width, const Color& color); + void set_physical_pixel_with_draw_op(u32& pixel, const Color&); + void fill_physical_scanline_with_draw_op(int y, int x, int width, const Color& color); void fill_rect_with_draw_op(const IntRect&, Color); void blit_with_alpha(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect); void blit_with_opacity(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity);