From 565f68f8bbe7e81f6ecbe576abba8955dead8e3f Mon Sep 17 00:00:00 2001 From: FrHun <28605587+frhun@users.noreply.github.com> Date: Wed, 15 Jun 2022 03:55:34 +0200 Subject: [PATCH] LibGUI: Reimplement Painter::draw_triangle to be more symmetrical This is a reimplementation of draw_triangle that manages without floating point arithmetic. It's most important property, compared to the previous implementation is that rotating the same triangle 90 degrees won't drastically change the appearance of that triangle. (it did before) --- Userland/Libraries/LibGfx/Painter.cpp | 104 ++++++++++++++++---------- 1 file changed, 65 insertions(+), 39 deletions(-) diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index efc3124e91..d549b418d5 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -695,6 +695,10 @@ void Painter::draw_triangle(IntPoint const& a, IntPoint const& b, IntPoint const if (p0.y() == p2.y()) return; + // return if all points are on the same line vertically + if (p0.x() == p1.x() && p1.x() == p2.x()) + return; + // return if top is below clip rect or bottom is above clip rect auto clip = clip_rect(); if (p0.y() >= clip.bottom()) @@ -702,56 +706,78 @@ void Painter::draw_triangle(IntPoint const& a, IntPoint const& b, IntPoint const if (p2.y() < clip.top()) return; + class BoundaryLine { + private: + IntPoint m_base {}; + IntPoint m_path {}; + + public: + BoundaryLine(IntPoint a, IntPoint b) + { + VERIFY(a.y() <= b.y()); + m_base = a; + m_path = b - a; + } + + int top_y() const { return m_base.y(); } + + int bottom_y() const { return m_base.y() + m_path.y(); } + + bool is_vertical() const { return m_path.x() == 0; } + + bool is_horizontal() const { return m_path.y() == 0; } + + bool in_y_range(int y) const { return y >= top_y() && y <= bottom_y(); } + + Optional intersection_on_x(int y) const + { + if (!in_y_range(y)) + return {}; + if (is_horizontal()) + return {}; + if (is_vertical()) + return m_base.x(); + + int y_diff = y - top_y(); + int x_d = m_path.x() * y_diff, y_d = m_path.y(); + + return (x_d / y_d) + m_base.x(); + } + }; + + BoundaryLine l0(p0, p1), l1(p0, p2), l2(p1, p2); + int rgba = color.value(); - float dx02 = (float)(p2.x() - p0.x()) / (p2.y() - p0.y()); - float x01 = p0.x(); - float x02 = p0.x(); + for (int y = max(p0.y(), clip.top()); y <= min(p2.y(), clip.bottom()); y++) { + Optional + x0 = l0.intersection_on_x(y), + x1 = l1.intersection_on_x(y), + x2 = l2.intersection_on_x(y); - if (p0.y() != p1.y()) { // p0 and p1 are on different lines - float dx01 = (float)(p1.x() - p0.x()) / (p1.y() - p0.y()); + int result_a = 0, result_b = 0; - int top = p0.y(); - if (top < clip.top()) { - x01 += dx01 * (clip.top() - top); - x02 += dx02 * (clip.top() - top); - top = clip.top(); - } - - for (int y = top; y < p1.y() && y < clip.bottom(); ++y) { // XXX <=? - int start = x01 > x02 ? max((int)x02, clip.left()) : max((int)x01, clip.left()); - int end = x01 > x02 ? min((int)x01, clip.right()) : min((int)x02, clip.right()); - auto* scanline = m_target->scanline(y); - for (int x = start; x < end; x++) { - scanline[x] = rgba; + if (x0.has_value()) { + result_a = x0.value(); + if (x1.has_value() && ((!x2.has_value()) || (result_a != x1.value()))) { + result_b = x1.value(); + } else { + result_b = x2.value(); } - x01 += dx01; - x02 += dx02; + } else if (x1.has_value()) { + result_a = x1.value(); + result_b = x2.value(); } - } - // return if middle point and bottom point are on same line - if (p1.y() == p2.y()) - return; + if (result_a > result_b) + swap(result_a, result_b); - float x12 = p1.x(); - float dx12 = (float)(p2.x() - p1.x()) / (p2.y() - p1.y()); - int top = p1.y(); - if (top < clip.top()) { - x02 += dx02 * (clip.top() - top); - x12 += dx12 * (clip.top() - top); - top = clip.top(); - } + int left_bound = result_a, right_bound = result_b; - for (int y = top; y < p2.y() && y < clip.bottom(); ++y) { // XXX <=? - int start = x12 > x02 ? max((int)x02, clip.left()) : max((int)x12, clip.left()); - int end = x12 > x02 ? min((int)x12, clip.right()) : min((int)x02, clip.right()); - auto* scanline = m_target->scanline(y); - for (int x = start; x < end; x++) { + ARGB32* scanline = m_target->scanline(y); + for (int x = max(left_bound, clip.left()); x <= min(right_bound, clip.right()); x++) { scanline[x] = rgba; } - x02 += dx02; - x12 += dx12; } }