Selaa lähdekoodia

new contour building algorithm wip

flabbet 2 vuotta sitten
vanhempi
commit
1279652401

+ 134 - 41
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -1,4 +1,5 @@
-using System.Numerics;
+using System.Collections;
+using System.Numerics;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using ChunkyImageLib.Operations;
 using ChunkyImageLib.Operations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
@@ -14,6 +15,11 @@ internal static class FloodFillHelper
 {
 {
     private const byte InSelection = 1;
     private const byte InSelection = 1;
     private const byte Visited = 2;
     private const byte Visited = 2;
+    
+    private static readonly VecI Up = new VecI(0, -1);
+    private static readonly VecI Down = new VecI(0, 1);
+    private static readonly VecI Left = new VecI(-1, 0);
+    private static readonly VecI Right = new VecI(1, 0);
 
 
     private static FloodFillChunkCache CreateCache(HashSet<Guid> membersToFloodFill, IReadOnlyDocument document)
     private static FloodFillChunkCache CreateCache(HashSet<Guid> membersToFloodFill, IReadOnlyDocument document)
     {
     {
@@ -231,9 +237,10 @@ internal static class FloodFillHelper
         HashSet<VecI> processedEmptyChunks = new();
         HashSet<VecI> processedEmptyChunks = new();
         HashSet<VecI> processedChunks = new();
         HashSet<VecI> processedChunks = new();
         Stack<(VecI chunkPos, VecI posOnChunk)> positionsToFloodFill = new();
         Stack<(VecI chunkPos, VecI posOnChunk)> positionsToFloodFill = new();
-        List<Line> lines = new();
         positionsToFloodFill.Push((initChunkPos, initPosOnChunk));
         positionsToFloodFill.Push((initChunkPos, initPosOnChunk));
         
         
+        Lines lines = new();
+        
         VectorPath selection = new();
         VectorPath selection = new();
         while (positionsToFloodFill.Count > 0)
         while (positionsToFloodFill.Count > 0)
         {
         {
@@ -268,6 +275,7 @@ internal static class FloodFillHelper
             var reallyReferenceChunk = referenceChunk.AsT0;
             var reallyReferenceChunk = referenceChunk.AsT0;
             if(processedChunks.Contains(chunkPos))
             if(processedChunks.Contains(chunkPos))
                 continue;
                 continue;
+
             var maybeArray = GetChunkFloodFill(
             var maybeArray = GetChunkFloodFill(
                 reallyReferenceChunk,
                 reallyReferenceChunk,
                 chunkSize,
                 chunkSize,
@@ -293,7 +301,7 @@ internal static class FloodFillHelper
             }
             }
         }
         }
 
 
-        if (lines.Count > 0)
+        if (lines.LineDict.Any(x => x.Value.Count > 0))
         {
         {
             selection = BuildContour(lines);
             selection = BuildContour(lines);
         }
         }
@@ -301,7 +309,7 @@ internal static class FloodFillHelper
         return selection;
         return selection;
     }
     }
 
 
-    private static void ProcessEmptySelectionChunk(List<Line> lines, VecI chunkPos, VecI realSize,
+    private static void ProcessEmptySelectionChunk(Lines lines, VecI chunkPos, VecI realSize,
         VecI imageSizeInChunks)
         VecI imageSizeInChunks)
     {
     {
         bool isEdgeChunk = chunkPos.X == 0 || chunkPos.Y == 0 || chunkPos.X == imageSizeInChunks.X - 1 ||
         bool isEdgeChunk = chunkPos.X == 0 || chunkPos.Y == 0 || chunkPos.X == imageSizeInChunks.X - 1 ||
@@ -321,31 +329,52 @@ internal static class FloodFillHelper
 
 
             if (isTopEdge)
             if (isTopEdge)
             {
             {
-                AddLine(new(new(posX, posY), new(endX, posY)), lines);
+                AddLine(new(new(posX, posY), new(endX, posY)), lines, Right);
             }
             }
 
 
             if (isBottomEdge)
             if (isBottomEdge)
             {
             {
-                AddLine(new(new(posX, endY), new(endX, endY)), lines);
+                AddLine(new(new(posX, endY), new(endX, endY)), lines, Left);
             }
             }
 
 
             if (isLeftEdge)
             if (isLeftEdge)
             {
             {
-                AddLine(new(new(posX, posY), new(posX, endY)), lines);
+                AddLine(new(new(posX, posY), new(posX, endY)), lines, Up);
             }
             }
             
             
             if (isRightEdge)
             if (isRightEdge)
             {
             {
-                AddLine(new(new(endX, posY), new(endX, endY)), lines);
+                AddLine(new(new(endX, posY), new(endX, endY)), lines, Down);
             }
             }
         }
         }
     }
     }
 
 
