浏览代码

Merge branch 'master' into fixes/10.01.2025

Krzysztof Krysiński 8 月之前
父节点
当前提交
90d084d94f

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit e9d3a727001548fe5d50793bfb31e5ececbfd8d1
+Subproject commit 52a989906b7d9254fa9bab4acf172097d9d0dbef

+ 81 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/BlurNode.cs

@@ -0,0 +1,81 @@
+using System.Diagnostics;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+
+[NodeInfo("BlurFilter")]
+public class BlurNode : FilterNode
+{
+    public InputProperty<bool> PreserveAlpha { get; }
+    
+    public InputProperty<VecD> Radius { get; }
+    
+    public BlurNode()
+    {
+        PreserveAlpha = CreateInput("PreserveAlpha", "PRESERVE_ALPHA", true);
+        Radius = CreateInput("Radius", "RADIUS", new VecD(1, 1)).WithRules(x => x.Min(new VecD(0, 0)));
+    }
+
+    protected override ImageFilter GetImageFilter()
+    {
+        var sigma = (VecF)Radius.Value;
+        var preserveAlpha = PreserveAlpha.Value;
+
+        var xFilter = GetGaussianFilter(sigma.X, true, preserveAlpha, null, out float[] xKernel);
+        
+        // Reuse xKernel if x == y
+        var yKernel = Math.Abs(sigma.Y - sigma.X) < 0.0001f ? xKernel : null;
+        var yFilter = GetGaussianFilter(sigma.Y, false, preserveAlpha, yKernel, out _);
+
+        return (xFilter, yFilter) switch
+        {
+            (null, _) => yFilter,
+            (_, null) => xFilter,
+            (_, _) => ImageFilter.CreateCompose(yFilter, xFilter)
+        };
+    }
+
+    private static ImageFilter? GetGaussianFilter(float sigma, bool isX, bool preserveAlpha, float[]? kernel, out float[] usedKernel)
+    {
+        usedKernel = null;
+        if (sigma < 0.0001f) return null;
+        
+        kernel ??= GenerateGaussianKernel(sigma);
+        usedKernel = kernel;
+        
+        var size = isX ? new VecI(kernel.Length, 1) : new VecI(1, kernel.Length);
+        var offset = isX ? new VecI(kernel.Length / 2, 0) : new VecI(0, kernel.Length / 2);
+        
+        return ImageFilter.CreateMatrixConvolution(size, kernel, 1, 0, offset, TileMode.Repeat, !preserveAlpha);
+    }
+    
+    public static float[] GenerateGaussianKernel(float sigma)
+    {
+        int radius = (int)Math.Ceiling(3 * sigma);
+        radius = Math.Min(radius, 300);
+        int kernelSize = 2 * radius + 1;
+
+        float[] kernel = new float[kernelSize];
+        float sum = 0f;
+        float twoSigmaSquare = 2 * sigma * sigma;
+
+        for (int i = 0; i < kernelSize; i++)
+        {
+            int x = i - radius;
+            kernel[i] = (float)Math.Exp(-(x * x) / twoSigmaSquare);
+            sum += kernel[i];
+        }
+
+        // Normalize the kernel to ensure the sum of elements is 1
+        for (int i = 0; i < kernelSize; i++)
+        {
+            kernel[i] /= sum;
+        }
+
+        return kernel;
+    }
+
+    public override Node CreateCopy() => new BlurNode();
+}

+ 56 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MathNode.cs

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Backend.Core.Shaders.Generation.Expressions;
+using PixiEditor.Common;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
@@ -22,7 +23,8 @@ public class MathNode : Node
     
     public FuncInputProperty<Float1> Y { get; }
     
