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

SharedGraphics: Give painter a state stack and save()/restore() operations.

This will make some painting code a lot less confusing since there's no
need to manually undo translations, clips, etc.
This commit is contained in:
Andreas Kling 2019-03-09 16:48:02 +01:00
parent 971dd46aec
commit f7097d21f6
2 changed files with 72 additions and 59 deletions

View file

@ -19,22 +19,25 @@
Painter::Painter(GraphicsBitmap& bitmap) Painter::Painter(GraphicsBitmap& bitmap)
: m_target(bitmap) : m_target(bitmap)
{ {
m_font = &Font::default_font(); m_state_stack.append(State());
m_clip_rect = { { 0, 0 }, bitmap.size() }; state().font = &Font::default_font();
m_clip_origin = m_clip_rect; state().clip_rect = { { 0, 0 }, bitmap.size() };
m_clip_origin = state().clip_rect;
} }
#ifdef LIBGUI #ifdef LIBGUI
Painter::Painter(GWidget& widget) Painter::Painter(GWidget& widget)
: m_font(&widget.font()) : m_window(widget.window())
, m_window(widget.window())
, m_target(*m_window->backing()) , m_target(*m_window->backing())
{ {
m_state_stack.append(State());
state().font = &widget.font();
auto origin_rect = widget.window_relative_rect(); auto origin_rect = widget.window_relative_rect();
m_translation.move_by(origin_rect.location()); state().translation = origin_rect.location();
m_clip_rect = origin_rect; state().clip_rect = origin_rect;
m_clip_origin = origin_rect; m_clip_origin = origin_rect;
m_clip_rect.intersect(m_target->rect()); state().clip_rect.intersect(m_target->rect());
} }
#endif #endif
@ -45,8 +48,8 @@ Painter::~Painter()
void Painter::fill_rect_with_draw_op(const Rect& a_rect, Color color) void Painter::fill_rect_with_draw_op(const Rect& a_rect, Color color)
{ {
auto rect = a_rect; auto rect = a_rect;
rect.move_by(m_translation); rect.move_by(state().translation);
rect.intersect(m_clip_rect); rect.intersect(clip_rect());
if (rect.is_empty()) if (rect.is_empty())
return; return;
@ -63,14 +66,14 @@ void Painter::fill_rect_with_draw_op(const Rect& a_rect, Color color)
void Painter::fill_rect(const Rect& a_rect, Color color) void Painter::fill_rect(const Rect& a_rect, Color color)
{ {
if (m_draw_op != DrawOp::Copy) { if (draw_op() != DrawOp::Copy) {
fill_rect_with_draw_op(a_rect, color); fill_rect_with_draw_op(a_rect, color);
return; return;
} }
auto rect = a_rect; auto rect = a_rect;
rect.move_by(m_translation); rect.move_by(state().translation);
rect.intersect(m_clip_rect); rect.intersect(clip_rect());
if (rect.is_empty()) if (rect.is_empty())
return; return;
@ -92,8 +95,8 @@ void Painter::fill_rect_with_gradient(const Rect& a_rect, Color gradient_start,
return fill_rect(a_rect, gradient_start); return fill_rect(a_rect, gradient_start);
#endif #endif
auto rect = a_rect; auto rect = a_rect;
rect.move_by(m_translation); rect.move_by(state().translation);
auto clipped_rect = Rect::intersection(rect, m_clip_rect); auto clipped_rect = Rect::intersection(rect, clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
@ -128,9 +131,9 @@ void Painter::fill_rect_with_gradient(const Rect& a_rect, Color gradient_start,
void Painter::draw_rect(const Rect& a_rect, Color color, bool rough) void Painter::draw_rect(const Rect& a_rect, Color color, bool rough)
{ {
Rect rect = a_rect; Rect rect = a_rect;
rect.move_by(m_translation); rect.move_by(state().translation);
auto clipped_rect = Rect::intersection(rect, m_clip_rect); auto clipped_rect = Rect::intersection(rect, clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
@ -174,8 +177,8 @@ void Painter::draw_rect(const Rect& a_rect, Color color, bool rough)
void Painter::draw_bitmap(const Point& p, const CharacterBitmap& bitmap, Color color) void Painter::draw_bitmap(const Point& p, const CharacterBitmap& bitmap, Color color)
{ {
Rect rect { p, bitmap.size() }; Rect rect { p, bitmap.size() };
rect.move_by(m_translation); rect.move_by(state().translation);
auto clipped_rect = Rect::intersection(rect, m_clip_rect); auto clipped_rect = Rect::intersection(rect, clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
const int first_row = clipped_rect.top() - rect.top(); const int first_row = clipped_rect.top() - rect.top();
@ -201,8 +204,8 @@ void Painter::draw_bitmap(const Point& p, const CharacterBitmap& bitmap, Color c
void Painter::draw_bitmap(const Point& p, const GlyphBitmap& bitmap, Color color) void Painter::draw_bitmap(const Point& p, const GlyphBitmap& bitmap, Color color)
{ {
Rect dst_rect { p, bitmap.size() }; Rect dst_rect { p, bitmap.size() };
dst_rect.move_by(m_translation); dst_rect.move_by(state().translation);
auto clipped_rect = Rect::intersection(dst_rect, m_clip_rect); auto clipped_rect = Rect::intersection(dst_rect, clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
const int first_row = clipped_rect.top() - dst_rect.top(); const int first_row = clipped_rect.top() - dst_rect.top();
@ -234,8 +237,8 @@ void Painter::blit_with_opacity(const Point& position, const GraphicsBitmap& sou
Rect safe_src_rect = Rect::intersection(src_rect, source.rect()); Rect safe_src_rect = Rect::intersection(src_rect, source.rect());
Rect dst_rect(position, safe_src_rect.size()); Rect dst_rect(position, safe_src_rect.size());
dst_rect.move_by(m_translation); dst_rect.move_by(state().translation);
auto clipped_rect = Rect::intersection(dst_rect, m_clip_rect); auto clipped_rect = Rect::intersection(dst_rect, clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
const int first_row = clipped_rect.top() - dst_rect.top(); const int first_row = clipped_rect.top() - dst_rect.top();
@ -264,8 +267,8 @@ void Painter::blit_with_alpha(const Point& position, const GraphicsBitmap& sourc
ASSERT(source.has_alpha_channel()); ASSERT(source.has_alpha_channel());
Rect safe_src_rect = Rect::intersection(src_rect, source.rect()); Rect safe_src_rect = Rect::intersection(src_rect, source.rect());
Rect dst_rect(position, safe_src_rect.size()); Rect dst_rect(position, safe_src_rect.size());
dst_rect.move_by(m_translation); dst_rect.move_by(state().translation);
auto clipped_rect = Rect::intersection(dst_rect, m_clip_rect); auto clipped_rect = Rect::intersection(dst_rect, clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
const int first_row = clipped_rect.top() - dst_rect.top(); const int first_row = clipped_rect.top() - dst_rect.top();
@ -299,8 +302,8 @@ void Painter::blit(const Point& position, const GraphicsBitmap& source, const Re
auto safe_src_rect = Rect::intersection(src_rect, source.rect()); auto safe_src_rect = Rect::intersection(src_rect, source.rect());
ASSERT(source.rect().contains(safe_src_rect)); ASSERT(source.rect().contains(safe_src_rect));
Rect dst_rect(position, safe_src_rect.size()); Rect dst_rect(position, safe_src_rect.size());
dst_rect.move_by(m_translation); dst_rect.move_by(state().translation);
auto clipped_rect = Rect::intersection(dst_rect, m_clip_rect); auto clipped_rect = Rect::intersection(dst_rect, clip_rect());
if (clipped_rect.is_empty()) if (clipped_rect.is_empty())
return; return;
const int first_row = clipped_rect.top() - dst_rect.top(); const int first_row = clipped_rect.top() - dst_rect.top();
@ -362,41 +365,41 @@ void Painter::draw_text(const Rect& rect, const String& text, TextAlignment alig
void Painter::set_pixel(const Point& p, Color color) void Painter::set_pixel(const Point& p, Color color)
{ {
auto point = p; auto point = p;
point.move_by(m_translation); point.move_by(state().translation);
if (!m_clip_rect.contains(point)) if (!clip_rect().contains(point))
return; return;
m_target->scanline(point.y())[point.x()] = color.value(); m_target->scanline(point.y())[point.x()] = color.value();
} }
[[gnu::always_inline]] inline void Painter::set_pixel_with_draw_op(dword& pixel, const Color& color) [[gnu::always_inline]] inline void Painter::set_pixel_with_draw_op(dword& pixel, const Color& color)
{ {
if (m_draw_op == DrawOp::Copy) if (draw_op() == DrawOp::Copy)
pixel = color.value(); pixel = color.value();
else if (m_draw_op == DrawOp::Xor) else if (draw_op() == DrawOp::Xor)
pixel ^= color.value(); pixel ^= color.value();
} }
void Painter::draw_line(const Point& p1, const Point& p2, Color color) void Painter::draw_line(const Point& p1, const Point& p2, Color color)
{ {
auto point1 = p1; auto point1 = p1;
point1.move_by(m_translation); point1.move_by(state().translation);
auto point2 = p2; auto point2 = p2;
point2.move_by(m_translation); point2.move_by(state().translation);
// Special case: vertical line. // Special case: vertical line.
if (point1.x() == point2.x()) { if (point1.x() == point2.x()) {
const int x = point1.x(); const int x = point1.x();
if (x < m_clip_rect.left() || x > m_clip_rect.right()) if (x < clip_rect().left() || x > clip_rect().right())
return; return;
if (point1.y() > point2.y()) if (point1.y() > point2.y())
swap(point1, point2); swap(point1, point2);
if (point1.y() > m_clip_rect.bottom()) if (point1.y() > clip_rect().bottom())
return; return;
if (point2.y() < m_clip_rect.top()) if (point2.y() < clip_rect().top())
return; return;
int min_y = max(point1.y(), m_clip_rect.top()); int min_y = max(point1.y(), clip_rect().top());
int max_y = min(point2.y(), m_clip_rect.bottom()); int max_y = min(point2.y(), clip_rect().bottom());
for (int y = min_y; y <= max_y; ++y) for (int y = min_y; y <= max_y; ++y)
set_pixel_with_draw_op(m_target->scanline(y)[x], color); set_pixel_with_draw_op(m_target->scanline(y)[x], color);
return; return;
@ -408,18 +411,18 @@ void Painter::draw_line(const Point& p1, const Point& p2, Color color)
// Special case: horizontal line. // Special case: horizontal line.
if (point1.y() == point2.y()) { if (point1.y() == point2.y()) {
const int y = point1.y(); const int y = point1.y();
if (y < m_clip_rect.top() || y > m_clip_rect.bottom()) if (y < clip_rect().top() || y > clip_rect().bottom())
return; return;
if (point1.x() > point2.x()) if (point1.x() > point2.x())
swap(point1, point2); swap(point1, point2);
if (point1.x() > m_clip_rect.right()) if (point1.x() > clip_rect().right())
return; return;
if (point2.x() < m_clip_rect.left()) if (point2.x() < clip_rect().left())
return; return;
int min_x = max(point1.x(), m_clip_rect.left()); int min_x = max(point1.x(), clip_rect().left());
int max_x = min(point2.x(), m_clip_rect.right()); int max_x = min(point2.x(), clip_rect().right());
auto* pixels = m_target->scanline(point1.y()); auto* pixels = m_target->scanline(point1.y());
if (m_draw_op == DrawOp::Copy) { if (draw_op() == DrawOp::Copy) {
fast_dword_fill(pixels + min_x, color.value(), max_x - min_x + 1); fast_dword_fill(pixels + min_x, color.value(), max_x - min_x + 1);
} else { } else {
for (int x = min_x; x <= max_x; ++x) for (int x = min_x; x <= max_x; ++x)
@ -461,11 +464,11 @@ void Painter::draw_focus_rect(const Rect& rect)
void Painter::set_clip_rect(const Rect& rect) void Painter::set_clip_rect(const Rect& rect)
{ {
m_clip_rect.intersect(rect.translated(m_clip_origin.location())); state().clip_rect.intersect(rect.translated(m_clip_origin.location()));
m_clip_rect.intersect(m_target->rect()); state().clip_rect.intersect(m_target->rect());
} }
void Painter::clear_clip_rect() void Painter::clear_clip_rect()
{ {
m_clip_rect = m_clip_origin; state().clip_rect = m_clip_origin;
} }

View file

@ -39,34 +39,44 @@ public:
void draw_text(const Rect&, const String&, TextAlignment = TextAlignment::TopLeft, Color = Color()); void draw_text(const Rect&, const String&, TextAlignment = TextAlignment::TopLeft, Color = Color());
void draw_glyph(const Point&, char, Color); void draw_glyph(const Point&, char, Color);
const Font& font() const { return *m_font; } const Font& font() const { return *state().font; }
void set_font(const Font& font) { m_font = &font; } void set_font(const Font& font) { state().font = &font; }
enum class DrawOp { Copy, Xor }; enum class DrawOp { Copy, Xor };
void set_draw_op(DrawOp op) { m_draw_op = op; } void set_draw_op(DrawOp op) { state().draw_op = op; }
DrawOp draw_op() const { return m_draw_op; } DrawOp draw_op() const { return state().draw_op; }
void set_clip_rect(const Rect& rect); void set_clip_rect(const Rect& rect);
void clear_clip_rect(); void clear_clip_rect();
Rect clip_rect() const { return m_clip_rect; } Rect clip_rect() const { return state().clip_rect; }
void translate(int dx, int dy) { m_translation.move_by(dx, dy); } void translate(int dx, int dy) { state().translation.move_by(dx, dy); }
void translate(const Point& delta) { m_translation.move_by(delta); } void translate(const Point& delta) { state().translation.move_by(delta); }
Point translation() const { return m_translation; } Point translation() const { return state().translation; }
GraphicsBitmap* target() { return m_target.ptr(); } GraphicsBitmap* target() { return m_target.ptr(); }
void save() { m_state_stack.append(m_state_stack.last()); }
void restore() { ASSERT(m_state_stack.size() > 1); m_state_stack.take_last(); }
private: private:
void set_pixel_with_draw_op(dword& pixel, const Color&); void set_pixel_with_draw_op(dword& pixel, const Color&);
void fill_rect_with_draw_op(const Rect&, Color); void fill_rect_with_draw_op(const Rect&, Color);
void blit_with_alpha(const Point&, const GraphicsBitmap&, const Rect& src_rect); void blit_with_alpha(const Point&, const GraphicsBitmap&, const Rect& src_rect);
const Font* m_font; struct State {
Point m_translation; const Font* font;
Rect m_clip_rect; Point translation;
Rect clip_rect;
DrawOp draw_op;
};
State& state() { return m_state_stack.last(); }
const State& state() const { return m_state_stack.last(); }
Rect m_clip_origin; Rect m_clip_origin;
GWindow* m_window { nullptr }; GWindow* m_window { nullptr };
Retained<GraphicsBitmap> m_target; Retained<GraphicsBitmap> m_target;
DrawOp m_draw_op { DrawOp::Copy }; Vector<State> m_state_stack;
}; };