Browse Source

Merge pull request #853 from PixiEditor/fixes/20.03.25

Fixes
Krzysztof Krysiński 4 months ago
parent
commit
6e0966f3dd
23 changed files with 277 additions and 45 deletions
  1. 4 1
      src/ChunkyImageLib/Chunk.cs
  2. 15 13
      src/ChunkyImageLib/ChunkPool.cs
  3. 1 1
      src/Drawie
  4. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  5. 16 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs
  6. 203 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Animable/EasingNode.cs
  7. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/EllipseNode.cs
  8. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  9. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelected_UpdateableChange.cs
  10. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/FlipImage_Change.cs
  11. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs
  12. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs
  13. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/RasterizeMember_Change.cs
  14. BIN
      src/PixiEditor.UI.Common/Fonts/PixiPerfect.ttf
  15. 1 0
      src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml.cs
  16. 3 1
      src/PixiEditor/Data/Localization/Languages/en.json
  17. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/MagicWandToolExecutor.cs
  18. 2 2
      src/PixiEditor/Models/Tools/DocumentScope.cs
  19. 4 6
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  20. 11 0
      src/PixiEditor/ViewModels/Document/Nodes/Animable/EasingNodeViewModel.cs
  21. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/ColorPickerToolViewModel.cs
  22. 2 2
      src/PixiEditor/Views/Dialogs/ExportFilePopup.axaml.cs
  23. 1 1
      src/PixiEditor/Views/Main/DocumentPreview.axaml.cs

+ 4 - 1
src/ChunkyImageLib/Chunk.cs

@@ -45,6 +45,8 @@ public class Chunk : IDisposable
     /// The resolution of the chunk
     /// </summary>
     public ChunkResolution Resolution { get; }
+
+    public ColorSpace ColorSpace { get; }
     
     public bool Disposed => returned;
 
@@ -54,6 +56,7 @@ public class Chunk : IDisposable
         int size = resolution.PixelSize();
 
         Resolution = resolution;
+        ColorSpace = colorSpace;
         PixelSize = new(size, size);
         internalSurface = new Surface(new ImageInfo(size, size, ColorType.RgbaF16, AlphaType.Premul, colorSpace));
     }
@@ -63,7 +66,7 @@ public class Chunk : IDisposable
     /// </summary>
     public static Chunk Create(ColorSpace chunkCs, ChunkResolution resolution = ChunkResolution.Full)
     {
-        var chunk = ChunkPool.Instance.Get(resolution) ?? new Chunk(resolution, chunkCs);
+        var chunk = ChunkPool.Instance.Get(resolution, chunkCs) ?? new Chunk(resolution, chunkCs);
         chunk.returned = false;
         Interlocked.Increment(ref chunkCounter);
         return chunk;

+ 15 - 13
src/ChunkyImageLib/ChunkPool.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using System.Collections.Concurrent;
+using Drawie.Backend.Core.Surfaces.ImageData;
 
 namespace ChunkyImageLib;
 
@@ -28,26 +29,27 @@ internal class ChunkPool
         }
     }
 
-    private readonly ConcurrentBag<Chunk> fullChunks = new();
-    private readonly ConcurrentBag<Chunk> halfChunks = new();
-    private readonly ConcurrentBag<Chunk> quarterChunks = new();
-    private readonly ConcurrentBag<Chunk> eighthChunks = new();
-    
+    private readonly ConcurrentDictionary<ColorSpace, ConcurrentBag<Chunk>> fullChunks = new();
+    private readonly ConcurrentDictionary<ColorSpace, ConcurrentBag<Chunk>> halfChunks = new();
+    private readonly ConcurrentDictionary<ColorSpace, ConcurrentBag<Chunk>> quarterChunks = new();
+    private readonly ConcurrentDictionary<ColorSpace, ConcurrentBag<Chunk>> eighthChunks = new();
+
     /// <summary>
     /// Tries to take a chunk from the pool, returns null if there's no Chunk available
     /// </summary>
     /// <param name="resolution">The resolution for the chunk</param>
