Optimizations and rendering improvements

This commit is contained in:
Krzysztof Krysiński 2025-06-01 21:53:27 +02:00
parent 066b281289
commit 2c2bb05fd9
27 changed files with 132 additions and 122 deletions

View File

@ -50,6 +50,7 @@ internal class Document : IChangeable, IReadOnlyDocument
public double VerticalSymmetryAxisX { get; set; } public double VerticalSymmetryAxisX { get; set; }
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
public Document() public Document()
{ {
AnimationData = new AnimationData(this); AnimationData = new AnimationData(this);
@ -279,14 +280,14 @@ internal class Document : IChangeable, IReadOnlyDocument
/// <returns>The node with the given <paramref name="guid"/> or null if it doesn't exist.</returns> /// <returns>The node with the given <paramref name="guid"/> or null if it doesn't exist.</returns>
public Node? FindNode(Guid guid) public Node? FindNode(Guid guid)
{ {
return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid); return NodeGraph.FindNode(guid);
} }
IReadOnlyNode IReadOnlyDocument.FindNode(Guid guid) => FindNodeOrThrow<Node>(guid); IReadOnlyNode IReadOnlyDocument.FindNode(Guid guid) => FindNodeOrThrow<Node>(guid);
public T? FindNode<T>(Guid guid) where T : Node public T? FindNode<T>(Guid guid) where T : Node
{ {
return NodeGraph.Nodes.FirstOrDefault(x => x.Id == guid && x is T) as T; return NodeGraph.FindNode<T>(guid);
} }
/// <summary> /// <summary>
@ -298,7 +299,7 @@ internal class Document : IChangeable, IReadOnlyDocument
/// <returns>True if the node could be found, otherwise false.</returns> /// <returns>True if the node could be found, otherwise false.</returns>
public bool TryFindNode<T>(Guid id, out T node) where T : Node public bool TryFindNode<T>(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<T>(id) ?? null;
return node != null; return node != null;
} }

View File

