Skip to content

Commit

Permalink
Cropped layer previews wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Equbuxu committed Feb 22, 2023
1 parent f079810 commit d5d953a
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 33 deletions.
34 changes: 28 additions & 6 deletions src/ChunkyImageLib/ChunkyImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ namespace ChunkyImageLib;
/// - BlendMode.Src: default mode, the latest chunks are the same as committed ones but with some or all queued operations applied.
/// This means that operations can work with the existing pixels.
/// - 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>
public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
{
Expand Down Expand Up @@ -117,7 +117,7 @@ public ChunkyImage(VecI size)
}

/// <exception cref="ObjectDisposedException">This image is disposed</exception>
public RectI? FindLatestBounds()
public RectI? FindChunkAlignedMostUpToDateBounds()
{
lock (lockObject)
{
Expand All @@ -129,7 +129,27 @@ public ChunkyImage(VecI size)
rect ??= chunkBounds;
rect = rect.Value.Union(chunkBounds);
}
foreach (var (pos, _) in latestChunks[ChunkResolution.Full])
foreach (var operation in queuedOperations)
{
foreach (var pos in operation.affectedArea.Chunks)
{
RectI chunkBounds = new RectI(pos * FullChunkSize, new VecI(FullChunkSize));
rect ??= chunkBounds;
rect = rect.Value.Union(chunkBounds);
}
}
return rect;
}
}

/// <exception cref="ObjectDisposedException">This image is disposed</exception>
public RectI? FindChunkAlignedCommittedBounds()
{
lock (lockObject)
{
ThrowIfDisposed();
RectI? rect = null;
foreach (var (pos, _) in committedChunks[ChunkResolution.Full])
{
RectI chunkBounds = new RectI(pos * FullChunkSize, new VecI(FullChunkSize));
rect ??= chunkBounds;
Expand All @@ -140,23 +160,25 @@ public ChunkyImage(VecI size)
}

/// <exception cref="ObjectDisposedException">This image is disposed</exception>
public RectI? FindPreciseCommittedBounds()
public RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full)
{
lock (lockObject)
{
ThrowIfDisposed();

var chunkSize = precision.PixelSize();
RectI? preciseBounds = null;
foreach (var (chunkPos, chunk) in committedChunks[ChunkResolution.Full])
foreach (var (chunkPos, chunk) in committedChunks[precision])
{
RectI? chunkPreciseBounds = chunk.FindPreciseBounds();
if(chunkPreciseBounds is null)
continue;
RectI globalChunkBounds = chunkPreciseBounds.Value.Offset(chunkPos * FullChunkSize);
RectI globalChunkBounds = chunkPreciseBounds.Value.Offset(chunkPos * chunkSize);

preciseBounds ??= globalChunkBounds;
preciseBounds = preciseBounds.Value.Union(globalChunkBounds);
}
preciseBounds = (RectI?)preciseBounds?.Scale(precision.InvertedMultiplier()).RoundOutwards();
preciseBounds = preciseBounds?.Intersect(new RectI(preciseBounds.Value.Pos, CommittedSize));

return preciseBounds;
Expand Down
15 changes: 15 additions & 0 deletions src/ChunkyImageLib/DataHolders/ChunkResolutionEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ public static double Multiplier(this ChunkResolution resolution)
};
}

/// <summary>
/// Returns the inverted multiplier of the <paramref name="resolution"/>.
/// </summary>
public static double InvertedMultiplier(this ChunkResolution resolution)
{
return resolution switch
{
ChunkResolution.Full => 1,
ChunkResolution.Half => 2,
ChunkResolution.Quarter => 4,
ChunkResolution.Eighth => 8,
_ => 1,
};
}

/// <summary>
/// Returns the size of a chunk of the resolution <paramref name="resolution"/>
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion src/ChunkyImageLib/IReadOnlyChunkyImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ public interface IReadOnlyChunkyImage
{
bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
RectI? FindLatestBounds();
RectI? FindChunkAlignedMostUpToDateBounds();
RectI? FindChunkAlignedCommittedBounds();
RectI? FindTightCommittedBounds(ChunkResolution precision = ChunkResolution.Full);
Color GetCommittedPixel(VecI posOnImage);
Color GetMostUpToDatePixel(VecI posOnImage);
bool LatestOrCommittedChunkExists(VecI chunkPos);
Expand Down
6 changes: 3 additions & 3 deletions src/PixiEditor.ChangeableDocument/Changeables/Document.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void Dispose()
throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));