-
+    public FuncInputProperty<Float1> Z { get; }
+    
     public MathNode()
     {
         Result = CreateFuncOutput<Float1>(nameof(Result), "RESULT", Calculate);
@@ -30,11 +32,12 @@ public class MathNode : Node
         Clamp = CreateInput(nameof(Clamp), "CLAMP", false);
         X = CreateFuncInput<Float1>(nameof(X), "X", 0d);
         Y = CreateFuncInput<Float1>(nameof(Y), "Y", 0d);
+        Z = CreateFuncInput<Float1>(nameof(Z), "Z", 0d);
     }
 
     private Float1 Calculate(FuncContext context)
     {
-        var (x, y) = GetValues(context);
+        var (x, y, z) = GetValues(context);
 
         if (context.HasContext)
         {
@@ -47,6 +50,27 @@ public class MathNode : Node
                 MathNodeMode.Sin => ShaderMath.Sin(x),
                 MathNodeMode.Cos => ShaderMath.Cos(x),
                 MathNodeMode.Tan => ShaderMath.Tan(x),
+                MathNodeMode.GreaterThan => ShaderMath.GreaterThan(x, y),
+                MathNodeMode.GreaterThanOrEqual => ShaderMath.GreaterThanOrEqual(x, y),
+                MathNodeMode.LessThan => ShaderMath.LessThan(x, y),
+                MathNodeMode.LessThanOrEqual => ShaderMath.LessThanOrEqual(x, y),
+                MathNodeMode.Compare => ShaderMath.Compare(x, y, z),
+                MathNodeMode.Power => ShaderMath.Power(x, y),
+                MathNodeMode.Logarithm => ShaderMath.Log(x, y),
+                MathNodeMode.NaturalLogarithm => ShaderMath.LogE(x),
+                MathNodeMode.Root => ShaderMath.Root(x, y),
+                MathNodeMode.InverseRoot => ShaderMath.InverseRoot(x, y),
+                MathNodeMode.Fraction => ShaderMath.Fraction(x),
+                MathNodeMode.Absolute => ShaderMath.Abs(x),
+                MathNodeMode.Negate => ShaderMath.Negate(x),
+                MathNodeMode.Floor => ShaderMath.Floor(x),
+                MathNodeMode.Ceil => ShaderMath.Ceil(x),
+                MathNodeMode.Round => ShaderMath.Round(x),
+                MathNodeMode.Modulo => ShaderMath.Modulo(x, y),
+                MathNodeMode.Min => ShaderMath.Min(x, y),
+                MathNodeMode.Max => ShaderMath.Max(x, y),
+                MathNodeMode.Step => ShaderMath.Step(x, y),
+                MathNodeMode.SmoothStep => ShaderMath.SmoothStep(x, y, z),
             };
 
             if (Clamp.Value)
@@ -59,7 +83,8 @@ public class MathNode : Node
 
         var xConst = x.ConstantValue;
         var yConst = y.ConstantValue;
-            
+        var zConst = z.ConstantValue;
+        
         var constValue = Mode.Value switch
         {
             MathNodeMode.Add => xConst + yConst,
@@ -69,14 +94,40 @@ public class MathNode : Node
             MathNodeMode.Sin => Math.Sin(xConst),
             MathNodeMode.Cos => Math.Cos(xConst),
             MathNodeMode.Tan => Math.Tan(xConst),
+            MathNodeMode.GreaterThan => xConst > yConst ? 1 : 0,
+            MathNodeMode.GreaterThanOrEqual => xConst >= yConst ? 1 : 0,
+            MathNodeMode.LessThan => xConst < yConst ? 1 : 0,
+            MathNodeMode.LessThanOrEqual => xConst <= yConst ? 1 : 0,
+            MathNodeMode.Compare => Math.Abs(xConst - yConst) < zConst ? 1 : 0,
+            MathNodeMode.Power => Math.Pow(xConst, yConst),
+            MathNodeMode.Logarithm => Math.Log(xConst, yConst),
+            MathNodeMode.NaturalLogarithm => Math.Log(xConst),
+            MathNodeMode.Root => Math.Pow(xConst, 1.0 / yConst),
+            MathNodeMode.InverseRoot => 1.0 / Math.Pow(xConst, 1.0 / yConst),
+            MathNodeMode.Fraction => 1.0 / xConst,
+            MathNodeMode.Absolute => Math.Abs(xConst),
+            MathNodeMode.Negate => -xConst,
+            MathNodeMode.Floor => Math.Floor(xConst),
+            MathNodeMode.Ceil => Math.Ceiling(xConst),
+            MathNodeMode.Round => Math.Round(xConst),
+            MathNodeMode.Modulo => xConst % yConst,
+            MathNodeMode.Min => Math.Min(xConst, yConst),
+            MathNodeMode.Max => Math.Max(xConst, yConst),
+            MathNodeMode.Step => xConst > yConst ? 1 : 0,
+            MathNodeMode.SmoothStep => MathEx.SmoothStep(xConst, yConst, zConst),
         };
+        
+        if (Clamp.Value)
+        {
+            constValue = Math.Clamp(constValue, 0, 1);
+        }
             
         return new Float1(string.Empty) { ConstantValue = constValue };
     }
 