@ -11,12 +11,16 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
private readonly List<Node> _nodes = new(); private readonly List<Node> _nodes = new();
public IReadOnlyCollection<Node> Nodes => _nodes; public IReadOnlyCollection<Node> Nodes => _nodes;
public IReadOnlyDictionary<Guid, Node> NodeLookup => nodeLookup;
public Node? OutputNode => CustomOutputNode ?? Nodes.OfType<OutputNode>().FirstOrDefault(); public Node? OutputNode => CustomOutputNode ?? Nodes.OfType<OutputNode>().FirstOrDefault();
public Node? CustomOutputNode { get; set; } public Node? CustomOutputNode { get; set; }
private Dictionary<Guid, Node> nodeLookup = new();
IReadOnlyCollection<IReadOnlyNode> IReadOnlyNodeGraph.AllNodes => Nodes; IReadOnlyCollection<IReadOnlyNode> IReadOnlyNodeGraph.AllNodes => Nodes;
IReadOnlyNode IReadOnlyNodeGraph.OutputNode => OutputNode; IReadOnlyNode IReadOnlyNodeGraph.OutputNode => OutputNode;
public void AddNode(Node node) public void AddNode(Node node)
{ {
if (Nodes.Contains(node)) if (Nodes.Contains(node))
@ -26,6 +30,7 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
node.ConnectionsChanged += ResetCache; node.ConnectionsChanged += ResetCache;
_nodes.Add(node); _nodes.Add(node);
nodeLookup[node.Id] = node;
ResetCache(); ResetCache();
} }
@ -38,9 +43,20 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
node.ConnectionsChanged -= ResetCache; node.ConnectionsChanged -= ResetCache;
_nodes.Remove(node); _nodes.Remove(node);
nodeLookup.Remove(node.Id);
ResetCache(); ResetCache();
} }
public Node? FindNode(Guid guid)
{
return nodeLookup.GetValueOrDefault(guid);
}
public T? FindNode<T>(Guid guid) where T : Node
{
return nodeLookup.TryGetValue(guid, out Node? node) && node is T typedNode ? typedNode : null;
}
public Queue<IReadOnlyNode> CalculateExecutionQueue(IReadOnlyNode outputNode) public Queue<IReadOnlyNode> CalculateExecutionQueue(IReadOnlyNode outputNode)
{ {
return new Queue<IReadOnlyNode>(CalculateExecutionQueueInternal(outputNode)); return new Queue<IReadOnlyNode>(CalculateExecutionQueueInternal(outputNode));

View File

@ -171,12 +171,11 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
bool useFilters, Paint paint) bool useFilters, Paint paint)
{ {
paint.Color = paint.Color.WithAlpha((byte)Math.Round(Opacity.Value * ctx.Opacity * 255)); paint.Color = paint.Color.WithAlpha((byte)Math.Round(Opacity.Value * ctx.Opacity * 255));
var finalPaint = paint;
var targetSurface = workingSurface; var targetSurface = workingSurface;
Texture? tex = null; Texture? tex = null;
int saved = -1; int saved = -1;
if (!ctx.ProcessingColorSpace.IsSrgb) if (!ctx.ProcessingColorSpace.IsSrgb && useFilters && Filters.Value != null)
{ {
saved = workingSurface.Canvas.Save(); saved = workingSurface.Canvas.Save();
@ -185,29 +184,22 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
workingSurface.Canvas.SetMatrix(Matrix3X3.Identity); workingSurface.Canvas.SetMatrix(Matrix3X3.Identity);
targetSurface = tex.DrawingSurface; targetSurface = tex.DrawingSurface;
finalPaint = new Paint();
} }
if (useFilters && Filters.Value != null) if (useFilters && Filters.Value != null)
{ {
paint.SetFilters(Filters.Value); paint.SetFilters(Filters.Value);
DrawWithFilters(ctx, targetSurface, finalPaint); DrawWithFilters(ctx, targetSurface, paint);
} }
else else
{ {
paint.SetFilters(null); paint.SetFilters(null);
DrawWithoutFilters(ctx, targetSurface, finalPaint); DrawWithoutFilters(ctx, targetSurface, paint);
}
if (finalPaint != paint)
{
finalPaint.Dispose();
} }
if (targetSurface != workingSurface) if (targetSurface != workingSurface)
{ {
workingSurface.Canvas.DrawSurface(targetSurface, 0, 0, paint); workingSurface.Canvas.DrawSurface(targetSurface, 0, 0);
tex.Dispose(); tex.Dispose();
workingSurface.Canvas.RestoreToCount(saved); workingSurface.Canvas.RestoreToCount(saved);
} }

View File

@ -50,7 +50,9 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
IReadOnlyShapeVectorData IReadOnlyVectorNode.ShapeData => RenderableShapeData; 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 override VecD GetSceneSize(KeyFrameTime time) => RenderableShapeData?.TransformedAABB.Size ?? VecD.Zero;
public VectorLayerNode() public VectorLayerNode()
@ -130,21 +132,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
return false; return false;
} }
Matrix3X3 matrix = RenderableShapeData.TransformationMatrix; Rasterize(renderOn, paint);
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);
}
return true; return true;
} }
@ -222,7 +210,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
return; return;
} }
if(EmbeddedShapeData is IScalable resizable) if (EmbeddedShapeData is IScalable resizable)
{ {
resizable.Resize(multiplier); resizable.Resize(multiplier);
} }

View File

