From 2c2bb05fd9282357d0c39d973831cd4228e48162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Krysi=C5=84ski?= Date: Sun, 1 Jun 2025 21:53:27 +0200 Subject: [PATCH] Optimizations and rendering improvements --- .../Changeables/Document.cs | 7 +- .../Changeables/Graph/NodeGraph.cs | 16 +++ .../Changeables/Graph/Nodes/LayerNode.cs | 16 +-- .../Graph/Nodes/VectorLayerNode.cs | 22 +--- .../DocumentModels/ActionAccumulator.cs | 6 +- .../Models/DocumentModels/DocumentUpdater.cs | 8 +- .../Public/DocumentStructureModule.cs | 6 +- .../Models/Handlers/INodeGraphHandler.cs | 1 + .../Models/Rendering/AffectedAreasGatherer.cs | 117 ++++++++---------- .../Models/Rendering/MemberPreviewUpdater.cs | 31 ++--- .../Document/AnimationDataViewModel.cs | 3 + .../ViewModels/Document/NodeGraphViewModel.cs | 7 +- .../SubViewModels/DiscordViewModel.cs | 9 ++ tests/PixiEditor.Tests/RenderTests.cs | 5 + .../RenderTests/BlendingLinearSrgb.pixi | Bin 0 -> 19268 bytes .../RenderTests/BlendingLinearSrgb.png | Bin 0 -> 3487 bytes .../TestFiles/RenderTests/BlendingSrgb.pixi | Bin 0 -> 19268 bytes .../TestFiles/RenderTests/BlendingSrgb.png | Bin 0 -> 3124 bytes .../VectorRectangleClippedToCircle.png | Bin 361 -> 355 bytes .../VectorRectangleClippedToCircleMasked.png | Bin 420 -> 429 bytes ...orRectangleClippedToCircleShadowFilter.png | Bin 416 -> 415 bytes .../RenderTests/VectorWithSepiaFilter.pixi | Bin 0 -> 8370 bytes .../RenderTests/VectorWithSepiaFilter.png | Bin 0 -> 3203 bytes .../VectorWithSepiaFilterChained.pixi | Bin 0 -> 9926 bytes .../VectorWithSepiaFilterChained.png | Bin 0 -> 3984 bytes .../VectorWithSepiaFilterSrgb.pixi | Bin 0 -> 8275 bytes .../RenderTests/VectorWithSepiaFilterSrgb.png | Bin 0 -> 3209 bytes 27 files changed, 132 insertions(+), 122 deletions(-) create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.pixi create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.png create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.pixi create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.png create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.pixi create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.png create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.pixi create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.png create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterSrgb.pixi create mode 100644 tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterSrgb.png diff --git a/src/PixiEditor.ChangeableDocument/Changeables/Document.cs b/src/PixiEditor.ChangeableDocument/Changeables/Document.cs index 3ee9853bc..ca3e121c9 100644 --- a/src/PixiEditor.ChangeableDocument/Changeables/Document.cs +++ b/src/PixiEditor.ChangeableDocument/Changeables/Document.cs @@ -50,6 +50,7 @@ internal class Document : IChangeable, IReadOnlyDocument public double VerticalSymmetryAxisX { get; set; } public bool IsDisposed { get; private set; } + public Document() { AnimationData = new AnimationData(this); @@ -279,14 +280,14 @@ internal class Document : IChangeable, IReadOnlyDocument /// The node with the given or null if it doesn't exist. public Node? FindNode(Guid guid) { - return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid); + return NodeGraph.FindNode(guid); } IReadOnlyNode IReadOnlyDocument.FindNode(Guid guid) => FindNodeOrThrow(guid); public T? FindNode(Guid guid) where T : Node { - return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid && x is T) as T; + return NodeGraph.FindNode(guid); } /// @@ -298,7 +299,7 @@ internal class Document : IChangeable, IReadOnlyDocument /// True if the node could be found, otherwise false. public bool TryFindNode(Guid id, out T node) where T : Node { - node = (T?)NodeGraph.Nodes.FirstOrDefault(x => x.Id == id && x is T) ?? default; + node = (T?)NodeGraph.FindNode(id) ?? null; return node != null; } diff --git a/src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs b/src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs index 02683023a..8c631e502 100644 --- a/src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs +++ b/src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs @@ -11,12 +11,16 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable private readonly List _nodes = new(); public IReadOnlyCollection Nodes => _nodes; + public IReadOnlyDictionary NodeLookup => nodeLookup; public Node? OutputNode => CustomOutputNode ?? Nodes.OfType().FirstOrDefault(); public Node? CustomOutputNode { get; set; } + private Dictionary nodeLookup = new(); + IReadOnlyCollection IReadOnlyNodeGraph.AllNodes => Nodes; IReadOnlyNode IReadOnlyNodeGraph.OutputNode => OutputNode; + public void AddNode(Node node) { if (Nodes.Contains(node)) @@ -26,6 +30,7 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable node.ConnectionsChanged += ResetCache; _nodes.Add(node); + nodeLookup[node.Id] = node; ResetCache(); } @@ -38,9 +43,20 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable node.ConnectionsChanged -= ResetCache; _nodes.Remove(node); + nodeLookup.Remove(node.Id); ResetCache(); } + public Node? FindNode(Guid guid) + { + return nodeLookup.GetValueOrDefault(guid); + } + + public T? FindNode(Guid guid) where T : Node + { + return nodeLookup.TryGetValue(guid, out Node? node) && node is T typedNode ? typedNode : null; + } + public Queue CalculateExecutionQueue(IReadOnlyNode outputNode) { return new Queue(CalculateExecutionQueueInternal(outputNode)); diff --git a/src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs b/src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs index 1656716a9..b080cfbb4 100644 --- a/src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs +++ b/src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs @@ -171,12 +171,11 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource bool useFilters, Paint paint) { paint.Color = paint.Color.WithAlpha((byte)Math.Round(Opacity.Value * ctx.Opacity * 255)); - var finalPaint = paint; var targetSurface = workingSurface; Texture? tex = null; int saved = -1; - if (!ctx.ProcessingColorSpace.IsSrgb) + if (!ctx.ProcessingColorSpace.IsSrgb && useFilters && Filters.Value != null) { saved = workingSurface.Canvas.Save(); @@ -185,29 +184,22 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource workingSurface.Canvas.SetMatrix(Matrix3X3.Identity); targetSurface = tex.DrawingSurface; - - finalPaint = new Paint(); } if (useFilters && Filters.Value != null) { paint.SetFilters(Filters.Value); - DrawWithFilters(ctx, targetSurface, finalPaint); + DrawWithFilters(ctx, targetSurface, paint); } else { paint.SetFilters(null); - DrawWithoutFilters(ctx, targetSurface, finalPaint); - } - - if (finalPaint != paint) - { - finalPaint.Dispose(); + DrawWithoutFilters(ctx, targetSurface, paint); } if (targetSurface != workingSurface) { - workingSurface.Canvas.DrawSurface(targetSurface, 0, 0, paint); + workingSurface.Canvas.DrawSurface(targetSurface, 0, 0); tex.Dispose(); workingSurface.Canvas.RestoreToCount(saved); } diff --git a/src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs b/src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs index 69b07852b..f84d2e261 100644 --- a/src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs +++ b/src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs @@ -50,7 +50,9 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN IReadOnlyShapeVectorData IReadOnlyVectorNode.ShapeData => RenderableShapeData; - public override VecD GetScenePosition(KeyFrameTime time) => RenderableShapeData?.TransformedAABB.Center ?? VecD.Zero; + public override VecD GetScenePosition(KeyFrameTime time) => + RenderableShapeData?.TransformedAABB.Center ?? VecD.Zero; + public override VecD GetSceneSize(KeyFrameTime time) => RenderableShapeData?.TransformedAABB.Size ?? VecD.Zero; public VectorLayerNode() @@ -130,21 +132,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN return false; } - Matrix3X3 matrix = RenderableShapeData.TransformationMatrix; - - if (!context.ProcessingColorSpace.IsSrgb) - { - int saved = renderOn.Canvas.Save(); - using Texture tex = Texture.ForProcessing(renderOn, ColorSpace.CreateSrgb()); - renderOn.Canvas.SetMatrix(Matrix3X3.Identity); - Rasterize(tex.DrawingSurface, paint); - renderOn.Canvas.DrawSurface(tex.DrawingSurface, 0, 0); - renderOn.Canvas.RestoreToCount(saved); - } - else - { - Rasterize(renderOn, paint); - } + Rasterize(renderOn, paint); return true; } @@ -222,7 +210,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN return; } - if(EmbeddedShapeData is IScalable resizable) + if (EmbeddedShapeData is IScalable resizable) { resizable.Resize(multiplier); } diff --git a/src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs b/src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs index d9e0d60af..7fd6952fa 100644 --- a/src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs +++ b/src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs @@ -121,6 +121,7 @@ internal class ActionAccumulator toExecute.Any(static action => action.action is RefreshViewport_PassthroughAction); bool changeFrameRequest = toExecute.Any(static action => action.action is SetActiveFrame_PassthroughAction); + foreach (IChangeInfo info in optimizedChanges) { internals.Updater.ApplyChangeFromChangeInfo(info); @@ -129,7 +130,6 @@ internal class ActionAccumulator if (undoBoundaryPassed) internals.Updater.AfterUndoBoundaryPassed(); - // update the contents of the bitmaps var affectedAreas = new AffectedAreasGatherer(document.AnimationHandler.ActiveFrameTime, internals.Tracker, optimizedChanges); @@ -145,8 +145,8 @@ internal class ActionAccumulator }*/ previewUpdater.UpdatePreviews( - affectedAreas.ImagePreviewAreas.Keys, - affectedAreas.MaskPreviewAreas.Keys, + affectedAreas.ChangedMembers, + affectedAreas.ChangedMasks, affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames); // force refresh viewports for better responsiveness diff --git a/src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs b/src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs index cf2a2048c..052956bc3 100644 --- a/src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs +++ b/src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs @@ -395,7 +395,9 @@ internal class DocumentUpdater IStructureMemberHandler memberVM; if (info is CreateLayer_ChangeInfo layerInfo) { - memberVM = doc.NodeGraphHandler.AllNodes.FirstOrDefault(x => x.Id == info.Id) as ILayerHandler; + memberVM = doc.NodeGraphHandler.NodeLookup.TryGetValue(layerInfo.Id, out var node) + ? node as IStructureMemberHandler + : null; if (memberVM is ITransparencyLockableMember transparencyLockableMember) { transparencyLockableMember.SetLockTransparency(layerInfo.LockTransparency); @@ -403,7 +405,9 @@ internal class DocumentUpdater } else if (info is CreateFolder_ChangeInfo) { - memberVM = doc.NodeGraphHandler.AllNodes.FirstOrDefault(x => x.Id == info.Id) as IFolderHandler; + memberVM = doc.NodeGraphHandler.NodeLookup.TryGetValue(info.Id, out var node) + ? node as IFolderHandler + : null; } else { diff --git a/src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs b/src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs index f3eff36a5..8154cadb1 100644 --- a/src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs +++ b/src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs @@ -1,6 +1,4 @@ -using System.Collections; -using System.Collections.Generic; -using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes; +using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes; using PixiEditor.Models.Handlers; namespace PixiEditor.Models.DocumentModels.Public; @@ -139,7 +137,7 @@ internal class DocumentStructureModule /// Returns all layers in the document. /// /// List of ILayerHandlers. Empty if no layers found. - public List GetAllLayers(bool includeFoldersWithMask = false) + public List GetAllLayers() { List layers = new List(); diff --git a/src/PixiEditor/Models/Handlers/INodeGraphHandler.cs b/src/PixiEditor/Models/Handlers/INodeGraphHandler.cs index 8fde140e2..85cf2425a 100644 --- a/src/PixiEditor/Models/Handlers/INodeGraphHandler.cs +++ b/src/PixiEditor/Models/Handlers/INodeGraphHandler.cs @@ -22,4 +22,5 @@ internal interface INodeGraphHandler public void RemoveConnections(Guid nodeId); public void UpdateAvailableRenderOutputs(); public void RequestUpdateComputedPropertyValue(INodePropertyHandler property); + public IReadOnlyDictionary NodeLookup { get; } } diff --git a/src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs b/src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs index e3d9199cd..5368ddf9e 100644 --- a/src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs +++ b/src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using ChunkyImageLib; using ChunkyImageLib.DataHolders; using ChunkyImageLib.Operations; @@ -28,13 +29,15 @@ internal class AffectedAreasGatherer private readonly DocumentChangeTracker tracker; public AffectedArea MainImageArea { get; private set; } = new(); - public Dictionary ImagePreviewAreas { get; private set; } = new(); - public Dictionary MaskPreviewAreas { get; private set; } = new(); - public List ChangedKeyFrames { get; private set; } = new(); - + public HashSet ChangedMembers { get; private set; } = new(); + public HashSet ChangedMasks { get; private set; } = new(); + public HashSet ChangedKeyFrames { get; private set; } = new(); + private KeyFrameTime ActiveFrame { get; set; } - public List ChangedNodes { get; set; } = new(); + public HashSet ChangedNodes { get; set; } = new(); + + private bool alreadyAddedWholeCanvasToEveryImagePreview = false; public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker, IReadOnlyList changes) @@ -42,11 +45,11 @@ internal class AffectedAreasGatherer this.tracker = tracker; ActiveFrame = activeFrame; ProcessChanges(changes); - + var outputNode = tracker.Document.NodeGraph.OutputNode; if (outputNode is null) return; - + if (tracker.Document.NodeGraph.CalculateExecutionQueue(tracker.Document.NodeGraph.OutputNode) .Any(x => x is ICustomShaderNode)) { @@ -64,20 +67,20 @@ internal class AffectedAreasGatherer if (info.Area.Chunks is null) throw new InvalidOperationException("Chunks must not be null"); AddToMainImage(info.Area); - AddToImagePreviews(info.Id, info.Area, true); - AddToMaskPreview(info.Id, info.Area); + AddToImagePreviews(info.Id, true); + AddToMaskPreview(info.Id); AddToNodePreviews(info.Id); break; case LayerImageArea_ChangeInfo info: if (info.Area.Chunks is null) throw new InvalidOperationException("Chunks must not be null"); AddToMainImage(info.Area); - AddToImagePreviews(info.Id, info.Area); + AddToImagePreviews(info.Id); AddToNodePreviews(info.Id); break; case TransformObject_ChangeInfo info: AddToMainImage(info.Area); - AddToImagePreviews(info.NodeGuid, info.Area); + AddToImagePreviews(info.NodeGuid); AddToNodePreviews(info.NodeGuid); break; case CreateStructureMember_ChangeInfo info: @@ -104,7 +107,6 @@ internal class AffectedAreasGatherer break; case StructureMemberMask_ChangeInfo info: AddWholeCanvasToMainImage(); - AddWholeCanvasToMaskPreview(info.Id); AddWholeCanvasToImagePreviews(info.Id, true); AddToNodePreviews(info.Id); break; @@ -172,7 +174,7 @@ internal class AffectedAreasGatherer { AddToNodePreviews(info.OutputNodeId.Value); } - + break; case PropertyValueUpdated_ChangeInfo info: AddWholeCanvasToMainImage(); @@ -187,12 +189,13 @@ internal class AffectedAreasGatherer break; case VectorShape_ChangeInfo info: AddToMainImage(info.Affected); - AddToImagePreviews(info.LayerId, info.Affected); + AddToImagePreviews(info.LayerId); AddToNodePreviews(info.LayerId); break; case ProcessingColorSpace_ChangeInfo: AddWholeCanvasToMainImage(); AddWholeCanvasToEveryImagePreview(); + AddWholeCanvasToEveryMaskPreview(); break; } } @@ -200,20 +203,20 @@ internal class AffectedAreasGatherer private void AddKeyFrame(Guid infoKeyFrameId) { - ChangedKeyFrames ??= new List(); + ChangedKeyFrames ??= new HashSet(); if (!ChangedKeyFrames.Contains(infoKeyFrameId)) ChangedKeyFrames.Add(infoKeyFrameId); } private void AddToNodePreviews(Guid nodeId) { - ChangedNodes ??= new List(); + ChangedNodes ??= new HashSet(); if (!ChangedNodes.Contains(nodeId)) { ChangedNodes.Add(nodeId); } } - + private void AddAllNodesToImagePreviews() { foreach (var node in tracker.Document.NodeGraph.AllNodes) @@ -234,8 +237,7 @@ internal class AffectedAreasGatherer return; } - var chunks = result.FindAllChunks(); - AddToImagePreviews(memberGuid, new AffectedArea(chunks), ignoreSelf); + AddToImagePreviews(member, ignoreSelf); } else if (member is IReadOnlyFolderNode folder) { @@ -248,9 +250,7 @@ internal class AffectedAreasGatherer var tightBounds = genericLayerNode.GetTightBounds(frame); if (tightBounds is not null) { - var affectedArea = new AffectedArea( - OperationHelper.FindChunksTouchingRectangle((RectI)tightBounds.Value, ChunkyImage.FullChunkSize)); - AddToImagePreviews(memberGuid, affectedArea, ignoreSelf); + AddToImagePreviews(member, ignoreSelf); } else { @@ -283,9 +283,9 @@ internal class AffectedAreasGatherer { var affectedArea = new AffectedArea( OperationHelper.FindChunksTouchingRectangle((RectI)tightBounds.Value, ChunkyImage.FullChunkSize)); - + var lastArea = new AffectedArea(affectedArea); - + AddToMainImage(affectedArea); } else @@ -312,7 +312,7 @@ internal class AffectedAreasGatherer if (member.EmbeddedMask is not null) { var chunks = member.EmbeddedMask.FindAllChunks(); - AddToMaskPreview(memberGuid, new AffectedArea(chunks)); + AddToMaskPreview(memberGuid); } if (member is IReadOnlyFolderNode folder) @@ -330,39 +330,34 @@ internal class AffectedAreasGatherer MainImageArea = temp; } - private void AddToImagePreviews(Guid memberGuid, AffectedArea area, bool ignoreSelf = false) + private void AddToImagePreviews(Guid memberGuid, bool ignoreSelf = false) { - var path = tracker.Document.GetParents(memberGuid); - path.Insert(0, tracker.Document.FindMember(memberGuid)); + var sourceMember = tracker.Document.FindMember(memberGuid); + if (sourceMember is null) + { + // If the member is not found, we cannot add it to previews + return; + } + + AddToImagePreviews(sourceMember, ignoreSelf); + } + + private void AddToImagePreviews(IReadOnlyStructureNode sourceMember, bool ignoreSelf) + { + var path = tracker.Document.GetParents(sourceMember.Id); + path.Insert(0, sourceMember); for (int i = ignoreSelf ? 1 : 0; i < path.Count; i++) { var member = path[i]; - if(member == null) continue; - if (!ImagePreviewAreas.ContainsKey(member.Id)) - { - ImagePreviewAreas[member.Id] = new AffectedArea(area); - } - else - { - var temp = ImagePreviewAreas[member.Id]; - temp.UnionWith(area); - ImagePreviewAreas[member.Id] = temp; - } + if (member == null) continue; + + ChangedMembers.Add(member.Id); } } - private void AddToMaskPreview(Guid memberGuid, AffectedArea area) + private void AddToMaskPreview(Guid memberGuid) { - if (!MaskPreviewAreas.ContainsKey(memberGuid)) - { - MaskPreviewAreas[memberGuid] = new AffectedArea(area); - } - else - { - var temp = MaskPreviewAreas[memberGuid]; - temp.UnionWith(area); - MaskPreviewAreas[memberGuid] = temp; - } + ChangedMasks.Add(memberGuid); } @@ -380,23 +375,19 @@ internal class AffectedAreasGatherer for (int i = ignoreSelf ? 1 : 0; i < path.Count; i++) { var member = path[i]; - if (!ImagePreviewAreas.ContainsKey(member.Id)) - ImagePreviewAreas[member.Id] = new AffectedArea(); - ImagePreviewAreas[member.Id] = AddWholeArea(ImagePreviewAreas[member.Id]); + if (member is null) continue; + + ChangedMembers.Add(member.Id); } } - private void AddWholeCanvasToMaskPreview(Guid memberGuid) - { - if (!MaskPreviewAreas.ContainsKey(memberGuid)) - MaskPreviewAreas[memberGuid] = new AffectedArea(); - MaskPreviewAreas[memberGuid] = AddWholeArea(MaskPreviewAreas[memberGuid]); - } - - private void AddWholeCanvasToEveryImagePreview() { + if (alreadyAddedWholeCanvasToEveryImagePreview) + return; + tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.Id)); + alreadyAddedWholeCanvasToEveryImagePreview = true; } private void AddWholeCanvasToEveryMaskPreview() @@ -404,7 +395,9 @@ internal class AffectedAreasGatherer tracker.Document.ForEveryReadonlyMember((member) => { if (member.EmbeddedMask is not null) - AddWholeCanvasToMaskPreview(member.Id); + { + ChangedMasks.Add(member.Id); + } }); } diff --git a/src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs b/src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs index a7b498428..19b3920c0 100644 --- a/src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs +++ b/src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs @@ -25,8 +25,8 @@ internal class MemberPreviewUpdater AnimationKeyFramePreviewRenderer = new AnimationKeyFramePreviewRenderer(internals); } - public void UpdatePreviews(IEnumerable membersToUpdate, - IEnumerable masksToUpdate, IEnumerable nodesToUpdate, IEnumerable keyFramesToUpdate) + public void UpdatePreviews(HashSet membersToUpdate, + HashSet masksToUpdate, HashSet nodesToUpdate, HashSet keyFramesToUpdate) { if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() && !keyFramesToUpdate.Any()) @@ -40,21 +40,16 @@ internal class MemberPreviewUpdater /// /// Members that should be rendered /// Masks that should be rendered - private void UpdatePreviewPainters(IEnumerable members, IEnumerable masksToUpdate, - IEnumerable nodesToUpdate, IEnumerable keyFramesToUpdate) + private void UpdatePreviewPainters(HashSet members, HashSet masksToUpdate, + HashSet nodesToUpdate, HashSet keyFramesToUpdate) { - Guid[] memberGuids = members as Guid[] ?? members.ToArray(); - Guid[] maskGuids = masksToUpdate as Guid[] ?? masksToUpdate.ToArray(); - Guid[] nodesGuids = nodesToUpdate as Guid[] ?? nodesToUpdate.ToArray(); - Guid[] keyFramesGuids = keyFramesToUpdate as Guid[] ?? keyFramesToUpdate.ToArray(); - RenderWholeCanvasPreview(); - RenderLayersPreview(memberGuids); - RenderMaskPreviews(maskGuids); + RenderLayersPreview(members); + RenderMaskPreviews(masksToUpdate); - RenderAnimationPreviews(memberGuids, keyFramesGuids); + RenderAnimationPreviews(members, keyFramesToUpdate); - RenderNodePreviews(nodesGuids); + RenderNodePreviews(nodesToUpdate); } /// @@ -77,7 +72,7 @@ internal class MemberPreviewUpdater doc.PreviewPainter.Repaint(); } - private void RenderLayersPreview(Guid[] memberGuids) + private void RenderLayersPreview(HashSet memberGuids) { foreach (var node in doc.NodeGraphHandler.AllNodes) { @@ -111,7 +106,7 @@ internal class MemberPreviewUpdater } } - private void RenderAnimationPreviews(Guid[] memberGuids, Guid[] keyFramesGuids) + private void RenderAnimationPreviews(HashSet memberGuids, HashSet keyFramesGuids) { foreach (var keyFrame in doc.AnimationHandler.KeyFrames) { @@ -191,7 +186,7 @@ internal class MemberPreviewUpdater } } - private void RenderMaskPreviews(Guid[] members) + private void RenderMaskPreviews(HashSet members) { foreach (var node in doc.NodeGraphHandler.AllNodes) { @@ -227,7 +222,7 @@ internal class MemberPreviewUpdater } } - private void RenderNodePreviews(Guid[] nodesGuids) + private void RenderNodePreviews(HashSet nodesGuids) { var outputNode = internals.Tracker.Document.NodeGraph.OutputNode; @@ -238,7 +233,7 @@ internal class MemberPreviewUpdater internals.Tracker.Document.NodeGraph .AllNodes; //internals.Tracker.Document.NodeGraph.CalculateExecutionQueue(outputNode); - if (nodesGuids.Length == 0) + if (nodesGuids.Count == 0) return; List actualRepaintedNodes = new(); diff --git a/src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs b/src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs index 0e371a804..36f55a5a9 100644 --- a/src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs +++ b/src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs @@ -397,6 +397,9 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler public void SortByLayers() { + if (keyFrames.Count < 2) + return; + var allLayers = Document.StructureHelper.GetAllLayers(); if (!OrderDifferent(keyFrames, allLayers)) return; diff --git a/src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs b/src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs index a21393fb3..2913fc2b8 100644 --- a/src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs +++ b/src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs @@ -26,9 +26,12 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl public ObservableCollection AvailableRenderOutputs { get; } = new(); public StructureTree StructureTree { get; } = new(); public INodeHandler? OutputNode { get; private set; } - public Dictionary CustomRenderOutputs { get; } = new(); + public Dictionary NodeLookup { get; } = new(); + + IReadOnlyDictionary INodeGraphHandler.NodeLookup => NodeLookup; + private DocumentInternalParts Internals { get; } public NodeGraphViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals) @@ -47,6 +50,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl AllNodes.Add(node); StructureTree.Update(this); + NodeLookup[node.Id] = node; UpdateAvailableRenderOutputs(); } @@ -60,6 +64,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl } StructureTree.Update(this); + NodeLookup.Remove(nodeId); UpdateAvailableRenderOutputs(); } diff --git a/src/PixiEditor/ViewModels/SubViewModels/DiscordViewModel.cs b/src/PixiEditor/ViewModels/SubViewModels/DiscordViewModel.cs index 7cde4e63a..be08227da 100644 --- a/src/PixiEditor/ViewModels/SubViewModels/DiscordViewModel.cs +++ b/src/PixiEditor/ViewModels/SubViewModels/DiscordViewModel.cs @@ -10,10 +10,13 @@ namespace PixiEditor.ViewModels.SubViewModels; internal class DiscordViewModel : SubViewModel, IDisposable { + public const double MinUpdateInterval = 5.0; // seconds private DiscordRpcClient client; private string clientId; private DocumentViewModel currentDocument; + private DateTime lastUpdate = DateTime.MinValue; + public bool Enabled { get => client != null; @@ -65,6 +68,11 @@ internal class DiscordViewModel : SubViewModel, IDisposable return; } + if(lastUpdate != DateTime.MinValue && (DateTime.UtcNow - lastUpdate).TotalSeconds < MinUpdateInterval) + { + return; // Prevent too frequent updates + } + RichPresence richPresence = NewDefaultRP(); if (document != null) @@ -96,6 +104,7 @@ internal class DiscordViewModel : SubViewModel, IDisposable } client.SetPresence(richPresence); + lastUpdate = DateTime.UtcNow; } private int CountLayers(NodeGraphViewModel graph) diff --git a/tests/PixiEditor.Tests/RenderTests.cs b/tests/PixiEditor.Tests/RenderTests.cs index 75f10cce3..2463f1bb7 100644 --- a/tests/PixiEditor.Tests/RenderTests.cs +++ b/tests/PixiEditor.Tests/RenderTests.cs @@ -30,6 +30,11 @@ public class RenderTests : FullPixiEditorTest [InlineData("VectorRectangleClippedToCircle")] [InlineData("VectorRectangleClippedToCircleShadowFilter")] [InlineData("VectorRectangleClippedToCircleMasked")] + [InlineData("BlendingLinearSrgb")] + [InlineData("BlendingSrgb")] + [InlineData("VectorWithSepiaFilter")] + [InlineData("VectorWithSepiaFilterSrgb")] + [InlineData("VectorWithSepiaFilterChained")] public void TestThatPixiFilesRenderTheSameResultAsSavedPng(string fileName) { if (!DrawingBackendApi.Current.IsHardwareAccelerated) diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.pixi b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.pixi new file mode 100644 index 0000000000000000000000000000000000000000..3b1b22c0b6bb010327600e36f67095244338f5c7 GIT binary patch literal 19268 zcmd^m3tY};|Np0hbdq#Z38`D9lDZ`c2|0zF)kCF_P>z+u=8%?E4ofjIVGKEq7-^2T zCTB86%$!eam~9zhT>sDY{@!GJ9((p|&-42Ix8>#Z?R#CH_vie%uKS&p+9$xL8WHL6 zZ+-qPzoyq9%Iq21sfMw=G5@zlm(Ibx`R^e4+t7gjTt8ymW&TSiv3I9-wCSWnIsZpL zv0azGhWs_faA*cEYSbmTZQn_Gx0W2sQSC^6ci%f*hxLDae5_mMwgUzC3c7b$F|)qL zV_$YupKGpH&qaM1ljCzUDa>UoO`37sy>I{i$v<{$zTG<@bm$dz%hD_nF^y`7()8Vx zmL|HnPc97mapKV_Co1mPwV3i?j5^97B~>S3$PPU!k8U?D`|;$1m95Cw=DWW6bvxZ@ zoacS*;U;IwbT$kaa0Rtn{V#-g|rfjoB5A^=x+OYX-0LS=#sq6P;_ze!SRh zZ|hUz%z{jc^XbF6XYW|+T4#hEv)rNAGj7j~uYVe!oRGdhk^D~!Lrr$CncodaD5{Z? zs`u7=w9V0zB8=aOr;X(gZr^>9Xl$7}?SmzY4xJ%~rGpY`6t9jrrF*LXjK(zJn?ok0 zYbWmYHZ0Te4`?$s>4k;i-H5RsbZ5)HsbWLQSi2Pais#kt7MT|3n2gqm&V1gmV$jhB z`BQ^e?Cu+S-r&Pw&q_X>aJ$U$VV>)OWZMYc=-DsIe(2QV(|z81gOiI6q?D|)Nh>KV zUeP@x-hPnYhpVSAsVw|p?Z@94E&ukdx_8Lo*d_b4#x~ooopL$gXX>SMYT@4G>$`0H zIz6&Fedpa`8$BCWuNh+-ITViHuU--#cW(8V6Sf~*DUEpGFvvXRjLk%owCy)W`EPUF zxOC*94_*xXhp6mW_RL`Ql&E97hiP`^HST`0aWsE-e$26yjS-o82F*ox^>)9*A59~-_lZAmsed3QQp zZxdS_7V?wY^4q4@HhEuaxF*nNa;&|2$nx{M4t#y>^A3-uUcc7;=C%8s>b>}}O=ac% zK^esrw~~&f6fPRGxra|o%x9;jT)v&!n5;6UhZb#Ce?L2IU)c4j2Ol`S*mQMg+TEnW zIrl9K`y|x%8CHDR`sNGgH6rCm8%NFF<6ZhK+n>>lJ{~orpjyX)W*75LxqN1m^03Lw zhuzy<>GW`v)8$6rds!Nk+_z7ks;Oaj*SXNcc0+EYrYOZucl?e{-#+>=tJj5%DNPb% zKdqKw95iammnC!ZHRnSX3~aH#dFidjH`};BDa(8q`g!xrNy{@@HZ+}8*Xm+i!scAB zb9Rqsm7l(sbg(F+-1+p}vy(+rwd!iA)!vr9=5IRQz$IdIu8o1GZ{&^@i-N+NepF+& zpTok4=xKwl*0z%c> z)I60Hv?gtg+og%!5)PN^UbCoULj(55Y&yUFyYTc9-BjUsV_N>S?K4m%A; zk8AZseDiku@{2l!7%kAhP-e8-Eq}wjR@GuIRX^NxR9(Nuwl6Mr6T1$47kFk*zngnq zS|#Rt2OkYR(tg#$Y;|x*zE%DAs3@f<{4>pojRgZ{_&A@>9PAoW`*4m~!|>MZHsC zHsj-QHBxTfi8FmN_2BdV;~q4VCW+QoauVBHQ2Jf9St{%KX7SI>oc-|g=3d7VRbu0F8b-Q#eb4&OHY%Hy8Z zg+}TFZ{2qumXRA6y{q%LP8X`@nVjkno;R`9@xF0aW*Wb{RCB+P*SuIKOS7)`njb0G z{U~VMhWexDjxSlbe7WJmHg313uH1cMRrAG@nzx)@`}3ta2CGe*U0z)?cXaW?EeiwN z^opSRjYdy6Uv1vv8ilv%t6-mI`djt4&UNc#@RetNnQ77OdkY)a3~2Rmxk-81;)h-< z#ym8ym~7CX<_urAbu|~XZ8SB-%Q14TW0BEar;_SNa~^gJP6+PSeO4~1Q|mU`>UuF- zbw(#;$*rUr+o)h+-j49W?Uz1Zdd}reRA$SPd+Tom^gY^i_*@Z~^Q`BPsXF_6*fN^p zr}j4n#~WrCWn`XBSnGH`Ga=q^)x*x}bcc!R6=Am?2Iv=#F4~s4CF=9c=^>R(nvVPS zyL*vypKR@O&)mCxnpe-KLEk#BGr2i!z2{ix)7@=rH=4HbS7 zh8v5FeoCt}8fSU#?1u9xoi~mu$js0W-nO+kEoAzmp-orbo}QU^dv(Q$vWl~DVLIl< zw}yT+e)i?Ndv;l#5LtH5LN}}*_~peFz4w}Cf8kMRa(G4g;*lpFJD2o0HP>3dxl^HT z^u1u8>;&C|9p|`pbaJYtN?meToii!euIBdVPj!zTP4_bjy!G(H3Ezfy?xszhZ`l4| zjae5OuIjNM^vKfdJ5%o8O$Z5#|FkloqH^84DND?EY)+mwdi=Z~pIRQ-zVA4${PEeG zp`W-OIrQ1vk%`k_#Y*yMrU>aeYwmqvZt&bs;Dg{?k0uxns=!J+d;K3TSXD##*e zf#lXw@U&^EVzRlYt8lE@Wk`+Grz?Vit4le#7q~n&Ma}GSY+d6Z(*@NBNk4`!A zr2U2|#wqjM?ih`3ePd(DiCw7=?aI!^dYp0Xw$E*+eqgVs!4qoTaVqIBD=R##<<0>& zT)p3a(Xr#kI$MTi#UA)FC{mnI9We}P6r!#f{N%-lK}}lhJ7?fpxVqY=lBdJ6o*N8x zJMR_qEbV)v8|oyxVKc+JtxC!syy2)vV$(h?cN-1)%yVqV19NOYIrKxuj<+{<%WZM! z?(R)_-Bm}^E$#NMdE9;L3x|}WsbjCTUDEhAUAHdtIOl$_)aaB)_{aC-^R4y6TJGAK zl$@BgJTA8S`!$Nww!gix)u|)S?*;FVHdoud-+EzI+=C*sQrG)A_7OJLUZE@#WZ%?Xio*!)$v9c(5%K(p*ezW=&B=^tG=sePMNJ!gm@B7(x&1*Ve z?K7@bx9wj%-@fqEf1GZ!$0^CIc(J&_FH5_uZ^WtXHZ=KwIyL8K-f9&|7`H`m=7anjoT=X>I@z;lShaJu+8=&(v zyzFs8WJ#mIo)M~DnF%*`FATGc+LE|X?eH1lJ1WBV z7ftP6a62u#V&R$Rap^DK?l?4fr|IZHaaZ2^@tcD?C&alJXAkZ;>*Li;E@pa;D-2o~ zl#}s6rdw9{-b3?3cbxOz8a*<|FpS&pO-owqXyv%k_nl{EgO41JeD_lDg+@s=7hDQ2 zySFnl;nC3MC;hUA_Sib){ZG_6H?vjF?pc#6YPq(#UOBe+oq_aa&)i`H_r90+$zYST z;7?SKb~i|OcJEq$^wbZ(Y7-=!ciT03Nj zvg6UxNr6{yO%F926!lf)vQaVn$M3PSSrR?v^47{)hG*;^hPCNEt)lgXz?>6$CEZnZ z4qsZ<_mQf0mkC*4){4zKdavlo;^EUY6`viccVlbGk2jy_zULW!=Uvyt1IO-}7(Qv@ zZ9Q-Ki@7TY^_kMCaG@&1qQk9hmCkl&pEfHWIgfm?{Ii z{r&wNg2GSM*khx&uxC-1(8Q4EU8_43etTuW&MWJUT@!|vn(VQsFLs>km@;U=nY<>$ z1FNUVaXMz)vi6D+ z2;&v2RhulYTU2Du9akQ4YQvn~;&PMkr>BpyjcD!mWVUX>oZd^P{3u%O@x6P$atRg8 zG0154yvfpv#ba8QH#k{*)#lKm@26jG=KRnpWR^J}qNq!U(BMt&qEqvNf({r8;$MR3 z0AfkG$BiG8JbI`mC%?zom?0yQCLe8+LX@+vOJe^Ki6aIl#D12uu3bXx=%L-m42?Z4 z*XFG19+Mca$@y6R_1bIs9Y-W2#g0$Z%*$TL=f9l7*MPJio;*5!GT@Y`qQ;FGF^o3~ zlE3*}`TPsPM@$_-dZZ`#ps6p&fDAN(s#A51qlOjbZLL=Ob%lCGd?R85c}u@y|n zL{JSr0FoJ*2{I>hURaB2X-MOUZ+L58#1bs#eZJ8OtJFX8Ey$K^ z1=*9mpgL5i*pY$YF5yT+b?AT7lRRGP1f4|cq>8msGcB4VuViQBuGUn zK@F*4v3!EZ`~q(R09cAmu-(rjUDsNU?Ip;ROrg3U8?w;?hakw{?FG4!n;>^`7vxEv zg1pE}P!nnb-SC)SU`t~GmO^`M2LuoSVQ?7hdPQqVBQg>Md%!$cSD)$&awcc3To3Yq zG(l=o3-TpjL4M>X$e;WLwV)PIACLJ3Rv!xh8Jl1`Yzz?)2C*;gen}fW@Cfk2Uv;Uj zRwe8O&3(v6P%~<#72c9s3JRn^!9f`$s2#Nv)SlYIE_jR=7$+MCOR)*IgRLL}!XOp^ z5Xfwyi$KNdAY|;wP7pMKL!hEJ@pl0xfC98Q0HFhQ5Y(AE3ksnSLEWgEpzhRNP$-3J z%sC$M7%$*eECOC^ge`e<-W|dq761U{XDBbcL}^Ku+SoxLx{|A)#?)9)Q)&t?X#ui% z1a+cLg1S;yK|QF4px)G5P+#gRs6X`=6h>i!!YN$i&0&qlcmc0s5td^k2F<302nd5% z0D!{*>SuV%v~h=rPzNtIpa$A#g9T6vn_xR^3=t3pu>b%l00npe3djKeGQv)4+a7#|C`6$`y+N8GWZ`sJtV$UK zKLTkqjn;Z$GELU1kV>h7X3|VS8I&O?lQIR(qFHbc9^(aQi$!oEHo}(JoK<5tLM#9P z3P1rKfC4hWqv5fSKG*sPT@Wn^&P2MPf^rAZrXvCx(Euw#=m;92H8e+))+xZ6PU(VX z(`-RmlqD#Kas(})1;vhx5s&c#UWLoBoIS>Vd#R1K@0iXaB-~lKg1N=)Lo%$yq zq0gbWz{`kW6iMVMf*ipCTOw*E(nPIi(kKnC5HyeGK?Om%lq+ZnEfKVgmI+!;%jHzW z54d-*j@nn*e}b?ae#4g79AY34q5%SMlyVKAfc($iD%DPEsLvQ-Fi*f7=%DCOa6jT3 z)d;DK6hOql(tw1>nnQC0&8PWVwV=^TS}ABXtrnC|`GVHbIt{yxAF!JI0;`DySc*-s z9X5st2!mJv02F`%JOBk`fQK4T=;a)FQag&Gm|$9~%CcX05G@1Yh^mEbM{*#jV11y3 zTRxx<1g)SIg4WO)K^tg;pv|;d&=%Sv=o9)x!_kKa{4G`$uks?+7n@)^Yzz?)2C)DD zC@=k}ZIV@)2Q{D&MrO`D))GmGK8E=fl?MeLvm53P@9)kjnC_)`T{vgoc5GV-e zvCFioY@=<0il|7?F4`q%5AD%-vqE?bUHMhk6-%)Rw!_8{0bvje0DuBefCr#}4De6` z3cai^C$v2o4n{$(idYP+C{w7zRoRH13iF~-A-Yg6kb_7+_y$@QP=TNwv;(>cDyCvV zB~&8l5FOH(vudm|USO@U086n6w!_8{0bvje09DxucmN8>Rj~_&pxVpcef;%$ik65@ zk7EFaa8w@*mnfwefiPvDMWeOB_$ZPHTcji+7XE^&`)Hq5%VTs*P$_X`_?$kMX^ICh z0A9lDypRo{q%Jmw2nd5%000z#0z3c(WPpbnQ0QmXmi5I*j&ovFuA+!xO2Fuk8je{N zL4*p3X$0AX5`s1XuONVtoCs355Bi)Sj;1qmtT<2S1zn_z@)*kxIBoF)Ud19Mfw3Jn zh6o6QSO5SNfC4-K1!RCn$$>&p?d7?H^~HF@M`G4Z#bK zLd|k27xaK0$YRV7SV6piSFs4oc_T)KjUfWUsuCDb01EH`6p(*WtA4I4E=RPd{Wt2% zr$p`f233Mj0~n<-f@((vz~Zq$TX49uXsZyHB;+ZA7aoPGf-0#}?5Cd2`mB=UuH>0H`WhfcHjyG5hfi5bk}BONnpLlSQEZ{podlJzw9TtM>T+*N9OjO+-G}{k?pjX0 zevG&5`3rAV-K+dLZ!h^i79H`gbHmJs+rPx!KYxaS-u-9)EQUW*LWH4?|LHyv zTh zxR%CE$p45laOMA;L2+Gc4KzF)nBXW(Vz|}AT>$Q)akGg|_`4Y+ued2lG#DSz3lAVT zAQQe|!T$m4<(vN!H|K%YBPeF53EXBh|B4Ot{PnL&{-8pB>8uCg30$MkW^8YpkT7Oq z=MizkgU62O%`~Ri@gqjZ6>ApjYPP(_PwJ7JG&VUY=M^{V)jzU-Mv}^&ulE{fYK^jL zUW#V1-iFZFi3-Nc8(CSktN)Uf^}2E8 zzYhrhIUpUON6gY<)|S5DpY~zDR41ygvC{avn=v~}mqiHvk)dkBkeHoiLd?$M9~i13 z%!t`p%wl79RxM(7mZL`B&)(a>fxdC~wih*t*;f|C>?SkCkfQnQ`9GBHaQbHdkYU@R+C&M)WobNUt(61A2F-RpP1Fuf|%80?yDeIdn)S5 zO#)#@x};cA+Q~~-)FrCx;H{wXA!aBwBWh+YS`xF90*Tp496v##9WgtpJuy2;tD>7X zl(*4^Y3==???r!m#iDjZc4`9!r#F$eiwGbJP$}DVAZ8eKCT19g5HpOr5i^Xs6EloL zi5W(IT06Kn+QX>odadA6f4acCTM{#c;2_xFm6$2in3yTll$a?bHCMTdPQ(nMuEY$X z9>feG{!y{sqAxK+s6R17D2$jP6i&<#(uIwj+*S3A{e;|#9RdN;G>S%LFqxv2r)WUT z^nnMZ@m&H$TVjTf6z3*-5wm;x5%n_{5yZ@%C}L($G%>R$hM3tin3&mPMk+%N4>u<) z41lREys@okD|;no&LZLiVOAmSh)DCcP#oNvn3*H}V5;aQ1&RSg15^$omY9t*oS2O> zl9-JXPt3+iAZFv(E0N`50fW2S_$sj@6`^`wfoAaC7gkC}$UIQP8hFcB2?F>GCY9DS z7m>s)njyq2nmA$>4P=cWX3>l%X3-=OvuKiuSu_)fSu`#(6rJ2{%oSJ2Z46Y3KGKd5 zrq+dcEB^ROPbRxe8zpjOfFct7U@rj^01qOk4^baa#n8;}9Hu}SOU#A=fJww`m?^|; zm}$gpnCbk(gd&BQ4WpK>aJ09=Qcq(y0~>7b2T{lhTYyl!!arWewZ%&bJ!HLy%t0A- z0q}l_Sy9Rtlg;XYGrS`R|J)hKL!4CXcSICG6cJ#kTD zN9KnTtupHrSHUvCFp-!{0^g(&lS!l{#5|&Twj!69NV0^ONV1HWNV1%mNW$gS*3r}- z3v{6be_AiMwQzzk7dLlLFU0dHr7xJsZeY!ITdCTx56YY@NQ&nWMo1(X)mHilT1YSu zbBOsL^NIN%Km#BviTNL^iTNM-#QcwS#QYB@cAdGgo8nigjjz6yjSFfU1M*gcOBi%Y zX;n34_pz1jrYf46apir@WT+@Ix0OVTm-#H?$k1PbCEX)FAZBf>AZBf>A!coCAZBfB zCT4AHA!coSLd@Fmvaq)@^R#vIH&ntp3<%#Juj%#JW{u+inXH%B~Tu|$ZC zr3ed%3}~fzmqE&e^iu{2bSHP&=6z*z4wQ|wvut^?d@2nhR+j5AvSv<_H7Zq>h1oIz z;n!sx@>YuNBsQ^+n9=}f$CyqpN{Qut&ipiug}njS&duy{E0rxaa4=W4l>!VI9fV65 zri9EWRr)`sBimOI=qbk$KRHAM$%ZeBhSD^nq?d=wwu0J@S~^pfuPj+8a%GaPlo_!> zhTb;zvyFoD1Ti1sjHEf9C*~tuWKy!m+}O{+#@5~e%Pf#Bj8FwZrsV6UBm?nRDJDHk zZ%Wa{l!bB0|5Yd=bNkDt9xWT~NZIz1Wbc_Ky`3S;D+<{XSwvRLjFom$;t;-;wloGb zagCTnaEq8kU>0C(?_h4BERahXp)Jt0lkqRb>nqbisg)xF^tC*ZBZyK~Fw%s{VIxKk z0P&o4E=vDF$$#Z#rHUY3cgfr+k?{xr!fwD-PW-9=1O8D2jSNTmO@jb0C-%48O3gtj zMd+&_cSH+OLJ6;iONtvb;7za&WO5~;19 zu9^L7Cbw1n&rEIQ=;^P_^1m@Sy;~`rZS@<1Qc0YPlF0wN0`rfgxS`U;vi^N~8~@JK zu9{qG%Tt%5JWBo1VyX1M%`(6L`vp_y_qQLHH?|%n$7K7_{e!LN_x5dN3VhYOmEjB1 z&ad}xoByD9|M7W88Q(D-VjM%|lmPj3jiv*q|Gs%gSKb|%{HHUbz)1sxy&Rzx56_f6 z5deR6UJU#XW<_PJR!X?Nyp?!0S}R8(boNef7_S}0#FwMBzo|09z;d~NXslL_9e$Ym zfBndzj5-74{3^SX81WmA9k##i$nh&DGG!iY&!^-!p33yAPGpMrQhEF`_+uxs({DVv z;)=rMU!7c)FSjyDRhGa&IN?%AlQ{yopohQxAD#;r+(jI8(RZbPFf%_-gTOsuEOn z<>ic%yamN+%u^mBeE1g81!FSb_yxVZJ#_!oo5M@+N}zHDWhIYz(k<$LbKhzHZ|^#< zAmV;@3yZ^}gMs&NyoJ5)t{jTxMT>F+tQ_0rNUef59dH9|@K3kDd}pk*7UZDxhOMr- vXTWQ>%121~`fVQ{Xco+~)j1Z_MOpJvuO402SACkWR_*1@zgqYdXV3e8o?dJ0 literal 0 HcmV?d00001 diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.png new file mode 100644 index 0000000000000000000000000000000000000000..1881ca58749de797391e8fb4541057fa6213e90f GIT binary patch literal 3487 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 literal 0 HcmV?d00001 diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.png new file mode 100644 index 0000000000000000000000000000000000000000..967ba15c1657a6fd21b06718d6c3972597318297 GIT binary patch literal 3124 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 D8GMCu diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleMasked.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleMasked.png index 3046842c3ff221cf89c6a5c2b3831347e8a42bc0..fb6fadfe271265ae68362e1d2725feb4027599f6 100644 GIT binary patch delta 373 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 S|24`{l$FQ6_9--6yZT><>Yg0{ literal 0 HcmV?d00001 diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.png new file mode 100644 index 0000000000000000000000000000000000000000..c5f1056f3a880e4db4aa4d13fec7927540d6814a GIT binary patch literal 3203 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 literal 0 HcmV?d00001 diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.pixi b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.pixi new file mode 100644 index 0000000000000000000000000000000000000000..3c13fec9738a6d5ba362095f72d82c3622eb3fb7 GIT binary patch literal 9926 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 literal 0 HcmV?d00001 diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.png new file mode 100644 index 0000000000000000000000000000000000000000..6d1501cff8f796e7cd1a22d68279a87c173dbfd9 GIT binary patch literal 3984 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 literal 0 HcmV?d00001