-    public static VectorPath BuildContour(List<Line> lines)
+    public static VectorPath BuildContour(Lines lines)
     {
     {
         VectorPath selection = new();
         VectorPath selection = new();
-        Line startLine = lines[0];
-        selection.MoveTo(startLine.Start);
+
+        VecI startingPoint = lines.LineDict[Up].First().Value.Start;
+        VecI prevPos = startingPoint;
+        selection.MoveTo(prevPos);
+        foreach (var line in lines)
+        {
+            Line nextLine;
+            if (!lines.TryGetLine(prevPos, out nextLine))
+            {
+                nextLine = line;
+                selection.MoveTo(nextLine.Start);
+                selection.LineTo(nextLine.End);
+                prevPos = nextLine.End;
+                lines.RemoveLine(line.Start);
+                continue;
+            }
+            
+            selection.LineTo(nextLine.End);
+            lines.RemoveLine(prevPos);
+            prevPos = nextLine.End;
+        }
+        
+        /*selection.MoveTo(startLine.Start);
         VecI lastPos = startLine.End;
         VecI lastPos = startLine.End;
         VecI lastDir = startLine.End - startLine.Start;
         VecI lastDir = startLine.End - startLine.Start;
         lines.RemoveAt(0);
         lines.RemoveAt(0);
@@ -380,20 +409,11 @@ internal static class FloodFillHelper
             lastDir = nextDir;
             lastDir = nextDir;
             lines.Remove(nextLine);
             lines.Remove(nextLine);
             i--;
             i--;
-        }
+        }*/
 
 
         return selection;
         return selection;
     }
     }
 
 
-    private static Line FindNextLine(List<Line> lines, VecI lastPos)
-    {
-        Line nextLine = lines.FirstOrDefault(x => x.Start == lastPos);
-        if(nextLine == default)
-            nextLine = lines.FirstOrDefault(x => x.End == lastPos);
-        
-        return nextLine;
-    }
-
     private static unsafe byte[]? GetChunkFloodFill(
     private static unsafe byte[]? GetChunkFloodFill(
         Chunk referenceChunk,
         Chunk referenceChunk,
         int chunkSize,
         int chunkSize,
@@ -401,7 +421,7 @@ internal static class FloodFillHelper
         VecI maxSize,
         VecI maxSize,
         VecI documentSize,
         VecI documentSize,
         VecI pos,
         VecI pos,
-        ColorBounds bounds, List<Line> lines)
+        ColorBounds bounds, Lines lines)
     {
     {
         byte[] pixelStates = new byte[chunkSize * chunkSize];
         byte[] pixelStates = new byte[chunkSize * chunkSize];
 
 
@@ -429,10 +449,10 @@ internal static class FloodFillHelper
         return pixelStates;
         return pixelStates;
     }
     }
 
 
-    private static unsafe void AddFillContourLines(int chunkSize, VecI chunkOffset, ColorBounds bounds, List<Line> lines,
+    private static unsafe void AddFillContourLines(int chunkSize, VecI chunkOffset, ColorBounds bounds, Lines lines,
         VecI curPos, byte[] pixelStates, int pixelOffset, Half* refPixel, Stack<VecI> toVisit, VecI clampedPos)
         VecI curPos, byte[] pixelStates, int pixelOffset, Half* refPixel, Stack<VecI> toVisit, VecI clampedPos)
     {
     {
-        // Left
+        // Left pixel
         if (curPos.X > 0 && pixelStates[pixelOffset - 1] != Visited)
         if (curPos.X > 0 && pixelStates[pixelOffset - 1] != Visited)
         {
         {
             if (bounds.IsWithinBounds(refPixel - 4))
             if (bounds.IsWithinBounds(refPixel - 4))
@@ -444,11 +464,11 @@ internal static class FloodFillHelper
                 AddLine(
                 AddLine(
                     new Line(
                     new Line(
                         new VecI(clampedPos.X, clampedPos.Y + 1) + chunkOffset,
                         new VecI(clampedPos.X, clampedPos.Y + 1) + chunkOffset,
-                        new VecI(clampedPos.X, clampedPos.Y) + chunkOffset), lines);
+                        new VecI(clampedPos.X, clampedPos.Y) + chunkOffset), lines, Up);
             }
             }
         }
         }
 
 