@ -121,6 +121,7 @@ internal class ActionAccumulator
toExecute.Any(static action => action.action is RefreshViewport_PassthroughAction); toExecute.Any(static action => action.action is RefreshViewport_PassthroughAction);
bool changeFrameRequest = bool changeFrameRequest =
toExecute.Any(static action => action.action is SetActiveFrame_PassthroughAction); toExecute.Any(static action => action.action is SetActiveFrame_PassthroughAction);
foreach (IChangeInfo info in optimizedChanges) foreach (IChangeInfo info in optimizedChanges)
{ {
internals.Updater.ApplyChangeFromChangeInfo(info); internals.Updater.ApplyChangeFromChangeInfo(info);
@ -129,7 +130,6 @@ internal class ActionAccumulator
if (undoBoundaryPassed) if (undoBoundaryPassed)
internals.Updater.AfterUndoBoundaryPassed(); internals.Updater.AfterUndoBoundaryPassed();
// update the contents of the bitmaps
var affectedAreas = new AffectedAreasGatherer(document.AnimationHandler.ActiveFrameTime, var affectedAreas = new AffectedAreasGatherer(document.AnimationHandler.ActiveFrameTime,
internals.Tracker, internals.Tracker,
optimizedChanges); optimizedChanges);
@ -145,8 +145,8 @@ internal class ActionAccumulator
}*/ }*/
previewUpdater.UpdatePreviews( previewUpdater.UpdatePreviews(
affectedAreas.ImagePreviewAreas.Keys, affectedAreas.ChangedMembers,
affectedAreas.MaskPreviewAreas.Keys, affectedAreas.ChangedMasks,
affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames); affectedAreas.ChangedNodes, affectedAreas.ChangedKeyFrames);
// force refresh viewports for better responsiveness // force refresh viewports for better responsiveness

View File

