Browse Source

Added grayscale node

CPKreuz 1 year ago
parent
commit
5245540939

+ 59 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/GrayscaleNode.cs

@@ -0,0 +1,59 @@
+using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
+
+[NodeInfo("GrayscaleFilter", "GRAYSCALE_FILTER_NODE")]
+public class GrayscaleNode : FilterNode
+{
+    public InputProperty<GrayscaleMode> Mode { get; }
+    
+    public InputProperty<bool> Normalize { get; }
+
+    // TODO: Hide when Mode != Custom
+    public InputProperty<VecD3> CustomWeight { get; }
+    
+    public GrayscaleNode()
+    {
+        Mode = CreateInput("Mode", "MODE", GrayscaleMode.Weighted);
+        Normalize = CreateInput("Normalize", "NORMALIZE", true);
+        CustomWeight = CreateInput("CustomWeight", "WEIGHT", new VecD3(1, 1, 1));
+    }
+
+    protected override ColorFilter GetColorFilter() => ColorFilter.CreateColorMatrix(Mode.Value switch
+    {
+        GrayscaleMode.Weighted => ColorMatrix.WeightedWavelengthGrayscale + ColorMatrix.UseAlpha,
+        GrayscaleMode.Average => ColorMatrix.AverageGrayscale + ColorMatrix.UseAlpha,
+        GrayscaleMode.Custom => ColorMatrix.WeightedGrayscale(GetAdjustedCustomWeight()) + ColorMatrix.UseAlpha
+    });
+
+    private VecD3 GetAdjustedCustomWeight()
+    {
+        var weight = CustomWeight.Value;
+        var normalize = Normalize.Value;
+
+        if (!normalize)
+        {
+            return weight;
+        }
+
+        var sum = weight.Sum();
+
+        if (sum == 0)
+        {
+            return VecD3.Zero;
+        }
+            
+        return weight / weight.Sum();
+
+    }
+
+    public override Node CreateCopy() => new GrayscaleNode();
+
+    public enum GrayscaleMode
+    {
+        Weighted,
+        Average,
+        Custom
+    }
+}

+ 20 - 5
src/PixiEditor.Numerics/ColorMatrix.cs

@@ -138,18 +138,33 @@ public record struct ColorMatrix
     /// (_, _, _, w) => (0, 0, 0, w + 1)
     /// </summary>
     public static ColorMatrix OpaqueAlphaOffset => Offset(0, 0, 0, 1);
-    
+
     /// <summary>
     /// The rgb values become averaged into a grayscale image. Alpha becomes zero <br/>
     /// (r, g, b, _) => (r, g, b, 0) / 3
     /// </summary>
