瀏覽代碼

Add option to reference all layers to flood fill

Equbuxu 3 年之前
父節點
當前提交
db7a6b8074

+ 24 - 0
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -26,7 +26,31 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         Selection.Dispose();
     }
 
+    public void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action) => ForEveryReadonlyMember(StructureRoot, action);
+    public void ForEveryMember(Action<StructureMember> action) => ForEveryMember(StructureRoot, action);
+
+    private void ForEveryReadonlyMember(IReadOnlyFolder folder, Action<IReadOnlyStructureMember> action)
+    {
+        foreach (var child in folder.Children)
+        {
+            action(child);
+            if (child is IReadOnlyFolder innerFolder)
+                ForEveryReadonlyMember(innerFolder, action);
+        }
+    }
+
+    private void ForEveryMember(Folder folder, Action<StructureMember> action)
+    {
+        foreach (var child in folder.Children)
+        {
+            action(child);
+            if (child is Folder innerFolder)
+                ForEveryMember(innerFolder, action);
+        }
+    }
+
     public StructureMember FindMemberOrThrow(Guid guid) => FindMember(guid) ?? throw new ArgumentException("Could not find member with guid " + guid.ToString());
+
     public StructureMember? FindMember(Guid guid)
     {
         var list = FindMemberPath(guid);

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -9,6 +9,7 @@ public interface IReadOnlyDocument
     bool VerticalSymmetryAxisEnabled { get; }
     int HorizontalSymmetryAxisY { get; }
     int VerticalSymmetryAxisX { get; }
+    void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);
     IReadOnlyStructureMember? FindMember(Guid guid);
     IReadOnlyStructureMember FindMemberOrThrow(Guid guid);
     (IReadOnlyStructureMember, IReadOnlyFolder) FindChildAndParentOrThrow(Guid guid);

+ 72 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs

@@ -0,0 +1,72 @@
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Rendering;
+using SkiaSharp;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
+
+internal class FloodFillChunkCache : IDisposable
+{
+    private SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
+
+    private readonly HashSet<Guid>? membersToRender;
+    private readonly IReadOnlyFolder? structureRoot;
+    private readonly IReadOnlyChunkyImage? image;
+
+    private readonly Dictionary<VecI, OneOf<Chunk, EmptyChunk>> acquiredChunks = new();
+
+    public FloodFillChunkCache(IReadOnlyChunkyImage image)
+    {
+        this.image = image;
+    }
+
+    public FloodFillChunkCache(HashSet<Guid> membersToRender, IReadOnlyFolder structureRoot)
+    {
+        this.membersToRender = membersToRender;
+        this.structureRoot = structureRoot;
+    }
+
+    public bool ChunkExistsInStorage(VecI pos)
+    {
+        if (image is not null)
+            return acquiredChunks.ContainsKey(pos);
+        return true;
+    }
+
+    public OneOf<Chunk, EmptyChunk> GetChunk(VecI pos)
+    {
+        // the chunk was already acquired before, return cached
+        if (acquiredChunks.ContainsKey(pos))
+            return acquiredChunks[pos];
+
+        // need to get the chunk by merging multiple members
+        if (image is null)
+        {
+            if (structureRoot is null || membersToRender is null)
+                throw new InvalidOperationException();
+            var chunk = ChunkRenderer.MergeChosenMembers(pos, ChunkResolution.Full, structureRoot, membersToRender);
+            acquiredChunks[pos] = chunk;
+            return chunk;
+        }
+
+        // there is only a single image, just get the chunk from it
+        if (!image.LatestOrCommittedChunkExists(pos))
+            return new EmptyChunk();
+        Chunk chunkOnImage = Chunk.Create(ChunkResolution.Full);
+
+        if (!image.DrawMostUpToDateChunkOn(pos, ChunkResolution.Full, chunkOnImage.Surface.SkiaSurface, VecI.Zero, ReplacingPaint))
+        {
+            chunkOnImage.Dispose();
+            acquiredChunks[pos] = new EmptyChunk();
+            return new EmptyChunk();
+        }
+        acquiredChunks[pos] = chunkOnImage;
+        return chunkOnImage;
+    }
+
+    public void Dispose()
+    {
+        foreach (var chunk in acquiredChunks.Values.Where(static chunk => chunk.IsT0))
+            chunk.AsT0.Dispose();
+        ReplacingPaint.Dispose();
+    }
+}

+ 0 - 49
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkStorage.cs