@ -395,7 +395,9 @@ internal class DocumentUpdater
IStructureMemberHandler memberVM; IStructureMemberHandler memberVM;
if (info is CreateLayer_ChangeInfo layerInfo) 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) if (memberVM is ITransparencyLockableMember transparencyLockableMember)
{ {
transparencyLockableMember.SetLockTransparency(layerInfo.LockTransparency); transparencyLockableMember.SetLockTransparency(layerInfo.LockTransparency);
@ -403,7 +405,9 @@ internal class DocumentUpdater
} }
else if (info is CreateFolder_ChangeInfo) 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 else
{ {

View File

@ -1,6 +1,4 @@
using System.Collections; using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
using System.Collections.Generic;
using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
using PixiEditor.Models.Handlers; using PixiEditor.Models.Handlers;
namespace PixiEditor.Models.DocumentModels.Public; namespace PixiEditor.Models.DocumentModels.Public;
@ -139,7 +137,7 @@ internal class DocumentStructureModule
/// Returns all layers in the document. /// Returns all layers in the document.
/// </summary> /// </summary>
/// <returns>List of ILayerHandlers. Empty if no layers found.</returns> /// <returns>List of ILayerHandlers. Empty if no layers found.</returns>
public List<ILayerHandler> GetAllLayers(bool includeFoldersWithMask = false) public List<ILayerHandler> GetAllLayers()
{ {
List<ILayerHandler> layers = new List<ILayerHandler>(); List<ILayerHandler> layers = new List<ILayerHandler>();

View File

@ -22,4 +22,5 @@ internal interface INodeGraphHandler
public void RemoveConnections(Guid nodeId); public void RemoveConnections(Guid nodeId);
public void UpdateAvailableRenderOutputs(); public void UpdateAvailableRenderOutputs();
public void RequestUpdateComputedPropertyValue(INodePropertyHandler property); public void RequestUpdateComputedPropertyValue(INodePropertyHandler property);
public IReadOnlyDictionary<Guid, INodeHandler> NodeLookup { get; }
} }

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using ChunkyImageLib; using ChunkyImageLib;
using ChunkyImageLib.DataHolders; using ChunkyImageLib.DataHolders;
using ChunkyImageLib.Operations; using ChunkyImageLib.Operations;
@ -28,13 +29,15 @@ internal class AffectedAreasGatherer
private readonly DocumentChangeTracker tracker; private readonly DocumentChangeTracker tracker;
public AffectedArea MainImageArea { get; private set; } = new(); public AffectedArea MainImageArea { get; private set; } = new();
public Dictionary<Guid, AffectedArea> ImagePreviewAreas { get; private set; } = new(); public HashSet<Guid> ChangedMembers { get; private set; } = new();
public Dictionary<Guid, AffectedArea> MaskPreviewAreas { get; private set; } = new(); public HashSet<Guid> ChangedMasks { get; private set; } = new();
public List<Guid> ChangedKeyFrames { get; private set; } = new(); public HashSet<Guid> ChangedKeyFrames { get; private set; } = new();
private KeyFrameTime ActiveFrame { get; set; } private KeyFrameTime ActiveFrame { get; set; }
public List<Guid> ChangedNodes { get; set; } = new(); public HashSet<Guid> ChangedNodes { get; set; } = new();
private bool alreadyAddedWholeCanvasToEveryImagePreview = false;
public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker, public AffectedAreasGatherer(KeyFrameTime activeFrame, DocumentChangeTracker tracker,
IReadOnlyList<IChangeInfo> changes) IReadOnlyList<IChangeInfo> changes)
@ -42,11 +45,11 @@ internal class AffectedAreasGatherer
this.tracker = tracker; this.tracker = tracker;
ActiveFrame = activeFrame; ActiveFrame = activeFrame;
ProcessChanges(changes); ProcessChanges(changes);
var outputNode = tracker.Document.NodeGraph.OutputNode; var outputNode = tracker.Document.NodeGraph.OutputNode;
if (outputNode is null) if (outputNode is null)
return; return;
if (tracker.Document.NodeGraph.CalculateExecutionQueue(tracker.Document.NodeGraph.OutputNode) if (tracker.Document.NodeGraph.CalculateExecutionQueue(tracker.Document.NodeGraph.OutputNode)
.Any(x => x is ICustomShaderNode)) .Any(x => x is ICustomShaderNode))
{ {
@ -64,20 +67,20 @@ internal class AffectedAreasGatherer
if (info.Area.Chunks is null) if (info.Area.Chunks is null)
throw new InvalidOperationException("Chunks must not be null"); throw new InvalidOperationException("Chunks must not be null");
AddToMainImage(info.Area); AddToMainImage(info.Area);
AddToImagePreviews(info.Id, info.Area, true); AddToImagePreviews(info.Id, true);
AddToMaskPreview(info.Id, info.Area); AddToMaskPreview(info.Id);
AddToNodePreviews(info.Id); AddToNodePreviews(info.Id);
break; break;
case LayerImageArea_ChangeInfo info: case LayerImageArea_ChangeInfo info:
if (info.Area.Chunks is null) if (info.Area.Chunks is null)
throw new InvalidOperationException("Chunks must not be null"); throw new InvalidOperationException("Chunks must not be null");
AddToMainImage(info.Area); AddToMainImage(info.Area);
AddToImagePreviews(info.Id, info.Area); AddToImagePreviews(info.Id);
AddToNodePreviews(info.Id); AddToNodePreviews(info.Id);
break; break;
case TransformObject_ChangeInfo info: case TransformObject_ChangeInfo info:
AddToMainImage(info.Area); AddToMainImage(info.Area);
AddToImagePreviews(info.NodeGuid, info.Area); AddToImagePreviews(info.NodeGuid);
AddToNodePreviews(info.NodeGuid); AddToNodePreviews(info.NodeGuid);
break; break;
case CreateStructureMember_ChangeInfo info: case CreateStructureMember_ChangeInfo info:
@ -104,7 +107,6 @@ internal class AffectedAreasGatherer
break; break;
case StructureMemberMask_ChangeInfo info: case StructureMemberMask_ChangeInfo info:
AddWholeCanvasToMainImage(); AddWholeCanvasToMainImage();
AddWholeCanvasToMaskPreview(info.Id);
AddWholeCanvasToImagePreviews(info.Id, true); AddWholeCanvasToImagePreviews(info.Id, true);
AddToNodePreviews(info.Id); AddToNodePreviews(info.Id);
break; break;
@ -172,7 +174,7 @@ internal class AffectedAreasGatherer
{ {
AddToNodePreviews(info.OutputNodeId.Value); AddToNodePreviews(info.OutputNodeId.Value);
} }
break; break;
case PropertyValueUpdated_ChangeInfo info: case PropertyValueUpdated_ChangeInfo info:
AddWholeCanvasToMainImage(); AddWholeCanvasToMainImage();
@ -187,12 +189,13 @@ internal class AffectedAreasGatherer
break; break;
case VectorShape_ChangeInfo info: case VectorShape_ChangeInfo info:
AddToMainImage(info.Affected); AddToMainImage(info.Affected);
AddToImagePreviews(info.LayerId, info.Affected); AddToImagePreviews(info.LayerId);
AddToNodePreviews(info.LayerId); AddToNodePreviews(info.LayerId);
break; break;
case ProcessingColorSpace_ChangeInfo: case ProcessingColorSpace_ChangeInfo:
AddWholeCanvasToMainImage(); AddWholeCanvasToMainImage();
AddWholeCanvasToEveryImagePreview(); AddWholeCanvasToEveryImagePreview();
AddWholeCanvasToEveryMaskPreview();
break; break;
} }
} }
@ -200,20 +203,20 @@ internal class AffectedAreasGatherer
private void AddKeyFrame(Guid infoKeyFrameId) private void AddKeyFrame(Guid infoKeyFrameId)
{ {
ChangedKeyFrames ??= new List<Guid>(); ChangedKeyFrames ??= new HashSet<Guid>();
if (!ChangedKeyFrames.Contains(infoKeyFrameId)) if (!ChangedKeyFrames.Contains(infoKeyFrameId))
ChangedKeyFrames.Add(infoKeyFrameId); ChangedKeyFrames.Add(infoKeyFrameId);
} }
private void AddToNodePreviews(Guid nodeId) private void AddToNodePreviews(Guid nodeId)
{ {
ChangedNodes ??= new List<Guid>(); ChangedNodes ??= new HashSet<Guid>();
if (!ChangedNodes.Contains(nodeId)) if (!ChangedNodes.Contains(nodeId))
{ {
ChangedNodes.Add(nodeId); ChangedNodes.Add(nodeId);
} }
} }
private void AddAllNodesToImagePreviews() private void AddAllNodesToImagePreviews()
{ {
foreach (var node in tracker.Document.NodeGraph.AllNodes) foreach (var node in tracker.Document.NodeGraph.AllNodes)
@ -234,8 +237,7 @@ internal class AffectedAreasGatherer
return; return;
} }
var chunks = result.FindAllChunks(); AddToImagePreviews(member, ignoreSelf);
AddToImagePreviews(memberGuid, new AffectedArea(chunks), ignoreSelf);
} }
else if (member is IReadOnlyFolderNode folder) else if (member is IReadOnlyFolderNode folder)
{ {
@ -248,9 +250,7 @@ internal class AffectedAreasGatherer
var tightBounds = genericLayerNode.GetTightBounds(frame); var tightBounds = genericLayerNode.GetTightBounds(frame);
if (tightBounds is not null) if (tightBounds is not null)
{ {
var affectedArea = new AffectedArea( AddToImagePreviews(member, ignoreSelf);
OperationHelper.FindChunksTouchingRectangle((RectI)tightBounds.Value, ChunkyImage.FullChunkSize));
AddToImagePreviews(memberGuid, affectedArea, ignoreSelf);
} }
else else
{ {
@ -283,9 +283,9 @@ internal class AffectedAreasGatherer
{ {
var affectedArea = new AffectedArea( var affectedArea = new AffectedArea(
OperationHelper.FindChunksTouchingRectangle((RectI)tightBounds.Value, ChunkyImage.FullChunkSize)); OperationHelper.FindChunksTouchingRectangle((RectI)tightBounds.Value, ChunkyImage.FullChunkSize));
var lastArea = new AffectedArea(affectedArea); var lastArea = new AffectedArea(affectedArea);
AddToMainImage(affectedArea); AddToMainImage(affectedArea);
} }
else else
@ -312,7 +312,7 @@ internal class AffectedAreasGatherer
if (member.EmbeddedMask is not null) if (member.EmbeddedMask is not null)
{ {
var chunks = member.EmbeddedMask.FindAllChunks(); var chunks = member.EmbeddedMask.FindAllChunks();
AddToMaskPreview(memberGuid, new AffectedArea(chunks)); AddToMaskPreview(memberGuid);
} }
if (member is IReadOnlyFolderNode folder) if (member is IReadOnlyFolderNode folder)
@ -330,39 +330,34 @@ internal class AffectedAreasGatherer
MainImageArea = temp; 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); var sourceMember = tracker.Document.FindMember(memberGuid);
path.Insert(0, 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++) for (int i = ignoreSelf ? 1 : 0; i < path.Count; i++)
{ {
var member = path[i]; var member = path[i];
if(member == null) continue; if (member == null) continue;
if (!ImagePreviewAreas.ContainsKey(member.Id))
{ ChangedMembers.Add(member.Id);
ImagePreviewAreas[member.Id] = new AffectedArea(area);
}
else
{
var temp = ImagePreviewAreas[member.Id];
temp.UnionWith(area);
ImagePreviewAreas[member.Id] = temp;
}
} }
} }
private void AddToMaskPreview(Guid memberGuid, AffectedArea area) private void AddToMaskPreview(Guid memberGuid)
{ {
if (!MaskPreviewAreas.ContainsKey(memberGuid)) ChangedMasks.Add(memberGuid);
{
MaskPreviewAreas[memberGuid] = new AffectedArea(area);
}
else
{
var temp = MaskPreviewAreas[memberGuid];
temp.UnionWith(area);
MaskPreviewAreas[memberGuid] = temp;
}
} }
@ -380,23 +375,19 @@ internal class AffectedAreasGatherer
for (int i = ignoreSelf ? 1 : 0; i < path.Count; i++) for (int i = ignoreSelf ? 1 : 0; i < path.Count; i++)
{ {
var member = path[i]; var member = path[i];
if (!ImagePreviewAreas.ContainsKey(member.Id)) if (member is null) continue;
ImagePreviewAreas[member.Id] = new AffectedArea();
ImagePreviewAreas[member.Id] = AddWholeArea(ImagePreviewAreas[member.Id]); 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() private void AddWholeCanvasToEveryImagePreview()
{ {
if (alreadyAddedWholeCanvasToEveryImagePreview)
return;
tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.Id)); tracker.Document.ForEveryReadonlyMember((member) => AddWholeCanvasToImagePreviews(member.Id));
alreadyAddedWholeCanvasToEveryImagePreview = true;
} }
private void AddWholeCanvasToEveryMaskPreview() private void AddWholeCanvasToEveryMaskPreview()
@ -404,7 +395,9 @@ internal class AffectedAreasGatherer
tracker.Document.ForEveryReadonlyMember((member) => tracker.Document.ForEveryReadonlyMember((member) =>
{ {
if (member.EmbeddedMask is not null) if (member.EmbeddedMask is not null)
AddWholeCanvasToMaskPreview(member.Id); {
ChangedMasks.Add(member.Id);
}
}); });
} }