-        // Right
+        // Right pixel
         if (curPos.X < chunkSize - 1 && pixelStates[pixelOffset + 1] != Visited)
         if (curPos.X < chunkSize - 1 && pixelStates[pixelOffset + 1] != Visited)
         {
         {
             if (bounds.IsWithinBounds(refPixel + 4))
             if (bounds.IsWithinBounds(refPixel + 4))
@@ -460,11 +480,11 @@ internal static class FloodFillHelper
                 AddLine(
                 AddLine(
                     new Line(
                     new Line(
                         new VecI(clampedPos.X + 1, clampedPos.Y) + chunkOffset,
                         new VecI(clampedPos.X + 1, clampedPos.Y) + chunkOffset,
-                        new VecI(clampedPos.X + 1, clampedPos.Y + 1) + chunkOffset), lines);
+                        new VecI(clampedPos.X + 1, clampedPos.Y + 1) + chunkOffset), lines, Down);
             }
             }
         }
         }
 
 
-        // Top
+        // Top pixel
         if (curPos.Y > 0 && pixelStates[pixelOffset - chunkSize] != Visited)
         if (curPos.Y > 0 && pixelStates[pixelOffset - chunkSize] != Visited)
         {
         {
             if (bounds.IsWithinBounds(refPixel - 4 * chunkSize))
             if (bounds.IsWithinBounds(refPixel - 4 * chunkSize))
@@ -476,11 +496,11 @@ internal static class FloodFillHelper
                 AddLine(
                 AddLine(
                     new Line(
                     new Line(
                         new VecI(clampedPos.X + 1, clampedPos.Y) + chunkOffset,
                         new VecI(clampedPos.X + 1, clampedPos.Y) + chunkOffset,
-                        new VecI(clampedPos.X, clampedPos.Y) + chunkOffset), lines);
+                        new VecI(clampedPos.X, clampedPos.Y) + chunkOffset), lines, Right);
             }
             }
         }
         }
 
 
-        //Bottom
+        //Bottom pixel
         if (curPos.Y < chunkSize - 1 && pixelStates[pixelOffset + chunkSize] != Visited)
         if (curPos.Y < chunkSize - 1 && pixelStates[pixelOffset + chunkSize] != Visited)
         {
         {
             if (bounds.IsWithinBounds(refPixel + 4 * chunkSize))
             if (bounds.IsWithinBounds(refPixel + 4 * chunkSize))
@@ -492,12 +512,12 @@ internal static class FloodFillHelper
                 AddLine(
                 AddLine(
                     new Line(
                     new Line(
                         new VecI(clampedPos.X + 1, clampedPos.Y + 1) + chunkOffset,
                         new VecI(clampedPos.X + 1, clampedPos.Y + 1) + chunkOffset,
-                        new VecI(clampedPos.X, clampedPos.Y + 1) + chunkOffset), lines);
+                        new VecI(clampedPos.X, clampedPos.Y + 1) + chunkOffset), lines, Left);
             }
             }
         }
         }
     }
     }
 
 
-    private static void AddCornerLines(VecI documentSize, VecI chunkOffset, List<Line> lines, VecI curPos, VecI clampedPos)
+    private static void AddCornerLines(VecI documentSize, VecI chunkOffset, Lines lines, VecI curPos, VecI clampedPos)
     {
     {
         VecI globalPos = curPos + chunkOffset;
         VecI globalPos = curPos + chunkOffset;
         if (globalPos.X == 0)
         if (globalPos.X == 0)
@@ -505,7 +525,7 @@ internal static class FloodFillHelper
             AddLine(
             AddLine(
                 new Line(
                 new Line(
                     new VecI(clampedPos.X, clampedPos.Y + 1) + chunkOffset,
                     new VecI(clampedPos.X, clampedPos.Y + 1) + chunkOffset,
-                    new VecI(clampedPos.X, clampedPos.Y) + chunkOffset), lines);
+                    new VecI(clampedPos.X, clampedPos.Y) + chunkOffset), lines, Up);
         }
         }
 
 
         if (globalPos.X == documentSize.X - 1)
         if (globalPos.X == documentSize.X - 1)
@@ -513,7 +533,7 @@ internal static class FloodFillHelper
             AddLine(
             AddLine(
                 new Line(
                 new Line(
                     new VecI(clampedPos.X + 1, clampedPos.Y) + chunkOffset,
                     new VecI(clampedPos.X + 1, clampedPos.Y) + chunkOffset,
-                    new VecI(clampedPos.X + 1, clampedPos.Y + 1) + chunkOffset), lines);
+                    new VecI(clampedPos.X + 1, clampedPos.Y + 1) + chunkOffset), lines, Down);
         }
         }
 
 
         if (globalPos.Y == 0)
         if (globalPos.Y == 0)
@@ -521,7 +541,7 @@ internal static class FloodFillHelper
             AddLine(
             AddLine(
                 new Line(
                 new Line(
                     new VecI(clampedPos.X, clampedPos.Y) + chunkOffset,
                     new VecI(clampedPos.X, clampedPos.Y) + chunkOffset,
-                    new VecI(clampedPos.X + 1, clampedPos.Y) + chunkOffset), lines);
+                    new VecI(clampedPos.X + 1, clampedPos.Y) + chunkOffset), lines, Right);
         }
         }
 
 
         if (globalPos.Y == documentSize.Y - 1)
         if (globalPos.Y == documentSize.Y - 1)
@@ -529,22 +549,32 @@ internal static class FloodFillHelper
             AddLine(
             AddLine(
                 new Line(
                 new Line(
                     new VecI(clampedPos.X + 1, clampedPos.Y + 1) + chunkOffset,
                     new VecI(clampedPos.X + 1, clampedPos.Y + 1) + chunkOffset,
-                    new VecI(clampedPos.X, clampedPos.Y + 1) + chunkOffset), lines);
+                    new VecI(clampedPos.X, clampedPos.Y + 1) + chunkOffset), lines, Left);
         }
         }
     }
     }
 
 
-    private static void AddLine(Line line, List<Line> lines)
+    private static void AddLine(Line line, Lines lines, VecI direction)
     {
     {
-        if(lines.Contains(line)) return;
-        lines.Add(line);
+        VecI calculatedDir = (VecI)(line.End - line.Start).Normalized();
+        if (calculatedDir == direction)
+        {
+            lines.LineDict[direction][line.Start] = line;
+        }
+        else if(calculatedDir == -direction)
+        {
+            lines.LineDict[direction][line.End] = new Line(line.End, line.Start);
+        }
+        else
+        {
+            throw new Exception(
+                $"Line direction {calculatedDir} is not perpendicular to the direction of the requested line direction {direction}");
+        }
     }
     }
 
 
     public struct Line
     public struct Line
     {
     {
         public VecI Start { get; set; }
         public VecI Start { get; set; }
         public VecI End { get; set; }
         public VecI End { get; set; }
-        
-        public VecD DirectionNormalized => (End - Start).Normalized();
 
 
         public Line(VecI start, VecI end)
         public Line(VecI start, VecI end)
         {
         {
@@ -574,4 +604,67 @@ internal static class FloodFillHelper
             return !(a == b);
             return !(a == b);
         }
         }
     }
     }
+
+    internal class Lines : IEnumerable<Line>
+    {
+        public Dictionary<VecI, Dictionary<VecI, Line>> LineDict { get; set; } = new Dictionary<VecI, Dictionary<VecI, Line>>();
+
+        public Lines()
+        {
+            LineDict[Right] = new Dictionary<VecI, Line>();
+            LineDict[Down] = new Dictionary<VecI, Line>();
+            LineDict[Left] = new Dictionary<VecI, Line>();
+            LineDict[Up] = new Dictionary<VecI, Line>();
+        }
+        
+        public bool TryGetLine(VecI start, out Line line)
+        {
+            foreach (var lineDict in LineDict.Values)
+            {
+                if (lineDict.TryGetValue(start, out line))
+                {
+                    return true;
+                }
+            }
+            
+            line = default;
+            return false;
+        }
+
+        public IEnumerator<Line> GetEnumerator()
+        {
+            foreach (var upLines in LineDict[Up])
+            {
+                yield return upLines.Value;
+            }
+            
+            foreach (var rightLines in LineDict[Right])
+            {
+                yield return rightLines.Value;
+            }
+            
+            foreach (var downLines in LineDict[Down])
+            {
+                yield return downLines.Value;
+            }
+            
+            foreach (var leftLines in LineDict[Left])
+            {
+                yield return leftLines.Value;
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public void RemoveLine(VecI prevPos)
+        {
+            foreach (var lineDict in LineDict.Values)
+            {
+                lineDict.Remove(prevPos);
+            }
+        }
+    }
 }
 }

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand_UpdateableChange.cs → src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand_Change.cs

@@ -6,7 +6,7 @@ using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 
 
-internal class MagicWand_UpdateableChange : Change
+internal class MagicWand_Change : Change
 {
 {
     private VectorPath? originalPath;
     private VectorPath? originalPath;
     private VectorPath path = new() { FillType = PathFillType.EvenOdd };
     private VectorPath path = new() { FillType = PathFillType.EvenOdd };
@@ -17,7 +17,7 @@ internal class MagicWand_UpdateableChange : Change
     private readonly SelectionMode mode;
     private readonly SelectionMode mode;
 
 
     [GenerateMakeChangeAction]
     [GenerateMakeChangeAction]
-    public MagicWand_UpdateableChange(Guid memberGuid, VecI point, SelectionMode mode, bool referenceAll, bool drawOnMask)
+    public MagicWand_Change(Guid memberGuid, VecI point, SelectionMode mode, bool referenceAll, bool drawOnMask)
     {
     {
         path.MoveTo(point);
         path.MoveTo(point);
         this.mode = mode;
         this.mode = mode;