소스 검색

Merge pull request #1150 from starman/posterization-node

Add posterization node
Krzysztof Krysiński 2 일 전
부모
커밋
aca54fa8df

+ 166 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/PosterizationNode.cs

@@ -0,0 +1,166 @@
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.ColorSpaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Effects;
+
+[NodeInfo("Posterization")]
+public class PosterizationNode : RenderNode, IRenderInput
+{
+    public RenderInputProperty Background { get; }
+    public InputProperty<PosterizationMode> Mode { get; }
+    public InputProperty<int> Levels { get; }
+    public InputProperty<ColorSpaceType> PosterizationColorSpace { get; }
+    
+    private Paint paint;
+    private Shader shader;
+    
+    private Shader? lastImageShader;
+    private VecI lastDocumentSize;
+    
+    private string shaderCode = """
+                                 uniform shader iImage;
+                                 uniform int iMode;
+                                 uniform float iLevels;
+                                 
+                                 half posterize(half value, float levels) {
+                                     return clamp(floor(value * (levels - 1) + 0.5) / (levels - 1), 0.0, 1.0);
+                                 }
+                                 
+                                 half4 posterizeRgb(half4 color, float levels) {
+                                     return half4(
+                                         posterize(color.r, levels),
+                                         posterize(color.g, levels),
+                                         posterize(color.b, levels),
+                                         color.a
+                                     );
+                                 }
+                                 
+                                 half4 posterizeLuminance(half4 color, float levels) {
+                                     half lum = dot(color.rgb, half3(0.299, 0.587, 0.114));
+                                     half posterizedLum = posterize(lum, levels);
+                                     return half4(posterizedLum, posterizedLum, posterizedLum, color.a);
+                                 }
+                                 
+                                 half4 main(float2 uv)
+                                 {
+                                    half4 color = iImage.eval(uv);
+                                    half4 result;
+                                    
+                                    if(iMode == 0) {
+                                        result = posterizeRgb(color, iLevels);
+                                    } else if(iMode == 1) {
+                                        result = posterizeLuminance(color, iLevels);
+                                    } 
+                                    
+                                    return result;
+                                 }
+                                 """;
+
+    protected override bool ExecuteOnlyOnCacheChange => true;
+
+    public PosterizationNode()
+    {
+        Background = CreateRenderInput("Background", "BACKGROUND");
+        Mode = CreateInput("Mode", "MODE", PosterizationMode.Rgb);
+        Levels = CreateInput("Levels", "LEVELS", 8)
+            .WithRules(v => v.Min(2).Max(256));
+        PosterizationColorSpace = CreateInput("PosterizationColorSpace", "COLOR_SPACE", ColorSpaceType.Srgb);
+        
+        paint = new Paint();
+        paint.BlendMode = BlendMode.Src;
+        Output.FirstInChain = null;
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        base.OnExecute(context);
+        lastDocumentSize = context.RenderOutputSize;
+        
+        Uniforms uniforms = new Uniforms();
+        uniforms.Add("iImage", new Uniform("iImage", lastImageShader));
+        uniforms.Add("iMode", new Uniform("iMode", (int)Mode.Value));
+        uniforms.Add("iLevels", new Uniform("iLevels", (float)Levels.Value));
+        shader?.Dispose();
+        shader = Shader.Create(shaderCode, uniforms, out _);
+    }
+    
+    protected override void OnPaint(RenderContext context, DrawingSurface surface)
+    {
+        if (Background.Value == null)
+        {
+            return;
+        }
+
+        ColorSpace colorSpace;
+        switch (PosterizationColorSpace.Value)
+        {
+            case ColorSpaceType.Srgb:
+                colorSpace = ColorSpace.CreateSrgb();
+                break;
+            case ColorSpaceType.LinearSrgb:
+                colorSpace = ColorSpace.CreateSrgbLinear();
+                break;
+            case ColorSpaceType.Inherit:
+                colorSpace = context.ProcessingColorSpace;
+                break;
+            default:
+                colorSpace = ColorSpace.CreateSrgb();
+                break;
+        }
+        
+        using Texture temp = Texture.ForProcessing(surface, colorSpace);
+        Background.Value.Paint(context, temp.DrawingSurface);
+        var snapshot = temp.DrawingSurface.Snapshot();
+        
+        lastImageShader?.Dispose();
+        lastImageShader = snapshot.ToShader();
+
+        Uniforms uniforms = new Uniforms();
+        uniforms.Add("iImage", new Uniform("iImage", lastImageShader));
+        uniforms.Add("iMode", new Uniform("iMode", (int)Mode.Value));
+        uniforms.Add("iLevels", new Uniform("iLevels", (float)Levels.Value));
+        shader = shader.WithUpdatedUniforms(uniforms);
+        paint.Shader = shader;
+        snapshot.Dispose();
+        
+        var savedTemp = temp.DrawingSurface.Canvas.Save();
+        temp.DrawingSurface.Canvas.SetMatrix(Matrix3X3.Identity);
+        temp.DrawingSurface.Canvas.DrawRect(0, 0, context.RenderOutputSize.X, context.RenderOutputSize.Y, paint);
+        temp.DrawingSurface.Canvas.RestoreToCount(savedTemp);
+        
+        var saved = surface.Canvas.Save();
+        surface.Canvas.SetMatrix(Matrix3X3.Identity);
+        surface.Canvas.DrawSurface(temp.DrawingSurface, 0, 0);
+        surface.Canvas.RestoreToCount(saved);
+    }
+    
+    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    {
+        return new RectD(0, 0, lastDocumentSize.X, lastDocumentSize.Y);
+    }
+
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    {
+        OnPaint(context, renderOn);
+        return true;
+    }
+
+    public override Node CreateCopy()
+    {
+        return new PosterizationNode();
+    }
+}
+
+public enum PosterizationMode
+{
+    Rgb = 0,
+    Luminance = 1,
+}

