Browse Source

MagicWandVisualizer

flabbet 2 years ago
parent
commit
ff5c3cac41

+ 28 - 5
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -3,6 +3,7 @@ using System.Numerics;
 using System.Runtime.CompilerServices;
 using ChunkyImageLib.Operations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Debugging;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
@@ -11,7 +12,7 @@ using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
 
-internal static class FloodFillHelper
+public static class FloodFillHelper
 {
     private const byte InSelection = 1;
     private const byte Visited = 2;
@@ -137,7 +138,7 @@ internal static class FloodFillHelper
         }
         return drawingChunks;
     }
-
+    
     private static unsafe byte[]? FloodFillChunk(
         Chunk referenceChunk,
         Chunk drawingChunk,
@@ -212,6 +213,8 @@ internal static class FloodFillHelper
             drawingSurface.Canvas.Flush();
         }
     }
+    
+    private static MagicWandVisualizer visualizer = new MagicWandVisualizer(Path.Combine("Debugging", "MagicWand"));
 
     public static VectorPath GetFloodFillSelection(VecI startingPos, HashSet<Guid> membersToFloodFill,
         IReadOnlyDocument document)
@@ -239,7 +242,7 @@ internal static class FloodFillHelper
         HashSet<VecI> processedChunks = new();
         Stack<(VecI chunkPos, VecI posOnChunk)> positionsToFloodFill = new();
         positionsToFloodFill.Push((initChunkPos, initPosOnChunk));
-        
+
         Lines lines = new();
         
         VectorPath selection = new();
@@ -305,6 +308,8 @@ internal static class FloodFillHelper
         {
             selection = BuildContour(lines);
         }
+        
+        visualizer.GenerateVisualization(document.Size.X, document.Size.Y, 500, 500);
 
         return selection;
     }
@@ -556,10 +561,13 @@ internal static class FloodFillHelper
         if (calculatedDir == direction)
         {
             lines.LineDict[direction][line.Start] = line;
+            visualizer.Steps.Add(line);
         }
         else if(calculatedDir == -direction)
         {
-            lines.LineDict[direction][line.End] = new Line(line.End, line.Start);
+            Line fixedLine = new Line(line.End, line.Start);
+            lines.LineDict[direction][line.End] = fixedLine;
+            visualizer.Steps.Add(fixedLine);
         }
         else
         {
@@ -570,6 +578,21 @@ internal static class FloodFillHelper
 
     public struct Line
     {
+        public bool Equals(Line other)
+        {
+            return Start.Equals(other.Start) && End.Equals(other.End) && NormalizedDirection.Equals(other.NormalizedDirection);
+        }
+
+        public override bool Equals(object? obj)
+        {
+            return obj is Line other && Equals(other);
+        }
+
+        public override int GetHashCode()
+        {
+            return HashCode.Combine(Start, End, NormalizedDirection);
+        }
+
         public VecI Start { get; set; }
         public VecI End { get; set; }
         public VecI NormalizedDirection { get; }
@@ -604,7 +627,7 @@ internal static class FloodFillHelper
         }
     }
 