View File

@ -25,8 +25,8 @@ internal class MemberPreviewUpdater
AnimationKeyFramePreviewRenderer = new AnimationKeyFramePreviewRenderer(internals); AnimationKeyFramePreviewRenderer = new AnimationKeyFramePreviewRenderer(internals);
} }
public void UpdatePreviews(IEnumerable<Guid> membersToUpdate, public void UpdatePreviews(HashSet<Guid> membersToUpdate,
IEnumerable<Guid> masksToUpdate, IEnumerable<Guid> nodesToUpdate, IEnumerable<Guid> keyFramesToUpdate) HashSet<Guid> masksToUpdate, HashSet<Guid> nodesToUpdate, HashSet<Guid> keyFramesToUpdate)
{ {
if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() && if (!membersToUpdate.Any() && !masksToUpdate.Any() && !nodesToUpdate.Any() &&
!keyFramesToUpdate.Any()) !keyFramesToUpdate.Any())
@ -40,21 +40,16 @@ internal class MemberPreviewUpdater
/// </summary> /// </summary>
/// <param name="members">Members that should be rendered</param> /// <param name="members">Members that should be rendered</param>
/// <param name="masksToUpdate">Masks that should be rendered</param> /// <param name="masksToUpdate">Masks that should be rendered</param>
private void UpdatePreviewPainters(IEnumerable<Guid> members, IEnumerable<Guid> masksToUpdate, private void UpdatePreviewPainters(HashSet<Guid> members, HashSet<Guid> masksToUpdate,
IEnumerable<Guid> nodesToUpdate, IEnumerable<Guid> keyFramesToUpdate) HashSet<Guid> nodesToUpdate, HashSet<Guid> 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(); RenderWholeCanvasPreview();
RenderLayersPreview(memberGuids); RenderLayersPreview(members);
RenderMaskPreviews(maskGuids); RenderMaskPreviews(masksToUpdate);
RenderAnimationPreviews(memberGuids, keyFramesGuids); RenderAnimationPreviews(members, keyFramesToUpdate);
RenderNodePreviews(nodesGuids); RenderNodePreviews(nodesToUpdate);
} }
/// <summary> /// <summary>
@ -77,7 +72,7 @@ internal class MemberPreviewUpdater
doc.PreviewPainter.Repaint(); doc.PreviewPainter.Repaint();
} }
private void RenderLayersPreview(Guid[] memberGuids) private void RenderLayersPreview(HashSet<Guid> memberGuids)
{ {
foreach (var node in doc.NodeGraphHandler.AllNodes) 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<Guid> memberGuids, HashSet<Guid> keyFramesGuids)
{ {
foreach (var keyFrame in doc.AnimationHandler.KeyFrames) foreach (var keyFrame in doc.AnimationHandler.KeyFrames)
{ {
@ -191,7 +186,7 @@ internal class MemberPreviewUpdater
} }
} }
private void RenderMaskPreviews(Guid[] members) private void RenderMaskPreviews(HashSet<Guid> members)
{ {
foreach (var node in doc.NodeGraphHandler.AllNodes) foreach (var node in doc.NodeGraphHandler.AllNodes)
{ {
@ -227,7 +222,7 @@ internal class MemberPreviewUpdater
} }
} }
private void RenderNodePreviews(Guid[] nodesGuids) private void RenderNodePreviews(HashSet<Guid> nodesGuids)
{ {
var outputNode = internals.Tracker.Document.NodeGraph.OutputNode; var outputNode = internals.Tracker.Document.NodeGraph.OutputNode;
@ -238,7 +233,7 @@ internal class MemberPreviewUpdater
internals.Tracker.Document.NodeGraph internals.Tracker.Document.NodeGraph
.AllNodes; //internals.Tracker.Document.NodeGraph.CalculateExecutionQueue(outputNode); .AllNodes; //internals.Tracker.Document.NodeGraph.CalculateExecutionQueue(outputNode);
if (nodesGuids.Length == 0) if (nodesGuids.Count == 0)
return; return;
List<Guid> actualRepaintedNodes = new(); List<Guid> actualRepaintedNodes = new();

View File

@ -397,6 +397,9 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
public void SortByLayers() public void SortByLayers()
{ {
if (keyFrames.Count < 2)
return;
var allLayers = Document.StructureHelper.GetAllLayers(); var allLayers = Document.StructureHelper.GetAllLayers();
if (!OrderDifferent(keyFrames, allLayers)) return; if (!OrderDifferent(keyFrames, allLayers)) return;

View File

@ -26,9 +26,12 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
public ObservableCollection<string> AvailableRenderOutputs { get; } = new(); public ObservableCollection<string> AvailableRenderOutputs { get; } = new();
public StructureTree StructureTree { get; } = new(); public StructureTree StructureTree { get; } = new();
public INodeHandler? OutputNode { get; private set; } public INodeHandler? OutputNode { get; private set; }
public Dictionary<string, INodeHandler> CustomRenderOutputs { get; } = new(); public Dictionary<string, INodeHandler> CustomRenderOutputs { get; } = new();
public Dictionary<Guid, INodeHandler> NodeLookup { get; } = new();
IReadOnlyDictionary<Guid, INodeHandler> INodeGraphHandler.NodeLookup => NodeLookup;
private DocumentInternalParts Internals { get; } private DocumentInternalParts Internals { get; }
public NodeGraphViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals) public NodeGraphViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals)
@ -47,6 +50,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
AllNodes.Add(node); AllNodes.Add(node);
StructureTree.Update(this); StructureTree.Update(this);
NodeLookup[node.Id] = node;
UpdateAvailableRenderOutputs(); UpdateAvailableRenderOutputs();
} }
@ -60,6 +64,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
} }
StructureTree.Update(this); StructureTree.Update(this);
NodeLookup.Remove(nodeId);
UpdateAvailableRenderOutputs(); UpdateAvailableRenderOutputs();
} }

