Optimizations and rendering improvements
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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>();
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
BIN
tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.pixi
Normal file
BIN
tests/PixiEditor.Tests/TestFiles/RenderTests/BlendingSrgb.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 420 B After Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 416 B After Width: | Height: | Size: 415 B |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.1 KiB |