+ 4 - 0
src/PixiEditor/Data/Localization/Languages/en.json

@@ -848,6 +848,10 @@
     "TYPE": "Type",
     "EFFECTS": "Effects",
     "OUTLINE_NODE": "Outline",
+    "POSTERIZATION_NODE": "Posterize",
+    "RGB_POSTERIZATION_MODE": "RGB",
+    "LUMINANCE_POSTERIZATION_MODE": "Luminance",
+    "LEVELS": "Levels",
     "SHADER_CODE": "Shader Code",
     "SHADER_NODE": "Shader",
     "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "Failed to open file",

+ 3 - 0
src/PixiEditor/Models/EnumTranslations.cs

@@ -99,6 +99,9 @@ using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 [assembly: LocalizeEnum<OutlineType>(OutlineType.Gaussian, "GAUSSIAN_OUTLINE_TYPE")]
 [assembly: LocalizeEnum<OutlineType>(OutlineType.PixelPerfect, "PIXEL_PERFECT_OUTLINE_TYPE")]
 
+[assembly: LocalizeEnum<PosterizationMode>(PosterizationMode.Rgb, "RGB_POSTERIZATION_MODE")]
+[assembly: LocalizeEnum<PosterizationMode>(PosterizationMode.Luminance, "LUMINANCE_POSTERIZATION_MODE")]
+
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F1, "F1_VORONOI_FEATURE")]
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2, "F2_VORONOI_FEATURE")]
 [assembly: LocalizeEnum<VoronoiFeature>(VoronoiFeature.F2MinusF1, "F2_MINUS_F1_VORONOI_FEATURE")]

+ 11 - 0
src/PixiEditor/ViewModels/Document/Nodes/Effects/PosterizationNodeViewModel.cs

@@ -0,0 +1,11 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Effects;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Effects;
+
+[NodeViewModel("POSTERIZATION_NODE", "EFFECTS", PixiPerfectIcons.Swatches)]
+internal class PosterizationNodeViewModel : NodeViewModel<PosterizationNode>
+{
+    
+}
+