View File

@ -10,10 +10,13 @@ namespace PixiEditor.ViewModels.SubViewModels;
internal class DiscordViewModel : SubViewModel<ViewModelMain>, IDisposable internal class DiscordViewModel : SubViewModel<ViewModelMain>, IDisposable
{ {
public const double MinUpdateInterval = 5.0; // seconds
private DiscordRpcClient client; private DiscordRpcClient client;
private string clientId; private string clientId;
private DocumentViewModel currentDocument; private DocumentViewModel currentDocument;
private DateTime lastUpdate = DateTime.MinValue;
public bool Enabled public bool Enabled
{ {
get => client != null; get => client != null;
@ -65,6 +68,11 @@ internal class DiscordViewModel : SubViewModel<ViewModelMain>, IDisposable
return; return;
} }
if(lastUpdate != DateTime.MinValue && (DateTime.UtcNow - lastUpdate).TotalSeconds < MinUpdateInterval)
{
return; // Prevent too frequent updates
}
RichPresence richPresence = NewDefaultRP(); RichPresence richPresence = NewDefaultRP();
if (document != null) if (document != null)
@ -96,6 +104,7 @@ internal class DiscordViewModel : SubViewModel<ViewModelMain>, IDisposable
} }
client.SetPresence(richPresence); client.SetPresence(richPresence);
lastUpdate = DateTime.UtcNow;
} }
private int CountLayers(NodeGraphViewModel graph) private int CountLayers(NodeGraphViewModel graph)

View File

@ -30,6 +30,11 @@ public class RenderTests : FullPixiEditorTest
[InlineData("VectorRectangleClippedToCircle")] [InlineData("VectorRectangleClippedToCircle")]
[InlineData("VectorRectangleClippedToCircleShadowFilter")] [InlineData("VectorRectangleClippedToCircleShadowFilter")]
[InlineData("VectorRectangleClippedToCircleMasked")] [InlineData("VectorRectangleClippedToCircleMasked")]
[InlineData("BlendingLinearSrgb")]
[InlineData("BlendingSrgb")]
[InlineData("VectorWithSepiaFilter")]
[InlineData("VectorWithSepiaFilterSrgb")]
[InlineData("VectorWithSepiaFilterChained")]
public void TestThatPixiFilesRenderTheSameResultAsSavedPng(string fileName) public void TestThatPixiFilesRenderTheSameResultAsSavedPng(string fileName)
{ {
if (!DrawingBackendApi.Current.IsHardwareAccelerated) if (!DrawingBackendApi.Current.IsHardwareAccelerated)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 B

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB