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 000000000..3b1b22c0b Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.pixi differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.png new file mode 100644 index 000000000..1881ca587 Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingLinearSrgb.png differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.pixi b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.pixi new file mode 100644 index 000000000..d8597d76f Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.pixi differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.png new file mode 100644 index 000000000..967ba15c1 Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.png differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircle.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircle.png index ae43fa6bb..5c7b87be7 100644 Binary files a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircle.png and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircle.png differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleMasked.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleMasked.png index 3046842c3..fb6fadfe2 100644 Binary files a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleMasked.png and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleMasked.png differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleShadowFilter.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleShadowFilter.png index 2bd6e95bd..860a35617 100644 Binary files a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleShadowFilter.png and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorRectangleClippedToCircleShadowFilter.png differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.pixi b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.pixi new file mode 100644 index 000000000..756c67312 Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.pixi differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.png new file mode 100644 index 000000000..c5f1056f3 Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilter.png differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.pixi b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.pixi new file mode 100644 index 000000000..3c13fec97 Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.pixi differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.png new file mode 100644 index 000000000..6d1501cff Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterChained.png differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterSrgb.pixi b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterSrgb.pixi new file mode 100644 index 000000000..b001ba250 Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterSrgb.pixi differ diff --git a/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterSrgb.png b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterSrgb.png new file mode 100644 index 000000000..f19efde17 Binary files /dev/null and b/tests/PixiEditor.Tests/TestFiles/RenderTests/VectorWithSepiaFilterSrgb.png differ