From 4219d50a21419b8ba370925655c93d9dc18ca254 Mon Sep 17 00:00:00 2001 From: Lucas CHOLLET Date: Tue, 8 Nov 2022 17:07:14 +0100 Subject: [PATCH] LibGfx: Use the Midpoint Ellipse Algorithm It is only used to draw non-antialiased and non-filled ellipses. --- .../PixelPaint/Tools/EllipseTool.cpp | 11 +--- Userland/Libraries/LibGfx/Painter.cpp | 57 ++++++++++++++++--- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp b/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp index f704028f09..04046caefa 100644 --- a/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp @@ -37,17 +37,10 @@ void EllipseTool::draw_using(GUI::Painter& painter, Gfx::IntPoint const& start_p switch (m_fill_mode) { case FillMode::Outline: - if (m_antialias_enabled) { + if (m_antialias_enabled) aa_painter.draw_ellipse(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), thickness); - } else { - // For some reason for non-AA draw_ellipse() the ellipse is outside of the rect (unlike all other ellipse drawing functions). - // Scale the ellipse rect by sqrt(2) to get an ellipse arc that appears as if it was inside of the rect. - // Ie. reduce the size by a factor of 1 - sqrt(1/2) - auto shrink_width = ellipse_intersecting_rect.width() * (1 - AK::Sqrt1_2); - auto shrink_height = ellipse_intersecting_rect.height() * (1 - AK::Sqrt1_2); - ellipse_intersecting_rect.shrink(shrink_width, shrink_height); + else painter.draw_ellipse_intersecting(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), thickness); - } break; case FillMode::Fill: if (m_antialias_enabled) diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index 9773b58fc3..a4d734a9fd 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -496,17 +496,58 @@ void Painter::draw_ellipse_intersecting(IntRect const& rect, Color color, int th if (thickness <= 0) return; - constexpr int number_samples = 100; // FIXME: dynamically work out the number of samples based upon the rect size - float increment = AK::Pi / number_samples; + auto const center = rect.center(); - auto ellipse_xy = [&rect](float theta) -> IntPoint { - float s, c; - AK::sincos(theta, s, c); - return IntPoint { (c * rect.width() * AK::Sqrt1_2), (s * rect.height() * AK::Sqrt1_2)} + rect.center(); + auto const draw_real_world_x4 = [this, &color, thickness, center](int x, int y) { + IntPoint const directions[4] = { { x, y }, { x, -y }, { -x, y }, { -x, -y } }; + for (auto const& delta : directions) { + auto const point = center + delta; + draw_line(point, point, color, thickness); + } }; - for (auto theta = 0.f; theta < 2 * AK::Pi; theta += increment) { - draw_line(ellipse_xy(theta), ellipse_xy(theta + increment), color, thickness); + // Note: This is an implementation of the Midpoint Ellipse Algorithm: + double const a = rect.width() / 2; + double const a_square = a * a; + double const b = rect.height() / 2; + double const b_square = b * b; + + int x = 0; + auto y = static_cast(b); + + double dx = 2 * b_square * x; + double dy = 2 * a_square * y; + + // For region 1: + auto decision_parameter = b_square - a_square * b + .25 * a_square; + + while (dx < dy) { + draw_real_world_x4(x, y); + + if (decision_parameter >= 0) { + y--; + dy -= 2 * a_square; + decision_parameter -= dy; + } + x++; + dx += 2 * b_square; + decision_parameter += dx + b_square; + } + + // For region 2: + decision_parameter = b_square * ((x + 0.5) * (x + 0.5)) + a_square * ((y - 1) * (y - 1)) - a_square * b_square; + + while (y >= 0) { + draw_real_world_x4(x, y); + + if (decision_parameter <= 0) { + x++; + dx += 2 * b_square; + decision_parameter += dx; + } + y--; + dy -= 2 * a_square; + decision_parameter += a_square - dy; } }