Browse Source

Lock transparency better implementation

Equbuxu 3 năm trước cách đây
mục cha
commit
402daca0d8

+ 143 - 56
src/ChunkyImageLib/ChunkyImage.cs

@@ -49,6 +49,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private static SKPaint InverseClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstOut };
     private static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
     private static SKPaint AddingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Plus };
+    private SKPaint blendModePaint = new SKPaint() { BlendMode = SKBlendMode.Src };
 
     public Vector2i CommittedSize { get; private set; }
     public Vector2i LatestSize { get; private set; }
@@ -56,7 +57,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private List<(IOperation operation, HashSet<Vector2i> affectedChunks)> queuedOperations = new();
     private List<ChunkyImage> activeClips = new();
     private SKBlendMode blendMode = SKBlendMode.Src;
-    private SKPaint blendModePaint = new SKPaint() { BlendMode = SKBlendMode.Src };
+    private bool lockTransparency = false;
 
     private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> committedChunks;
     private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> latestChunks;
@@ -89,53 +90,72 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         };
     }
 
-    public ChunkyImage CloneFromLatest()
+    public ChunkyImage CloneFromCommitted()
     {
         lock (lockObject)
         {
             ChunkyImage output = new(LatestSize);
-            var chunks = FindAllChunks();
+            var chunks = FindCommittedChunks();
             foreach (var chunk in chunks)
             {
-                var image = (Chunk?)GetLatestChunk(chunk, ChunkResolution.Full);
-                if (image is not null)
-                    output.EnqueueDrawImage(chunk * ChunkSize, image.Surface);
+                var image = GetCommittedChunk(chunk, ChunkResolution.Full);
+                if (image is null)
+                    continue;
+                output.EnqueueDrawImage(chunk * ChunkSize, image.Surface);
             }
             output.CommitChanges();
             return output;
         }
     }
 
-    public bool DrawLatestChunkOn(Vector2i chunkPos, ChunkResolution resolution, SKSurface surface, Vector2i pos, SKPaint? paint = null)
+    /// <returns>
+    /// True if the chunk existed and was drawn, otherwise false
+    /// </returns>
+    public bool DrawMostUpToDateChunkOn(Vector2i chunkPos, ChunkResolution resolution, SKSurface surface, Vector2i pos, SKPaint? paint = null)
     {
         lock (lockObject)
         {
-            var chunk = GetLatestChunk(chunkPos, resolution);
-            if (chunk is null)
-                return false;
-            //if the latest chunks don't need to be superimposed or if there is nothing to superimpose onto
-            if (blendMode == SKBlendMode.Src || !committedChunks[ChunkResolution.Full].TryGetValue(chunkPos, out Chunk? output))
+            var latestChunk = GetLatestChunk(chunkPos, resolution);
+            var committedChunk = GetCommittedChunk(chunkPos, resolution);
+
+            // latest chunk does not exist, draw committed if it exists
+            if (latestChunk is null)
             {
-                chunk.DrawOnSurface(surface, pos, paint);
+                if (committedChunk is null)
+                    return false;
+                committedChunk.DrawOnSurface(surface, pos, paint);
                 return true;
             }
 
-            var commitedChunk = GetCommittedChunk(chunkPos, resolution);
+            // latest chunk exists
+            if (blendMode == SKBlendMode.Src || committedChunk is null)
+            {
+                // no need to combine with committed, draw directly
+                latestChunk.DrawOnSurface(surface, pos, paint);
+                return true;
+            }
+
+            // combine with committed and then draw
             using var tempChunk = Chunk.Create(resolution);
+            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(committedChunk!.Surface.SkiaSurface, 0, 0, ReplacingPaint);
             blendModePaint.BlendMode = blendMode;
-            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(commitedChunk!.Surface.SkiaSurface, 0, 0, ReplacingPaint);
-            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
+            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(latestChunk.Surface.SkiaSurface, 0, 0, blendModePaint);
+            if (lockTransparency)
+                ClampAlpha(tempChunk.Surface.SkiaSurface, committedChunk.Surface.SkiaSurface);
             tempChunk.DrawOnSurface(surface, pos, paint);
 
             return true;
         }
     }
 
-    public bool LatestChunkExists(Vector2i chunkPos, ChunkResolution resolution)
+    public bool LatestOrCommittedChunkExists(Vector2i chunkPos)
     {
         lock (lockObject)
         {
-            return GetLatestChunk(chunkPos, resolution) is not null;
+            return (
+                MaybeGetLatestChunk(chunkPos, ChunkResolution.Full) ??
+                MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full)
+                ) is not null;
         }
     }
 
