Parcourir la source

LibGfx: Avoid fill_path() crashes due to rounding errors

It seems for very narrow tall paths it is possible for the dxdy value
to round to a value (that after many repeated additions) overshoots
the desired end x. This caused a (rather rare) crash on this 3D canvas
demo: https://www.kevs3d.co.uk/dev/html5logo/. For now, lets just avoid
a crash here. This does not make any noticeable visual differences on
the demos I tired.
MacDue il y a 2 ans
Parent
commit
976d9b32d6
1 fichiers modifiés avec 36 ajouts et 19 suppressions
  1. 36 19
      Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp

+ 36 - 19
Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp

@@ -5,6 +5,7 @@
  */
 
 #include <AK/Array.h>
+#include <AK/Debug.h>
 #include <AK/IntegralMath.h>
 #include <AK/Types.h>
 #include <LibGfx/AntiAliasingPainter.h>
@@ -51,14 +52,19 @@ static Vector<Detail::Edge> prepare_edges(ReadonlySpan<FloatLine> lines, unsigne
         if (p0.y() == p1.y())
             continue;
 
-        auto dx = p1.x() - p0.x();
-        auto dy = p1.y() - p0.y();
-        float dxdy = float(dx) / dy;
-        float x = p0.x();
+        auto min_y = static_cast<int>(p0.y());
+        auto max_y = static_cast<int>(p1.y());
+        float start_x = p0.x();
+        float end_x = p1.x();
+
+        auto dx = end_x - start_x;
+        auto dy = max_y - min_y;
+        auto dxdy = dx / dy;
+
         edges.unchecked_append(Detail::Edge {
-            x,
-            static_cast<int>(p0.y()),
-            static_cast<int>(p1.y()),
+            start_x,
+            min_y,
+            max_y,
             dxdy,
             winding,
             nullptr });
@@ -132,18 +138,32 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::fill_internal(Painter& painter, Pa
         max_scanline = max(max_scanline, end_scanline);
     }
 
-    Detail::Edge* active_edges = nullptr;
-
     // FIXME: We could probably clip some of the egde plotting if we know it won't be shown.
     // Though care would have to be taken to ensure the active edges are correct at the first drawn scaline.
+
+    auto for_each_sample = [&](Detail::Edge& edge, int start_subpixel_y, int end_subpixel_y, auto callback) {
+        for (int y = start_subpixel_y; y < end_subpixel_y; y++) {
+            int xi = static_cast<int>(edge.x + SubpixelSample::nrooks_subpixel_offsets[y]);
+            if (xi < 0 || xi >= (int)m_scanline.size()) {
+                // FIXME: For very low dxdy values, floating point error can push the sample outside the scanline.
+                // This does not seem to make a visible difference most of the time (and is more likely from generated
+                // paths, such as this 3D canvas demo: https://www.kevs3d.co.uk/dev/html5logo/).
+                dbgln_if(FILL_PATH_DEBUG, "fill_path: Sample out of bounds: {} not  in [0, {})", xi, m_scanline.size());
+                return;
+            }
+            SampleType sample = 1 << y;
+            callback(xi, y, sample);
+            edge.x += edge.dxdy;
+        }
+    };
+
+    Detail::Edge* active_edges = nullptr;
+
     if (winding_rule == Painter::WindingRule::EvenOdd) {
         auto plot_edge = [&](Detail::Edge& edge, int start_subpixel_y, int end_subpixel_y) {
-            for (int y = start_subpixel_y; y < end_subpixel_y; y++) {
-                int xi = static_cast<int>(edge.x + SubpixelSample::nrooks_subpixel_offsets[y]);
-                SampleType sample = 1 << y;
+            for_each_sample(edge, start_subpixel_y, end_subpixel_y, [&](int xi, int, SampleType sample) {
                 m_scanline[xi] ^= sample;
-                edge.x += edge.dxdy;
-            }
+            });
         };
         for (int scanline = min_scanline; scanline <= max_scanline; scanline++) {
             active_edges = plot_edges_for_scanline(scanline, plot_edge, active_edges);
@@ -157,13 +177,10 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::fill_internal(Painter& painter, Pa
             m_windings.resize(m_size.width());
 
         auto plot_edge = [&](Detail::Edge& edge, int start_subpixel_y, int end_subpixel_y) {
-            for (int y = start_subpixel_y; y < end_subpixel_y; y++) {
-                int xi = static_cast<int>(edge.x + SubpixelSample::nrooks_subpixel_offsets[y]);
-                SampleType sample = 1 << y;
+            for_each_sample(edge, start_subpixel_y, end_subpixel_y, [&](int xi, int y, SampleType sample) {
                 m_scanline[xi] |= sample;
                 m_windings[xi].counts[y] += edge.winding;
-                edge.x += edge.dxdy;
-            }
+            });
         };
         for (int scanline = min_scanline; scanline <= max_scanline; scanline++) {
             active_edges = plot_edges_for_scanline(scanline, plot_edge, active_edges);