@@ -1,49 +0,0 @@
-using SkiaSharp;
-
-namespace PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
-internal class FloodFillChunkStorage : IDisposable
-{
-    private readonly ChunkyImage image;
-    private Dictionary<VecI, Chunk> acquiredChunks = new();
-    private SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
-
-    public FloodFillChunkStorage(ChunkyImage image)
-    {
-        this.image = image;
-    }
-
-    public bool ChunkExistsInStorageOrInImage(VecI pos)
-    {
-        return acquiredChunks.ContainsKey(pos) || image.LatestOrCommittedChunkExists(pos);
-    }
-
-    public Chunk GetChunk(VecI pos)
-    {
-        if (acquiredChunks.ContainsKey(pos))
-            return acquiredChunks[pos];
-        Chunk chunk = Chunk.Create(ChunkResolution.Full);
-        if (!image.DrawMostUpToDateChunkOn(pos, ChunkResolution.Full, chunk.Surface.SkiaSurface, VecI.Zero, ReplacingPaint))
-            chunk.Surface.SkiaSurface.Canvas.Clear();
-        acquiredChunks[pos] = chunk;
-        return chunk;
-    }
-
-    public (CommittedChunkStorage, HashSet<VecI>) DrawOnChunkyImage(ChunkyImage chunkyImage)
-    {
-        foreach (var (pos, chunk) in acquiredChunks)
-        {
-            chunkyImage.EnqueueDrawImage(pos * ChunkResolution.Full.PixelSize(), chunk.Surface, ReplacingPaint, false);
-        }
-        var affected = chunkyImage.FindAffectedChunks();
-        var affectedChunkStorage = new CommittedChunkStorage(chunkyImage, affected);
-        chunkyImage.CommitChanges();
-        return (affectedChunkStorage, affected);
-    }
-
-    public void Dispose()
-    {
-        foreach (var chunk in acquiredChunks.Values)
-            chunk.Dispose();
-        ReplacingPaint.Dispose();
-    }
-}

+ 77 - 27
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -1,34 +1,59 @@
 using System.Runtime.CompilerServices;
 using ChunkyImageLib.Operations;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using SkiaSharp;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