@@ -160,44 +180,20 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <summary>
-    /// Returns the latest version of the chunk, with uncommitted changes applied if they exist
+    /// Returns the latest version of the chunk if it exists or should exist based on queued operation. The returned chunk is fully up to date.
     /// </summary>
     private Chunk? GetLatestChunk(Vector2i pos, ChunkResolution resolution)
     {
-        //no queued operations
         if (queuedOperations.Count == 0)
-        {
-            var sameResChunk = MaybeGetCommittedChunk(pos, resolution);
-            if (sameResChunk is not null)
-                return sameResChunk;
-
-            var fullResChunk = MaybeGetCommittedChunk(pos, ChunkResolution.Full);
-            if (fullResChunk is not null)
-                return GetOrCreateCommittedChunk(pos, resolution);
-
             return null;
-        }
 
-        // there are queued operations, target chunk is affected
         MaybeCreateAndProcessQueueForChunk(pos, resolution);
         var maybeNewlyProcessedChunk = MaybeGetLatestChunk(pos, resolution);
-        if (maybeNewlyProcessedChunk is not null)
-            return maybeNewlyProcessedChunk;
-
-        // there are queued operations, target chunk is unaffected
-        var maybeSameResCommitedChunk = MaybeGetCommittedChunk(pos, resolution);
-        if (maybeSameResCommitedChunk is not null)
-            return maybeSameResCommitedChunk;
-
-        var maybeFullResCommitedChunk = MaybeGetCommittedChunk(pos, ChunkResolution.Full);
-        if (maybeFullResCommitedChunk is not null)
-            return GetOrCreateCommittedChunk(pos, resolution);
-
-        return null;
+        return maybeNewlyProcessedChunk;
     }
 
     /// <summary>
