Browse Source

ChunkyImage preview blend mode

Equbuxu 3 years ago
parent
commit
db23addf1e

+ 78 - 12
src/ChunkyImageLib/ChunkyImage.cs

@@ -55,6 +55,8 @@ 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 Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> committedChunks;
     private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> latestChunks;
@@ -111,7 +113,20 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             var chunk = GetLatestChunk(chunkPos, resolution);
             if (chunk is null)
                 return false;
-            chunk.DrawOnSurface(surface, pos, paint);
+            //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))
+            {
+                chunk.DrawOnSurface(surface, pos, paint);
+                return true;
+            }
+
+            var commitedChunk = GetCommittedChunk(chunkPos, resolution);
+            using var tempChunk = Chunk.Create(resolution);
+            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.DrawOnSurface(surface, pos, paint);
+
             return true;
         }
     }
@@ -210,6 +225,19 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
     }
 
+    /// <summary>
+    /// Don't pass in porter duff compositing operators (apart from SrcOver), they won't have the intended effect.
+    /// </summary>
+    public void SetBlendMode(SKBlendMode mode)
+    {
+        lock (lockObject)
+        {
+            if (queuedOperations.Count > 0)
+                throw new InvalidOperationException("This function can only be executed when there are no queued operations");
+            blendMode = mode;
+        }
+    }
+
     public void EnqueueDrawRectangle(ShapeData rect)
     {
         lock (lockObject)
@@ -277,7 +305,10 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             foreach (var operation in queuedOperations)
                 operation.Item1.Dispose();
             queuedOperations.Clear();
+
+            //clear additional state
             activeClips.Clear();
+            blendMode = SKBlendMode.Src;
 
             //clear latest chunks
             foreach (var (_, chunksOfRes) in latestChunks)
@@ -313,6 +344,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             CommittedSize = LatestSize;
             queuedOperations.Clear();
             activeClips.Clear();
+            blendMode = SKBlendMode.Src;
 
             commitCounter++;
             if (commitCounter % 30 == 0)
@@ -322,18 +354,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
     private void CommitLatestChunks()
     {
-        // move fully processed latest chunks to committed
+        // move/draw fully processed latest chunks to/on committed
         foreach (var (resolution, chunks) in latestChunks)
         {
             foreach (var (pos, chunk) in chunks)
             {
-                if (committedChunks[resolution].ContainsKey(pos))
-                {
-                    var oldChunk = committedChunks[resolution][pos];
-                    committedChunks[resolution].Remove(pos);
-                    oldChunk.Dispose();
-                }
-
+                // get chunk if exists
                 LatestChunkData data = latestChunksData[resolution][pos];
                 if (data.QueueProgress != queuedOperations.Count)
                 {
@@ -348,10 +374,46 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     }
                 }
 
-                if (!data.IsDeleted)
-                    committedChunks[resolution].Add(pos, chunk);
+                // do a swap
+                if (blendMode == SKBlendMode.Src)
+                {
+                    // delete committed version
+                    if (committedChunks[resolution].ContainsKey(pos))
+                    {
+                        var oldChunk = committedChunks[resolution][pos];
+                        committedChunks[resolution].Remove(pos);
+                        oldChunk.Dispose();
+                    }
+
+                    // put the latest version in place of the committed one
+                    if (!data.IsDeleted)
+                        committedChunks[resolution].Add(pos, chunk);
+                    else
+                        chunk.Dispose();
+                }
+                // do blending
                 else
+                {
+                    // nothing to blend, continue
+                    if (data.IsDeleted)
+                    {
+                        chunk.Dispose();
+                        continue;
+                    }
+
+                    // nothing to blend with, swap
+                    var maybeCommitted = MaybeGetCommittedChunk(pos, resolution);
+                    if (maybeCommitted is null)
+                    {
+                        committedChunks[resolution].Add(pos, chunk);
+                        continue;
+                    }
+
+                    //blend
+                    blendModePaint.BlendMode = blendMode;
+                    maybeCommitted.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
                     chunk.Dispose();
+                }
             }
         }
 
@@ -642,7 +704,10 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         if (maybeCommittedAnyRes is not null)
         {
             Chunk newChunk = Chunk.Create(resolution);
-            maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
+            if (blendMode == SKBlendMode.Src)
+                maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
+            else
+                newChunk.Surface.SkiaSurface.Canvas.Clear();
             latestChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
@@ -675,6 +740,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 return;
             CancelChanges();
             DisposeAll();
+            blendModePaint.Dispose();
             GC.SuppressFinalize(this);
         }
     }

+ 3 - 1
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -4,7 +4,7 @@ namespace ChunkyImageLib.DataHolders;
 
 public record struct ShapeData
 {
-    public ShapeData(Vector2i pos, Vector2i size, int strokeWidth, SKColor strokeColor, SKColor fillColor)
+    public ShapeData(Vector2i pos, Vector2i size, int strokeWidth, SKColor strokeColor, SKColor fillColor, SKBlendMode blendMode = SKBlendMode.SrcOver)
     {
         Pos = pos;
         MaxPos = new(pos.X + size.X - 1, pos.Y + size.Y - 1);
@@ -12,6 +12,7 @@ public record struct ShapeData
         StrokeColor = strokeColor;
         FillColor = fillColor;
         StrokeWidth = strokeWidth;
+        BlendMode = blendMode;
     }
 
     public Vector2i Pos { get; }
@@ -19,5 +20,6 @@ public record struct ShapeData
     public Vector2i Size { get; }
     public SKColor StrokeColor { get; }
     public SKColor FillColor { get; }
+    public SKBlendMode BlendMode { get; }
     public int StrokeWidth { get; }
 }

+ 1 - 0
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -32,6 +32,7 @@ internal record class RectangleOperation : IDrawOperation
         {
             Color = Data.FillColor,
             Style = SKPaintStyle.Fill,
+            BlendMode = Data.BlendMode,
         };
 
         if (Data.FillColor.Alpha > 0)

+ 6 - 0
src/ChunkyImageLibTest/ChunkyImageTests.cs

@@ -15,11 +15,17 @@ public class ChunkyImageTests
             image.DrawLatestChunkOn(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));
             image.CommitChanges();
+            image.SetBlendMode(SKBlendMode.Overlay);
+            image.EnqueueDrawRectangle(new(new(0, 0), image.CommittedSize, 2, SKColors.AliceBlue, SKColors.Snow, SKBlendMode.Multiply));
             image.EnqueueDrawRectangle(new(new(0, 0), image.CommittedSize, 2, SKColors.AliceBlue, SKColors.Snow));
             image.CommitChanges();
+            image.SetBlendMode(SKBlendMode.Screen);
             image.EnqueueDrawRectangle(new(new(0, 0), image.CommittedSize, 2, SKColors.AliceBlue, SKColors.Snow));
             image.CancelChanges();
+            image.SetBlendMode(SKBlendMode.SrcOver);
+            image.EnqueueDrawRectangle(new(new(0, 0), image.CommittedSize, 2, SKColors.AliceBlue, SKColors.Snow));
         }
         image.Dispose();