-    internal Chunk? Get(ChunkResolution resolution) => GetBag(resolution).TryTake(out Chunk? item) ? item : null;
+    /// <param name="chunkCs"></param>
+    internal Chunk? Get(ChunkResolution resolution, ColorSpace chunkCs) => GetBag(resolution, chunkCs).TryTake(out Chunk? item) ? item : null;
 
-    private ConcurrentBag<Chunk> GetBag(ChunkResolution resolution)
+    private ConcurrentBag<Chunk> GetBag(ChunkResolution resolution, ColorSpace colorSpace)
     {
         return resolution switch
         {
-            ChunkResolution.Full => fullChunks,
-            ChunkResolution.Half => halfChunks,
-            ChunkResolution.Quarter => quarterChunks,
-            ChunkResolution.Eighth => eighthChunks,
-            _ => fullChunks
+            ChunkResolution.Full => fullChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
+            ChunkResolution.Half => halfChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
+            ChunkResolution.Quarter => quarterChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
+            ChunkResolution.Eighth => eighthChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
+            _ => fullChunks.GetOrAdd(colorSpace, _ => new ConcurrentBag<Chunk>()),
         };
     }
 
@@ -56,7 +58,7 @@ internal class ChunkPool
     /// </summary>
     internal void Push(Chunk chunk)
     {
-        var chunks = GetBag(chunk.Resolution);
+        var chunks = GetBag(chunk.Resolution, chunk.ColorSpace);
         //a race condition can cause the count to go above 200, but likely not by much
         if (chunks.Count < 200)
             chunks.Add(chunk);

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 92082eb78437163d5e94e9aa57281f1971419cca
+Subproject commit d749d83f6090e058c9a0d68620316e32d753a977

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -85,7 +85,7 @@ internal class Document : IChangeable, IReadOnlyDocument
 
         tightBounds = tightBounds.Value.Intersect(RectI.Create(0, 0, Size.X, Size.Y));
 
-        Surface surface = new Surface(tightBounds.Value.Size);
+        Surface surface = Surface.ForProcessing(tightBounds.Value.Size, ProcessingColorSpace);
 
         using var paint = new Paint();
 
@@ -94,7 +94,7 @@ internal class Document : IChangeable, IReadOnlyDocument
         if (layer is IReadOnlyImageNode imageNode)
         {
             var chunkyImage = imageNode.GetLayerImageAtFrame(frame);
-            using Surface chunkSurface = new Surface(chunkyImage.CommittedSize);
+            using Surface chunkSurface = Surface.ForProcessing(chunkyImage.CommittedSize, chunkyImage.ProcessingColorSpace);
             chunkyImage.DrawCommittedRegionOn(
                 new RectI(0, 0, chunkyImage.CommittedSize.X, chunkyImage.CommittedSize.Y),
                 ChunkResolution.Full,

+ 16 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs

@@ -221,6 +221,18 @@ public class InputProperty : IInputProperty
         {
             hash.Add(cacheable.GetCacheHash());
         }
+        else if (Value is Delegate func && Connection == null)
+        {
+            try
+            {
+                var constant = func.DynamicInvoke(FuncContext.NoContext);
+                if (constant is ShaderExpressionVariable shaderExpression)
+                {
+                    hash.Add(shaderExpression.GetConstant());
+                }
+            }
+            catch { }
+        }
         else
         {
             hash.Add(Value?.GetHashCode() ?? 0);
@@ -248,13 +260,13 @@ public class InputProperty<T> : InputProperty, IInputProperty<T>
                 value = shaderExpression.GetConstant();
             }
 
-            var validated = Validator.GetClosestValidValue(value);
-
-            if (!ConversionTable.TryConvert(validated, ValueType, out object result))
+            if (!ConversionTable.TryConvert(value, ValueType, out object result))
             {
-                return default(T);
+                result = default(T);
             }
 
+            result = Validator.GetClosestValidValue(result);
+
             return (T)result;
         }
     }

+ 203 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Animable/EasingNode.cs

@@ -0,0 +1,203 @@
+using Drawie.Backend.Core.Shaders.Generation.Expressions;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
+
+[NodeInfo("Easing")]
+public class EasingNode : Node
+{
+    public FuncInputProperty<Float1> Value { get; }
+    public InputProperty<EasingType> Easing { get; }
+    public FuncOutputProperty<Float1> Output { get; }
+
+    public EasingNode()
+    {
+        Value = CreateFuncInput<Float1>("Value", "VALUE", 0.0);
+        Easing = CreateInput("EasingType", "EASING_TYPE", EasingType.Linear);
+        Output = CreateFuncOutput<Float1>("Output", "OUTPUT", Evaluate);
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+    }
+
+    public Float1 Evaluate(FuncContext context)
+    {
+        var x = context.GetValue(Value);
+        if (!context.HasContext)
+        {
+            return new Float1(string.Empty) { ConstantValue = EvalCpu(x.ConstantValue) };
+        }
+
+        return context.NewFloat1(GetExpression(context));
+    }
+
+    private double EvalCpu(double x)
+    {
+        const double c1 = 1.70158;
+        const double c2 = c1 * 1.525;
+        const double c3 = c1 + 1;
+        const double c4 = (2 * Math.PI) / 3;
+        const double c5 = (2 * Math.PI) / 4.5;
+
+        EasingType easing = Easing.Value;
+        return easing switch
+        {
+            EasingType.Linear => x,
+            EasingType.InSine => 1 - Math.Cos((x * Math.PI) / 2),
+            EasingType.OutSine => Math.Sin((x * Math.PI) / 2),
+            EasingType.InOutSine => -(Math.Cos(Math.PI * x) - 1) / 2,
+            EasingType.InQuad => x * x,
+            EasingType.OutQuad => x * (2 - x),
+            EasingType.InOutQuad => x < 0.5 ? 2 * x * x : 1 - Math.Pow(-2 * x + 2, 2) / 2,
+            EasingType.InCubic => x * x * x,
+            EasingType.OutCubic => 1 - Math.Pow(1 - x, 3),
+            EasingType.InOutCubic => x < 0.5 ? 4 * x * x * x : 1 - Math.Pow(-2 * x + 2, 3) / 2,
+            EasingType.InQuart => x * x * x * x,
+            EasingType.OutQuart => 1 - Math.Pow(1 - x, 4),
+            EasingType.InOutQuart => x < 0.5 ? 8 * x * x * x * x : 1 - Math.Pow(-2 * x + 2, 4) / 2,
+            EasingType.InQuint => x * x * x * x * x,
+            EasingType.OutQuint => 1 - Math.Pow(1 - x, 5),
+            EasingType.InOutQuint => x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.Pow(-2 * x + 2, 5) / 2,
+            EasingType.InExpo => x == 0 ? 0 : Math.Pow(2, 10 * x - 10),
+            EasingType.OutExpo => x == 1 ? 1 : 1 - Math.Pow(2, -10 * x),
+            EasingType.InOutExpo => x == 0 ? 0 :
+                x == 1 ? 1 :
+                x < 0.5 ? Math.Pow(2, 20 * x - 10) / 2 : (2 - Math.Pow(2, -20 * x + 10)) / 2,
+            EasingType.InCirc => 1 - Math.Sqrt(1 - Math.Pow(x, 2)),
+            EasingType.OutCirc => Math.Sqrt(1 - Math.Pow(x - 1, 2)),
+            EasingType.InOutCirc => x < 0.5
+                ? (1 - Math.Sqrt(1 - Math.Pow(2 * x, 2))) / 2
+                : (Math.Sqrt(1 - Math.Pow(-2 * x + 2, 2)) + 1) / 2,
+            EasingType.InBack => c3 * x * x * x - c1 * x * x,
+            EasingType.OutBack => 1 + c3 * Math.Pow(x - 1, 3) + c1 * Math.Pow(x - 1, 2),
+            EasingType.InOutBack => x < 0.5
+                ? (Math.Pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2
+                : (Math.Pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2,
+            EasingType.InElastic => x switch
+            {
+                0 => 0,
+                1 => 1,
+                _ => -Math.Pow(2, 10 * x - 10) * Math.Sin((x * 10 - 10.75) * c4)
+            },
+            EasingType.OutElastic => x switch
+            {
+                0 => 0,
+                1 => 1,
+                _ => Math.Pow(2, -10 * x) * Math.Sin((x * 10 - 0.75) * c4) + 1
+            },
+            EasingType.InOutElastic => x == 0 ? 0 :
+                x == 1 ? 1 :
+                x < 0.5 ? -(Math.Pow(2, 20 * x - 10) * Math.Sin((20 * x - 11.125) * c5)) / 2 :
+                (Math.Pow(2, -20 * x + 10) * Math.Sin((20 * x - 11.125) * c5)) / 2 + 1,
+            EasingType.InBounce => 1 - OutBounce(1 - x),
+            EasingType.OutBounce => OutBounce(x),
+            EasingType.InOutBounce => x < 0.5 ? (1 - OutBounce(1 - 2 * x)) / 2 : (1 + OutBounce(2 * x - 1)) / 2,
+            _ => throw new ArgumentOutOfRangeException()
+        };
+    }
+
+    private double OutBounce(double x)
+    {
+        const double n1 = 7.5625;
+        const double d1 = 2.75;
+
+        if (x < 1 / d1)
+        {
+            return n1 * x * x;
+        }
+
+        if (x < 2 / d1)
+        {
+            return n1 * (x -= 1.5 / d1) * x + 0.75;
+        }
+
+        if (x < 2.5 / d1)
+        {
+            return n1 * (x -= 2.25 / d1) * x + 0.9375;
+        }
+
+        return n1 * (x -= 2.625 / d1) * x + 0.984375;
+    }
+
+    private Expression? GetExpression(FuncContext ctx)
+    {
+        Float1 x = ctx.GetValue(Value);
+        return Easing.Value switch
+        {
+            EasingType.Linear => ctx.GetValue(Value),
+            EasingType.InSine => ctx.Builder.Functions.GetInSine(x),
+            EasingType.OutSine => ctx.Builder.Functions.GetOutSine(x),
+            EasingType.InOutSine => ctx.Builder.Functions.GetInOutSine(x),
+            EasingType.InQuad => ctx.Builder.Functions.GetInQuad(x),
+            EasingType.OutQuad => ctx.Builder.Functions.GetOutQuad(x),
+            EasingType.InOutQuad => ctx.Builder.Functions.GetInOutQuad(x),
+            EasingType.InCubic => ctx.Builder.Functions.GetInCubic(x),
+            EasingType.OutCubic => ctx.Builder.Functions.GetOutCubic(x),
+            EasingType.InOutCubic => ctx.Builder.Functions.GetInOutCubic(x),
+            EasingType.InQuart => ctx.Builder.Functions.GetInQuart(x),
+            EasingType.OutQuart => ctx.Builder.Functions.GetOutQuart(x),
+            EasingType.InOutQuart => ctx.Builder.Functions.GetInOutQuart(x),
+            EasingType.InQuint => ctx.Builder.Functions.GetInQuint(x),
+            EasingType.OutQuint => ctx.Builder.Functions.GetOutQuint(x),
+            EasingType.InOutQuint => ctx.Builder.Functions.GetInOutQuint(x),
+            EasingType.InExpo => ctx.Builder.Functions.GetInExpo(x),
+            EasingType.OutExpo => ctx.Builder.Functions.GetOutExpo(x),
+            EasingType.InOutExpo => ctx.Builder.Functions.GetInOutExpo(x),
+            EasingType.InCirc => ctx.Builder.Functions.GetInCirc(x),
+            EasingType.OutCirc => ctx.Builder.Functions.GetOutCirc(x),
+            EasingType.InOutCirc => ctx.Builder.Functions.GetInOutCirc(x),
+            EasingType.InBack => ctx.Builder.Functions.GetInBack(x),
+            EasingType.OutBack => ctx.Builder.Functions.GetOutBack(x),
+            EasingType.InOutBack => ctx.Builder.Functions.GetInOutBack(x),
+            EasingType.InElastic => ctx.Builder.Functions.GetInElastic(x),
+            EasingType.OutElastic => ctx.Builder.Functions.GetOutElastic(x),
+            EasingType.InOutElastic => ctx.Builder.Functions.GetInOutElastic(x),
+            EasingType.InBounce => ctx.Builder.Functions.GetInBounce(x),
+            EasingType.OutBounce => ctx.Builder.Functions.GetOutBounce(x),
+            EasingType.InOutBounce => ctx.Builder.Functions.GetInOutBounce(x),
+            _ => throw new ArgumentOutOfRangeException()
+        };
+    }
+
+    public override Node CreateCopy()
+    {
+        return new EasingNode();
+    }
+}
+
+public enum EasingType
+{
+    Linear,
+    InSine,
+    OutSine,
+    InOutSine,
+    InQuad,
+    OutQuad,
+    InOutQuad,
+    InCubic,
+    OutCubic,
+    InOutCubic,
+    InQuart,
+    OutQuart,
+    InOutQuart,
+    InQuint,
+    OutQuint,
+    InOutQuint,
+    InExpo,
+    OutExpo,
+    InOutExpo,
+    InCirc,
+    OutCirc,
+    InOutCirc,
+    InBack,
+    OutBack,
+    InOutBack,
+    InElastic,
+    OutElastic,
+    InOutElastic,
+    InBounce,
+    OutBounce,
+    InOutBounce
+}

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/EllipseNode.cs

@@ -19,7 +19,7 @@ public class EllipseNode : ShapeNode<EllipseVectorData>
     {
         Center = CreateInput<VecD>("Position", "POSITION", VecI.Zero);
         Radius = CreateInput<VecD>("Radius", "RADIUS", new VecD(32, 32)).WithRules(
-            v => v.Min(new VecD(1)));
+            v => v.Min(new VecD(0)));
         StrokeColor = CreateInput<Paintable>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
         FillColor = CreateInput<Paintable>("FillColor", "FILL_COLOR", new Color(0, 0, 0, 255));
         StrokeWidth = CreateInput<int>("StrokeWidth", "STROKE_WIDTH", 1);

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -240,7 +240,7 @@ public static class FloodFillHelper
 
     public static Surface FillSelection(IReadOnlyDocument document, VectorPath selection)
     {
-        Surface surface = new Surface(document.Size);
+        Surface surface = Surface.ForProcessing(document.Size, document.ProcessingColorSpace);
 
         var inverse = new VectorPath();
         inverse.AddRect((RectD)new RectI(new(0, 0), document.Size));

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelected_UpdateableChange.cs

@@ -226,7 +226,7 @@ internal class TransformSelected_UpdateableChange : InterruptableUpdateableChang
         clipPath.Transform(Matrix3X3.CreateTranslation(-pathBounds.X, -pathBounds.Y));
 
         // draw
-        Surface output = new(pathBounds.Size);
+        Surface output = Surface.ForProcessing(pathBounds.Size, image.ProcessingColorSpace);
         output.DrawingSurface.Canvas.Save();
         output.DrawingSurface.Canvas.ClipPath(clipPath);
         image.DrawMostUpToDateRegionOn(pathBounds, ChunkResolution.Full, output.DrawingSurface, VecI.Zero);

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/FlipImage_Change.cs

@@ -66,14 +66,14 @@ internal sealed class FlipImage_Change : Change
             }
         }
 
-        using Surface originalSurface = new(img.LatestSize);
+        using Surface originalSurface = Surface.ForProcessing(img.LatestSize, img.ProcessingColorSpace);
         img.DrawMostUpToDateRegionOn(
             new RectI(VecI.Zero, img.LatestSize), 
             ChunkResolution.Full,
             originalSurface.DrawingSurface,
             VecI.Zero);
 
-        using Surface flipped = new Surface(img.LatestSize);
+        using Surface flipped = Surface.ForProcessing(img.LatestSize, img.ProcessingColorSpace);
 
         bool flipX = flipType == FlipType.Horizontal;
         bool flipY = flipType == FlipType.Vertical;

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs

@@ -51,7 +51,7 @@ internal class ResizeImage_Change : Change
 
     private void ScaleChunkyImage(ChunkyImage image)
     {
-        using Surface originalSurface = new(originalSize);
+        using Surface originalSurface = Surface.ForProcessing(originalSize, image.ProcessingColorSpace);
         image.DrawMostUpToDateRegionOn(
             new(VecI.Zero, originalSize),
             ChunkResolution.Full,
@@ -62,7 +62,7 @@ internal class ResizeImage_Change : Change
         FilterQuality quality = ToFilterQuality(method, downscaling);
         using Paint paint = new() { FilterQuality = quality, BlendMode = BlendMode.Src, };
 
-        using Surface newSurface = new(newSize);
+        using Surface newSurface = Surface.ForProcessing(newSize, image.ProcessingColorSpace);
         newSurface.DrawingSurface.Canvas.Save();
         newSurface.DrawingSurface.Canvas.Scale(newSize.X / (float)originalSize.X, newSize.Y / (float)originalSize.Y);
         newSurface.DrawingSurface.Canvas.DrawSurface(originalSurface.DrawingSurface, 0, 0, paint);

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs

@@ -100,14 +100,14 @@ internal sealed class RotateImage_Change : Change
 
         using Paint paint = new() { BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.Src };
 
-        using Surface originalSurface = new(oldSize);
+        using Surface originalSurface = Surface.ForProcessing(oldSize, img.ProcessingColorSpace);
         img.DrawMostUpToDateRegionOn(
             bounds,
             ChunkResolution.Full,
             originalSurface.DrawingSurface,
             VecI.Zero);
 
-        using Surface flipped = new Surface(newSize);
+        using Surface flipped = Surface.ForProcessing(newSize, img.ProcessingColorSpace);
 
         float translationX = newSize.X;
         float translationY = newSize.Y;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Structure/RasterizeMember_Change.cs

@@ -50,7 +50,7 @@ internal class RasterizeMember_Change : Change
 
         target.NodeGraph.AddNode(imageLayer);
         
-        using Surface surface = new Surface(target.Size);
+        using Surface surface = Surface.ForProcessing(target.Size, target.ProcessingColorSpace);
         rasterizable.Rasterize(surface.DrawingSurface, null);
         
         var image = imageLayer.GetLayerImageAtFrame(0);

BIN
src/PixiEditor.UI.Common/Fonts/PixiPerfect.ttf


+ 1 - 0
src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml.cs

@@ -153,6 +153,7 @@ public static class PixiPerfectIcons
     public const string TextRound = "\uE999";
     public const string Cone = "\uE99c";
     public const string Camera = "\uE99d";
+    public const string ChartSpline = "\ue99e";
 
     public static Stream GetFontStream()
     {

+ 3 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -519,7 +519,7 @@
   "FAILED_ASSOCIATE_PIXI": "Failed to associate .pixi file with PixiEditor.",
   "COULD_NOT_SAVE_PALETTE": "There was an error while saving the palette.",
   "NO_COLORS_TO_SAVE": "There are no colors to save.",
-  "ALL_LAYERS": "All Layers",
+  "CANVAS": "Canvas",
   "SINGLE_LAYER": "Single Layer",
   "CHOOSE": "Choose",
   "REMOVE": "Remove",
@@ -920,4 +920,6 @@
   "MINUTE_UNIVERSAL": "min",
   "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Autosave to selected file",
   "LOAD_LAZY_FILE_MESSAGE": "To improve startup time, PixiEditor didn't load this file. Click the button below to load it.",
+  "EASING_NODE": "Easing",
+  "EASING_TYPE": "Easing Type"
 }

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/MagicWandToolExecutor.cs

@@ -27,7 +27,7 @@ internal class MagicWandToolExecutor : UpdateableChangeExecutor
 
         mode = magicWand.SelectMode;
         memberGuids = members;
-        considerAllLayers = magicWand.DocumentScope == DocumentScope.AllLayers;
+        considerAllLayers = magicWand.DocumentScope == DocumentScope.Canvas;
         if (considerAllLayers)
             memberGuids = document!.StructureHelper.GetAllLayers().Select(x => x.Id).ToList();
         var pos = controller!.LastPixelPosition;

+ 2 - 2
src/PixiEditor/Models/Tools/DocumentScope.cs

@@ -6,6 +6,6 @@ public enum DocumentScope
 {
     [Description("SINGLE_LAYER")]
     SingleLayer,
-    [Description("ALL_LAYERS")]
-    AllLayers
+    [Description("CANVAS")]
+    Canvas
 }

+ 4 - 6
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -765,12 +765,10 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         {
             // it might've been a better idea to implement this function asynchronously
             // via a passthrough action to avoid all the try catches
-            if (scope == DocumentScope.AllLayers)
+            if (scope == DocumentScope.Canvas)
             {
-                using Surface tmpSurface = new Surface(SizeBindable);
-                HashSet<Guid> layers = StructureHelper.TraverseAllMembers().Select(x => x.Id).ToHashSet();
-                Renderer.RenderLayers(tmpSurface.DrawingSurface, layers, frameTime.Frame, ChunkResolution.Full,
-                    SizeBindable);
+                using Surface tmpSurface = new Surface(SizeBindable); // new Surface is on purpose, Surface.ForDisplay doesn't work here
+                Renderer.RenderDocument(tmpSurface.DrawingSurface, frameTime, SizeBindable);
 
                 return tmpSurface.GetSrgbPixel(pos);
             }
@@ -782,7 +780,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             {
                 if (maybeMember is IRasterizable rasterizable)
                 {
-                    using Texture texture = new Texture(SizeBindable);
+                    using Texture texture = Texture.ForDisplay(SizeBindable);
                     using Paint paint = new Paint();
                     rasterizable.Rasterize(texture.DrawingSurface, paint);
                     return texture.GetSRGBPixel(pos);

+ 11 - 0
src/PixiEditor/ViewModels/Document/Nodes/Animable/EasingNodeViewModel.cs

@@ -0,0 +1,11 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Animable;
+using PixiEditor.UI.Common.Fonts;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Animable;
+
+[NodeViewModel("EASING_NODE", "ANIMATION", PixiPerfectIcons.ChartSpline)]
+internal class EasingNodeViewModel : NodeViewModel<EasingNode>
+{
+
+}

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/ColorPickerToolViewModel.cs

@@ -66,7 +66,7 @@ internal class ColorPickerToolViewModel : ToolViewModel, IColorPickerHandler
 
     public bool PickOnlyFromReferenceLayer => !pickFromCanvas && pickFromReferenceLayer;
 
-    [Settings.Enum("SCOPE_LABEL", DocumentScope.AllLayers)]
+    [Settings.Enum("SCOPE_LABEL", DocumentScope.Canvas)]
     public DocumentScope Mode => GetValue<DocumentScope>();
 
     public ColorPickerToolViewModel()

+ 2 - 2
src/PixiEditor/Views/Dialogs/ExportFilePopup.axaml.cs

@@ -289,7 +289,7 @@ internal partial class ExportFilePopup : PixiEditorPopup
         if (previewSize != ExportPreview.Size)
         {
             ExportPreview?.Dispose();
-            ExportPreview = new Surface(previewSize);
+            ExportPreview = Surface.ForDisplay(previewSize);
 
             Task.Run(() =>
             {
@@ -340,7 +340,7 @@ internal partial class ExportFilePopup : PixiEditorPopup
                 if (previewSize != ExportPreview.Size)
                 {
                     ExportPreview?.Dispose();
-                    ExportPreview = new Surface(previewSize);
+                    ExportPreview = Surface.ForDisplay(previewSize);
                 }
 
                 IsGeneratingPreview = false;

+ 1 - 1
src/PixiEditor/Views/Main/DocumentPreview.axaml.cs

@@ -158,7 +158,7 @@ internal partial class DocumentPreview : UserControl
 
         ColorCursorPosition = new VecI(x, y);
         
-        var color = Document.PickColor(new(x, y), DocumentScope.AllLayers, false, true, Document.AnimationDataViewModel.ActiveFrameBindable);
+        var color = Document.PickColor(new(x, y), DocumentScope.Canvas, false, true, Document.AnimationDataViewModel.ActiveFrameBindable);
         ColorCursorColor = Color.FromArgb(color.A, color.R, color.G, color.B);
     }
 }