-    internal class Lines : IEnumerable<Line>
+    public class Lines : IEnumerable<Line>
     {
         public Dictionary<VecI, Dictionary<VecI, Line>> LineDict { get; set; } = new Dictionary<VecI, Dictionary<VecI, Line>>();
 

+ 86 - 0
src/PixiEditor.ChangeableDocument/Debugging/MagicWandVisualizer.cs

@@ -0,0 +1,86 @@
+using PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+namespace PixiEditor.ChangeableDocument.Debugging;
+
+public class MagicWandVisualizer
+{
+    public string FilePath { get; }
+    private Paint drawingPaint;
+    private Paint replacementPaint;
+    public List<FloodFillHelper.Line> Steps = new List<FloodFillHelper.Line>();
+    
+    public MagicWandVisualizer(string filePath)
+    {
+        drawingPaint = new Paint();
+        drawingPaint.BlendMode = BlendMode.Src;
+        
+        replacementPaint = new Paint();
+        replacementPaint.BlendMode = BlendMode.Src;
+        replacementPaint.ColorFilter = ColorFilter.CreateBlendMode(Colors.Black, BlendMode.ColorBurn);
+        
+        FilePath = filePath;
+        if(!Directory.Exists(filePath))
+        {
+            Directory.CreateDirectory(filePath);
+        }
+        
+        Directory.EnumerateFiles(filePath).ToList().ForEach(File.Delete);
+    }
+
+    public void GenerateVisualization(int originalHeight, int originalWidth, int width, int height)
+    {
+        DrawingSurface surface = DrawingSurface.Create(new ImageInfo(width, height));
+        surface.Canvas.Clear(Colors.White);
+        Image previousImage = surface.Snapshot();
+        VecI scaledStart = new VecI(Steps[0].Start.X * (width / originalWidth), Steps[0].Start.Y * (height / originalHeight));
+        VecI scaledEnd = new VecI(Steps[0].End.X * (width / originalWidth), Steps[0].End.Y * (height / originalHeight));
+        
+        DrawArrow(surface, scaledStart, scaledEnd, 2, Colors.Green);
+        using (FileStream stream = new FileStream(Path.Join(FilePath, "Frame 1.png"), FileMode.Create))
+        {
+            surface.Snapshot().Encode().SaveTo(stream);
+        }
+
+        for (int i = 1; i < Steps.Count; i++)
+        {
+            surface = DrawingSurface.Create(new ImageInfo(width, height));
+            surface.Canvas.Clear(Colors.White);
+            surface.Canvas.DrawImage(previousImage, RectD.Create(VecI.Zero, new VecI(previousImage.Width, previousImage.Height)), replacementPaint);
+            
+            scaledStart = new VecI(Steps[i].Start.X * (width / originalWidth), Steps[i].Start.Y * (height / originalHeight));
+            scaledEnd = new VecI(Steps[i].End.X * (width / originalWidth), Steps[i].End.Y * (height / originalHeight));
+            
+            DrawArrow(surface, scaledStart, scaledEnd, 2, Colors.Green);
+            
+            using (FileStream stream = new FileStream(Path.Join(FilePath, $"Frame {i}.png"), FileMode.Create))
+            {
+                surface.Snapshot().Encode().SaveTo(stream);
+            }
+            
+            previousImage = surface.Snapshot();
+        }
+    }
+
+    private void DrawArrow(DrawingSurface surface, VecI start, VecI end, int thickness, Color color)
+    {
+        drawingPaint.Color = color;
+        drawingPaint.StrokeWidth = thickness;
+        surface.Canvas.DrawLine(start, end, drawingPaint);
+        
+        // Draw arrow head
+        
+        VecI direction = end - start;
+        VecI perpendicular = new VecI(-direction.Y, direction.X);
+        
+        VecI arrowHead1 = end - (VecI)direction.Normalized() * 10 + (VecI)perpendicular.Normalized() * 5;
+        VecI arrowHead2 = end - (VecI)direction.Normalized() * 10 - (VecI)perpendicular.Normalized() * 5;
+        
+        surface.Canvas.DrawLine(end, arrowHead1, drawingPaint);
+        surface.Canvas.DrawLine(end, arrowHead2, drawingPaint);
+    }
+}

+ 1 - 0
src/PixiEditor.DrawingApi.Core/Bridge/IDrawingBackend.cs

@@ -17,5 +17,6 @@ namespace PixiEditor.DrawingApi.Core.Bridge
         public IColorSpaceImplementation ColorSpaceImplementation { get; }
         public IImgDataImplementation ImgDataImplementation { get; }
         public IBitmapImplementation BitmapImplementation { get; }
+        public IColorFilterImplementation ColorFilterImplementation { get; set; }
     }
 }

+ 12 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IColorFilterImplementation.cs

@@ -0,0 +1,12 @@
+using System;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+
+public interface IColorFilterImplementation
+{
+    public IntPtr CreateBlendMode(Color color, BlendMode blendMode);
+    public void Dispose(ColorFilter colorFilter);
+}

+ 3 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs

@@ -24,5 +24,8 @@ namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl
         public void SetStrokeCap(Paint paint, StrokeCap value);
         public float GetStrokeWidth(Paint paint);
         public void SetStrokeWidth(Paint paint, float value);
+        public ColorFilter GetColorFilter(Paint paint);
+
+        public void SetColorFilter(Paint paint, ColorFilter value);
     }
 }

+ 24 - 0
src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/ColorFilter.cs

@@ -0,0 +1,24 @@
+using System;
+using PixiEditor.DrawingApi.Core.Bridge;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+
+namespace PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+public class ColorFilter : NativeObject
+{
+    public ColorFilter(IntPtr objPtr) : base(objPtr)
+    {
+        
+    }
+
+    public static ColorFilter CreateBlendMode(Color color, BlendMode blendMode)
+    {
+        ColorFilter filter = new ColorFilter(DrawingBackendApi.Current.ColorFilterImplementation.CreateBlendMode(color, blendMode));
+        return filter;
+    }
+
+    public override void Dispose()
+    {
+        DrawingBackendApi.Current.ColorFilterImplementation.Dispose(this);
+    }
+}

+ 6 - 0
src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/Paint.cs

@@ -50,6 +50,12 @@ namespace PixiEditor.DrawingApi.Core.Surface.PaintImpl
             get => DrawingBackendApi.Current.PaintImplementation.GetStrokeWidth(this);
             set => DrawingBackendApi.Current.PaintImplementation.SetStrokeWidth(this, value);
         }
