|
@@ -4,11 +4,16 @@ using ChunkyImageLib.DataHolders;
|
|
using ChunkyImageLib.Operations;
|
|
using ChunkyImageLib.Operations;
|
|
using OneOf;
|
|
using OneOf;
|
|
using OneOf.Types;
|
|
using OneOf.Types;
|
|
-using PixiEditor.DrawingApi.Core.ColorsImpl;
|
|
|
|
-using PixiEditor.DrawingApi.Core.Numerics;
|
|
|
|
-using PixiEditor.DrawingApi.Core.Surface;
|
|
|
|
-using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
|
|
|
|
-using PixiEditor.DrawingApi.Core.Surface.Vector;
|
|
|
|
|
|
+using PixiEditor.Common;
|
|
|
|
+using Drawie.Backend.Core;
|
|
|
|
+using Drawie.Backend.Core.ColorsImpl;
|
|
|
|
+using Drawie.Backend.Core.ColorsImpl.Paintables;
|
|
|
|
+using Drawie.Backend.Core.Numerics;
|
|
|
|
+using Drawie.Backend.Core.Surfaces;
|
|
|
|
+using Drawie.Backend.Core.Surfaces.ImageData;
|
|
|
|
+using Drawie.Backend.Core.Surfaces.PaintImpl;
|
|
|
|
+using Drawie.Backend.Core.Vector;
|
|
|
|
+using Drawie.Numerics;
|
|
|
|
|
|
[assembly: InternalsVisibleTo("ChunkyImageLibTest")]
|
|
[assembly: InternalsVisibleTo("ChunkyImageLibTest")]
|
|
|
|
|
|
@@ -39,7 +44,7 @@ namespace ChunkyImageLib;
|
|
/// - Any other blend mode: the latest chunks contain only the things drawn by the queued operations.
|
|
/// - Any other blend mode: the latest chunks contain only the things drawn by the queued operations.
|
|
/// They need to be drawn over the committed chunks to obtain the final image. In this case, operations won't have access to the existing pixels.
|
|
/// They need to be drawn over the committed chunks to obtain the final image. In this case, operations won't have access to the existing pixels.
|
|
/// </summary>
|
|
/// </summary>
|
|
-public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
|
|
|
|
+public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICacheable
|
|
{
|
|
{
|
|
private struct LatestChunkData
|
|
private struct LatestChunkData
|
|
{
|
|
{
|
|
@@ -57,14 +62,24 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
private readonly object lockObject = new();
|
|
private readonly object lockObject = new();
|
|
private int commitCounter = 0;
|
|
private int commitCounter = 0;
|
|
|
|
|
|
|
|
+ private RectI cachedPreciseBounds = RectI.Empty;
|
|
|
|
+ private int lastBoundsCacheHash = -1;
|
|
|
|
+
|
|
public const int FullChunkSize = ChunkPool.FullChunkSize;
|
|
public const int FullChunkSize = ChunkPool.FullChunkSize;
|
|
private static Paint ClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstIn };
|
|
private static Paint ClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstIn };
|
|
private static Paint InverseClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstOut };
|
|
private static Paint InverseClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstOut };
|
|
private static Paint ReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src };
|
|
private static Paint ReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src };
|
|
- private static Paint SmoothReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium };
|
|
|
|
|
|
+
|
|
|
|
+ private static Paint SmoothReplacingPaint { get; } =
|
|
|
|
+ new Paint() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium };
|
|
|
|
+
|
|
private static Paint AddingPaint { get; } = new Paint() { BlendMode = BlendMode.Plus };
|
|
private static Paint AddingPaint { get; } = new Paint() { BlendMode = BlendMode.Plus };
|
|
private readonly Paint blendModePaint = new Paint() { BlendMode = BlendMode.Src };
|
|
private readonly Paint blendModePaint = new Paint() { BlendMode = BlendMode.Src };
|
|
|
|
|
|
|
|
+ public ColorSpace ProcessingColorSpace { get; set; }
|
|
|
|
+
|
|
|
|
+ public int CommitCounter => commitCounter;
|
|
|
|
+
|
|
public VecI CommittedSize { get; private set; }
|
|
public VecI CommittedSize { get; private set; }
|
|
public VecI LatestSize { get; private set; }
|
|
public VecI LatestSize { get; private set; }
|
|
|
|
|
|
@@ -85,11 +100,13 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
private double? horizontalSymmetryAxis = null;
|
|
private double? horizontalSymmetryAxis = null;
|
|
private double? verticalSymmetryAxis = null;
|
|
private double? verticalSymmetryAxis = null;
|
|
|
|
|
|
|
|
+ private int operationCounter = 0;
|
|
|
|
+
|
|
private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> committedChunks;
|
|
private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> committedChunks;
|
|
private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> latestChunks;
|
|
private readonly Dictionary<ChunkResolution, Dictionary<VecI, Chunk>> latestChunks;
|
|
private readonly Dictionary<ChunkResolution, Dictionary<VecI, LatestChunkData>> latestChunksData;
|
|
private readonly Dictionary<ChunkResolution, Dictionary<VecI, LatestChunkData>> latestChunksData;
|
|
|
|
|
|
- public ChunkyImage(VecI size)
|
|
|
|
|
|
+ public ChunkyImage(VecI size, ColorSpace colorSpace)
|
|
{
|
|
{
|
|
CommittedSize = size;
|
|
CommittedSize = size;
|
|
LatestSize = size;
|
|
LatestSize = size;
|
|
@@ -114,6 +131,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
[ChunkResolution.Quarter] = new(),
|
|
[ChunkResolution.Quarter] = new(),
|
|
[ChunkResolution.Eighth] = new(),
|
|
[ChunkResolution.Eighth] = new(),
|
|
};
|
|
};
|
|
|
|
+
|
|
|
|
+ ProcessingColorSpace = colorSpace;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public ChunkyImage(Surface image, ColorSpace colorSpace) : this(image.Size, colorSpace)
|
|
|
|
+ {
|
|
|
|
+ EnqueueDrawImage(VecI.Zero, image);
|
|
|
|
+ CommitChanges();
|
|
}
|
|
}
|
|
|
|
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
@@ -129,6 +154,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
rect ??= chunkBounds;
|
|
rect ??= chunkBounds;
|
|
rect = rect.Value.Union(chunkBounds);
|
|
rect = rect.Value.Union(chunkBounds);
|
|
}
|
|
}
|
|
|
|
+
|
|
foreach (var operation in queuedOperations)
|
|
foreach (var operation in queuedOperations)
|
|
{
|
|
{
|
|
foreach (var pos in operation.affectedArea.Chunks)
|
|
foreach (var pos in operation.affectedArea.Chunks)
|
|
@@ -138,6 +164,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
rect = rect.Value.Union(chunkBounds);
|
|
rect = rect.Value.Union(chunkBounds);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
return rect;
|
|
return rect;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -155,6 +182,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
rect ??= chunkBounds;
|
|
rect ??= chunkBounds;
|
|
rect = rect.Value.Union(chunkBounds);
|
|
rect = rect.Value.Union(chunkBounds);
|
|
}
|
|
}
|
|
|
|
+
|
|
return rect;
|
|
return rect;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -163,22 +191,29 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
/// Finds the precise bounds in <paramref name="suggestedResolution"/>. If there are no chunks rendered for that resolution, full res chunks are used instead.
|
|
/// Finds the precise bounds in <paramref name="suggestedResolution"/>. If there are no chunks rendered for that resolution, full res chunks are used instead.
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
- public RectI? FindTightCommittedBounds(ChunkResolution suggestedResolution = ChunkResolution.Full)
|
|
|
|
|
|
+ public RectI? FindTightCommittedBounds(ChunkResolution suggestedResolution = ChunkResolution.Full, bool fallbackToChunkAligned = false)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
|
|
|
|
|
|
+ if (lastBoundsCacheHash == GetCacheHash())
|
|
|
|
+ {
|
|
|
|
+ return cachedPreciseBounds;
|
|
|
|
+ }
|
|
|
|
+
|
|
var chunkSize = suggestedResolution.PixelSize();
|
|
var chunkSize = suggestedResolution.PixelSize();
|
|
var multiplier = suggestedResolution.Multiplier();
|
|
var multiplier = suggestedResolution.Multiplier();
|
|
RectI scaledCommittedSize = (RectI)(new RectD(VecI.Zero, CommittedSize * multiplier)).RoundOutwards();
|
|
RectI scaledCommittedSize = (RectI)(new RectD(VecI.Zero, CommittedSize * multiplier)).RoundOutwards();
|
|
|
|
|
|
RectI? preciseBounds = null;
|
|
RectI? preciseBounds = null;
|
|
|
|
+
|
|
foreach (var (chunkPos, fullResChunk) in committedChunks[ChunkResolution.Full])
|
|
foreach (var (chunkPos, fullResChunk) in committedChunks[ChunkResolution.Full])
|
|
{
|
|
{
|
|
if (committedChunks[suggestedResolution].TryGetValue(chunkPos, out Chunk? requestedResChunk))
|
|
if (committedChunks[suggestedResolution].TryGetValue(chunkPos, out Chunk? requestedResChunk))
|
|
{
|
|
{
|
|
- RectI visibleArea = new RectI(chunkPos * chunkSize, new VecI(chunkSize)).Intersect(scaledCommittedSize).Translate(-chunkPos * chunkSize);
|
|
|
|
|
|
+ RectI visibleArea = new RectI(chunkPos * chunkSize, new VecI(chunkSize))
|
|
|
|
+ .Intersect(scaledCommittedSize).Translate(-chunkPos * chunkSize);
|
|
|
|
|
|
RectI? chunkPreciseBounds = requestedResChunk.FindPreciseBounds(visibleArea);
|
|
RectI? chunkPreciseBounds = requestedResChunk.FindPreciseBounds(visibleArea);
|
|
if (chunkPreciseBounds is null)
|
|
if (chunkPreciseBounds is null)
|
|
@@ -190,20 +225,31 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- RectI visibleArea = new RectI(chunkPos * FullChunkSize, new VecI(FullChunkSize)).Intersect(new RectI(VecI.Zero, CommittedSize)).Translate(-chunkPos * FullChunkSize);
|
|
|
|
|
|
+ if (fallbackToChunkAligned)
|
|
|
|
+ {
|
|
|
|
+ return FindChunkAlignedCommittedBounds();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ RectI visibleArea = new RectI(chunkPos * FullChunkSize, new VecI(FullChunkSize))
|
|
|
|
+ .Intersect(new RectI(VecI.Zero, CommittedSize)).Translate(-chunkPos * FullChunkSize);
|
|
|
|
|
|
RectI? chunkPreciseBounds = fullResChunk.FindPreciseBounds(visibleArea);
|
|
RectI? chunkPreciseBounds = fullResChunk.FindPreciseBounds(visibleArea);
|
|
if (chunkPreciseBounds is null)
|
|
if (chunkPreciseBounds is null)
|
|
continue;
|
|
continue;
|
|
- RectI globalChunkBounds = (RectI)chunkPreciseBounds.Value.Scale(multiplier).Offset(chunkPos * chunkSize).RoundOutwards();
|
|
|
|
|
|
+ RectI globalChunkBounds = (RectI)chunkPreciseBounds.Value.Scale(multiplier)
|
|
|
|
+ .Offset(chunkPos * chunkSize).RoundOutwards();
|
|
|
|
|
|
preciseBounds ??= globalChunkBounds;
|
|
preciseBounds ??= globalChunkBounds;
|
|
preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
|
|
preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
preciseBounds = (RectI?)preciseBounds?.Scale(suggestedResolution.InvertedMultiplier()).RoundOutwards();
|
|
preciseBounds = (RectI?)preciseBounds?.Scale(suggestedResolution.InvertedMultiplier()).RoundOutwards();
|
|
preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, CommittedSize));
|
|
preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, CommittedSize));
|
|
|
|
|
|
|
|
+ cachedPreciseBounds = preciseBounds.GetValueOrDefault();
|
|
|
|
+ lastBoundsCacheHash = GetCacheHash();
|
|
|
|
+
|
|
return preciseBounds;
|
|
return preciseBounds;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -214,7 +260,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
- ChunkyImage output = new(LatestSize);
|
|
|
|
|
|
+ ChunkyImage output = new(LatestSize, ProcessingColorSpace);
|
|
var chunks = FindCommittedChunks();
|
|
var chunks = FindCommittedChunks();
|
|
foreach (var chunk in chunks)
|
|
foreach (var chunk in chunks)
|
|
{
|
|
{
|
|
@@ -240,7 +286,23 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
return MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) switch
|
|
return MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) switch
|
|
{
|
|
{
|
|
null => Colors.Transparent,
|
|
null => Colors.Transparent,
|
|
- var chunk => chunk.Surface.GetSRGBPixel(posInChunk)
|
|
|
|
|
|
+ var chunk => chunk.Surface.GetSrgbPixel(posInChunk)
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
|
|
+ public Color GetCommittedPixelRaw(VecI posOnImage)
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ var chunkPos = OperationHelper.GetChunkPos(posOnImage, FullChunkSize);
|
|
|
|
+ var posInChunk = posOnImage - chunkPos * FullChunkSize;
|
|
|
|
+ return MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) switch
|
|
|
|
+ {
|
|
|
|
+ null => Colors.Transparent,
|
|
|
|
+ var chunk => chunk.Surface.GetRawPixel(posInChunk)
|
|
};
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -261,7 +323,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
return committedChunk switch
|
|
return committedChunk switch
|
|
{
|
|
{
|
|
null => Colors.Transparent,
|
|
null => Colors.Transparent,
|
|
- _ => committedChunk.Surface.GetSRGBPixel(posInChunk)
|
|
|
|
|
|
+ _ => committedChunk.Surface.GetSrgbPixel(posInChunk)
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
@@ -272,7 +334,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
return latestChunk switch
|
|
return latestChunk switch
|
|
{
|
|
{
|
|
null => Colors.Transparent,
|
|
null => Colors.Transparent,
|
|
- _ => latestChunk.Surface.GetSRGBPixel(posInChunk)
|
|
|
|
|
|
+ _ => latestChunk.Surface.GetSrgbPixel(posInChunk)
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
@@ -280,20 +342,20 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
Chunk? committedChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
|
|
Chunk? committedChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
|
|
Chunk? latestChunk = GetLatestChunk(chunkPos, ChunkResolution.Full);
|
|
Chunk? latestChunk = GetLatestChunk(chunkPos, ChunkResolution.Full);
|
|
- Color committedColor = committedChunk is null ?
|
|
|
|
- Colors.Transparent :
|
|
|
|
- committedChunk.Surface.GetSRGBPixel(posInChunk);
|
|
|
|
- Color latestColor = latestChunk is null ?
|
|
|
|
- Colors.Transparent :
|
|
|
|
- latestChunk.Surface.GetSRGBPixel(posInChunk);
|
|
|
|
|
|
+ Color committedColor = committedChunk is null
|
|
|
|
+ ? Colors.Transparent
|
|
|
|
+ : committedChunk.Surface.GetSrgbPixel(posInChunk);
|
|
|
|
+ Color latestColor = latestChunk is null
|
|
|
|
+ ? Colors.Transparent
|
|
|
|
+ : latestChunk.Surface.GetSrgbPixel(posInChunk);
|
|
// using a whole chunk just to draw 1 pixel is kinda dumb,
|
|
// using a whole chunk just to draw 1 pixel is kinda dumb,
|
|
// but this should be faster than any approach that requires allocations
|
|
// but this should be faster than any approach that requires allocations
|
|
- using Chunk tempChunk = Chunk.Create(ChunkResolution.Eighth);
|
|
|
|
|
|
+ using Chunk tempChunk = Chunk.Create(ProcessingColorSpace, ChunkResolution.Eighth);
|
|
using Paint committedPaint = new Paint() { Color = committedColor, BlendMode = BlendMode.Src };
|
|
using Paint committedPaint = new Paint() { Color = committedColor, BlendMode = BlendMode.Src };
|
|
using Paint latestPaint = new Paint() { Color = latestColor, BlendMode = this.blendMode };
|
|
using Paint latestPaint = new Paint() { Color = latestColor, BlendMode = this.blendMode };
|
|
tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, committedPaint);
|
|
tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, committedPaint);
|
|
tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, latestPaint);
|
|
tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, latestPaint);
|
|
- return tempChunk.Surface.GetSRGBPixel(VecI.Zero);
|
|
|
|
|
|
+ return tempChunk.Surface.GetSrgbPixel(VecI.Zero);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -302,7 +364,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
/// True if the chunk existed and was drawn, otherwise false
|
|
/// True if the chunk existed and was drawn, otherwise false
|
|
/// </returns>
|
|
/// </returns>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
- public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
|
|
|
|
|
|
+ public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
|
|
|
|
+ Paint? paint = null)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
@@ -327,7 +390,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
if (committedChunk is null)
|
|
if (committedChunk is null)
|
|
return false;
|
|
return false;
|
|
- committedChunk.DrawOnSurface(surface, pos, paint);
|
|
|
|
|
|
+ committedChunk.DrawChunkOn(surface, pos, paint);
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -336,7 +399,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
if (latestChunk.IsT2)
|
|
if (latestChunk.IsT2)
|
|
{
|
|
{
|
|
- latestChunk.AsT2.DrawOnSurface(surface, pos, paint);
|
|
|
|
|
|
+ latestChunk.AsT2.DrawChunkOn(surface, pos, paint);
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -344,13 +407,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
|
|
|
|
// combine with committed and then draw
|
|
// combine with committed and then draw
|
|
- using var tempChunk = Chunk.Create(resolution);
|
|
|
|
- tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0, ReplacingPaint);
|
|
|
|
|
|
+ using var tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
|
|
|
|
+ tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0,
|
|
|
|
+ ReplacingPaint);
|
|
blendModePaint.BlendMode = blendMode;
|
|
blendModePaint.BlendMode = blendMode;
|
|
- tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.DrawingSurface, 0, 0, blendModePaint);
|
|
|
|
|
|
+ tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.DrawingSurface, 0, 0,
|
|
|
|
+ blendModePaint);
|
|
if (lockTransparency)
|
|
if (lockTransparency)
|
|
OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
|
|
OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
|
|
- tempChunk.DrawOnSurface(surface, pos, paint);
|
|
|
|
|
|
+ tempChunk.DrawChunkOn(surface, pos, paint);
|
|
|
|
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
@@ -375,8 +440,25 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public bool LatestOrCommittedChunkExists()
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ var chunks = FindAllChunks();
|
|
|
|
+ foreach (var chunk in chunks)
|
|
|
|
+ {
|
|
|
|
+ if (LatestOrCommittedChunkExists(chunk))
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
- public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
|
|
|
|
|
|
+ public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos,
|
|
|
|
+ Paint? paint = null)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
@@ -384,7 +466,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
var chunk = GetCommittedChunk(chunkPos, resolution);
|
|
var chunk = GetCommittedChunk(chunkPos, resolution);
|
|
if (chunk is null)
|
|
if (chunk is null)
|
|
return false;
|
|
return false;
|
|
- chunk.DrawOnSurface(surface, pos, paint);
|
|
|
|
|
|
+ chunk.DrawChunkOn(surface, pos, paint);
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -414,7 +496,6 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// 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.
|
|
/// 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(VecI pos, ChunkResolution resolution)
|
|
private Chunk? GetCommittedChunk(VecI pos, ChunkResolution resolution)
|
|
{
|
|
{
|
|
var maybeSameRes = MaybeGetCommittedChunk(pos, resolution);
|
|
var maybeSameRes = MaybeGetCommittedChunk(pos, resolution);
|
|
@@ -441,7 +522,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
if (queuedOperations.Count > 0)
|
|
if (queuedOperations.Count > 0)
|
|
- throw new InvalidOperationException("This function can only be executed when there are no queued operations");
|
|
|
|
|
|
+ throw new InvalidOperationException(
|
|
|
|
+ "This function can only be executed when there are no queued operations");
|
|
activeClips.Add(clippingMask);
|
|
activeClips.Add(clippingMask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -453,7 +535,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
if (queuedOperations.Count > 0)
|
|
if (queuedOperations.Count > 0)
|
|
- throw new InvalidOperationException("This function can only be executed when there are no queued operations");
|
|
|
|
|
|
+ throw new InvalidOperationException(
|
|
|
|
+ "This function can only be executed when there are no queued operations");
|
|
this.clippingPath = clippingPath;
|
|
this.clippingPath = clippingPath;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -468,7 +551,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
if (queuedOperations.Count > 0)
|
|
if (queuedOperations.Count > 0)
|
|
- throw new InvalidOperationException("This function can only be executed when there are no queued operations");
|
|
|
|
|
|
+ throw new InvalidOperationException(
|
|
|
|
+ "This function can only be executed when there are no queued operations");
|
|
blendMode = mode;
|
|
blendMode = mode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -480,7 +564,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
if (queuedOperations.Count > 0)
|
|
if (queuedOperations.Count > 0)
|
|
- throw new InvalidOperationException("This function can only be executed when there are no queued operations");
|
|
|
|
|
|
+ throw new InvalidOperationException(
|
|
|
|
+ "This function can only be executed when there are no queued operations");
|
|
horizontalSymmetryAxis = position;
|
|
horizontalSymmetryAxis = position;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -492,7 +577,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
if (queuedOperations.Count > 0)
|
|
if (queuedOperations.Count > 0)
|
|
- throw new InvalidOperationException("This function can only be executed when there are no queued operations");
|
|
|
|
|
|
+ throw new InvalidOperationException(
|
|
|
|
+ "This function can only be executed when there are no queued operations");
|
|
verticalSymmetryAxis = position;
|
|
verticalSymmetryAxis = position;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -530,12 +616,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
|
|
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
- public void EnqueueDrawEllipse(RectI location, Color strokeColor, Color fillColor, int strokeWidth, Paint? paint = null)
|
|
|
|
|
|
+ public void EnqueueDrawEllipse(RectD location, Paintable? strokeColor, Paintable? fillColor, float strokeWidth,
|
|
|
|
+ double rotationRad = 0, bool antiAliased = false,
|
|
|
|
+ Paint? paint = null)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
- EllipseOperation operation = new(location, strokeColor, fillColor, strokeWidth, paint);
|
|
|
|
|
|
+ EllipseOperation operation = new(location, strokeColor, fillColor, strokeWidth, rotationRad, antiAliased,
|
|
|
|
+ paint);
|
|
EnqueueOperation(operation);
|
|
EnqueueOperation(operation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -584,7 +673,52 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
EnqueueOperation(operation);
|
|
EnqueueOperation(operation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Be careful about the copyImage argument. The default is true, and this is a thread safe version without any side effects.
|
|
|
|
+ /// It will however copy the surface right away which can be slow (in updateable changes especially).
|
|
|
|
+ /// If copyImage is set to false, the image won't be copied and instead a reference will be stored.
|
|
|
|
+ /// Texture is NOT THREAD SAFE, so if you pass a Texture here with copyImage == false you must not do anything with that texture anywhere (not even read) until CommitChanges/CancelChanges is called.
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
|
|
+ public void EnqueueDrawTexture(Matrix3X3 transformMatrix, Texture image, Paint? paint = null, bool copyImage = true)
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ TextureOperation operation = new(transformMatrix, image, paint, copyImage);
|
|
|
|
+ EnqueueOperation(operation);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Be careful about the copyImage argument, see other overload for details
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
|
|
+ public void EnqueueDrawTexture(ShapeCorners corners, Texture image, Paint? paint = null, bool copyImage = true)
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ TextureOperation operation = new(corners, image, paint, copyImage);
|
|
|
|
+ EnqueueOperation(operation);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Be careful about the copyImage argument, see other overload for details
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
|
|
+ public void EnqueueDrawTexture(VecI pos, Texture image, Paint? paint = null, bool copyImage = true)
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ TextureOperation operation = new(pos, image, paint, copyImage);
|
|
|
|
+ EnqueueOperation(operation);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
public void EnqueueApplyMask(ChunkyImage mask)
|
|
public void EnqueueApplyMask(ChunkyImage mask)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
@@ -597,7 +731,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
|
|
|
|
/// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
|
|
/// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
- public void EnqueueDrawPath(VectorPath path, Color color, float strokeWidth, StrokeCap strokeCap, BlendMode blendMode, RectI? customBounds = null)
|
|
|
|
|
|
+ public void EnqueueDrawPath(VectorPath path, Color color, float strokeWidth, StrokeCap strokeCap,
|
|
|
|
+ BlendMode blendMode, RectI? customBounds = null)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
@@ -608,18 +743,19 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
|
|
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
- public void EnqueueDrawBresenhamLine(VecI from, VecI to, Color color, BlendMode blendMode)
|
|
|
|
|
|
+ public void EnqueueDrawBresenhamLine(VecI from, VecI to, Paintable paintable, BlendMode blendMode)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
- BresenhamLineOperation operation = new(from, to, color, blendMode);
|
|
|
|
|
|
+ BresenhamLineOperation operation = new(from, to, paintable, blendMode);
|
|
EnqueueOperation(operation);
|
|
EnqueueOperation(operation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
- public void EnqueueDrawSkiaLine(VecI from, VecI to, StrokeCap strokeCap, float strokeWidth, Color color, BlendMode blendMode)
|
|
|
|
|
|
+ public void EnqueueDrawSkiaLine(VecD from, VecD to, StrokeCap strokeCap, float strokeWidth, Color color,
|
|
|
|
+ BlendMode blendMode)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
@@ -629,6 +765,16 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public void EnqueueDrawSkiaLine(VecD from, VecD to, Paint paint)
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ DrawingSurfaceLineOperation operation = new(from, to, paint);
|
|
|
|
+ EnqueueOperation(operation);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
public void EnqueueDrawPixels(IEnumerable<VecI> pixels, Color color, BlendMode blendMode)
|
|
public void EnqueueDrawPixels(IEnumerable<VecI> pixels, Color color, BlendMode blendMode)
|
|
{
|
|
{
|
|
@@ -663,16 +809,23 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
|
|
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
- public void EnqueueDrawChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
|
|
|
|
|
|
+ public void EnqueueDrawCommitedChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
|
|
{
|
|
{
|
|
lock (lockObject)
|
|
lock (lockObject)
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
- ChunkyImageOperation operation = new(image, pos, flipHor, flipVer);
|
|
|
|
|
|
+ ChunkyImageOperation operation = new(image, pos, flipHor, flipVer, false);
|
|
EnqueueOperation(operation);
|
|
EnqueueOperation(operation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public void EnqueueDrawUpToDateChunkyImage(VecI pos, ChunkyImage image, bool flipHor = false, bool flipVer = false)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ ChunkyImageOperation operation = new(image, pos, flipHor, flipVer, true);
|
|
|
|
+ EnqueueOperation(operation);
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
/// <exception cref="ObjectDisposedException">This image is disposed</exception>
|
|
public void EnqueueClearRegion(RectI region)
|
|
public void EnqueueClearRegion(RectI region)
|
|
{
|
|
{
|
|
@@ -718,6 +871,17 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
|
|
+ public void EnqueueDrawPaint(Paint paint)
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ PaintOperation operation = new(paint);
|
|
|
|
+ EnqueueOperation(operation);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
private void EnqueueOperation(IDrawOperation operation)
|
|
private void EnqueueOperation(IDrawOperation operation)
|
|
{
|
|
{
|
|
List<IDrawOperation> operations = new(4) { operation };
|
|
List<IDrawOperation> operations = new(4) { operation };
|
|
@@ -740,6 +904,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
if (operation.IgnoreEmptyChunks)
|
|
if (operation.IgnoreEmptyChunks)
|
|
area.Chunks.IntersectWith(FindAllChunks());
|
|
area.Chunks.IntersectWith(FindAllChunks());
|
|
EnqueueOperation(op, area);
|
|
EnqueueOperation(op, area);
|
|
|
|
+ operationCounter++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -835,7 +1000,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
if (resolution == ChunkResolution.Full)
|
|
if (resolution == ChunkResolution.Full)
|
|
{
|
|
{
|
|
- throw new InvalidOperationException("Trying to commit a full res chunk that wasn't fully processed");
|
|
|
|
|
|
+ throw new InvalidOperationException(
|
|
|
|
+ "Trying to commit a full res chunk that wasn't fully processed");
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
@@ -883,14 +1049,18 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
blendModePaint.BlendMode = blendMode;
|
|
blendModePaint.BlendMode = blendMode;
|
|
if (lockTransparency)
|
|
if (lockTransparency)
|
|
{
|
|
{
|
|
- using Chunk tempChunk = Chunk.Create(resolution);
|
|
|
|
- tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(maybeCommitted.Surface.DrawingSurface, 0, 0, ReplacingPaint);
|
|
|
|
- maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0, blendModePaint);
|
|
|
|
- OperationHelper.ClampAlpha(maybeCommitted.Surface.DrawingSurface, tempChunk.Surface.DrawingSurface);
|
|
|
|
|
|
+ using Chunk tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
|
|
|
|
+ tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(maybeCommitted.Surface.DrawingSurface, 0, 0,
|
|
|
|
+ ReplacingPaint);
|
|
|
|
+ maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0,
|
|
|
|
+ blendModePaint);
|
|
|
|
+ OperationHelper.ClampAlpha(maybeCommitted.Surface.DrawingSurface,
|
|
|
|
+ tempChunk.Surface.DrawingSurface);
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0, blendModePaint);
|
|
|
|
|
|
+ maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0,
|
|
|
|
+ blendModePaint);
|
|
}
|
|
}
|
|
|
|
|
|
chunk.Dispose();
|
|
chunk.Dispose();
|
|
@@ -905,7 +1075,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
if (resolution == ChunkResolution.Full)
|
|
if (resolution == ChunkResolution.Full)
|
|
continue;
|
|
continue;
|
|
- if (!latestChunksData[resolution].TryGetValue(pos, out var halfChunk) || halfChunk.QueueProgress != queuedOperations.Count)
|
|
|
|
|
|
+ if (!latestChunksData[resolution].TryGetValue(pos, out var halfChunk) ||
|
|
|
|
+ halfChunk.QueueProgress != queuedOperations.Count)
|
|
{
|
|
{
|
|
if (committedChunks[resolution].TryGetValue(pos, out var committedLowResChunk))
|
|
if (committedChunks[resolution].TryGetValue(pos, out var committedLowResChunk))
|
|
{
|
|
{
|
|
@@ -964,7 +1135,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
var chunks = new HashSet<VecI>();
|
|
var chunks = new HashSet<VecI>();
|
|
RectI? rect = null;
|
|
RectI? rect = null;
|
|
-
|
|
|
|
|
|
+
|
|
for (int i = fromOperationIndex; i < queuedOperations.Count; i++)
|
|
for (int i = fromOperationIndex; i < queuedOperations.Count; i++)
|
|
{
|
|
{
|
|
var (_, area) = queuedOperations[i];
|
|
var (_, area) = queuedOperations[i];
|
|
@@ -979,13 +1150,25 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public void SetCommitedChunk(Chunk chunk, VecI pos, ChunkResolution resolution)
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ committedChunks[resolution][pos] = chunk;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <summary>
|
|
/// <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.
|
|
/// 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>
|
|
/// </summary>
|
|
private void MaybeCreateAndProcessQueueForChunk(VecI chunkPos, ChunkResolution resolution)
|
|
private void MaybeCreateAndProcessQueueForChunk(VecI chunkPos, ChunkResolution resolution)
|
|
{
|
|
{
|
|
if (!latestChunksData[resolution].TryGetValue(chunkPos, out LatestChunkData chunkData))
|
|
if (!latestChunksData[resolution].TryGetValue(chunkPos, out LatestChunkData chunkData))
|
|
- chunkData = new() { QueueProgress = 0, IsDeleted = !committedChunks[ChunkResolution.Full].ContainsKey(chunkPos) };
|
|
|
|
|
|
+ chunkData = new()
|
|
|
|
+ {
|
|
|
|
+ QueueProgress = 0, IsDeleted = !committedChunks[ChunkResolution.Full].ContainsKey(chunkPos)
|
|
|
|
+ };
|
|
if (chunkData.QueueProgress == queuedOperations.Count)
|
|
if (chunkData.QueueProgress == queuedOperations.Count)
|
|
return;
|
|
return;
|
|
|
|
|
|
@@ -1008,12 +1191,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
}
|
|
}
|
|
|
|
|
|
if (chunkData.QueueProgress <= i)
|
|
if (chunkData.QueueProgress <= i)
|
|
- chunkData.IsDeleted = ApplyOperationToChunk(operation, affArea, combinedRasterClips, targetChunk!, chunkPos, resolution, chunkData);
|
|
|
|
|
|
+ chunkData.IsDeleted = ApplyOperationToChunk(operation, affArea, combinedRasterClips, targetChunk!,
|
|
|
|
+ chunkPos, resolution, chunkData);
|
|
}
|
|
}
|
|
|
|
|
|
if (initialized)
|
|
if (initialized)
|
|
{
|
|
{
|
|
- if (lockTransparency && !chunkData.IsDeleted && MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
|
|
|
|
|
|
+ if (lockTransparency && !chunkData.IsDeleted &&
|
|
|
|
+ MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
|
|
{
|
|
{
|
|
var committed = GetCommittedChunk(chunkPos, resolution);
|
|
var committed = GetCommittedChunk(chunkPos, resolution);
|
|
OperationHelper.ClampAlpha(targetChunk!.Surface.DrawingSurface, committed!.Surface.DrawingSurface);
|
|
OperationHelper.ClampAlpha(targetChunk!.Surface.DrawingSurface, committed!.Surface.DrawingSurface);
|
|
@@ -1039,14 +1224,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
return new FilledChunk();
|
|
return new FilledChunk();
|
|
}
|
|
}
|
|
|
|
|
|
- var intersection = Chunk.Create(resolution);
|
|
|
|
|
|
+ var intersection = Chunk.Create(ProcessingColorSpace, resolution);
|
|
intersection.Surface.DrawingSurface.Canvas.Clear(Colors.White);
|
|
intersection.Surface.DrawingSurface.Canvas.Clear(Colors.White);
|
|
|
|
|
|
foreach (var mask in activeClips)
|
|
foreach (var mask in activeClips)
|
|
{
|
|
{
|
|
if (mask.CommittedChunkExists(chunkPos))
|
|
if (mask.CommittedChunkExists(chunkPos))
|
|
{
|
|
{
|
|
- mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
|
|
|
|
|
|
+ mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.DrawingSurface, VecI.Zero,
|
|
|
|
+ ClippingPaint);
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
@@ -1091,15 +1277,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
// drawing with raster clipping
|
|
// drawing with raster clipping
|
|
var clip = combinedRasterClips.AsT2;
|
|
var clip = combinedRasterClips.AsT2;
|
|
|
|
|
|
- using var tempChunk = Chunk.Create(targetChunk.Resolution);
|
|
|
|
- targetChunk.DrawOnSurface(tempChunk.Surface.DrawingSurface, VecI.Zero, ReplacingPaint);
|
|
|
|
|
|
+ using var tempChunk = Chunk.Create(ProcessingColorSpace, targetChunk.Resolution);
|
|
|
|
+ targetChunk.DrawChunkOn(tempChunk.Surface.DrawingSurface, VecI.Zero, ReplacingPaint);
|
|
|
|
|
|
CallDrawWithClip(chunkOperation, operationAffectedArea.GlobalArea, tempChunk, resolution, chunkPos);
|
|
CallDrawWithClip(chunkOperation, operationAffectedArea.GlobalArea, tempChunk, resolution, chunkPos);
|
|
|
|
|
|
- clip.DrawOnSurface(tempChunk.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
|
|
|
|
- clip.DrawOnSurface(targetChunk.Surface.DrawingSurface, VecI.Zero, InverseClippingPaint);
|
|
|
|
|
|
+ clip.DrawChunkOn(tempChunk.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
|
|
|
|
+ clip.DrawChunkOn(targetChunk.Surface.DrawingSurface, VecI.Zero, InverseClippingPaint);
|
|
|
|
|
|
- tempChunk.DrawOnSurface(targetChunk.Surface.DrawingSurface, VecI.Zero, AddingPaint);
|
|
|
|
|
|
+ tempChunk.DrawChunkOn(targetChunk.Surface.DrawingSurface, VecI.Zero, AddingPaint);
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1111,7 +1297,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
return chunkData.IsDeleted;
|
|
return chunkData.IsDeleted;
|
|
}
|
|
}
|
|
|
|
|
|
- private void CallDrawWithClip(IDrawOperation operation, RectI? operationAffectedArea, Chunk targetChunk, ChunkResolution resolution, VecI chunkPos)
|
|
|
|
|
|
+ private void CallDrawWithClip(IDrawOperation operation, RectI? operationAffectedArea, Chunk targetChunk,
|
|
|
|
+ ChunkResolution resolution, VecI chunkPos)
|
|
{
|
|
{
|
|
if (operationAffectedArea is null)
|
|
if (operationAffectedArea is null)
|
|
return;
|
|
return;
|
|
@@ -1123,7 +1310,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
using VectorPath transformedPath = new(clippingPath);
|
|
using VectorPath transformedPath = new(clippingPath);
|
|
VecD trans = -chunkPos * FullChunkSize * scale;
|
|
VecD trans = -chunkPos * FullChunkSize * scale;
|
|
-
|
|
|
|
|
|
+
|
|
transformedPath.Transform(Matrix3X3.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
|
|
transformedPath.Transform(Matrix3X3.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
|
|
targetChunk.Surface.DrawingSurface.Canvas.ClipPath(transformedPath);
|
|
targetChunk.Surface.DrawingSurface.Canvas.ClipPath(transformedPath);
|
|
}
|
|
}
|
|
@@ -1149,7 +1336,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
{
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfDisposed();
|
|
if (queuedOperations.Count > 0)
|
|
if (queuedOperations.Count > 0)
|
|
- throw new InvalidOperationException("This function can only be used when there are no queued operations");
|
|
|
|
|
|
+ throw new InvalidOperationException(
|
|
|
|
+ "This function can only be used when there are no queued operations");
|
|
FindAndDeleteEmptyCommittedChunks();
|
|
FindAndDeleteEmptyCommittedChunks();
|
|
return committedChunks[ChunkResolution.Full].Count == 0;
|
|
return committedChunks[ChunkResolution.Full].Count == 0;
|
|
}
|
|
}
|
|
@@ -1164,7 +1352,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
|
|
|
|
private static bool IsOutsideBounds(VecI chunkPos, VecI imageSize)
|
|
private static bool IsOutsideBounds(VecI chunkPos, VecI imageSize)
|
|
{
|
|
{
|
|
- return chunkPos.X < 0 || chunkPos.Y < 0 || chunkPos.X * FullChunkSize >= imageSize.X || chunkPos.Y * FullChunkSize >= imageSize.Y;
|
|
|
|
|
|
+ return chunkPos.X < 0 || chunkPos.Y < 0 || chunkPos.X * FullChunkSize >= imageSize.X ||
|
|
|
|
+ chunkPos.Y * FullChunkSize >= imageSize.Y;
|
|
}
|
|
}
|
|
|
|
|
|
private void FindAndDeleteEmptyCommittedChunks()
|
|
private void FindAndDeleteEmptyCommittedChunks()
|
|
@@ -1203,7 +1392,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
// for full res chunks: nothing exists, create brand new chunk
|
|
// for full res chunks: nothing exists, create brand new chunk
|
|
if (resolution == ChunkResolution.Full)
|
|
if (resolution == ChunkResolution.Full)
|
|
{
|
|
{
|
|
- var newChunk = Chunk.Create(resolution);
|
|
|
|
|
|
+ var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
|
|
committedChunks[resolution][chunkPos] = newChunk;
|
|
committedChunks[resolution][chunkPos] = newChunk;
|
|
return newChunk;
|
|
return newChunk;
|
|
}
|
|
}
|
|
@@ -1212,11 +1401,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
Chunk? existingFullResChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
|
|
Chunk? existingFullResChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
|
|
if (existingFullResChunk is not null)
|
|
if (existingFullResChunk is not null)
|
|
{
|
|
{
|
|
- var newChunk = Chunk.Create(resolution);
|
|
|
|
|
|
+ var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
|
|
newChunk.Surface.DrawingSurface.Canvas.Save();
|
|
newChunk.Surface.DrawingSurface.Canvas.Save();
|
|
newChunk.Surface.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
|
|
newChunk.Surface.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
|
|
|
|
|
|
- newChunk.Surface.DrawingSurface.Canvas.DrawSurface(existingFullResChunk.Surface.DrawingSurface, 0, 0, SmoothReplacingPaint);
|
|
|
|
|
|
+ newChunk.Surface.DrawingSurface.Canvas.DrawSurface(existingFullResChunk.Surface.DrawingSurface, 0, 0,
|
|
|
|
+ SmoothReplacingPaint);
|
|
newChunk.Surface.DrawingSurface.Canvas.Restore();
|
|
newChunk.Surface.DrawingSurface.Canvas.Restore();
|
|
committedChunks[resolution][chunkPos] = newChunk;
|
|
committedChunks[resolution][chunkPos] = newChunk;
|
|
return newChunk;
|
|
return newChunk;
|
|
@@ -1225,7 +1415,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
// for low res chunks: full res version doesn't exist
|
|
// for low res chunks: full res version doesn't exist
|
|
{
|
|
{
|
|
GetOrCreateCommittedChunk(chunkPos, ChunkResolution.Full);
|
|
GetOrCreateCommittedChunk(chunkPos, ChunkResolution.Full);
|
|
- var newChunk = Chunk.Create(resolution);
|
|
|
|
|
|
+ var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
|
|
committedChunks[resolution][chunkPos] = newChunk;
|
|
committedChunks[resolution][chunkPos] = newChunk;
|
|
return newChunk;
|
|
return newChunk;
|
|
}
|
|
}
|
|
@@ -1245,7 +1435,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
var maybeCommittedAnyRes = MaybeGetCommittedChunk(chunkPos, resolution);
|
|
var maybeCommittedAnyRes = MaybeGetCommittedChunk(chunkPos, resolution);
|
|
if (maybeCommittedAnyRes is not null)
|
|
if (maybeCommittedAnyRes is not null)
|
|
{
|
|
{
|
|
- Chunk newChunk = Chunk.Create(resolution);
|
|
|
|
|
|
+ Chunk newChunk = Chunk.Create(ProcessingColorSpace, resolution);
|
|
if (blendMode == BlendMode.Src)
|
|
if (blendMode == BlendMode.Src)
|
|
maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
|
|
maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
|
|
else
|
|
else
|
|
@@ -1261,14 +1451,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
//create low res committed chunk
|
|
//create low res committed chunk
|
|
var committedChunkLowRes = GetOrCreateCommittedChunk(chunkPos, resolution);
|
|
var committedChunkLowRes = GetOrCreateCommittedChunk(chunkPos, resolution);
|
|
//create latest based on it
|
|
//create latest based on it
|
|
- Chunk newChunk = Chunk.Create(resolution);
|
|
|
|
|
|
+ Chunk newChunk = Chunk.Create(ProcessingColorSpace, resolution);
|
|
committedChunkLowRes.Surface.CopyTo(newChunk.Surface);
|
|
committedChunkLowRes.Surface.CopyTo(newChunk.Surface);
|
|
latestChunks[resolution][chunkPos] = newChunk;
|
|
latestChunks[resolution][chunkPos] = newChunk;
|
|
return newChunk;
|
|
return newChunk;
|
|
}
|
|
}
|
|
|
|
|
|
// no previous chunks exist
|
|
// no previous chunks exist
|
|
- var newLatestChunk = Chunk.Create(resolution);
|
|
|
|
|
|
+ var newLatestChunk = Chunk.Create(ProcessingColorSpace, resolution);
|
|
newLatestChunk.Surface.DrawingSurface.Canvas.Clear();
|
|
newLatestChunk.Surface.DrawingSurface.Canvas.Clear();
|
|
latestChunks[resolution][chunkPos] = newLatestChunk;
|
|
latestChunks[resolution][chunkPos] = newLatestChunk;
|
|
return newLatestChunk;
|
|
return newLatestChunk;
|
|
@@ -1313,4 +1503,23 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
|
|
|
|
|
|
disposed = true;
|
|
disposed = true;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ public object Clone()
|
|
|
|
+ {
|
|
|
|
+ lock (lockObject)
|
|
|
|
+ {
|
|
|
|
+ ThrowIfDisposed();
|
|
|
|
+ ChunkyImage clone = CloneFromCommitted();
|
|
|
|
+ return clone;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public int GetCacheHash()
|
|
|
|
+ {
|
|
|
|
+ return commitCounter + queuedOperations.Count + operationCounter + activeClips.Count
|
|
|
|
+ + (int)blendMode + (lockTransparency ? 1 : 0)
|
|
|
|
+ + (horizontalSymmetryAxis is not null ? (int)(horizontalSymmetryAxis * 100) : 0)
|
|
|
|
+ + (verticalSymmetryAxis is not null ? (int)(verticalSymmetryAxis * 100) : 0)
|
|
|
|
+ + (clippingPath is not null ? 1 : 0);
|
|
|
|
+ }
|
|
}
|
|
}
|