123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736 |
- #nullable enable
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using ChunkyImageLib;
- using ChunkyImageLib.DataHolders;
- using ChunkyImageLib.Operations;
- using PixiEditor.AvaloniaUI.Helpers;
- using PixiEditor.AvaloniaUI.Models.DocumentModels;
- using PixiEditor.AvaloniaUI.Models.Handlers;
- using PixiEditor.AvaloniaUI.Models.Rendering.RenderInfos;
- using PixiEditor.AvaloniaUI.ViewModels.Document;
- using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
- using PixiEditor.ChangeableDocument.Changeables.Interfaces;
- using PixiEditor.ChangeableDocument.Rendering;
- using PixiEditor.DrawingApi.Core.Numerics;
- using PixiEditor.DrawingApi.Core.Surface;
- using PixiEditor.DrawingApi.Core.Surface.ImageData;
- using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
- using PixiEditor.Numerics;
- namespace PixiEditor.AvaloniaUI.Models.Rendering;
- internal class MemberPreviewUpdater
- {
- private const float smoothingThreshold = 1.5f;
- private readonly IDocument doc;
- private readonly DocumentInternalParts internals;
- private Dictionary<Guid, RectI> lastMainPreviewTightBounds = new();
- private Dictionary<Guid, RectI> lastMaskPreviewTightBounds = new();
- private Dictionary<Guid, AffectedArea> mainPreviewAreasAccumulator = new();
- private Dictionary<Guid, AffectedArea> maskPreviewAreasAccumulator = new();
- private static readonly Paint SmoothReplacingPaint = new()
- {
- BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium, IsAntiAliased = true
- };
- private static readonly Paint ReplacingPaint = new() { BlendMode = BlendMode.Src };
- private static readonly Paint ClearPaint =
- new() { BlendMode = BlendMode.Src, Color = DrawingApi.Core.ColorsImpl.Colors.Transparent };
- public MemberPreviewUpdater(IDocument doc, DocumentInternalParts internals)
- {
- this.doc = doc;
- this.internals = internals;
- }
- /// <summary>
- /// Don't call this outside ActionAccumulator
- /// </summary>
- public async Task<List<IRenderInfo>> UpdateGatheredChunks
- (AffectedAreasGatherer chunkGatherer, bool rerenderPreviews)
- {
- AddAreasToAccumulator(chunkGatherer);
- if (!rerenderPreviews)
- return new List<IRenderInfo>();
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?>? changedMainPreviewBounds = null;
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?>? changedMaskPreviewBounds = null;
- int atFrame = doc.AnimationHandler.ActiveFrameBindable;
- await Task.Run(() =>
- {
- changedMainPreviewBounds = FindChangedTightBounds(atFrame, false);
- changedMaskPreviewBounds = FindChangedTightBounds(atFrame, true);
- }).ConfigureAwait(true);
- RecreatePreviewBitmaps(changedMainPreviewBounds!, changedMaskPreviewBounds!);
- var renderInfos = await Task.Run(() => Render(changedMainPreviewBounds!, changedMaskPreviewBounds))
- .ConfigureAwait(true);
- CleanupUnusedTightBounds();
- foreach (var a in changedMainPreviewBounds)
- {
- if (a.Value is not null)
- lastMainPreviewTightBounds[a.Key] = a.Value.Value.tightBounds;
- else
- lastMainPreviewTightBounds.Remove(a.Key);
- }
- foreach (var a in changedMaskPreviewBounds)
- {
- if (a.Value is not null)
- lastMaskPreviewTightBounds[a.Key] = a.Value.Value.tightBounds;
- else
- lastMaskPreviewTightBounds.Remove(a.Key);
- }
- return renderInfos;
- }
- /// <summary>
- /// Don't call this outside ActionAccumulator
- /// </summary>
- public List<IRenderInfo> UpdateGatheredChunksSync
- (AffectedAreasGatherer chunkGatherer, bool rerenderPreviews)
- {
- AddAreasToAccumulator(chunkGatherer);
- if (!rerenderPreviews)
- return new List<IRenderInfo>();
- int frame = doc.AnimationHandler.ActiveFrameBindable;
- var changedMainPreviewBounds = FindChangedTightBounds(frame, false);
- var changedMaskPreviewBounds = FindChangedTightBounds(frame, true);
- RecreatePreviewBitmaps(changedMainPreviewBounds, changedMaskPreviewBounds);
- var renderInfos = Render(changedMainPreviewBounds, changedMaskPreviewBounds);
- CleanupUnusedTightBounds();
- foreach (var a in changedMainPreviewBounds)
- {
- if (a.Value is not null)
- lastMainPreviewTightBounds[a.Key] = a.Value.Value.tightBounds;
- }
- foreach (var a in changedMaskPreviewBounds)
- {
- if (a.Value is not null)
- lastMaskPreviewTightBounds[a.Key] = a.Value.Value.tightBounds;
- }
- return renderInfos;
- }
- /// <summary>
- /// Cleans up <see cref="lastMainPreviewTightBounds"/> and <see cref="lastMaskPreviewTightBounds"/> to get rid of tight bounds that belonged to now deleted layers
- /// </summary>
- private void CleanupUnusedTightBounds()
- {
- Dictionary<Guid, RectI> clearedLastMainPreviewTightBounds = new Dictionary<Guid, RectI>();
- Dictionary<Guid, RectI> clearedLastMaskPreviewTightBounds = new Dictionary<Guid, RectI>();
- internals.Tracker.Document.ForEveryReadonlyMember(member =>
- {
- if (lastMainPreviewTightBounds.ContainsKey(member.Id))
- clearedLastMainPreviewTightBounds.Add(member.Id, lastMainPreviewTightBounds[member.Id]);
- if (lastMaskPreviewTightBounds.ContainsKey(member.Id))
- clearedLastMaskPreviewTightBounds.Add(member.Id, lastMaskPreviewTightBounds[member.Id]);
- });
- lastMainPreviewTightBounds = clearedLastMainPreviewTightBounds;
- lastMaskPreviewTightBounds = clearedLastMaskPreviewTightBounds;
- }
- /// <summary>
- /// Unions the areas inside <see cref="mainPreviewAreasAccumulator"/> and <see cref="maskPreviewAreasAccumulator"/> with the newly updated areas
- /// </summary>
- private void AddAreasToAccumulator(AffectedAreasGatherer areasGatherer)
- {
- AddAreas(areasGatherer.ImagePreviewAreas, mainPreviewAreasAccumulator);
- AddAreas(areasGatherer.MaskPreviewAreas, maskPreviewAreasAccumulator);
- }
- private static void AddAreas(Dictionary<Guid, AffectedArea> from, Dictionary<Guid, AffectedArea> to)
- {
- foreach ((Guid guid, AffectedArea area) in from)
- {
- if (!to.ContainsKey(guid))
- to[guid] = new AffectedArea();
- var toArea = to[guid];
- toArea.UnionWith(area);
- to[guid] = toArea;
- }
- }
- /// <summary>
- /// Looks at the accumulated areas and determines which members need to have their preview bitmaps resized or deleted
- /// </summary>
- private Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> FindChangedTightBounds(int atFrame, bool forMasks)
- {
- // VecI? == null stands for "layer is empty, the preview needs to be deleted"
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> newPreviewBitmapSizes = new();
- var targetAreas = forMasks ? maskPreviewAreasAccumulator : mainPreviewAreasAccumulator;
- var targetLastBounds = forMasks ? lastMaskPreviewTightBounds : lastMainPreviewTightBounds;
- foreach (var (guid, area) in targetAreas)
- {
- var member = internals.Tracker.Document.FindMember(guid);
- if (member is null)
- continue;
- if (forMasks && member.Mask.Value is null)
- {
- newPreviewBitmapSizes.Add(guid, null);
- continue;
- }
- RectI? tightBounds = GetOrFindMemberTightBounds(member, atFrame, area, forMasks);
- RectI? maybeLastBounds = targetLastBounds.TryGetValue(guid, out RectI lastBounds) ? lastBounds : null;
- if (tightBounds == maybeLastBounds)
- continue;
- if (tightBounds is null)
- {
- newPreviewBitmapSizes.Add(guid, null);
- continue;
- }
- VecI previewSize = StructureHelpers.CalculatePreviewSize(tightBounds.Value.Size);
- newPreviewBitmapSizes.Add(guid, (previewSize, tightBounds.Value));
- }
- return newPreviewBitmapSizes;
- }
- /// <summary>
- /// Recreates the preview bitmaps using the passed sizes (or deletes them when new size is null)
- /// </summary>
- private void RecreatePreviewBitmaps(
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> newPreviewSizes,
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> newMaskSizes)
- {
- // update previews
- foreach (var (guid, newSize) in newPreviewSizes)
- {
- IStructureMemberHandler member = doc.StructureHelper.FindOrThrow(guid);
- if (newSize is null)
- {
- member.PreviewSurface?.Dispose();
- member.PreviewSurface = null;
- }
- else
- {
- if (member.PreviewSurface is not null && member.PreviewSurface.Size.X == newSize.Value.previewSize.X &&
- member.PreviewSurface.Size.Y == newSize.Value.previewSize.Y)
- {
- member.PreviewSurface!.DrawingSurface.Canvas.Clear();
- }
- else
- {
- member.PreviewSurface?.Dispose();
- member.PreviewSurface = new Surface(newSize.Value.previewSize);
- }
- }
- //TODO: Make sure PreviewBitmap implementation raises PropertyChanged
- //member.OnPropertyChanged(nameof(member.PreviewBitmap));
- }
- // update masks
- foreach (var (guid, newSize) in newMaskSizes)
- {
- IStructureMemberHandler member = doc.StructureHelper.FindOrThrow(guid);
- member.MaskPreviewSurface?.Dispose();
- if (newSize is null)
- {
- member.MaskPreviewSurface = null;
- }
- else
- {
- member.MaskPreviewSurface = new Surface(newSize.Value.previewSize); // TODO: premul bgra8888 was here
- }
- //TODO: Make sure MaskPreviewBitmap implementation raises PropertyChanged
- //member.OnPropertyChanged(nameof(member.MaskPreviewBitmap));
- }
- }
- /// <summary>
- /// Returns the previosly known committed tight bounds if there are no reasons to believe they have changed (based on the passed <paramref name="currentlyAffectedArea"/>).
- /// Otherwise, calculates the new bounds via <see cref="FindLayerTightBounds"/> and returns them.
- /// </summary>
- private RectI? GetOrFindMemberTightBounds(IReadOnlyStructureNode member, int atFrame,
- AffectedArea currentlyAffectedArea, bool forMask)
- {
- if (forMask && member.Mask.Value is null)
- throw new InvalidOperationException();
- RectI? prevTightBounds = null;
- var targetLastCollection = forMask ? lastMaskPreviewTightBounds : lastMainPreviewTightBounds;
- if (targetLastCollection.TryGetValue(member.Id, out RectI tightBounds))
- prevTightBounds = tightBounds;
- if (prevTightBounds is not null && currentlyAffectedArea.GlobalArea is not null &&
- prevTightBounds.Value.ContainsExclusive(currentlyAffectedArea.GlobalArea.Value))
- {
- // if the affected area is fully inside the previous tight bounds, the tight bounds couldn't possibly have changed
- return prevTightBounds.Value;
- }
- return member switch
- {
- IReadOnlyLayerNode layer => FindLayerTightBounds(layer, atFrame, forMask),
- IReadOnlyFolderNode folder => FindFolderTightBounds(folder, atFrame, forMask),
- _ => throw new ArgumentOutOfRangeException()
- };
- }
- /// <summary>
- /// Finds the current committed tight bounds for a layer.
- /// </summary>
- private RectI? FindLayerTightBounds(IReadOnlyLayerNode layer, int frame, bool forMask)
- {
- if (layer.Mask.Value is null && forMask)
- throw new InvalidOperationException();
- if (layer.Mask.Value is not null && forMask)
- return FindImageTightBoundsFast(layer.Mask.Value);
- if (layer is IReadOnlyImageNode raster)
- {
- return FindImageTightBoundsFast(raster.GetLayerImageAtFrame(frame));
- }
- return layer.GetTightBounds(frame);
- }
- /// <summary>
- /// Finds the current committed tight bounds for a folder recursively.
- /// </summary>
- private RectI? FindFolderTightBounds(IReadOnlyFolderNode folder, int frame, bool forMask)
- {
- if (forMask)
- {
- if (folder.Mask.Value is null)
- throw new InvalidOperationException();
- return FindImageTightBoundsFast(folder.Mask.Value);
- }
- /*RectI? combinedBounds = null;
- foreach (var child in folder.Children)
- {
- RectI? curBounds = null;
- if (child is IReadOnlyLayerNode childLayer)
- curBounds = FindLayerTightBounds(childLayer, frame, false);
- else if (child is IReadOnlyFolderNode childFolder)
- curBounds = FindFolderTightBounds(childFolder, frame, false);
- if (combinedBounds is null)
- combinedBounds = curBounds;
- else if (curBounds is not null)
- combinedBounds = combinedBounds.Value.Union(curBounds.Value);
- }
- return combinedBounds;*/
- return folder.GetTightBounds(frame);
- }
- /// <summary>
- /// Finds the current committed tight bounds for an image in a reasonably efficient way.
- /// Looks at the low-res chunks for large images, meaning the resulting bounds aren't 100% precise.
- /// </summary>
- private RectI? FindImageTightBoundsFast(IReadOnlyChunkyImage targetImage)
- {
- RectI? bounds = targetImage.FindChunkAlignedCommittedBounds();
- if (bounds is null)
- return null;
- int biggest = bounds.Value.Size.LongestAxis;
- ChunkResolution resolution = biggest switch
- {
- > ChunkyImage.FullChunkSize * 9 => ChunkResolution.Eighth,
- > ChunkyImage.FullChunkSize * 5 => ChunkResolution.Quarter,
- > ChunkyImage.FullChunkSize * 3 => ChunkResolution.Half,
- _ => ChunkResolution.Full,
- };
- return targetImage.FindTightCommittedBounds(resolution);
- }
- /// <summary>
- /// Re-renders changed chunks using <see cref="mainPreviewAreasAccumulator"/> and <see cref="maskPreviewAreasAccumulator"/> along with the passed lists of bitmaps that need full re-render.
- /// </summary>
- private List<IRenderInfo> Render(
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> recreatedMainPreviewSizes,
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> recreatedMaskPreviewSizes)
- {
- List<IRenderInfo> infos = new();
- var (mainPreviewChunksToRerender, maskPreviewChunksToRerender) = GetChunksToRerenderAndResetAccumulator();
- RenderWholeCanvasPreview(mainPreviewChunksToRerender, maskPreviewChunksToRerender, infos);
- RenderMainPreviews(mainPreviewChunksToRerender, recreatedMainPreviewSizes, infos);
- RenderMaskPreviews(maskPreviewChunksToRerender, recreatedMaskPreviewSizes, infos);
- RenderNodePreviews(infos);
- return infos;
- // asynchronously re-render changed chunks (where tight bounds didn't change) or the whole preview image (where the tight bounds did change)
- // don't forget to get rid of the bitmap recreation code in DocumentUpdater
- }
- private (Dictionary<Guid, AffectedArea> main, Dictionary<Guid, AffectedArea> mask)
- GetChunksToRerenderAndResetAccumulator()
- {
- var result = (mainPreviewPostponedChunks: mainPreviewAreasAccumulator,
- maskPreviewPostponedChunks: maskPreviewAreasAccumulator);
- mainPreviewAreasAccumulator = new();
- maskPreviewAreasAccumulator = new();
- return result;
- }
- /// <summary>
- /// Re-renders the preview of the whole canvas which is shown as the tab icon
- /// </summary>
- private void RenderWholeCanvasPreview(Dictionary<Guid, AffectedArea> mainPreviewChunks,
- Dictionary<Guid, AffectedArea> maskPreviewChunks, List<IRenderInfo> infos)
- {
- var cumulative = mainPreviewChunks
- .Concat(maskPreviewChunks)
- .Aggregate(new AffectedArea(), (set, pair) =>
- {
- set.UnionWith(pair.Value);
- return set;
- });
- if (cumulative.GlobalArea is null)
- return;
- var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
- float scaling = (float)previewSize.X / doc.SizeBindable.X;
- bool somethingChanged = false;
- foreach (var chunkPos in cumulative.Chunks)
- {
- somethingChanged = true;
- ChunkResolution resolution = scaling switch
- {
- > 1 / 2f => ChunkResolution.Full,
- > 1 / 4f => ChunkResolution.Half,
- > 1 / 8f => ChunkResolution.Quarter,
- _ => ChunkResolution.Eighth,
- };
- var pos = chunkPos * resolution.PixelSize();
- var rendered = doc.Renderer.RenderChunk(chunkPos, resolution, doc.AnimationHandler.ActiveFrameTime);
- doc.PreviewSurface.DrawingSurface.Canvas.Save();
- doc.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
- doc.PreviewSurface.DrawingSurface.Canvas.ClipRect((RectD)cumulative.GlobalArea);
- doc.PreviewSurface.DrawingSurface.Canvas.Scale(1 / (float)resolution.Multiplier());
- if (rendered.IsT1)
- {
- doc.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(),
- resolution.PixelSize(), ClearPaint);
- }
- else if (rendered.IsT0)
- {
- using var renderedChunk = rendered.AsT0;
- renderedChunk.DrawChunkOn(doc.PreviewSurface.DrawingSurface, pos, SmoothReplacingPaint);
- }
- doc.PreviewSurface.DrawingSurface.Canvas.Restore();
- }
- if (somethingChanged)
- infos.Add(new CanvasPreviewDirty_RenderInfo());
- }
- private void RenderMainPreviews(
- Dictionary<Guid, AffectedArea> mainPreviewChunks,
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> recreatedPreviewSizes,
- List<IRenderInfo> infos)
- {
- foreach (var guid in mainPreviewChunks.Select(a => a.Key).Concat(recreatedPreviewSizes.Select(a => a.Key)))
- {
- // find the true affected area
- AffectedArea? affArea = null;
- RectI? tightBounds = null;
- if (mainPreviewChunks.TryGetValue(guid, out AffectedArea areaFromChunks))
- affArea = areaFromChunks;
- if (recreatedPreviewSizes.TryGetValue(guid, out (VecI _, RectI tightBounds)? value))
- {
- if (value is null)
- continue;
- tightBounds = value.Value.tightBounds;
- affArea = new AffectedArea(
- OperationHelper.FindChunksTouchingRectangle(value.Value.tightBounds, ChunkyImage.FullChunkSize),
- value.Value.tightBounds);
- }
- if (affArea is null || affArea.Value.GlobalArea is null ||
- affArea.Value.GlobalArea.Value.IsZeroOrNegativeArea)
- continue;
- // re-render the area
- var memberVM = doc.StructureHelper.Find(guid);
- if (memberVM is null || memberVM.PreviewSurface is null)
- continue;
- if (tightBounds is null)
- tightBounds = lastMainPreviewTightBounds[guid];
- var member = internals.Tracker.Document.FindMemberOrThrow(guid);
- var previewSize = StructureHelpers.CalculatePreviewSize(tightBounds.Value.Size);
- float scaling = (float)previewSize.X / tightBounds.Value.Width;
- VecI position = tightBounds.Value.Pos;
- if (memberVM is ILayerHandler)
- {
- RenderLayerMainPreview((IReadOnlyLayerNode)member, memberVM, affArea.Value, position, scaling);
- if (doc.AnimationHandler.FindKeyFrame(guid, out IKeyFrameHandler? keyFrame))
- {
- if (keyFrame is IKeyFrameGroupHandler group)
- {
- foreach (var child in group.Children)
- {
- if (member is IReadOnlyImageNode rasterLayer)
- {
- RenderAnimationFramePreview(rasterLayer, child, affArea.Value);
- }
- }
- }
- }
- infos.Add(new PreviewDirty_RenderInfo(guid));
- }
- else if (memberVM is IFolderHandler)
- {
- RenderFolderMainPreview((IReadOnlyFolderNode)member, memberVM, affArea.Value, position, scaling);
- infos.Add(new PreviewDirty_RenderInfo(guid));
- }
- else
- {
- throw new ArgumentOutOfRangeException();
- }
- }
- }
- /// <summary>
- /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> folder
- /// </summary>
- private void RenderFolderMainPreview(IReadOnlyFolderNode folder, IStructureMemberHandler memberVM,
- AffectedArea area,
- VecI position, float scaling)
- {
- memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
- memberVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
- memberVM.PreviewSurface.DrawingSurface.Canvas.Translate(-position);
- memberVM.PreviewSurface.DrawingSurface.Canvas.ClipRect((RectD)area.GlobalArea);
- foreach (var chunk in area.Chunks)
- {
- var pos = chunk * ChunkResolution.Full.PixelSize();
- // drawing in full res here is kinda slow
- // we could switch to a lower resolution based on (canvas size / preview size) to make it run faster
- var contentNode = folder.Content.Connection?.Node;
- OneOf<Chunk, EmptyChunk> rendered;
- if (contentNode is null)
- {
- rendered = new EmptyChunk();
- }
- else
- {
- rendered = doc.Renderer.RenderChunk(chunk, ChunkResolution.Full, contentNode, doc.AnimationHandler.ActiveFrameBindable);
- }
-
- if (rendered.IsT0)
- {
- memberVM.PreviewSurface.DrawingSurface.Canvas.DrawSurface(rendered.AsT0.Surface.DrawingSurface, pos,
- scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint);
- rendered.AsT0.Dispose();
- }
- else
- {
- memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkResolution.Full.PixelSize(),
- ChunkResolution.Full.PixelSize(), ClearPaint);
- }
- }
- memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
- }
- /// <summary>
- /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> layer
- /// </summary>
- private void RenderLayerMainPreview(IReadOnlyLayerNode layer, IStructureMemberHandler memberVM, AffectedArea area,
- VecI position, float scaling)
- {
- memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
- memberVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
- memberVM.PreviewSurface.DrawingSurface.Canvas.Translate(-position);
- memberVM.PreviewSurface.DrawingSurface.Canvas.ClipRect((RectD)area.GlobalArea);
- foreach (var chunk in area.Chunks)
- {
- var pos = chunk * ChunkResolution.Full.PixelSize();
- if (layer is not IReadOnlyImageNode raster) return;
- IReadOnlyChunkyImage? result = raster.GetLayerImageAtFrame(doc.AnimationHandler.ActiveFrameBindable);
- if (!result.DrawCommittedChunkOn(
- chunk,
- ChunkResolution.Full, memberVM.PreviewSurface.DrawingSurface, pos,
- scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint))
- {
- memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize,
- ChunkyImage.FullChunkSize, ClearPaint);
- }
- }
- memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
- }
- private void RenderAnimationFramePreview(IReadOnlyImageNode node, IKeyFrameHandler keyFrameVM, AffectedArea area)
- {
- if (keyFrameVM.PreviewSurface is null)
- {
- keyFrameVM.PreviewSurface =
- new Surface(StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size));
- }
- keyFrameVM.PreviewSurface!.DrawingSurface.Canvas.Save();
- float scaling = (float)keyFrameVM.PreviewSurface.Size.X / internals.Tracker.Document.Size.X;
- keyFrameVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
- foreach (var chunk in area.Chunks)
- {
- var pos = chunk * ChunkResolution.Full.PixelSize();
- if (!node.GetLayerImageByKeyFrameGuid(keyFrameVM.Id).DrawCommittedChunkOn(chunk, ChunkResolution.Full,
- keyFrameVM.PreviewSurface!.DrawingSurface, pos, ReplacingPaint))
- {
- keyFrameVM.PreviewSurface!.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize,
- ChunkyImage.FullChunkSize, ClearPaint);
- }
- }
- keyFrameVM.PreviewSurface!.DrawingSurface.Canvas.Restore();
- }
- private void RenderMaskPreviews(
- Dictionary<Guid, AffectedArea> maskPreviewChunks,
- Dictionary<Guid, (VecI previewSize, RectI tightBounds)?> recreatedMaskSizes,
- List<IRenderInfo> infos)
- {
- foreach (Guid guid in maskPreviewChunks.Select(a => a.Key).Concat(recreatedMaskSizes.Select(a => a.Key)))
- {
- // find the true affected area
- AffectedArea? affArea = null;
- RectI? tightBounds = null;
- if (maskPreviewChunks.TryGetValue(guid, out AffectedArea areaFromChunks))
- affArea = areaFromChunks;
- if (recreatedMaskSizes.TryGetValue(guid, out (VecI _, RectI tightBounds)? value))
- {
- if (value is null)
- continue;
- tightBounds = value.Value.tightBounds;
- affArea = new AffectedArea(
- OperationHelper.FindChunksTouchingRectangle(value.Value.tightBounds, ChunkyImage.FullChunkSize),
- value.Value.tightBounds);
- }
- if (affArea is null || affArea.Value.GlobalArea is null ||
- affArea.Value.GlobalArea.Value.IsZeroOrNegativeArea)
- continue;
- // re-render the area
- var memberVM = doc.StructureHelper.Find(guid);
- if (memberVM is null || !memberVM.HasMaskBindable || memberVM.MaskPreviewSurface is null)
- continue;
- if (tightBounds is null)
- tightBounds = lastMaskPreviewTightBounds[guid];
- var previewSize = StructureHelpers.CalculatePreviewSize(tightBounds.Value.Size);
- float scaling = (float)previewSize.X / tightBounds.Value.Width;
- VecI position = tightBounds.Value.Pos;
- var member = internals.Tracker.Document.FindMemberOrThrow(guid);
- memberVM.MaskPreviewSurface!.DrawingSurface.Canvas.Save();
- memberVM.MaskPreviewSurface.DrawingSurface.Canvas.Scale(scaling);
- memberVM.MaskPreviewSurface.DrawingSurface.Canvas.Translate(-position);
- memberVM.MaskPreviewSurface.DrawingSurface.Canvas.ClipRect((RectD)affArea.Value.GlobalArea);
- foreach (var chunk in affArea.Value.Chunks)
- {
- var pos = chunk * ChunkResolution.Full.PixelSize();
- member.Mask!.Value.DrawMostUpToDateChunkOn
- (chunk, ChunkResolution.Full, memberVM.MaskPreviewSurface.DrawingSurface, pos,
- scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint);
- }
- memberVM.MaskPreviewSurface.DrawingSurface.Canvas.Restore();
- infos.Add(new MaskPreviewDirty_RenderInfo(guid));
- }
- }
- private void RenderNodePreviews(List<IRenderInfo> infos)
- {
- internals.Tracker.Document.NodeGraph.TryTraverse((node) =>
- {
- if (node is null)
- return;
- if (node.CachedResult == null)
- {
- return;
- }
- var nodeVm = doc.StructureHelper.FindNode<INodeHandler>(node.Id);
- if (nodeVm == null)
- {
- return;
- }
-
- if (nodeVm.ResultPreview == null)
- {
- nodeVm.ResultPreview =
- new Surface(StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size));
- }
- float scalingX = (float)nodeVm.ResultPreview.Size.X / node.CachedResult.Size.X;
- float scalingY = (float)nodeVm.ResultPreview.Size.Y / node.CachedResult.Size.Y;
- nodeVm.ResultPreview.DrawingSurface.Canvas.Save();
- nodeVm.ResultPreview.DrawingSurface.Canvas.Scale(scalingX, scalingY);
- RectI region = new RectI(0, 0, node.CachedResult.Size.X, node.CachedResult.Size.Y);
-
- nodeVm.ResultPreview.DrawingSurface.Canvas.DrawSurface(node.CachedResult.DrawingSurface, 0, 0, ReplacingPaint);
- nodeVm.ResultPreview.DrawingSurface.Canvas.Restore();
- infos.Add(new NodePreviewDirty_RenderInfo(node.Id));
- });
- }
- }
|