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:
parent
971dd46aec
commit
f7097d21f6
2 changed files with 72 additions and 59 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue