From 3aea7cfdea45f3bbbaa7b377e2522fa7e078a91e Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Mon, 7 Apr 2025 18:20:04 +0200 Subject: [PATCH] Fix CanvasItem lines appearing thicker when antialiasing is enabled This was due to the antialiasing feather not being compensated for when drawing the line. The feather makes the line appear thicker, so we have to reduce the line width to compensate. This also affected filled circles and rectangles, which are now compensated for by adjusting their radius and size respectively. --- servers/rendering/renderer_canvas_cull.cpp | 76 +++++++++++++++++----- servers/rendering/renderer_canvas_cull.h | 2 + 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 878f64fae5..0622b90506 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -725,6 +725,29 @@ void RendererCanvasCull::canvas_item_set_update_when_visible(RID p_item, bool p_ canvas_item->update_when_visible = p_update; } +// Compensate the width added by the antialiasing feather by reducing the base line width. +// For line widths lower than or equal to 1.0, this is done with a multiplier, +// while line widths greater than 1.0 use a constant offset clamped to a width of 1.0. +// While the offset is not equal to `FEATHER_SIZE`, this is empirically determined +// to give a good result on various foreground/background colors. +// This method assumes the check for whether the line is antialiased +// has already been done. +float RendererCanvasCull::canvas_item_get_compensated_antialiasing_width(float p_width) const { + if (p_width > 0.0f) { + if (p_width <= (FEATHER_SIZE * 2.0f + CMP_EPSILON)) { + return p_width * 0.5f; + } else if (p_width <= (FEATHER_SIZE * 4.0f + CMP_EPSILON)) { + // Progressively lerp between the two methods (multiplier and offset). + return Math::remap(p_width, FEATHER_SIZE * 2.0f, FEATHER_SIZE * 4.0f, p_width * 0.5f, p_width - FEATHER_SIZE * 0.5f); + } else { + // Use a constant offset. + return p_width - FEATHER_SIZE * 0.5f; + } + } + + return p_width; +} + void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from, const Point2 &p_to, const Color &p_color, float p_width, bool p_antialiased) { Item *canvas_item = canvas_item_owner.get_or_null(p_item); ERR_FAIL_NULL(canvas_item); @@ -734,6 +757,11 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from, Vector2 diff = (p_from - p_to); Vector2 dir = diff.orthogonal().normalized(); + + if (p_antialiased) { + p_width = canvas_item_get_compensated_antialiasing_width(p_width); + } + Vector2 t = dir * p_width * 0.5; Vector2 begin_left; @@ -966,6 +994,10 @@ void RendererCanvasCull::canvas_item_add_polyline(RID p_item, const Vectoralloc_command(); ERR_FAIL_NULL(pline); + if (p_antialiased) { + p_width = canvas_item_get_compensated_antialiasing_width(p_width); + } + if (p_width < 0) { if (p_antialiased) { WARN_PRINT("Antialiasing is not supported for thin polylines drawn using line strips (`p_width < 0`)."); @@ -1220,6 +1252,9 @@ void RendererCanvasCull::canvas_item_add_multiline(RID p_item, const Vectoralloc_command(); ERR_FAIL_NULL(rect); rect->modulate = p_color; - rect->rect = p_rect; + rect->rect = rect_adjusted; // Add feathers. if (p_antialiased) { float border_size = FEATHER_SIZE; - const real_t size = MIN(p_rect.size.width, p_rect.size.height); + const real_t size = MIN(rect_adjusted.size.width, rect_adjusted.size.height); if (0.0f <= size && size < 1.0f) { border_size *= size; } - const Vector2 vec_down = Vector2(0.0f, p_rect.size.height); - const Vector2 vec_right = Vector2(p_rect.size.width, 0.0f); + const Vector2 vec_down = Vector2(0.0f, rect_adjusted.size.height); + const Vector2 vec_right = Vector2(rect_adjusted.size.width, 0.0f); - const Vector2 begin_left = p_rect.position; - const Vector2 begin_right = p_rect.position + vec_down; - const Vector2 end_left = p_rect.position + vec_right; - const Vector2 end_right = p_rect.position + p_rect.size; + const Vector2 begin_left = rect_adjusted.position; + const Vector2 begin_right = rect_adjusted.position + vec_down; + const Vector2 end_left = rect_adjusted.position + vec_right; + const Vector2 end_right = rect_adjusted.position + rect_adjusted.size; const Vector2 dir = Vector2(0.0f, -1.0f); const Vector2 dir2 = Vector2(-1.0f, 0.0f); @@ -1436,6 +1474,14 @@ void RendererCanvasCull::canvas_item_add_ellipse(RID p_item, const Point2 &p_pos static const int ellipse_segments = 64; + float major = p_major; + float minor = p_minor; + if (p_antialiased) { + // Adjust the diameter to account for the antialiasing width. + major = MAX(0.0f, major - FEATHER_SIZE * 0.25f); + minor = MAX(0.0f, minor - FEATHER_SIZE * 0.25f); + } + { Item::CommandPolygon *ellipse = canvas_item->alloc_command(); ERR_FAIL_NULL(ellipse); @@ -1455,8 +1501,8 @@ void RendererCanvasCull::canvas_item_add_ellipse(RID p_item, const Point2 &p_pos for (int i = 0; i < ellipse_segments + 1; i++) { float angle = i * ellipse_point_step; - points_ptr[i].x = Math::cos(angle) * p_major; - points_ptr[i].y = Math::sin(angle) * p_minor; + points_ptr[i].x = Math::cos(angle) * major; + points_ptr[i].y = Math::sin(angle) * minor; points_ptr[i] += p_pos; } @@ -1477,7 +1523,7 @@ void RendererCanvasCull::canvas_item_add_ellipse(RID p_item, const Point2 &p_pos if (p_antialiased) { float border_size = FEATHER_SIZE; - const float max_axis = fmax(p_major, p_minor) * 2.0f; + const float max_axis = fmax(major, minor) * 2.0f; if (0.0f <= max_axis && max_axis < 1.0f) { border_size *= max_axis * 0.5f; } @@ -1505,12 +1551,12 @@ void RendererCanvasCull::canvas_item_add_ellipse(RID p_item, const Point2 &p_pos const float c = Math::cos(angle); const float s = Math::sin(angle); - points_ptr[i * 2].x = c * p_major; - points_ptr[i * 2].y = s * p_minor; + points_ptr[i * 2].x = c * major; + points_ptr[i * 2].y = s * minor; points_ptr[i * 2] += p_pos; - points_ptr[i * 2 + 1].x = c * (p_major + border_size); - points_ptr[i * 2 + 1].y = s * (p_minor + border_size); + points_ptr[i * 2 + 1].x = c * (major + border_size); + points_ptr[i * 2 + 1].y = s * (minor + border_size); points_ptr[i * 2 + 1] += p_pos; colors_ptr[i * 2] = p_color; diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h index f2ff85dd59..de9b929060 100644 --- a/servers/rendering/renderer_canvas_cull.h +++ b/servers/rendering/renderer_canvas_cull.h @@ -257,6 +257,8 @@ public: void canvas_item_set_update_when_visible(RID p_item, bool p_update); + float canvas_item_get_compensated_antialiasing_width(float p_width) const; + void canvas_item_add_line(RID p_item, const Point2 &p_from, const Point2 &p_to, const Color &p_color, float p_width = -1.0, bool p_antialiased = false); void canvas_item_add_polyline(RID p_item, const Vector &p_points, const Vector &p_colors, float p_width = -1.0, bool p_antialiased = false); void canvas_item_add_multiline(RID p_item, const Vector &p_points, const Vector &p_colors, float p_width = -1.0, bool p_antialiased = false);