From 83ed47abf429bdc54e952988a06d7253bf1b3eac Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 9 Apr 2026 09:33:52 -0400 Subject: [PATCH] fix: scan-line polygon fill incorrectly filled non-convex polygon gaps Image::Fill(Polygon) implements scan-line polygon fill but iterated through active edges one at a time instead of in pairs. For convex polygons (always exactly 2 active edges per scan line) this happened to work, but for non-convex polygons it would fill the gaps between concave sections. A banana-shaped zone, for example, would have its inner concave area incorrectly marked as inside the zone, causing motion detection to trigger on the area the user explicitly drew the zone to avoid. Fix by stepping the iterator by 2 to fill between pairs of edges following the standard parity rule for scan-line polygon fill. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/zm_image.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 8c7189e6b..3fbb458f5 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -2720,9 +2720,12 @@ void Image::Fill(Rgb colour, int density, const Polygon &polygon) { std::sort(active_edges.begin(), active_edges.end(), PolygonFill::Edge::CompareX); if (!(scan_line % density)) { - for (auto it = active_edges.begin(); it < active_edges.end() - 1; ++it) { + // Fill between pairs of active edges (parity rule). Stepping one + // edge at a time would incorrectly fill the gaps between arms of + // a non-convex polygon (e.g. a banana shape). + for (auto it = active_edges.begin(); it + 1 < active_edges.end(); it += 2) { int32 lo_x = static_cast(it->min_x); - int32 hi_x = static_cast(std::next(it)->min_x); + int32 hi_x = static_cast((it + 1)->min_x); if (colours == ZM_COLOUR_GRAY8) { uint8 *p = &buffer[(scan_line * width) + lo_x];