+
 internal static class FloodFillHelper
 {
     private const byte InSelection = 1;
     private const byte Visited = 2;
 
-    public static FloodFillChunkStorage FloodFill(ChunkyImage image, SKPath? selection, VecI startingPos, SKColor drawingColor)
+    private static FloodFillChunkCache CreateCache(HashSet<Guid> membersToFloodFill, IReadOnlyDocument document)
+    {
+        if (membersToFloodFill.Count == 1)
+        {
+            Guid guid = membersToFloodFill.First();
+            var member = document.FindMemberOrThrow(guid);
+            if (member is IReadOnlyFolder folder)
+                return new FloodFillChunkCache(membersToFloodFill, document.StructureRoot);
+            return new FloodFillChunkCache(((IReadOnlyLayer)member).LayerImage);
+        }
+        return new FloodFillChunkCache(membersToFloodFill, document.StructureRoot);
+    }
+
+    public static Dictionary<VecI, Chunk> FloodFill(
+        HashSet<Guid> membersToFloodFill,
+        IReadOnlyDocument document,
+        SKPath? selection,
+        VecI startingPos,
+        SKColor drawingColor)
     {
         int chunkSize = ChunkResolution.Full.PixelSize();
 
-        FloodFillChunkStorage storage = new(image);
+        FloodFillChunkCache cache = CreateCache(membersToFloodFill, document);
 
         VecI initChunkPos = OperationHelper.GetChunkPos(startingPos, chunkSize);
-        VecI imageSizeInChunks = (VecI)(image.LatestSize / (double)chunkSize).Ceiling();
+        VecI imageSizeInChunks = (VecI)(document.Size / (double)chunkSize).Ceiling();
         VecI initPosOnChunk = startingPos - initChunkPos * chunkSize;
-        SKColor colorToReplace = storage.GetChunk(initChunkPos).Surface.GetSRGBPixel(initPosOnChunk);
+        SKColor colorToReplace = cache.GetChunk(initChunkPos).Match(
+            (Chunk chunk) => chunk.Surface.GetSRGBPixel(initPosOnChunk),
+            static (EmptyChunk _) => SKColors.Transparent
+        );
 
         if (colorToReplace.Alpha == 0 && drawingColor.Alpha == 0 || colorToReplace == drawingColor)
-            return storage;
+            return new();
 
-        RectI globalSelectionBounds = (RectI?)selection?.TightBounds ?? new RectI(VecI.Zero, image.LatestSize);
+        RectI globalSelectionBounds = (RectI?)selection?.TightBounds ?? new RectI(VecI.Zero, document.Size);
 
-        // Premultiplies the color and convert it to floats. Since floats are imprecise, a range is used.
+        // Pre-multiplies the color and convert it to floats. Since floats are imprecise, a range is used.
         // Used for faster pixel checking
         FloodFillColorRange colorRange = new(colorToReplace);
         ulong uLongColor = ToULong(drawingColor);
 
+        Dictionary<VecI, Chunk> drawingChunks = new();
+        HashSet<VecI> processedEmptyChunks = new();
         // flood fill chunks using a basic 4-way approach with a stack (each chunk is kinda like a pixel)
         // once the chunk is filled all places where it spills over to neighboring chunks are saved in the stack
         Stack<(VecI chunkPos, VecI posOnChunk)> positionsToFloodFill = new();
@@ -37,13 +62,21 @@ internal static class FloodFillHelper
         {
             var (chunkPos, posOnChunk) = positionsToFloodFill.Pop();
 
-            // if the chunks is empty and we are replacing a transparent color clear the whole chunk right away
-            if (!storage.ChunkExistsInStorageOrInImage(chunkPos))
+            if (!drawingChunks.ContainsKey(chunkPos))
             {
-                if (colorToReplace.Alpha == 0)
+                var chunk = Chunk.Create();
+                chunk.Surface.SkiaSurface.Canvas.Clear(SKColors.Transparent);
+                drawingChunks[chunkPos] = chunk;
+            }
+            var drawingChunk = drawingChunks[chunkPos];
+            var referenceChunk = cache.GetChunk(chunkPos);
+
+            // don't call floodfill if the chunk is empty
+            if (referenceChunk.IsT1)
+            {
+                if (colorToReplace.Alpha == 0 && !processedEmptyChunks.Contains(chunkPos))
                 {
-                    var chunkToClear = storage.GetChunk(chunkPos);
-                    chunkToClear.Surface.SkiaSurface.Canvas.Clear(drawingColor);
+                    drawingChunk.Surface.SkiaSurface.Canvas.Clear(drawingColor);
                     for (int i = 0; i < chunkSize; i++)
                     {
                         if (chunkPos.Y > 0)
@@ -55,14 +88,25 @@ internal static class FloodFillHelper
                         if (chunkPos.X < imageSizeInChunks.X - 1)
                             positionsToFloodFill.Push((new(chunkPos.X + 1, chunkPos.Y), new(0, i)));
                     }
+                    processedEmptyChunks.Add(chunkPos);
                 }
                 continue;
             }
 
             // use regular flood fill for chunks that have something in them
-            Chunk chunk = storage.GetChunk(chunkPos);
-            var maybeArray = FloodFillChunk
-                (chunk, selection, globalSelectionBounds, chunkPos, chunkSize, uLongColor, drawingColor, posOnChunk, colorRange);
+            var reallyReferenceChunk = referenceChunk.AsT0;
+            var maybeArray = FloodFillChunk(
+                reallyReferenceChunk,
+                drawingChunk,
+                selection,
+                globalSelectionBounds,
+                chunkPos,
+                chunkSize,
+                uLongColor,
+                drawingColor,
+                posOnChunk,
+                colorRange);
+
             if (maybeArray is null)
                 continue;
             for (int i = 0; i < chunkSize; i++)
@@ -77,10 +121,10 @@ internal static class FloodFillHelper
                     positionsToFloodFill.Push((new(chunkPos.X + 1, chunkPos.Y), new(0, i)));
             }
         }
-        return storage;
+        return drawingChunks;
     }
 