-    private (Float1 xConst, Float1 y) GetValues(FuncContext context)
+    private (Float1 xConst, Float1 y, Float1 z) GetValues(FuncContext context)
     {
-        return (context.GetValue(X), context.GetValue(Y));
+        return (context.GetValue(X), context.GetValue(Y), context.GetValue(Z));
     }
 
 

+ 63 - 1
src/PixiEditor.ChangeableDocument/Enums/MathNodeMode.cs

@@ -18,9 +18,71 @@ public enum MathNodeMode
     Cos,
     [Description("TAN")]
     Tan,
+    [Description("GREATER_THAN")]
+    GreaterThan,
+    [Description("GREATER_THAN_OR_EQUAL")]
+    GreaterThanOrEqual,
+    [Description("LESS_THAN")]
+    LessThan,
+    [Description("LESS_THAN_OR_EQUAL")]
+    LessThanOrEqual,
+    [Description("COMPARE")]
+    Compare,
+    [Description("MATH_POWER")]
+    Power,
+    [Description("LOGARITHM")]
+    Logarithm,
+    [Description("NATURAL_LOGARITHM")]
+    NaturalLogarithm,
+    [Description("ROOT")]
+    Root,
+    [Description("INVERSE_ROOT")]
+    InverseRoot,
+    [Description("FRACTION")]
+    Fraction,
+    [Description("ABSOLUTE")]
+    Absolute,
+    [Description("NEGATE")]
+    Negate,
+    [Description("FLOOR")]
+    Floor,
+    [Description("CEIL")]
+    Ceil,
+    [Description("ROUND")]
+    Round,
+    [Description("MODULO")]
+    Modulo,
+    [Description("MIN")]
+    Min,
+    [Description("MAX")]
+    Max,
+    [Description("STEP")]
+    Step,
+    [Description("SMOOTH_STEP")]
+    SmoothStep,
 }
 
 public static class MathNodeModeExtensions
 {
-    public static bool UsesYValue(this MathNodeMode mode) => !(mode is >= MathNodeMode.Sin and <= MathNodeMode.Tan);
+    public static bool UsesYValue(this MathNodeMode mode) =>
+        mode != MathNodeMode.Sin &&
+        mode != MathNodeMode.Cos &&
+        mode != MathNodeMode.Tan &&
+        mode != MathNodeMode.Fraction &&
+        mode != MathNodeMode.Absolute &&
+        mode != MathNodeMode.Negate &&
+        mode != MathNodeMode.Floor &&
+        mode != MathNodeMode.Ceil &&
+        mode != MathNodeMode.Round &&
+        mode != MathNodeMode.NaturalLogarithm;
+
+
+    public static bool UsesZValue(this MathNodeMode mode) =>
+        mode is MathNodeMode.Compare or MathNodeMode.SmoothStep;
+
+    public static (string x, string y, string z) GetNaming(this MathNodeMode mode) => mode switch
+    {
+        MathNodeMode.Compare => ("VALUE", "TARGET", "EPSILON"),
+        _ => ("X", "Y", "Z")
+    };
 }

+ 10 - 0
src/PixiEditor.Common/MathEx.cs

@@ -0,0 +1,10 @@
+namespace PixiEditor.Common;
+
+public static class MathEx
+{
+    public static double SmoothStep(double edge0, double edge1, double x)
+    {
+        x = Math.Clamp((x - edge0) / (edge1 - edge0), 0, 1);
+        return x * x * (3 - 2 * x);
+    }
+}

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

@@ -743,6 +743,23 @@
   "SIN": "Sin",
   "COS": "Cos",
   "TAN": "Tan",
+  "GREATER_THAN": "Greater than",
+  "LESS_THAN": "Less than",
+  "LESS_THAN_OR_EQUAL": "Less than or equal",
+  "COMPARE": "Compare",
+  "MATH_POWER": "Power",
+  "LOGARITHM": "Logarithm",
+  "NATURAL_LOGARITHM": "Natural logarithm",
+  "ROOT": "Root",
+  "INVERSE_ROOT": "Inverse root",
+  "FRACTION": "Fraction",
+  "NEGATE": "Negate",
+  "FLOOR": "Floor",
+  "CEIL": "Ceil",
+  "ROUND": "Round",
+  "MODULO": "Modulo",
+  "STEP": "Step",
+  "SMOOTH_STEP": "Smoothstep",
   "PIXEL_ART_TOOLSET": "Pixel Art",
   "VECTOR_TOOLSET": "Vector",
   "VECTOR_LAYER": "Vector Layer",