+        
+        public ColorFilter ColorFilter 
+        {
+            get => DrawingBackendApi.Current.PaintImplementation.GetColorFilter(this);
+            set => DrawingBackendApi.Current.PaintImplementation.SetColorFilter(this, value);
+        }
 
         public Paint(IntPtr objPtr) : base(objPtr)
         {

+ 27 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaColorFilterImplementation.cs

@@ -0,0 +1,27 @@
+using System;
+using PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using SkiaSharp;
+
+namespace PixiEditor.DrawingApi.Skia.Implementations
+{
+    public class SkiaColorFilterImplementation : SkObjectImplementation<SKColorFilter>, IColorFilterImplementation
+    {
+        public IntPtr CreateBlendMode(Color color, BlendMode blendMode)
+        {
+            SKColorFilter skColorFilter = SKColorFilter.CreateBlendMode(color.ToSKColor(), (SKBlendMode)blendMode);
+            ManagedInstances[skColorFilter.Handle] = skColorFilter;
+
+            return skColorFilter.Handle;
+        }
+
+        public void Dispose(ColorFilter colorFilter)
+        {
+            SKColorFilter skColorFilter = ManagedInstances[colorFilter.ObjectPointer];
+            skColorFilter.Dispose();
+            ManagedInstances.TryRemove(skColorFilter.Handle, out _);
+        }
+    }
+}

+ 31 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPaintImplementation.cs

@@ -9,10 +9,22 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
 {
     public class SkiaPaintImplementation : SkObjectImplementation<SKPaint>, IPaintImplementation
     {
+        private readonly SkiaColorFilterImplementation colorFilterImplementation;
+ 
+        public SkiaPaintImplementation(SkiaColorFilterImplementation colorFilterImpl)
+        {
+            colorFilterImplementation = colorFilterImpl;
+        }
+        
         public IntPtr CreatePaint()
         {
             SKPaint skPaint = new SKPaint();
             ManagedInstances[skPaint.Handle] = skPaint;
+            if (skPaint.ColorFilter != null)
+            {
+                colorFilterImplementation.ManagedInstances[skPaint.ColorFilter.Handle] = skPaint.ColorFilter;
+            }
+
             return skPaint.Handle;
         }
 
@@ -20,6 +32,13 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         {
             if (!ManagedInstances.ContainsKey(paintObjPointer)) return;
             SKPaint paint = ManagedInstances[paintObjPointer];
+
+            if (paint.ColorFilter != null)
+            {
+                paint.ColorFilter.Dispose();
+                colorFilterImplementation.ManagedInstances.TryRemove(paint.ColorFilter.Handle, out _);
+            }
+
             paint.Dispose();
             ManagedInstances.TryRemove(paintObjPointer, out _);
         }
@@ -114,5 +133,17 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             SKPaint skPaint = ManagedInstances[paint.ObjectPointer];
             skPaint.StrokeWidth = value;
         }
+
+        public ColorFilter GetColorFilter(Paint paint)
+        {
+            SKPaint skPaint = ManagedInstances[paint.ObjectPointer];
+            return new ColorFilter(skPaint.ColorFilter.Handle);
+        }
+
+        public void SetColorFilter(Paint paint, ColorFilter value)
+        {
+            SKPaint skPaint = ManagedInstances[paint.ObjectPointer];
+            skPaint.ColorFilter = colorFilterImplementation[value.ObjectPointer];
+        }
     }
 }

+ 6 - 2
src/PixiEditor.DrawingApi.Skia/SkiaDrawingBackend.cs

@@ -18,7 +18,8 @@ namespace PixiEditor.DrawingApi.Skia
         public ISurfaceImplementation SurfaceImplementation { get; }
         public IColorSpaceImplementation ColorSpaceImplementation { get; }
         public IBitmapImplementation BitmapImplementation { get; }
-        
+        public IColorFilterImplementation ColorFilterImplementation { get; set; }
+
         public SkiaDrawingBackend()
         {
             ColorImplementation = new SkiaColorImplementation();
@@ -29,7 +30,10 @@ namespace PixiEditor.DrawingApi.Skia
             SkiaImageImplementation imgImpl = new SkiaImageImplementation(dataImpl);
             ImageImplementation = imgImpl;
             
-            SkiaPaintImplementation paintImpl = new SkiaPaintImplementation();
+            SkiaColorFilterImplementation colorFilterImpl = new SkiaColorFilterImplementation();
+            ColorFilterImplementation = colorFilterImpl;
+            
+            SkiaPaintImplementation paintImpl = new SkiaPaintImplementation(colorFilterImpl);
             PaintImplementation = paintImpl;
             
             SkiaPathImplementation pathImpl = new SkiaPathImplementation();