-    private unsafe static ulong ToULong(SKColor color)
+    private static unsafe ulong ToULong(SKColor color)
     {
         ulong result = 0;
         Half* ptr = (Half*)&result;
@@ -110,7 +154,8 @@ internal static class FloodFillHelper
     }
 
     private static unsafe byte[]? FloodFillChunk(
-        Chunk chunk,
+        Chunk referenceChunk,
+        Chunk drawingChunk,
         SKPath? selection,
         RectI globalSelectionBounds,
         VecI chunkPos,
@@ -120,13 +165,17 @@ internal static class FloodFillHelper
         VecI pos,
         FloodFillColorRange bounds)
     {
-        if (chunk.Surface.GetSRGBPixel(pos) == color)
+        if (referenceChunk.Surface.GetSRGBPixel(pos) == color || drawingChunk.Surface.GetSRGBPixel(pos) == color)
             return null;
 
         byte[] pixelStates = new byte[chunkSize * chunkSize];
         DrawSelection(pixelStates, selection, globalSelectionBounds, chunkPos, chunkSize);
-        using var pixmap = chunk.Surface.SkiaSurface.PeekPixels();
-        Half* array = (Half*)pixmap.GetPixels();
+
+        using var refPixmap = referenceChunk.Surface.SkiaSurface.PeekPixels();
+        Half* refArray = (Half*)refPixmap.GetPixels();
+
+        using var drawPixmap = drawingChunk.Surface.SkiaSurface.PeekPixels();
+        Half* drawArray = (Half*)drawPixmap.GetPixels();
 
         Stack<VecI> toVisit = new();
         toVisit.Push(pos);
@@ -135,17 +184,18 @@ internal static class FloodFillHelper
         {
             VecI curPos = toVisit.Pop();
             int pixelOffset = curPos.X + curPos.Y * chunkSize;
-            Half* pixel = array + pixelOffset * 4;
-            *(ulong*)pixel = colorBits;
+            Half* drawPixel = drawArray + pixelOffset * 4;
+            Half* refPixel = refArray + pixelOffset * 4;
+            *(ulong*)drawPixel = colorBits;
             pixelStates[pixelOffset] = Visited;
 
-            if (curPos.X > 0 && pixelStates[pixelOffset - 1] == InSelection && IsWithinBounds(ref bounds, pixel - 4))
+            if (curPos.X > 0 && pixelStates[pixelOffset - 1] == InSelection && IsWithinBounds(ref bounds, refPixel - 4))
                 toVisit.Push(new(curPos.X - 1, curPos.Y));
-            if (curPos.X < chunkSize - 1 && pixelStates[pixelOffset + 1] == InSelection && IsWithinBounds(ref bounds, pixel + 4))
+            if (curPos.X < chunkSize - 1 && pixelStates[pixelOffset + 1] == InSelection && IsWithinBounds(ref bounds, refPixel + 4))
                 toVisit.Push(new(curPos.X + 1, curPos.Y));
-            if (curPos.Y > 0 && pixelStates[pixelOffset - chunkSize] == InSelection && IsWithinBounds(ref bounds, pixel - 4 * chunkSize))
+            if (curPos.Y > 0 && pixelStates[pixelOffset - chunkSize] == InSelection && IsWithinBounds(ref bounds, refPixel - 4 * chunkSize))
                 toVisit.Push(new(curPos.X, curPos.Y - 1));
-            if (curPos.Y < chunkSize - 1 && pixelStates[pixelOffset + chunkSize] == InSelection && IsWithinBounds(ref bounds, pixel + 4 * chunkSize))
+            if (curPos.Y < chunkSize - 1 && pixelStates[pixelOffset + chunkSize] == InSelection && IsWithinBounds(ref bounds, refPixel + 4 * chunkSize))
                 toVisit.Push(new(curPos.X, curPos.Y + 1));
         }
         return pixelStates;

+ 20 - 3
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFill_Change.cs

@@ -1,20 +1,23 @@
 using SkiaSharp;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
+
 internal class FloodFill_Change : Change
 {
     private readonly Guid memberGuid;
     private readonly VecI pos;
     private readonly SKColor color;
+    private readonly bool referenceAll;
     private readonly bool drawOnMask;
     private CommittedChunkStorage? chunkStorage = null;
 
     [GenerateMakeChangeAction]
-    public FloodFill_Change(Guid memberGuid, VecI pos, SKColor color, bool drawOnMask)
+    public FloodFill_Change(Guid memberGuid, VecI pos, SKColor color, bool referenceAll, bool drawOnMask)
     {
         this.memberGuid = memberGuid;
         this.pos = pos;
         this.color = color;
+        this.referenceAll = referenceAll;
         this.drawOnMask = drawOnMask;
     }
 
@@ -32,8 +35,22 @@ internal class FloodFill_Change : Change
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
 
         SKPath? selection = target.Selection.SelectionPath.IsEmpty ? null : target.Selection.SelectionPath;
-        using var floodFilledChunks = FloodFillHelper.FloodFill(image, selection, pos, color);
-        (chunkStorage, var affectedChunks) = floodFilledChunks.DrawOnChunkyImage(image);
+        HashSet<Guid> membersToReference = new();
+        if (referenceAll)
+            target.ForEveryReadonlyMember(member => membersToReference.Add(member.GuidValue));
+        else
+            membersToReference.Add(memberGuid);
+        var floodFilledChunks = FloodFillHelper.FloodFill(membersToReference, target, selection, pos, color);
+
+        foreach (var (chunkPos, chunk) in floodFilledChunks)
+        {
+            image.EnqueueDrawImage(chunkPos * ChunkyImage.FullChunkSize, chunk.Surface, null, false);
+        }
+        var affectedChunks = image.FindAffectedChunks();
+        chunkStorage = new CommittedChunkStorage(image, affectedChunks);
+        image.CommitChanges();
+        foreach (var chunk in floodFilledChunks.Values)
+            chunk.Dispose();
 
         ignoreInUndo = false;
         return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affectedChunks, drawOnMask);

+ 1 - 0
src/PixiEditorPrototype.sln

@@ -18,6 +18,7 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2AF84829-B00E-4B10-B010-C9306C6DF278}"
 	ProjectSection(SolutionItems) = preProject
 		.editorconfig = .editorconfig