@@ -810,5 +827,12 @@
   "PASTE_NODES_DESCRIPTIVE": "Paste copied nodes",
   "COPY_CELS": "Copy cels",
   "COPY_CELS_DESCRIPTIVE": "Copy selected cels",
-  "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "Toggle onion skinning"
+  "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "Toggle onion skinning",
+  "VALUE": "Value",
+  "TARGET": "Target",
+  "EPSILON": "Epsilon",
+  "PRESERVE_ALPHA": "Preserve alpha",
+  "BLUR_FILTER_NODE": "Gaussian Blur",
+  "LENGTH": "Length",
+  "GREATER_THAN_OR_EQUAL": "Greater than or equal"
 }

+ 0 - 14
src/PixiEditor/Helpers/MathUtil.cs

@@ -1,14 +0,0 @@
-namespace PixiEditor.Helpers;
-
-public static class MathUtil
-{
-    public static double DegreesToRadians(double angle)
-    {
-        return angle * Math.PI / 180;
-    }
-
-    public static double RadiansToDegrees(double angle)
-    {
-        return angle * 180 / Math.PI;
-    }
-}

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/FilterNodes/BlurNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.FilterNodes;
+
+[NodeViewModel("BLUR_FILTER_NODE", "FILTERS", null)] // TODO: Blur Icon
+internal class BlurNodeViewModel : NodeViewModel<BlurNode>;

+ 20 - 2
src/PixiEditor/ViewModels/Document/Nodes/MathNodeViewModel.cs

@@ -14,22 +14,40 @@ internal class MathNodeViewModel : NodeViewModel<MathNode>
 {
     private GenericEnumPropertyViewModel Mode { get; set; }
     
+    private NodePropertyViewModel X { get; set; }
+    
     private NodePropertyViewModel Y { get; set; }
     
+    private NodePropertyViewModel Z { get; set; }
+    
     public override void OnInitialized()
     {
         Mode = FindInputProperty("Mode") as GenericEnumPropertyViewModel;
+        X = FindInputProperty("X");
         Y = FindInputProperty("Y");
+        Z = FindInputProperty("Z");
         
-        Mode.ValueChanged += ModeChanged;
+        Mode.ValueChanged += (_, _) => ModeChanged();
+        ModeChanged();
     }
 
-    private void ModeChanged(INodePropertyHandler property, NodePropertyValueChangedArgs args)
+    private void ModeChanged()
     {
         if (Mode.Value is not MathNodeMode mode)
             return;
 
         DisplayName = mode.GetDescription();
         Y.IsVisible = mode.UsesYValue();
+        Z.IsVisible = mode.UsesZValue();
+
+        var (x, y, z) = mode.GetNaming();
+
+        x = new LocalizedString(x);
+        y = new LocalizedString(y);
+        z = new LocalizedString(z);
+
+        X.DisplayName = x;
+        Y.DisplayName = y;
+        Z.DisplayName = z;
     }
 }

+ 12 - 5
src/PixiEditor/Views/Nodes/Properties/GenericEnumPropertyView.axaml

@@ -12,10 +12,17 @@
     <Design.DataContext>
         <properties1:GenericEnumPropertyViewModel />
     </Design.DataContext>
-    
-    <Grid HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
+
+    <Grid
+        HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
+        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
         <ComboBox HorizontalAlignment="Right" MinWidth="100" IsVisible="{Binding ShowInputField}"
-                  SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" ItemsSource="{Binding Values}" />
+                  SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" ItemsSource="{Binding Values}">
+            <ComboBox.ItemTemplate>
+                <DataTemplate>
+                    <TextBlock ui:Translator.Key="{Binding Converter={converters:EnumToLocalizedStringConverter}}" />
+                </DataTemplate>
+            </ComboBox.ItemTemplate>
+        </ComboBox>
     </Grid>
-</properties:NodePropertyView>
+</properties:NodePropertyView>

+ 1 - 1
src/PixiParser

@@ -1 +1 @@
-Subproject commit 345e15022ad031e97907bdeba8a91cc4c7fd2a8d
+Subproject commit 357930a8fcc6669df3a4fb6411bdbf7ad3f4e7e6