RectI? tightBounds = layer.LayerImage.FindLatestBounds();
RectI? tightBounds = layer.LayerImage.FindChunkAlignedMostUpToDateBounds();

if (tightBounds is null)
return null;
Expand All @@ -69,15 +69,15 @@ public void Dispose()
return surface;
}

public RectI? GetLayerTightBounds(Guid layerGuid)
public RectI? GetChunkAlignedLayerBounds(Guid layerGuid)
{
var layer = (IReadOnlyLayer?)FindMember(layerGuid);

if (layer is null)
throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));


return layer.LayerImage.FindLatestBounds();
return layer.LayerImage.FindChunkAlignedMostUpToDateBounds();
}

public void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action) => ForEveryReadonlyMember(StructureRoot, action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public interface IReadOnlyDocument
void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);

public Surface? GetLayerImage(Guid layerGuid);
public RectI? GetLayerTightBounds(Guid layerGuid);
public RectI? GetChunkAlignedLayerBounds(Guid layerGuid);

/// <summary>
/// Finds the member with the <paramref name="guid"/> or returns null if not found
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public override bool InitializeAndValidate(Document target)
public OneOf<None, (Surface image, RectI extractedRect)> ExtractArea(ChunkyImage image, VectorPath path, RectI pathBounds)
{
// get rid of transparent areas on edges
var memberImageBounds = image.FindLatestBounds();
var memberImageBounds = image.FindChunkAlignedMostUpToDateBounds();
if (memberImageBounds is null)
return new None();
pathBounds = pathBounds.Intersect(memberImageBounds.Value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private VecI CalculateCurrentOffset(Document document)
foreach (var layerGuid in affectedLayers)
{
Layer layer = document.FindMemberOrThrow<Layer>(layerGuid);
RectI? tightBounds = layer.LayerImage.FindPreciseCommittedBounds();
RectI? tightBounds = layer.LayerImage.FindTightCommittedBounds();
if (tightBounds.HasValue)
{
currentBounds = currentBounds.HasValue ? currentBounds.Value.Union(tightBounds.Value) : tightBounds;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal class ClipCanvas_Change : ResizeBasedChangeBase
{
if (member is Layer layer)
{
var layerBounds = layer.LayerImage.FindPreciseCommittedBounds();
var layerBounds = layer.LayerImage.FindTightCommittedBounds();
if (layerBounds.HasValue)
{
bounds ??= layerBounds.Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private void FlipImage(ChunkyImage img)
RectI bounds = new RectI(VecI.Zero, img.LatestSize);
if (membersToFlip.Count > 0)
{
var preciseBounds = img.FindPreciseCommittedBounds();
var preciseBounds = img.FindTightCommittedBounds();
if (preciseBounds.HasValue)
{
bounds = preciseBounds.Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public override bool InitializeAndValidate(Document target)
RectI bounds = new RectI(VecI.Zero, img.CommittedSize);
if (membersToRotate.Count > 0)
{
var preciseBounds = img.FindPreciseCommittedBounds();
var preciseBounds = img.FindTightCommittedBounds();
if (preciseBounds.HasValue)
{
bounds = preciseBounds.Value;
Expand Down
5 changes: 5 additions & 0 deletions src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ public static RectI FromTwoPixels(VecI pixel, VecI oppositePixel)
return x > left && x < right && y > top && y < bottom;
}

public readonly bool ContainsExclusive(RectI rect)
{
return ContainsExclusive(rect.TopLeft) && ContainsExclusive(rect.BottomRight);
}

public readonly bool ContainsPixel(VecI pixelTopLeft) => ContainsPixel(pixelTopLeft.X, pixelTopLeft.Y);
public readonly bool ContainsPixel(int pixelTopLeftX, int pixelTopLeftY)
{
Expand Down
101 changes: 87 additions & 14 deletions src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
using PixiEditor.Models.DocumentModels;
using PixiEditor.Models.Rendering.RenderInfos;
using PixiEditor.ViewModels.SubViewModels.Document;
using System.Diagnostics;
using System.Drawing.Text;

namespace PixiEditor.Models.Rendering;
internal class MemberPreviewUpdater
{
private readonly DocumentViewModel doc;
private readonly DocumentInternalParts internals;

private Dictionary<Guid, RectI> lastTightBounds = new();
private Dictionary<Guid, AffectedArea> previewDelayedAreas = new();
private Dictionary<Guid, AffectedArea> maskPreviewDelayedAreas = new();

Expand Down Expand Up @@ -52,15 +55,21 @@ public List<IRenderInfo> UpdateGatheredChunksSync

private List<IRenderInfo> Render(AffectedAreasGatherer chunkGatherer, bool rerenderPreviews)
{
Stopwatch sw = Stopwatch.StartNew();
List<IRenderInfo> infos = new();

var (imagePreviewChunksToRerender, maskPreviewChunksToRerender) = FindPreviewChunksToRerender(chunkGatherer, !rerenderPreviews);
var previewSize = StructureMemberViewModel.CalculatePreviewSize(internals.Tracker.Document.Size);
float scaling = (float)previewSize.X / doc.SizeBindable.X;
UpdateImagePreviews(imagePreviewChunksToRerender, scaling, infos);
if (rerenderPreviews)
Trace.WriteLine("image" + (sw.ElapsedTicks * 1000 / (double)Stopwatch.Frequency).ToString());
UpdateMaskPreviews(maskPreviewChunksToRerender, scaling, infos);

return infos;
if (rerenderPreviews)
Trace.WriteLine(sw.ElapsedTicks * 1000 / (double)Stopwatch.Frequency );
sw.Stop();
return infos;
}

private static void AddAreas(Dictionary<Guid, AffectedArea> from, Dictionary<Guid, AffectedArea> to)
Expand Down Expand Up @@ -137,6 +146,77 @@ private void UpdateWholeCanvasPreview(Dictionary<Guid, AffectedArea> imagePrevie
infos.Add(new CanvasPreviewDirty_RenderInfo());
}

private RectI? FindLayerTightBounds(IReadOnlyLayer layer)
{
// premature optimization here we go
RectI? bounds = layer.LayerImage.FindChunkAlignedCommittedBounds();
if (bounds is null)
return null;

int biggest = bounds.Value.Size.LongestAxis;
ChunkResolution resolution = biggest switch
{
> 2048 => ChunkResolution.Eighth,
> 1024 => ChunkResolution.Quarter,
> 512 => ChunkResolution.Half,
_ => ChunkResolution.Full,
};
return layer.LayerImage.FindTightCommittedBounds(resolution);
}

private void UpdateLayerPreviewSurface(IReadOnlyLayer layer, StructureMemberViewModel memberVM, AffectedArea area, float scaling)
{
RectI? prevTightBounds = null;
if (lastTightBounds.TryGetValue(layer.GuidValue, out RectI tightBounds))
prevTightBounds = tightBounds;

RectI? newTightBounds;

if (prevTightBounds is null)
{
newTightBounds = FindLayerTightBounds(layer);
}
else if (prevTightBounds.Value.ContainsExclusive(area.GlobalArea.Value))
{
// if the affected area is fully inside the previous tight bounds, the tight bounds couldn't possibly have changed
newTightBounds = prevTightBounds.Value;
}
else
{
newTightBounds = FindLayerTightBounds(layer);
}

if (newTightBounds is null)
{
memberVM.PreviewSurface.Canvas.Clear();
return;
}

if (newTightBounds == prevTightBounds)
{
memberVM.PreviewSurface.Canvas.Save();
memberVM.PreviewSurface.Canvas.Scale(scaling);
memberVM.PreviewSurface.Canvas.ClipRect((RectD)area.GlobalArea);

foreach (var chunk in area.Chunks)
{
var pos = chunk * ChunkResolution.Full.PixelSize();
if (!layer.LayerImage.DrawMostUpToDateChunkOn(chunk, ChunkResolution.Full, memberVM.PreviewSurface, pos, SmoothReplacingPaint))
memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize, ClearPaint);
}

memberVM.PreviewSurface.Canvas.Restore();
return;
}

int biggestAxis = newTightBounds.Value.Size.LongestAxis;
RectI targetBounds = (RectI)RectD.FromCenterAndSize(newTightBounds.Value.Center, new(biggestAxis)).RoundOutwards();

memberVM.PreviewSurface.Canvas.Save();
memberVM.PreviewSurface.Canvas.Scale(scaling);
memberVM.PreviewSurface.Canvas.Scale()
}

private void UpdateMembersImagePreviews(Dictionary<Guid, AffectedArea> imagePreviewChunks, float scaling, List<IRenderInfo> infos)
{
foreach (var (guid, area) in imagePreviewChunks)
Expand All @@ -148,24 +228,17 @@ private void UpdateMembersImagePreviews(Dictionary<Guid, AffectedArea> imagePrev
continue;
var member = internals.Tracker.Document.FindMemberOrThrow(guid);

memberVM.PreviewSurface.Canvas.Save();
memberVM.PreviewSurface.Canvas.Scale(scaling);
memberVM.PreviewSurface.Canvas.ClipRect((RectD)area.GlobalArea);

if (memberVM is LayerViewModel)
{
var layer = (IReadOnlyLayer)member;
foreach (var chunk in area.Chunks)
{
var pos = chunk * ChunkResolution.Full.PixelSize();
// the full res chunks are already rendered so drawing them again should be fast
if (!layer.LayerImage.DrawMostUpToDateChunkOn
(chunk, ChunkResolution.Full, memberVM.PreviewSurface, pos, SmoothReplacingPaint))
memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize, ClearPaint);
}
UpdateLayerPreviewSurface((IReadOnlyLayer)member, memberVM, area, scaling);
infos.Add(new PreviewDirty_RenderInfo(guid));
}
else if (memberVM is FolderViewModel)
{
memberVM.PreviewSurface.Canvas.Save();
memberVM.PreviewSurface.Canvas.Scale(scaling);
memberVM.PreviewSurface.Canvas.ClipRect((RectD)area.GlobalArea);
var folder = (IReadOnlyFolder)member;
foreach (var chunk in area.Chunks)
{
Expand All @@ -183,9 +256,9 @@ private void UpdateMembersImagePreviews(Dictionary<Guid, AffectedArea> imagePrev
memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkResolution.Full.PixelSize(), ChunkResolution.Full.PixelSize(), ClearPaint);
}
}
memberVM.PreviewSurface.Canvas.Restore();
infos.Add(new PreviewDirty_RenderInfo(guid));
}
memberVM.PreviewSurface.Canvas.Restore();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private static ImageLayer ToSerializable(IReadOnlyLayer layer, IReadOnlyDocument
{
var result = document.GetLayerImage(layer.GuidValue);

var tightBounds = document.GetLayerTightBounds(layer.GuidValue);
var tightBounds = document.GetChunkAlignedLayerBounds(layer.GuidValue);
using var data = result?.DrawingSurface.Snapshot().Encode();
byte[] bytes = data?.AsSpan().ToArray();
var serializable = new ImageLayer
Expand All @@ -130,7 +130,7 @@ private static Mask GetMask(IReadOnlyChunkyImage mask, bool maskVisible)
if (mask == null)
return null;

var maskBound = mask.FindLatestBounds();
var maskBound = mask.FindChunkAlignedMostUpToDateBounds();

if (maskBound == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ public void MarkAsUnsaved()
RectI? memberImageBounds;
try
{
memberImageBounds = layer.LayerImage.FindLatestBounds();
memberImageBounds = layer.LayerImage.FindChunkAlignedMostUpToDateBounds();
}
catch (ObjectDisposedException)
{
Expand Down

0 comments on commit d5d953a

Please sign in to comment.