+		README.md = README.md
 	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.ChangeableDocument.Gen", "PixiEditor.ChangeableDocument.Gen\PixiEditor.ChangeableDocument.Gen.csproj", "{07EE9CD1-5B4B-454D-A362-F7B42C49412F}"

+ 9 - 13
src/PixiEditorPrototype/Models/Rendering/AffectedChunkGatherer.cs

@@ -11,6 +11,7 @@ using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
 namespace PixiEditorPrototype.Models.Rendering;
+
 internal class AffectedChunkGatherer
 {
     private readonly DocumentChangeTracker tracker;
@@ -108,6 +109,7 @@ internal class AffectedChunkGatherer
                 AddAllToImagePreviews(child.GuidValue);
         }
     }
+
     private void AddAllToMainImage(Guid memberGuid, bool useMask = true)
     {
         var member = tracker.Document.FindMember(memberGuid);
@@ -123,6 +125,7 @@ internal class AffectedChunkGatherer
             AddWholeCanvasToMainImage();
         }
     }
+
     private void AddAllToMaskPreview(Guid memberGuid)
     {
         var member = tracker.Document.FindMember(memberGuid);
@@ -145,6 +148,7 @@ internal class AffectedChunkGatherer
     {
         mainImageChunks.UnionWith(chunks);
     }
+
     private void AddToImagePreviews(Guid memberGuid, HashSet<VecI> chunks, bool ignoreSelf = false)
     {
         var path = tracker.Document.FindMemberPath(memberGuid);
@@ -159,6 +163,7 @@ internal class AffectedChunkGatherer
                 imagePreviewChunks[member.GuidValue].UnionWith(chunks);
         }
     }
+
     private void AddToMaskPreview(Guid memberGuid, HashSet<VecI> chunks)
     {
         if (!maskPreviewChunks.ContainsKey(memberGuid))
@@ -172,6 +177,7 @@ internal class AffectedChunkGatherer
     {
         AddAllChunks(mainImageChunks);
     }
+
     private void AddWholeCanvasToImagePreviews(Guid memberGuid, bool ignoreSelf = false)
     {
         var path = tracker.Document.FindMemberPath(memberGuid);
@@ -186,6 +192,7 @@ internal class AffectedChunkGatherer
             AddAllChunks(imagePreviewChunks[member.GuidValue]);
         }
     }
+
     private void AddWholeCanvasToMaskPreview(Guid memberGuid)
     {
         if (!maskPreviewChunks.ContainsKey(memberGuid))
@@ -196,23 +203,12 @@ internal class AffectedChunkGatherer
 
     private void AddWholeCanvasToEveryImagePreview()
     {
-        ForEveryMember(tracker.Document.StructureRoot, (member) => AddWholeCanvasToImagePreviews(member.GuidValue));
+        tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.GuidValue));
     }
 
     private void AddWholeCanvasToEveryMaskPreview()
     {
-        ForEveryMember(tracker.Document.StructureRoot, (member) => AddWholeCanvasToMaskPreview(member.GuidValue));
-    }
-
-
-    private void ForEveryMember(IReadOnlyFolder folder, Action<IReadOnlyStructureMember> action)
-    {
-        foreach (var child in folder.Children)
-        {
-            action(child);
-            if (child is IReadOnlyFolder innerFolder)
-                ForEveryMember(innerFolder, action);
-        }
+        tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToMaskPreview(member.GuidValue));
     }
 
     private void AddAllChunks(HashSet<VecI> chunks)

