Search code examples
wpfxamlgradientwpf-4.0wpf-brushes

AngleGradient in WPF


As you know, there is a gradient option in PhotoShop named AngleGradient. But, WPF has only LinearGradientBrush and RadialGradientBrush gradients. Have you any idea to how to create an AngleGradient by using XAML?

UPDATE: Samples -by photoshop:

enter image description here


Solution

  • I wrote a shader for this, compile it and add the .ps file as a resource to your project:

    // no input texture, the output is completely generated in code
    sampler2D  inputSampler : register(S0);
    /// <summary>The center of the gradient. </summary>
    /// <minValue>0,0</minValue>
    /// <maxValue>1,1</maxValue>
    /// <defaultValue>.5,.5</defaultValue>
    float2 centerPoint : register(C0);
    
    /// <summary>The primary color of the gradient. </summary>
    /// <defaultValue>Blue</defaultValue>
    float4 primaryColor : register(C1);
    
    /// <summary>The secondary color of the gradient. </summary>
    /// <defaultValue>Red</defaultValue>
    float4 secondaryColor : register(C2);
    
    float4 main(float2 uv : TEXCOORD) : COLOR
    {
        float4 src= tex2D(inputSampler, uv);
        float2 p = float2(centerPoint)-uv;
        float angle = (atan2(p.x, p.y) + 3.141596) / (2 * 3.141596);
        float3 f = lerp(primaryColor.rgb, secondaryColor.rgb, angle);
        float4 color = float4(src.a < 0.01 
                                    ? float3(0, 0, 0) // WPF uses pre-multiplied alpha everywhere internally for a number of performance reasons.
                                    : f, src.a < 0.01 ? 0 : 1);
        return color;
    }
    

    Wrap it like this:

    public class AngularGradientEffect : ShaderEffect {
        public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty(
            "Input", 
            typeof(AngularGradientEffect),
            0);
    
        public static readonly DependencyProperty CenterPointProperty = DependencyProperty.Register(
            "CenterPoint",
            typeof(Point),
            typeof(AngularGradientEffect), 
            new UIPropertyMetadata(new Point(0.5D, 0.5D), PixelShaderConstantCallback(0)));
    
        public static readonly DependencyProperty PrimaryColorProperty = DependencyProperty.Register(
            "PrimaryColor", 
            typeof(Color), 
            typeof(AngularGradientEffect), 
            new UIPropertyMetadata(Color.FromArgb(255, 0, 0, 255), PixelShaderConstantCallback(1)));
    
        public static readonly DependencyProperty SecondaryColorProperty = DependencyProperty.Register(
            "SecondaryColor", 
            typeof(Color),
            typeof(AngularGradientEffect), 
            new UIPropertyMetadata(Color.FromArgb(255, 255, 0, 0), PixelShaderConstantCallback(2)));
    
        public AngularGradientEffect() {
            PixelShader pixelShader = new PixelShader();
            pixelShader.UriSource = new Uri("/So.Wpf;component/Effects/AngularGradientEffect.ps", UriKind.Relative);
            this.PixelShader = pixelShader;
    
            this.UpdateShaderValue(InputProperty);
            this.UpdateShaderValue(CenterPointProperty);
            this.UpdateShaderValue(PrimaryColorProperty);
            this.UpdateShaderValue(SecondaryColorProperty);
        }
        public Brush Input {
            get {
                return ((Brush)(this.GetValue(InputProperty)));
            }
            set {
                this.SetValue(InputProperty, value);
            }
        }
        /// <summary>The center of the gradient. </summary>
        public Point CenterPoint {
            get {
                return ((Point)(this.GetValue(CenterPointProperty)));
            }
            set {
                this.SetValue(CenterPointProperty, value);
            }
        }
        /// <summary>The primary color of the gradient. </summary>
        public Color PrimaryColor {
            get {
                return ((Color)(this.GetValue(PrimaryColorProperty)));
            }
            set {
                this.SetValue(PrimaryColorProperty, value);
            }
        }
        /// <summary>The secondary color of the gradient. </summary>
        public Color SecondaryColor {
            get {
                return ((Color)(this.GetValue(SecondaryColorProperty)));
            }
            set {
                this.SetValue(SecondaryColorProperty, value);
            }
        }
    }
    

    Use it in Xaml like this, the effect replaces all non transparent pixels with the gradient, hence the shape must have a color, any color:

    <Ellipse x:Name="ShaderEllipse" Fill="Transparent" Stroke="Blue" StrokeThickness="15">
        <Ellipse.Effect>
            <effects:AngularGradientEffect PrimaryColor="Red" 
                                           SecondaryColor="Transparent"/>
        </Ellipse.Effect>
    </Ellipse>
    

    And the output is:

    enter image description here

    Added it to this lib