-    public static ColorMatrix AverageGrayscale => new(
-        (1 / 3f, 1 / 3f, 1 / 3f, 0, 0),
-        (1 / 3f, 1 / 3f, 1 / 3f, 0, 0),
-        (1 / 3f, 1 / 3f, 1 / 3f, 0, 0),
+    public static ColorMatrix AverageGrayscale { get; } = WeightedGrayscale(1 / 3f, 1 / 3f, 1 / 3f, 0);
+
+    public static ColorMatrix WeightedWavelengthGrayscale { get; } = WeightedGrayscale(0.299f, 0.587f, 0.114f, 0);
+
+    /// <summary>
+    /// The rgb values become grayscale according to the weights image. Alpha becomes zero <br/>
+    /// (r, g, b, a) => (rgb: r * rWeight + g * gWeight + b * bWeight + a * aWeight, 0)
+    /// </summary>
+    public static ColorMatrix WeightedGrayscale(float rWeight, float gWeight, float bWeight, float aWeight) => new(
+        (rWeight, gWeight, bWeight, aWeight, 0),
+        (rWeight, gWeight, bWeight, aWeight, 0),
+        (rWeight, gWeight, bWeight, aWeight, 0),
         (0, 0, 0, 0, 0)
     );
 
+    /// <summary>
+    /// The rgb values become grayscale according to the weights image. Alpha becomes zero <br/>
+    /// (r, g, b, a) => (rgb: r * rWeight + g * gWeight + b * bWeight + a * aWeight, 0)
+    /// </summary>
+    public static ColorMatrix WeightedGrayscale(VecD3 vector) =>
+        WeightedGrayscale((float)vector.X, (float)vector.Y, (float)vector.Z, 0);
+    
     public static ColorMatrix operator +(ColorMatrix left, ColorMatrix right) => new(left.M11 + right.M11,
         left.M12 + right.M12, left.M13 + right.M13, left.M14 + right.M14, left.M15 + right.M15, left.M21 + right.M21,
         left.M22 + right.M22, left.M23 + right.M23, left.M24 + right.M24, left.M25 + right.M25, left.M31 + right.M31,

+ 172 - 0
src/PixiEditor.Numerics/VecD3.cs

@@ -0,0 +1,172 @@
+namespace PixiEditor.Numerics;
+
+public struct VecD3
+{
+    public double X { get; set; }
+    public double Y { get; set; }
+    public double Z { get; set; }
+
+    public VecD XY => new(X, Y);
+
+    public double TaxicabLength => Math.Abs(X) + Math.Abs(Y) + Math.Abs(Z);
+    public double Length => Math.Sqrt(LengthSquared);
+    public double LengthSquared => X * X + Y * Y + Z * Z;
+
+    public static VecD3 Zero { get; } = new(0, 0, 0);
+
+    public VecD3(double x, double y, double z)
+    {
+        X = x;
+        Y = y;
+        Z = z;
+    }
+
+    public VecD3(VecD xy, double z) : this(xy.X, xy.Y, z)
+    {
+    }
+
+    public VecD3(double bothAxesValue) : this(bothAxesValue, bothAxesValue, bothAxesValue)
+    {
+    }
+    
+    public VecD3 Round()
+    {
+        return new VecD3(Math.Round(X), Math.Round(Y), Math.Round(Z));
+    }
+    public VecD3 Ceiling()
+    {
+        return new VecD3(Math.Ceiling(X), Math.Ceiling(Y), Math.Ceiling(Z));
+    }
+    public VecD3 Floor()
+    {
+        return new VecD3(Math.Floor(X), Math.Floor(Y), Math.Floor(Z));
+    }
+    
+    public VecD3 Lerp(VecD3 other, double factor)
+    {
+        return (other - this) * factor + this;
+    }
+    
+    public VecD3 Normalize()
+    {
+        return new VecD3(X / Length, Y / Length, Z / Length);
+    }
+    
+    public VecD3 Abs()
+    {
+        return new VecD3(Math.Abs(X), Math.Abs(Y), Math.Abs(Z));
+    }
+    
+    public VecD3 Signs()
+    {
+        return new VecD3(X >= 0 ? 1 : -1, Y >= 0 ? 1 : -1, Z >= 0 ? 1 : -1);
+    }
+    
+    public double Dot(VecD3 other) => (X * other.X) + (Y * other.Y) + (Z * other.Z);
+    
+    public VecD3 Multiply(VecD3 other)
+    {
+        return new VecD3(X * other.X, Y * other.Y, Z * other.Z);
+    }
+    
+    public VecD3 Divide(VecD3 other)
+    {
+        return new VecD3(X / other.X, Y / other.Y, Z / other.Z);
+    }
+    
+    public static VecD3 operator +(VecD3 a, VecD3 b)
+    {
+        return new VecD3(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
+    }
+    
+    public static VecD3 operator -(VecD3 a, VecD3 b)
+    {
+        return new VecD3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
+    }
+
+    public static VecD3 operator -(VecD3 a)
+    {
+        return new VecD3(-a.X, -a.Y, -a.Z);
+    }
+    
+    public static VecD3 operator *(double b, VecD3 a)
+    {
+        return new VecD3(a.X * b, a.Y * b, a.Z * b);
+    }
+
+    public static double operator *(VecD3 a, VecD3 b) => a.Dot(b);
+    
+    public static VecD3 operator *(VecD3 a, double b)
+    {
+        return new VecD3(a.X * b, a.Y * b, a.Z * b);
+    }
+    
+    public static VecD3 operator /(VecD3 a, double b)
+    {
+        return new VecD3(a.X / b, a.Y / b, a.Z / b);
+    }
+    
+    public static VecD3 operator %(VecD3 a, double b)
+    {
+        return new VecD3(a.X % b, a.Y % b, a.Z % b);
+    }
+
+    public static bool operator ==(VecD3 a, VecD3 b)
+    {
+        return a.X == b.X && a.Y == b.Y && a.Z == b.Z;
+    }
+    public static bool operator !=(VecD3 a, VecD3 b)
+    {
+        return !(a.X == b.X && a.Y == b.Y && a.Z == b.Z);
+    }
+
+    public double Sum() => X + Y + Z;
+
+    public static implicit operator VecD3((double, double, double, double) tuple)
+    {
+        return new VecD3(tuple.Item1, tuple.Item2, tuple.Item3);
+    }
+    
+    public void Deconstruct(out double x, out double y, out double z)
+    {
+        x = X;
+        y = Y;
+        z = Z;
+    }
+    
+    public bool IsNaNOrInfinity()
+    {
+        return double.IsNaN(X) || double.IsNaN(Y) || double.IsInfinity(X) || double.IsInfinity(Y) || double.IsNaN(Z) || double.IsInfinity(Z);
+    }
+
+    public override string ToString()
+    {
+        return $"({X}; {Y}; {Z})";
+    }
+
+    public override bool Equals(object? obj)
+    {
+        if (obj is not VecD3 item)
+            return false;
+        
+        return this == (VecD3?)item;
+    }
+
+    public override int GetHashCode()
+    {
+        return HashCode.Combine(X, Y, Z);
+    }
+
+    public bool Equals(VecD3 other)
+    {
+        return other.X == X && other.Y == Y && other.Z == Z;
+    }
+
+    public bool AlmostEquals(VecD3 other, double axisEpsilon = 0.001)
+    {
+        double dX = Math.Abs(X - other.X);
+        double dY = Math.Abs(Y - other.Y);
+        double dZ = Math.Abs(Z - other.Z);
+        return dX < axisEpsilon && dY < axisEpsilon && dZ < axisEpsilon;
+    }
+}

+ 42 - 0
src/PixiEditor/ViewModels/Nodes/Properties/VecD3PropertyViewModel.cs

@@ -0,0 +1,42 @@
+using System.ComponentModel;
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ViewModels.Nodes.Properties;
+
+internal class VecD3PropertyViewModel : NodePropertyViewModel<VecD3>
+{
+    public VecD3PropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
+    {
+        PropertyChanged += OnPropertyChanged;
+    }
+    
+    private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+    {
+        if (e.PropertyName != nameof(Value))
+        {
+            return;
+        }
+        
+        OnPropertyChanged(nameof(XValue));
+        OnPropertyChanged(nameof(YValue));
+        OnPropertyChanged(nameof(ZValue));
+    }
+
+    public double XValue
+    {
+        get => Value.X;
+        set => Value = new VecD3(value, Value.Y, Value.Z);
+    }
+    
+    public double YValue
+    {
+        get => Value.Y;
+        set => Value = new VecD3(Value.X, value, Value.Z);
+    }
+    
+    public double ZValue
+    {
+        get => Value.Z;
+        set => Value = new VecD3(Value.X, Value.Y, value);
+    }
+}

+ 19 - 0
src/PixiEditor/Views/Nodes/Properties/VecD3PropertyView.axaml

@@ -0,0 +1,19 @@
+<properties:NodePropertyView xmlns="https://github.com/avaloniaui"
+                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+                             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+                             xmlns:properties="clr-namespace:PixiEditor.Views.Nodes.Properties"
+                             xmlns:input="clr-namespace:PixiEditor.Views.Input"
+                             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+                             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+                             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+                             x:Class="PixiEditor.Views.Nodes.Properties.VecD3PropertyView">
+    <StackPanel HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
+        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
+        <StackPanel IsVisible="{Binding ShowInputField}">
+            <input:NumberInput EnableScrollChange="False" MinWidth="100" Value="{Binding XValue, Mode=TwoWay}" Margin="0,2" />
+            <input:NumberInput EnableScrollChange="False" MinWidth="100" Value="{Binding YValue, Mode=TwoWay}" Margin="0,2" />
+            <input:NumberInput EnableScrollChange="False" MinWidth="100" Value="{Binding ZValue, Mode=TwoWay}" Margin="0,2" />
+        </StackPanel>
+    </StackPanel>
+</properties:NodePropertyView>

+ 14 - 0
src/PixiEditor/Views/Nodes/Properties/VecD3PropertyView.axaml.cs

@@ -0,0 +1,14 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PixiEditor.Views.Nodes.Properties;
+
+public partial class VecD3PropertyView : NodePropertyView
+{
+    public VecD3PropertyView()
+    {
+        InitializeComponent();
+    }
+}
+