+ 2 - 2
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -580,12 +580,12 @@ internal class DocumentViewModel : INotifyPropertyChanged
         }
     }
 
-    public void FloodFill(VecI pos, SKColor color)
+    public void FloodFill(VecI pos, SKColor color, bool referenceAllLayers)
     {
         var member = FindFirstSelectedMember();
         if (updateableChangeActive || member is null)
             return;
-        Helpers.ActionAccumulator.AddFinishedActions(new FloodFill_Action(member.GuidValue, pos, color, member.ShouldDrawOnMask));
+        Helpers.ActionAccumulator.AddFinishedActions(new FloodFill_Action(member.GuidValue, pos, color, referenceAllLayers, member.ShouldDrawOnMask));
     }
 
     public void AddOrUpdateViewport(ViewportInfo info)

+ 7 - 4
src/PixiEditorPrototype/ViewModels/ViewModelMain.cs

@@ -2,15 +2,16 @@
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.IO;
+using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Media;
 using ChunkyImageLib.DataHolders;
 using Microsoft.Win32;
-using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Parser;
 using PixiEditor.Zoombox;
 using PixiEditorPrototype.Models;
 using SkiaSharp;
+using SelectionMode = PixiEditor.ChangeableDocument.Enums.SelectionMode;
 
 namespace PixiEditorPrototype.ViewModels;
 
@@ -27,6 +28,7 @@ internal class ViewModelMain : INotifyPropertyChanged
 
     public Color SelectedColor { get; set; } = Colors.Black;
     public bool KeepOriginalImageOnTransform { get; set; } = false;
+    public bool FillAllLayers { get; set; } = false;
     public float StrokeWidth { get; set; } = 1f;
 
     public event PropertyChangedEventHandler? PropertyChanged;
@@ -81,6 +83,7 @@ internal class ViewModelMain : INotifyPropertyChanged
 
     public ObservableCollection<DocumentViewModel> Documents { get; } = new();
 
+
     private VecD mouseDownCanvasPos;
 
     private bool mouseHasMoved = false;
@@ -123,7 +126,7 @@ internal class ViewModelMain : INotifyPropertyChanged
 
         mouseIsDown = true;
         var args = (MouseButtonEventArgs)(param!);
-        var source = (System.Windows.Controls.Image)args.Source;
+        var source = (Image)args.Source;
         var pos = args.GetPosition(source);
         mouseDownCanvasPos = new() { X = pos.X / source.Width * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelWidth, Y = pos.Y / source.Height * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelHeight };
         toolOnMouseDown = activeTool;
@@ -134,7 +137,7 @@ internal class ViewModelMain : INotifyPropertyChanged
     {
         if (toolOnMouseDown is Tool.FloodFill)
         {
-            ActiveDocument!.FloodFill((VecI)pos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
+            ActiveDocument!.FloodFill((VecI)pos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A), FillAllLayers);
         }
         else if (toolOnMouseDown == Tool.PathBasedPen)
         {
@@ -160,7 +163,7 @@ internal class ViewModelMain : INotifyPropertyChanged
             return;
         mouseHasMoved = true;
         var args = (MouseEventArgs)(param!);
-        var source = (System.Windows.Controls.Image)args.Source;
+        var source = (Image)args.Source;
         var pos = args.GetPosition(source);
         double curX = pos.X / source.Width * ActiveDocument!.Bitmaps[ChunkResolution.Full].PixelWidth;
         double curY = pos.Y / source.Height * ActiveDocument.Bitmaps[ChunkResolution.Full].PixelHeight;

+ 5 - 0
src/PixiEditorPrototype/Views/MainWindow.xaml

@@ -633,6 +633,11 @@
                     IsChecked="{Binding KeepOriginalImageOnTransform}">
                     Keep area
                 </CheckBox>
+                <CheckBox
+                    Margin="5, 0"
+                    IsChecked="{Binding FillAllLayers}">
+                    Fill all layers
+                </CheckBox>
                 <CheckBox
                     x:Name="horizontalSymmetryCheckbox"
                     Margin="5,0"

+ 1 - 1
src/README.md

@@ -97,7 +97,7 @@ Decouples the state of a document from the UI.
         - [x] Path-based pen
         - [x] Regular pen
         - [x] Pixel-perfect pen
-        - [ ] Fill
+        - [x] Fill
         - [ ] Brightness
         - [x] Basic selection changes
         - [x] Selection modes