1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 11:07:45 +00:00

LibGfx/Painter: Keep translation and clip_rect in logical coordinates

Moves Painter away from general affine transport support a bit, but
this scale factor business does feel like it's a bit different.

This is conceptually cleaner (everything should use logical coordinates
as much as possible), and it means the code in GUI::Painter() will work
without changes due to that, but the draw function implementations
overall get a bit murkier (draw_rect() becomes nicer though). Still,
feels like the right direction.

No behavior change.
This commit is contained in:
Nico Weber 2021-01-20 09:59:22 -05:00 committed by Andreas Kling
parent 362bde4a86
commit 91aa0d9997
2 changed files with 73 additions and 64 deletions

View file

@ -77,7 +77,7 @@ Painter::Painter(Gfx::Bitmap& bitmap)
ASSERT(bitmap.physical_height() % scale == 0); ASSERT(bitmap.physical_height() % scale == 0);
m_state_stack.append(State()); m_state_stack.append(State());
state().font = &FontDatabase::default_font(); state().font = &FontDatabase::default_font();
state().clip_rect = { { 0, 0 }, bitmap.physical_size() }; state().clip_rect = { { 0, 0 }, bitmap.size() };
state().scale = scale; state().scale = scale;
m_clip_origin = state().clip_rect; m_clip_origin = state().clip_rect;
} }
@ -106,11 +106,12 @@ void Painter::fill_rect_with_draw_op(const IntRect& a_rect, Color color)
void Painter::clear_rect(const IntRect& a_rect, Color color) void Painter::clear_rect(const IntRect& a_rect, Color color)
{ {
auto rect = to_physical(a_rect).intersected(clip_rect()); auto rect = a_rect.translated(translation()).intersected(clip_rect());
if (rect.is_empty()) if (rect.is_empty())
return; return;
ASSERT(m_target->physical_rect().contains(rect)); ASSERT(m_target->rect().contains(rect));
rect *= scale();
RGBA32* dst = m_target->scanline(rect.top()) + rect.left(); RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
@ -121,18 +122,14 @@ void Painter::clear_rect(const IntRect& a_rect, Color color)
} }
} }
void Painter::fill_physical_rect(const IntRect& a_physical_rect, Color color) void Painter::fill_physical_rect(const IntRect& physical_rect, Color color)
{ {
auto rect = a_physical_rect.intersected(clip_rect()); // Callers must do clipping.
if (rect.is_empty()) RGBA32* dst = m_target->scanline(physical_rect.top()) + physical_rect.left();
return;
ASSERT(m_target->physical_rect().contains(rect));
RGBA32* dst = m_target->scanline(rect.top()) + rect.left();
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
for (int i = rect.height() - 1; i >= 0; --i) { for (int i = physical_rect.height() - 1; i >= 0; --i) {
for (int j = 0; j < rect.width(); ++j) for (int j = 0; j < physical_rect.width(); ++j)
dst[j] = Color::from_rgba(dst[j]).blend(color).value(); dst[j] = Color::from_rgba(dst[j]).blend(color).value();
dst += dst_skip; dst += dst_skip;
} }
@ -153,7 +150,12 @@ void Painter::fill_rect(const IntRect& a_rect, Color color)
return; return;
} }
fill_physical_rect(to_physical(a_rect), color); auto rect = a_rect.translated(translation()).intersected(clip_rect());
if (rect.is_empty())
return;
ASSERT(m_target->rect().contains(rect));
fill_physical_rect(rect * scale(), color);
} }
void Painter::fill_rect_with_dither_pattern(const IntRect& a_rect, Color color_a, Color color_b) void Painter::fill_rect_with_dither_pattern(const IntRect& a_rect, Color color_a, Color color_b)
@ -209,7 +211,7 @@ void Painter::fill_rect_with_gradient(Orientation orientation, const IntRect& a_
#endif #endif
auto rect = to_physical(a_rect); auto rect = to_physical(a_rect);
auto clipped_rect = IntRect::intersection(rect, clip_rect()); auto clipped_rect = IntRect::intersection(rect, clip_rect() * scale());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
@ -323,35 +325,28 @@ void Painter::draw_focus_rect(const IntRect& rect, Color color)
void Painter::draw_rect(const IntRect& a_rect, Color color, bool rough) void Painter::draw_rect(const IntRect& a_rect, Color color, bool rough)
{ {
IntRect rect = to_physical(a_rect); IntRect rect = a_rect.translated(translation());
auto clipped_rect = rect.intersected(clip_rect()); auto clipped_rect = rect.intersected(clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
int min_y = clipped_rect.top(); int min_y = clipped_rect.top();
int max_y = clipped_rect.bottom(); int max_y = clipped_rect.bottom();
int scale = this->scale();
// 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()) { if (rect.top() >= clipped_rect.top() && rect.top() <= clipped_rect.bottom()) {
int start_x = rough ? max(rect.x() + scale(), clipped_rect.x()) : clipped_rect.x(); int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
int width = rough ? min(rect.width() - 2 * scale(), clipped_rect.width()) : clipped_rect.width(); int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width();
for (int i = 0; i < scale(); ++i) { for (int i = 0; i < scale; ++i)
fill_physical_scanline_with_draw_op(rect.top() + i, start_x, width, color); fill_physical_scanline_with_draw_op(rect.top() * scale + i, start_x * scale, width * scale, color);
++min_y; ++min_y;
}
} }
if (rect.bottom() >= clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) { if (rect.bottom() >= clipped_rect.top() && rect.bottom() <= clipped_rect.bottom()) {
int start_x = rough ? max(rect.x() + scale(), clipped_rect.x()) : clipped_rect.x(); int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
int width = rough ? min(rect.width() - 2 * scale(), clipped_rect.width()) : clipped_rect.width(); int width = rough ? min(rect.width() - 2, clipped_rect.width()) : clipped_rect.width();
for (int i = 0; i < scale(); ++i) { for (int i = 0; i < scale; ++i)
fill_physical_scanline_with_draw_op(max_y_rounded_to_logical_increment + i, start_x, width, color); fill_physical_scanline_with_draw_op(max_y * scale + i, start_x * scale, width * scale, color);
--max_y; --max_y;
}
} }
bool draw_left_side = rect.left() >= clipped_rect.left(); bool draw_left_side = rect.left() >= clipped_rect.left();
@ -359,22 +354,22 @@ void Painter::draw_rect(const IntRect& a_rect, Color color, bool rough)
if (draw_left_side && draw_right_side) { if (draw_left_side && draw_right_side) {
// Specialized loop when drawing both sides. // Specialized loop when drawing both sides.
for (int y = min_y; y <= max_y; ++y) { for (int y = min_y * scale; y <= max_y * scale; ++y) {
auto* bits = m_target->scanline(y); auto* bits = m_target->scanline(y);
for (int i = 0; i < scale(); ++i) for (int i = 0; i < scale; ++i)
set_physical_pixel_with_draw_op(bits[rect.left() + i], color); set_physical_pixel_with_draw_op(bits[rect.left() * scale + i], color);
for (int i = 0; i < scale(); ++i) for (int i = 0; i < scale; ++i)
set_physical_pixel_with_draw_op(bits[max_x_rounded_to_logical_increment + i], color); set_physical_pixel_with_draw_op(bits[rect.right() * scale + i], color);
} }
} else { } else {
for (int y = min_y; y <= max_y; ++y) { for (int y = min_y * scale; y <= max_y * scale; ++y) {
auto* bits = m_target->scanline(y); auto* bits = m_target->scanline(y);
if (draw_left_side) if (draw_left_side)
for (int i = 0; i < scale(); ++i) for (int i = 0; i < scale; ++i)
set_physical_pixel_with_draw_op(bits[rect.left() + i], color); set_physical_pixel_with_draw_op(bits[rect.left() * scale + i], color);
if (draw_right_side) if (draw_right_side)
for (int i = 0; i < scale(); ++i) for (int i = 0; i < scale; ++i)
set_physical_pixel_with_draw_op(bits[max_x_rounded_to_logical_increment + i], color); set_physical_pixel_with_draw_op(bits[rect.right() * scale + i], color);
} }
} }
} }
@ -409,7 +404,7 @@ void Painter::draw_bitmap(const IntPoint& p, const CharacterBitmap& bitmap, Colo
void Painter::draw_bitmap(const IntPoint& p, const GlyphBitmap& bitmap, Color color) void Painter::draw_bitmap(const IntPoint& p, const GlyphBitmap& bitmap, Color color)
{ {
auto dst_rect = to_physical(IntRect(p, bitmap.size())); auto dst_rect = IntRect(p, bitmap.size()).translated(translation());
auto clipped_rect = dst_rect.intersected(clip_rect()); auto clipped_rect = dst_rect.intersected(clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
@ -417,10 +412,12 @@ void Painter::draw_bitmap(const IntPoint& p, const GlyphBitmap& bitmap, Color co
const int last_row = clipped_rect.bottom() - dst_rect.top(); const int last_row = clipped_rect.bottom() - dst_rect.top();
const int first_column = clipped_rect.left() - dst_rect.left(); const int first_column = clipped_rect.left() - dst_rect.left();
const int last_column = clipped_rect.right() - dst_rect.left(); const int last_column = clipped_rect.right() - dst_rect.left();
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
int scale = this->scale();
RGBA32* dst = m_target->scanline(clipped_rect.y() * scale) + clipped_rect.x() * scale;
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
if (scale() == 1) { if (scale == 1) {
for (int row = first_row; row <= last_row; ++row) { for (int row = first_row; row <= last_row; ++row) {
for (int j = 0; j <= (last_column - first_column); ++j) { for (int j = 0; j <= (last_column - first_column); ++j) {
if (bitmap.bit_at(j + first_column, row)) if (bitmap.bit_at(j + first_column, row))
@ -431,10 +428,13 @@ void Painter::draw_bitmap(const IntPoint& p, const GlyphBitmap& bitmap, Color co
} else { } else {
for (int row = first_row; row <= last_row; ++row) { for (int row = first_row; row <= last_row; ++row) {
for (int j = 0; j <= (last_column - first_column); ++j) { for (int j = 0; j <= (last_column - first_column); ++j) {
if (bitmap.bit_at((j + first_column) / scale(), row / scale())) if (bitmap.bit_at((j + first_column), row)) {
dst[j] = color.value(); for (int iy = 0; iy < scale; ++iy)
for (int ix = 0; ix < scale; ++ix)
dst[j * scale + ix + iy * dst_skip] = color.value();
}
} }
dst += dst_skip; dst += dst_skip * scale;
} }
} }
} }
@ -701,16 +701,22 @@ void Painter::blit_with_alpha(const IntPoint& position, const Gfx::Bitmap& sourc
ASSERT(source.has_alpha_channel()); ASSERT(source.has_alpha_channel());
IntRect safe_src_rect = a_src_rect.intersected(source.rect()); IntRect safe_src_rect = a_src_rect.intersected(source.rect());
auto dst_rect = to_physical(IntRect(position, safe_src_rect.size())); auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
auto src_rect = a_src_rect * source.scale();
auto clipped_rect = dst_rect.intersected(clip_rect()); auto clipped_rect = dst_rect.intersected(clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
int scale = this->scale();
auto src_rect = a_src_rect * scale;
clipped_rect *= scale;
dst_rect *= scale;
const int first_row = clipped_rect.top() - dst_rect.top(); const int first_row = clipped_rect.top() - dst_rect.top();
const int last_row = clipped_rect.bottom() - dst_rect.top(); const int last_row = clipped_rect.bottom() - dst_rect.top();
const int first_column = clipped_rect.left() - dst_rect.left(); const int first_column = clipped_rect.left() - dst_rect.left();
const int last_column = clipped_rect.right() - dst_rect.left(); const int last_column = clipped_rect.right() - dst_rect.left();
RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x(); RGBA32* dst = m_target->scanline(clipped_rect.y()) + clipped_rect.x();
const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column; const RGBA32* src = source.scanline(src_rect.top() + first_row) + src_rect.left() + first_column;
const size_t dst_skip = m_target->pitch() / sizeof(RGBA32); const size_t dst_skip = m_target->pitch() / sizeof(RGBA32);
@ -744,15 +750,19 @@ void Painter::blit(const IntPoint& position, const Gfx::Bitmap& source, const In
// If we get here, the Painter might have a scale factor, but the source bitmap has the same scale factor. // If we get here, the Painter might have a scale factor, but the source bitmap has the same scale factor.
// We need to transform from logical to physical coordinates, but we can just copy pixels without resampling. // We need to transform from logical to physical coordinates, but we can just copy pixels without resampling.
// All computations below are in physical coordinates (except for safe_src_rect).
auto safe_src_rect = a_src_rect.intersected(source.rect()); auto safe_src_rect = a_src_rect.intersected(source.rect());
ASSERT(source.rect().contains(safe_src_rect)); ASSERT(source.rect().contains(safe_src_rect));
auto dst_rect = to_physical(IntRect(position, safe_src_rect.size())); auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
auto src_rect = a_src_rect * source.scale();
auto clipped_rect = dst_rect.intersected(clip_rect()); auto clipped_rect = dst_rect.intersected(clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
// All computations below are in physical coordinates.
int scale = this->scale();
auto src_rect = a_src_rect * scale;
clipped_rect *= scale;
dst_rect *= scale;
const int first_row = clipped_rect.top() - dst_rect.top(); const int first_row = clipped_rect.top() - dst_rect.top();
const int last_row = clipped_rect.bottom() - dst_rect.top(); const int last_row = clipped_rect.bottom() - dst_rect.top();
const int first_column = clipped_rect.left() - dst_rect.left(); const int first_column = clipped_rect.left() - dst_rect.left();
@ -849,8 +859,7 @@ void Painter::draw_scaled_bitmap(const IntRect& a_dst_rect, const Gfx::Bitmap& s
auto dst_rect = to_physical(a_dst_rect); auto dst_rect = to_physical(a_dst_rect);
auto src_rect = a_src_rect * source.scale(); auto src_rect = a_src_rect * source.scale();
auto clipped_rect = dst_rect.intersected(clip_rect() * scale());
auto clipped_rect = dst_rect.intersected(clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
@ -1261,7 +1270,7 @@ void Painter::draw_line(const IntPoint& p1, const IntPoint& p2, Color color, int
if (color.alpha() == 0) if (color.alpha() == 0)
return; return;
auto clip_rect = this->clip_rect(); auto clip_rect = this->clip_rect() * scale();
auto point1 = to_physical(p1); auto point1 = to_physical(p1);
auto point2 = to_physical(p2); auto point2 = to_physical(p2);
@ -1495,8 +1504,8 @@ void Painter::draw_elliptical_arc(const IntPoint& p1, const IntPoint& p2, const
void Painter::add_clip_rect(const IntRect& rect) void Painter::add_clip_rect(const IntRect& rect)
{ {
state().clip_rect.intersect(to_physical(rect)); state().clip_rect.intersect(rect.translated(translation()));
state().clip_rect.intersect(m_target->physical_rect()); // FIXME: This shouldn't be necessary? state().clip_rect.intersect(m_target->rect()); // FIXME: This shouldn't be necessary?
} }
void Painter::clear_clip_rect() void Painter::clear_clip_rect()

View file

@ -118,7 +118,7 @@ public:
void clear_clip_rect(); void clear_clip_rect();
void translate(int dx, int dy) { translate({ dx, dy }); } void translate(int dx, int dy) { translate({ dx, dy }); }
void translate(const IntPoint& delta) { state().translation.move_by(delta * state().scale); } void translate(const IntPoint& delta) { state().translation.move_by(delta); }
Gfx::Bitmap* target() { return m_target.ptr(); } Gfx::Bitmap* target() { return m_target.ptr(); }
@ -131,8 +131,8 @@ public:
protected: protected:
IntPoint translation() const { return state().translation; } IntPoint translation() const { return state().translation; }
IntRect to_physical(const IntRect& r) const { return (r * scale()).translated(translation()); } IntRect to_physical(const IntRect& r) const { return r.translated(translation()) * scale(); }
IntPoint to_physical(const IntPoint& p) const { return (p * scale()).translated(translation()); } IntPoint to_physical(const IntPoint& p) const { return p.translated(translation()) * scale(); }
int scale() const { return state().scale; } int scale() const { return state().scale; }
void set_physical_pixel_with_draw_op(u32& pixel, const 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_physical_scanline_with_draw_op(int y, int x, int width, const Color& color);
@ -146,7 +146,7 @@ protected:
const Font* font; const Font* font;
IntPoint translation; IntPoint translation;
int scale = 1; int scale = 1;
IntRect clip_rect; // In physical coordinates. IntRect clip_rect;
DrawOp draw_op; DrawOp draw_op;
}; };