-    /// Returns the committed version of the chunk ignoring any uncommitted changes
+    /// Tries it's best to return a committed chunk, either if it exists or if it can be created from it's high res version. Returns null if it can't.
     /// </summary>
     private Chunk? GetCommittedChunk(Vector2i pos, ChunkResolution resolution)
     {
@@ -226,7 +222,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <summary>
-    /// Don't pass in porter duff compositing operators (apart from SrcOver), they won't have the intended effect.
+    /// Don't pass in porter duff compositing operators (apart from SrcOver) as they won't have the intended effect.
     /// </summary>
     public void SetBlendMode(SKBlendMode mode)
     {
@@ -238,6 +234,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    public void EnableLockTransparency()
+    {
+        lock (lockObject)
+        {
+            lockTransparency = true;
+        }
+    }
+
     public void EnqueueDrawRectangle(ShapeData rect)
     {
         lock (lockObject)
@@ -309,6 +313,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             //clear additional state
             activeClips.Clear();
             blendMode = SKBlendMode.Src;
+            lockTransparency = false;
 
             //clear latest chunks
             foreach (var (_, chunksOfRes) in latestChunks)
@@ -340,11 +345,13 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             {
                 operation.Dispose();
             }
+
             CommitLatestChunks();
             CommittedSize = LatestSize;
             queuedOperations.Clear();
             activeClips.Clear();
             blendMode = SKBlendMode.Src;
+            lockTransparency = false;
 
             commitCounter++;
             if (commitCounter % 30 == 0)
@@ -352,6 +359,9 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    /// <summary>
+    /// Does all necessery steps to convert latest chunks into committed ones. The latest chunk dictionary become empty after this function is called.
+    /// </summary>
     private void CommitLatestChunks()
     {
         // move/draw fully processed latest chunks to/on committed
@@ -411,7 +421,17 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
                     //blend
                     blendModePaint.BlendMode = blendMode;
-                    maybeCommitted.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
+                    if (lockTransparency)
+                    {
+                        using Chunk tempChunk = Chunk.Create(resolution);
+                        tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(maybeCommitted.Surface.SkiaSurface, 0, 0, ReplacingPaint);
+                        maybeCommitted.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
+                        ClampAlpha(maybeCommitted.Surface.SkiaSurface, tempChunk.Surface.SkiaSurface);
+                    }
+                    else
+                    {
+                        maybeCommitted.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
+                    }
                     chunk.Dispose();
                 }
             }
@@ -443,15 +463,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
-    /// <summary>
-    /// Returns all chunks that have something in them, including latest (uncommitted) ones
-    /// </summary>
+    /// <returns>
+    /// All chunks that have something in them, including latest (uncommitted) ones
+    /// </returns>
     public HashSet<Vector2i> FindAllChunks()
     {
         lock (lockObject)
         {
             var allChunks = committedChunks[ChunkResolution.Full].Select(chunk => chunk.Key).ToHashSet();
-            foreach (var (operation, opChunks) in queuedOperations)
+            foreach (var (_, opChunks) in queuedOperations)
             {
                 allChunks.UnionWith(opChunks);
             }
@@ -459,9 +479,17 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
-    /// <summary>
-    /// Returns chunks affected by operations that haven't been committed yet
-    /// </summary>
+    public HashSet<Vector2i> FindCommittedChunks()
+    {
+        lock (lockObject)
+        {
+            return committedChunks[ChunkResolution.Full].Select(chunk => chunk.Key).ToHashSet();
+        }
+    }
+
+    /// <returns>
+    /// Chunks affected by operations that haven't been committed yet
+    /// </returns>
     public HashSet<Vector2i> FindAffectedChunks()
     {
         lock (lockObject)
@@ -475,6 +503,9 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    /// <summary>
+    /// Applies all operations queued for a specific (latest) chunk. If the latest chunk doesn't exist yet, creates it. If none of the existing operations affect the chunk does nothing.
+    /// </summary>
     private void MaybeCreateAndProcessQueueForChunk(Vector2i chunkPos, ChunkResolution resolution)
     {
         if (!latestChunksData[resolution].TryGetValue(chunkPos, out LatestChunkData chunkData))
@@ -506,6 +537,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
         if (initialized)
         {
+            if (lockTransparency && !chunkData.IsDeleted && MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
+            {
+                var committed = GetCommittedChunk(chunkPos, resolution);
+                ClampAlpha(targetChunk!.Surface.SkiaSurface, committed!.Surface.SkiaSurface);
+            }
+
             chunkData.QueueProgress = queuedOperations.Count;
             latestChunksData[resolution][chunkPos] = chunkData;
         }
@@ -519,11 +556,16 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     /// </summary>
     private OneOf<All, None, Chunk> CombineClipsForChunk(Vector2i chunkPos, ChunkResolution resolution)
     {
+        if (lockTransparency && MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is null)
+        {
+            return new None();
+        }
         if (activeClips.Count == 0)
         {
             return new All();
         }
 
+
         var intersection = Chunk.Create(resolution);
         intersection.Surface.SkiaSurface.Canvas.Clear(SKColors.White);
 
@@ -557,8 +599,46 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <summary>
-    /// Returns true if the chunk was fully cleared (and should be deleted)
+    /// toModify[x,y].Alpha = Math.Min(toModify[x,y].Alpha, toGetAlphaFrom[x,y].Alpha)
     /// </summary>
+    private unsafe void ClampAlpha(SKSurface toModify, SKSurface toGetAlphaFrom)
+    {
+        using (var map = toModify.PeekPixels())
+        {
+            using (var refMap = toGetAlphaFrom.PeekPixels())
+            {
+                long* pixels = (long*)map.GetPixels();
+                long* refPixels = (long*)refMap.GetPixels();
+                int size = map.Width * map.Height;
+                if (map.Width != refMap.Width || map.Height != refMap.Height)
+                    throw new ArgumentException("The surfaces must have the same size");
+
+                for (int i = 0; i < size; i++)
+                {
+                    long* offset = pixels + i;
+                    long* refOffset = refPixels + i;
+                    Half* alpha = (Half*)offset + 3;
+                    Half* refAlpha = (Half*)refOffset + 3;
+                    if (*refAlpha < *alpha)
+                    {
+                        float a = (float)(*alpha);
+                        float r = (float)(*((Half*)offset)) / a;
+                        float g = (float)(*((Half*)offset + 1)) / a;
+                        float b = (float)(*((Half*)offset + 2)) / a;
+                        float newA = (float)(*refAlpha);
+                        Half newR = (Half)(r * newA);
+                        Half newG = (Half)(g * newA);
+                        Half newB = (Half)(b * newA);
+                        *offset = ((long)*(ushort*)(&newR)) | ((long)*(ushort*)(&newG)) << 16 | ((long)*(ushort*)(&newB)) << 32 | ((long)*(ushort*)(refAlpha)) << 48;
+                    }
+                }
+            }
+        }
+    }
+
+    /// <returns>
+    /// True if the chunk was fully cleared (and should be deleted).
+    /// </returns>
     private bool ApplyOperationToChunk(
         IOperation operation,
         OneOf<All, None, Chunk> combinedClips,
@@ -608,7 +688,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <summary>
-    /// Note: this function modifies the internal state, it is not thread safe! (same as all the other functions that change the image in some way)
+    /// Finds and deletes empty committed chunks. Returns true if all existing chunks were deleted.
+    /// Note: this function modifies the internal state, it is not thread safe! Use it only in changes (same as all the other functions that change the image in some way).
     /// </summary>
     public bool CheckIfCommittedIsEmpty()
     {
@@ -653,6 +734,9 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    /// <summary>
+    /// Gets existing committed chunk or creates a new one. Doesn't apply any operations to the chunk, returns it as it is.
+    /// </summary>
     private Chunk GetOrCreateCommittedChunk(Vector2i chunkPos, ChunkResolution resolution)
     {
         // commited chunk of the same resolution exists
@@ -691,6 +775,9 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    /// <summary>
+    /// Gets existing latest chunk or creates a new one, based on a committed one if it exists. Doesn't do any operations to the chunk.
+    /// </summary>
     private Chunk GetOrCreateLatestChunk(Vector2i chunkPos, ChunkResolution resolution)
     {
         // latest chunk exists

+ 3 - 2
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -5,8 +5,9 @@ namespace ChunkyImageLib;
 
 public interface IReadOnlyChunkyImage
 {
-    bool DrawLatestChunkOn(Vector2i chunkPos, ChunkResolution resolution, SKSurface surface, Vector2i pos, SKPaint? paint = null);
-    bool LatestChunkExists(Vector2i chunkPos, ChunkResolution resolution);
+    bool DrawMostUpToDateChunkOn(Vector2i chunkPos, ChunkResolution resolution, SKSurface surface, Vector2i pos, SKPaint? paint = null);
+    bool LatestOrCommittedChunkExists(Vector2i chunkPos);
     HashSet<Vector2i> FindAffectedChunks();
+    HashSet<Vector2i> FindCommittedChunks();
     HashSet<Vector2i> FindAllChunks();
 }

+ 1 - 1
src/ChunkyImageLibTest/ChunkyImageTests.cs

@@ -12,7 +12,7 @@ public class ChunkyImageTests
         image.EnqueueDrawRectangle(new(new(5, 5), new(80, 80), 2, SKColors.AliceBlue, SKColors.Snow));
         using (Chunk target = Chunk.Create())
         {
-            image.DrawLatestChunkOn(new(0, 0), ChunkyImageLib.DataHolders.ChunkResolution.Full, target.Surface.SkiaSurface, new(0, 0));
+            image.DrawMostUpToDateChunkOn(new(0, 0), ChunkyImageLib.DataHolders.ChunkResolution.Full, target.Surface.SkiaSurface, new(0, 0));
             image.CancelChanges();
             image.EnqueueResize(new(ChunkyImage.ChunkSize * 4, ChunkyImage.ChunkSize * 4));
             image.EnqueueDrawRectangle(new(new(0, 0), image.CommittedSize, 2, SKColors.AliceBlue, SKColors.Snow, SKBlendMode.Multiply));

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Folder.cs

@@ -22,7 +22,7 @@ internal class Folder : StructureMember, IReadOnlyFolder
             Name = Name,
             Opacity = Opacity,
             Children = clonedChildren,
-            Mask = Mask?.CloneFromLatest()
+            Mask = Mask?.CloneFromCommitted()
         };
     }
 

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Layer.cs

@@ -28,13 +28,13 @@ internal class Layer : StructureMember, IReadOnlyLayer
 
     internal override Layer Clone()
     {
-        return new Layer(LayerImage.CloneFromLatest())
+        return new Layer(LayerImage.CloneFromCommitted())
         {
             GuidValue = GuidValue,
             IsVisible = IsVisible,
             Name = Name,
             Opacity = Opacity,
-            Mask = Mask?.CloneFromLatest(),
+            Mask = Mask?.CloneFromCommitted(),
         };
     }
 }

+ 1 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs

@@ -27,12 +27,11 @@ internal class DrawRectangle_UpdateableChange : UpdateableChange
     {
         var oldAffectedChunks = targetImage.FindAffectedChunks();
         targetImage.CancelChanges();
-
         if (!target.Selection.IsEmptyAndInactive)
             targetImage.AddRasterClip(target.Selection.SelectionImage);
         var targetMember = target.FindMemberOrThrow(memberGuid);
         if (targetMember is Layer layer && layer.LockTransparency)
-            targetImage.AddRasterClip(targetImage);
+            targetImage.EnableLockTransparency();
 
         targetImage.EnqueueDrawRectangle(rect);
 

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Properties/DeleteStructureMemberMask_Change.cs

@@ -20,7 +20,7 @@ internal class DeleteStructureMemberMask_Change : Change
         var member = target.FindMemberOrThrow(memberGuid);
         if (member.Mask is null)
             throw new InvalidOperationException("Cannot delete the mask; Target member has no mask");
-        storedMask = member.Mask.CloneFromLatest();
+        storedMask = member.Mask.CloneFromCommitted();
     }
 
     public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
@@ -40,7 +40,7 @@ internal class DeleteStructureMemberMask_Change : Change
         var member = target.FindMemberOrThrow(memberGuid);
         if (member.Mask is not null)
             throw new InvalidOperationException("Cannot revert mask deletion; The target member already has a mask");
-        member.Mask = storedMask!.CloneFromLatest();
+        member.Mask = storedMask!.CloneFromCommitted();
 
         return new StructureMemberMask_ChangeInfo() { GuidValue = memberGuid };
     }

+ 5 - 5
src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs

@@ -56,7 +56,7 @@ public static class ChunkRenderer
                 continue;
 
             // chunk fully masked out
-            if (child.ReadOnlyMask is not null && !child.ReadOnlyMask.LatestChunkExists(chunkPos, resolution))
+            if (child.ReadOnlyMask is not null && !child.ReadOnlyMask.LatestOrCommittedChunkExists(chunkPos))
                 continue;
 
             // layer
@@ -66,15 +66,15 @@ public static class ChunkRenderer
                 {
                     PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
                     PaintToDrawChunksWith.BlendMode = GetSKBlendMode(layer.BlendMode);
-                    layer.ReadOnlyLayerImage.DrawLatestChunkOn(chunkPos, resolution, targetChunk.Surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
+                    layer.ReadOnlyLayerImage.DrawMostUpToDateChunkOn(chunkPos, resolution, targetChunk.Surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
                 }
                 else
                 {
                     using (Chunk tempChunk = Chunk.Create(resolution))
                     {
-                        if (!layer.ReadOnlyLayerImage.DrawLatestChunkOn(chunkPos, resolution, tempChunk.Surface.SkiaSurface, new(0, 0), ReplacingPaint))
+                        if (!layer.ReadOnlyLayerImage.DrawMostUpToDateChunkOn(chunkPos, resolution, tempChunk.Surface.SkiaSurface, new(0, 0), ReplacingPaint))
                             continue;
-                        layer.ReadOnlyMask.DrawLatestChunkOn(chunkPos, resolution, tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
+                        layer.ReadOnlyMask.DrawMostUpToDateChunkOn(chunkPos, resolution, tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
 
                         PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
                         PaintToDrawChunksWith.BlendMode = GetSKBlendMode(layer.BlendMode);
@@ -89,7 +89,7 @@ public static class ChunkRenderer
             {
                 using Chunk renderedChunk = RenderChunkRecursively(chunkPos, resolution, depth + 1, innerFolder, visibleLayers);
                 if (innerFolder.ReadOnlyMask is not null)
-                    innerFolder.ReadOnlyMask.DrawLatestChunkOn(chunkPos, resolution, renderedChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
+                    innerFolder.ReadOnlyMask.DrawMostUpToDateChunkOn(chunkPos, resolution, renderedChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
 
                 PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
                 PaintToDrawChunksWith.BlendMode = GetSKBlendMode(innerFolder.BlendMode);

+ 1 - 1
src/PixiEditorPrototype/Models/Rendering/WriteableBitmapUpdater.cs

@@ -186,6 +186,6 @@ internal class WriteableBitmapUpdater
         if (helpers.Tracker.Document.ReadOnlySelection.ReadOnlyIsEmptyAndInactive)
             return;
 
-        helpers.Tracker.Document.ReadOnlySelection.ReadOnlySelectionImage.DrawLatestChunkOn(chunkPos, resolution, screenSurface, chunkPos * resolution.PixelSize(), SelectionPaint);
+        helpers.Tracker.Document.ReadOnlySelection.ReadOnlySelectionImage.DrawMostUpToDateChunkOn(chunkPos, resolution, screenSurface, chunkPos * resolution.PixelSize(), SelectionPaint);
     }
 }