|
@@ -1,6 +1,8 @@
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.CompilerServices;
|
|
using ChunkyImageLib.DataHolders;
|
|
using ChunkyImageLib.DataHolders;
|
|
using ChunkyImageLib.Operations;
|
|
using ChunkyImageLib.Operations;
|
|
|
|
+using OneOf;
|
|
|
|
+using OneOf.Types;
|
|
using SkiaSharp;
|
|
using SkiaSharp;
|
|
|
|
|
|
[assembly: InternalsVisibleTo("ChunkyImageLibTest")]
|
|
[assembly: InternalsVisibleTo("ChunkyImageLibTest")]
|
|
@@ -52,6 +54,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
public Vector2i LatestSize { get; private set; }
|
|
public Vector2i LatestSize { get; private set; }
|
|
|
|
|
|
private List<(IOperation operation, HashSet<Vector2i> affectedChunks)> queuedOperations = new();
|
|
private List<(IOperation operation, HashSet<Vector2i> affectedChunks)> queuedOperations = new();
|
|
|
|
+ private List<ChunkyImage> activeClips = new();
|
|
|
|
|
|
private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> committedChunks;
|
|
private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> committedChunks;
|
|
private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> latestChunks;
|
|
private Dictionary<ChunkResolution, Dictionary<Vector2i, Chunk>> latestChunks;
|
|
@@ -94,7 +97,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
var image = (Chunk?)GetLatestChunk(chunk, ChunkResolution.Full);
|
|
var image = (Chunk?)GetLatestChunk(chunk, ChunkResolution.Full);
|
|
if (image is not null)
|
|
if (image is not null)
|
|
- output.DrawImage(chunk * ChunkSize, image.Surface);
|
|
|
|
|
|
+ output.EnqueueDrawImage(chunk * ChunkSize, image.Surface);
|
|
}
|
|
}
|
|
output.CommitChanges();
|
|
output.CommitChanges();
|
|
return output;
|
|
return output;
|
|
@@ -197,52 +200,53 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
private Chunk? MaybeGetLatestChunk(Vector2i pos, ChunkResolution resolution) => latestChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
|
|
private Chunk? MaybeGetLatestChunk(Vector2i pos, ChunkResolution resolution) => latestChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
|
|
private Chunk? MaybeGetCommittedChunk(Vector2i pos, ChunkResolution resolution) => committedChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
|
|
private Chunk? MaybeGetCommittedChunk(Vector2i pos, ChunkResolution resolution) => committedChunks[resolution].TryGetValue(pos, out Chunk? value) ? value : null;
|
|
|
|
|
|
- public void DrawRectangle(ShapeData rect)
|
|
|
|
|
|
+ public void AddRasterClip(ChunkyImage clippingMask)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
- RectangleOperation operation = new(rect);
|
|
|
|
- EnqueueOperation(operation);
|
|
|
|
|
|
+ if (queuedOperations.Count > 0)
|
|
|
|
+ throw new InvalidOperationException("This function can only be executed when there are no queued operations");
|
|
|
|
+ activeClips.Add(clippingMask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public void DrawImage(Vector2i pos, Surface image)
|
|
|
|
|
|
+ public void EnqueueDrawRectangle(ShapeData rect)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
- ImageOperation operation = new(pos, image);
|
|
|
|
|
|
+ RectangleOperation operation = new(rect);
|
|
EnqueueOperation(operation);
|
|
EnqueueOperation(operation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public void ClearRegion(Vector2i pos, Vector2i size)
|
|
|
|
|
|
+ public void EnqueueDrawImage(Vector2i pos, Surface image)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
- ClearRegionOperation operation = new(pos, size);
|
|
|
|
|
|
+ ImageOperation operation = new(pos, image);
|
|
EnqueueOperation(operation);
|
|
EnqueueOperation(operation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public void Clear()
|
|
|
|
|
|
+ public void EnqueueClearRegion(Vector2i pos, Vector2i size)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
- ClearOperation operation = new();
|
|
|
|
- EnqueueOperation(operation, FindAllChunks());
|
|
|
|
|
|
+ ClearRegionOperation operation = new(pos, size);
|
|
|
|
+ EnqueueOperation(operation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public void ApplyRasterClip(ChunkyImage clippingMask)
|
|
|
|
|
|
+ public void EnqueueClear()
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
- RasterClipOperation operation = new(clippingMask);
|
|
|
|
- EnqueueOperation(operation, new());
|
|
|
|
|
|
+ ClearOperation operation = new();
|
|
|
|
+ EnqueueOperation(operation, FindAllChunks());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public void Resize(Vector2i newSize)
|
|
|
|
|
|
+ public void EnqueueResize(Vector2i newSize)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
@@ -273,6 +277,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
foreach (var operation in queuedOperations)
|
|
foreach (var operation in queuedOperations)
|
|
operation.Item1.Dispose();
|
|
operation.Item1.Dispose();
|
|
queuedOperations.Clear();
|
|
queuedOperations.Clear();
|
|
|
|
+ activeClips.Clear();
|
|
|
|
|
|
//clear latest chunks
|
|
//clear latest chunks
|
|
foreach (var (_, chunksOfRes) in latestChunks)
|
|
foreach (var (_, chunksOfRes) in latestChunks)
|
|
@@ -307,6 +312,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
CommitLatestChunks();
|
|
CommitLatestChunks();
|
|
CommittedSize = LatestSize;
|
|
CommittedSize = LatestSize;
|
|
queuedOperations.Clear();
|
|
queuedOperations.Clear();
|
|
|
|
+ activeClips.Clear();
|
|
|
|
|
|
commitCounter++;
|
|
commitCounter++;
|
|
if (commitCounter % 30 == 0)
|
|
if (commitCounter % 30 == 0)
|
|
@@ -415,41 +421,77 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
return;
|
|
return;
|
|
|
|
|
|
Chunk? targetChunk = null;
|
|
Chunk? targetChunk = null;
|
|
- List<ChunkyImage> activeClips = new();
|
|
|
|
- bool isFullyMaskedOut = false;
|
|
|
|
- bool somethingWasApplied = false;
|
|
|
|
|
|
+ OneOf<All, None, Chunk> combinedClips = new All();
|
|
|
|
+
|
|
|
|
+ bool initialized = false;
|
|
|
|
+
|
|
for (int i = 0; i < queuedOperations.Count; i++)
|
|
for (int i = 0; i < queuedOperations.Count; i++)
|
|
{
|
|
{
|
|
var (operation, operChunks) = queuedOperations[i];
|
|
var (operation, operChunks) = queuedOperations[i];
|
|
- if (operation is RasterClipOperation clipOperation)
|
|
|
|
- {
|
|
|
|
- // handle self-clipping as a special case to avoid deadlock
|
|
|
|
- bool clippingChunkExists = ReferenceEquals(this, clipOperation.ClippingMask) ?
|
|
|
|
- MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) != null :
|
|
|
|
- clipOperation.ClippingMask.CommitedChunkExists(chunkPos, resolution);
|
|
|
|
- if (clippingChunkExists)
|
|
|
|
- activeClips.Add(clipOperation.ClippingMask);
|
|
|
|
- else
|
|
|
|
- isFullyMaskedOut = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
if (!operChunks.Contains(chunkPos))
|
|
if (!operChunks.Contains(chunkPos))
|
|
continue;
|
|
continue;
|
|
- if (!somethingWasApplied)
|
|
|
|
|
|
+
|
|
|
|
+ if (!initialized)
|
|
{
|
|
{
|
|
- somethingWasApplied = true;
|
|
|
|
|
|
+ initialized = true;
|
|
targetChunk = GetOrCreateLatestChunk(chunkPos, resolution);
|
|
targetChunk = GetOrCreateLatestChunk(chunkPos, resolution);
|
|
|
|
+ combinedClips = CombineClipsForChunk(chunkPos, resolution);
|
|
}
|
|
}
|
|
|
|
|
|
if (chunkData.QueueProgress <= i)
|
|
if (chunkData.QueueProgress <= i)
|
|
- chunkData.IsDeleted = ApplyOperationToChunk(operation, activeClips, isFullyMaskedOut, targetChunk!, chunkPos, resolution, chunkData);
|
|
|
|
|
|
+ chunkData.IsDeleted = ApplyOperationToChunk(operation, combinedClips, targetChunk!, chunkPos, resolution, chunkData);
|
|
}
|
|
}
|
|
|
|
|
|
- if (somethingWasApplied)
|
|
|
|
|
|
+ if (initialized)
|
|
{
|
|
{
|
|
chunkData.QueueProgress = queuedOperations.Count;
|
|
chunkData.QueueProgress = queuedOperations.Count;
|
|
latestChunksData[resolution][chunkPos] = chunkData;
|
|
latestChunksData[resolution][chunkPos] = chunkData;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (combinedClips.TryPickT2(out Chunk value, out var _))
|
|
|
|
+ value.Dispose();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// (All) -> All is visible, (None) -> None is visible, (Chunk) -> Combined clip
|
|
|
|
+ /// </summary>
|
|
|
|
+ private OneOf<All, None, Chunk> CombineClipsForChunk(Vector2i chunkPos, ChunkResolution resolution)
|
|
|
|
+ {
|
|
|
|
+ if (activeClips.Count == 0)
|
|
|
|
+ {
|
|
|
|
+ return new All();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var intersection = Chunk.Create(resolution);
|
|
|
|
+ intersection.Surface.SkiaSurface.Canvas.Clear(SKColors.White);
|
|
|
|
+
|
|
|
|
+ foreach (var mask in activeClips)
|
|
|
|
+ {
|
|
|
|
+ // handle self-clipping as a special case to avoid deadlock
|
|
|
|
+ if (!ReferenceEquals(this, mask))
|
|
|
|
+ {
|
|
|
|
+ if (mask.CommitedChunkExists(chunkPos, resolution))
|
|
|
|
+ {
|
|
|
|
+ mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.SkiaSurface, new(0, 0), ClippingPaint);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ intersection.Dispose();
|
|
|
|
+ return new None();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ var maskChunk = GetCommittedChunk(chunkPos, resolution);
|
|
|
|
+ if (maskChunk is null)
|
|
|
|
+ {
|
|
|
|
+ intersection.Dispose();
|
|
|
|
+ return new None();
|
|
|
|
+ }
|
|
|
|
+ maskChunk.DrawOnSurface(intersection.Surface.SkiaSurface, new(0, 0), ClippingPaint);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return intersection;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -457,8 +499,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
/// </summary>
|
|
/// </summary>
|
|
private bool ApplyOperationToChunk(
|
|
private bool ApplyOperationToChunk(
|
|
IOperation operation,
|
|
IOperation operation,
|
|
- IReadOnlyList<ChunkyImage> activeClips,
|
|
|
|
- bool isFullyMaskedOut,
|
|
|
|
|
|
+ OneOf<All, None, Chunk> combinedClips,
|
|
Chunk targetChunk,
|
|
Chunk targetChunk,
|
|
Vector2i chunkPos,
|
|
Vector2i chunkPos,
|
|
ChunkResolution resolution,
|
|
ChunkResolution resolution,
|
|
@@ -469,46 +510,29 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
|
|
|
|
if (operation is IDrawOperation chunkOperation)
|
|
if (operation is IDrawOperation chunkOperation)
|
|
{
|
|
{
|
|
- if (isFullyMaskedOut)
|
|
|
|
|
|
+ if (combinedClips.IsT1) //None is visible
|
|
return chunkData.IsDeleted;
|
|
return chunkData.IsDeleted;
|
|
|
|
|
|
if (chunkData.IsDeleted)
|
|
if (chunkData.IsDeleted)
|
|
targetChunk.Surface.SkiaSurface.Canvas.Clear();
|
|
targetChunk.Surface.SkiaSurface.Canvas.Clear();
|
|
|
|
|
|
// just regular drawing
|
|
// just regular drawing
|
|
- if (activeClips.Count == 0)
|
|
|
|
|
|
+ if (combinedClips.IsT0) //All is visible
|
|
{
|
|
{
|
|
chunkOperation.DrawOnChunk(targetChunk, chunkPos);
|
|
chunkOperation.DrawOnChunk(targetChunk, chunkPos);
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// drawing with clipping
|
|
// drawing with clipping
|
|
|
|
+ var clip = combinedClips.AsT2;
|
|
|
|
+
|
|
using var tempChunk = Chunk.Create(targetChunk.Resolution);
|
|
using var tempChunk = Chunk.Create(targetChunk.Resolution);
|
|
targetChunk.DrawOnSurface(tempChunk.Surface.SkiaSurface, new(0, 0), ReplacingPaint);
|
|
targetChunk.DrawOnSurface(tempChunk.Surface.SkiaSurface, new(0, 0), ReplacingPaint);
|
|
|
|
+
|
|
chunkOperation.DrawOnChunk(tempChunk, chunkPos);
|
|
chunkOperation.DrawOnChunk(tempChunk, chunkPos);
|
|
|
|
|
|
- if (activeClips.Count > 1)
|
|
|
|
- {
|
|
|
|
- using var intersection = IntersectMasks(chunkPos, activeClips, resolution);
|
|
|
|
- intersection.DrawOnSurface(tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
|
|
|
|
- intersection.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), InverseClippingPaint);
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- if (!ReferenceEquals(this, activeClips[0]))
|
|
|
|
- {
|
|
|
|
- activeClips[0].DrawCommittedChunkOn(chunkPos, resolution, tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
|
|
|
|
- activeClips[0].DrawCommittedChunkOn(chunkPos, resolution, targetChunk.Surface.SkiaSurface, new(0, 0), InverseClippingPaint);
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- var maskChunk = GetCommittedChunk(chunkPos, resolution);
|
|
|
|
- if (maskChunk is null)
|
|
|
|
- return true; // this should never happen, there is a check in MaybeCreateAndProcessQueueForChunk
|
|
|
|
- maskChunk.DrawOnSurface(tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
|
|
|
|
- maskChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), InverseClippingPaint);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ clip.DrawOnSurface(tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
|
|
|
|
+ clip.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), InverseClippingPaint);
|
|
|
|
|
|
tempChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), AddingPaint);
|
|
tempChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), AddingPaint);
|
|
return false;
|
|
return false;
|
|
@@ -521,32 +545,6 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
return chunkData.IsDeleted;
|
|
return chunkData.IsDeleted;
|
|
}
|
|
}
|
|
|
|
|
|
- private Chunk IntersectMasks(Vector2i chunkPos, IReadOnlyList<ChunkyImage> activeClips, ChunkResolution resolution)
|
|
|
|
- {
|
|
|
|
- var maskIntersection = Chunk.Create(resolution);
|
|
|
|
- maskIntersection.Surface.SkiaSurface.Canvas.Clear(SKColors.White);
|
|
|
|
-
|
|
|
|
- foreach (var mask in activeClips)
|
|
|
|
- {
|
|
|
|
- // handle self-clipping as a special case to avoid deadlock
|
|
|
|
- if (!ReferenceEquals(this, mask))
|
|
|
|
- {
|
|
|
|
- mask.DrawCommittedChunkOn(chunkPos, resolution, maskIntersection.Surface.SkiaSurface, new(0, 0), ClippingPaint);
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- var maskChunk = GetCommittedChunk(chunkPos, resolution);
|
|
|
|
- if (maskChunk is null)
|
|
|
|
- {
|
|
|
|
- maskIntersection.Surface.SkiaSurface.Canvas.Clear();
|
|
|
|
- return maskIntersection;
|
|
|
|
- }
|
|
|
|
- maskChunk.DrawOnSurface(maskIntersection.Surface.SkiaSurface, new(0, 0), ClippingPaint);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return maskIntersection;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/// <summary>
|
|
/// <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)
|
|
/// 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)
|
|
/// </summary>
|
|
/// </summary>
|