Support more than one color input in my Pixel Shad

2019-07-21 06:26发布

问题:

I've been working on an app that can provide Color Replacement, and had a lot of help from @Jet Chopper on a solution. He's provided me the following code which essentially uses a ControlSpectrum control for Source and Target colors. The idea is you specify a Source Color which then gets replaced by a Target color. Here's the current working code:

This is my original post that contains the original solution with a GIF. Original Post

XAML:

<Grid>
    <xaml:CanvasAnimatedControl x:Name="AnimatedControl"
                            CreateResources="AnimatedControl_OnCreateResources"
                            Draw="AnimatedControl_OnDraw"/>

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Bottom">
        <TextBlock Text="Source Color" FontSize="32" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="256" Height="256" ColorChanged="SourceColorSpectrum_OnColorChanged"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom">
        <TextBlock Text="Replace Color" FontSize="32" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="256" Height="256" ColorChanged="TargetColorSpectrum_OnColorChanged"/>
    </StackPanel>

    <Slider Width="512" ValueChanged="RangeBase_OnValueChanged" VerticalAlignment="Center"/>
</Grid>

CODE:

private PixelShaderEffect _textureShader;
private GaussianBlurEffect _blur;
private BlendEffect _blend;

private void AnimatedControl_OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
{
    args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}

private async Task CreateResourcesAsync(CanvasAnimatedControl sender)
{
    var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Shaders/TextureTest.bin"));
    var buffer = await FileIO.ReadBufferAsync(file);

    var sourceImage = await CanvasBitmap.LoadAsync(sender, new Uri("ms-appx:///Assets/image.jpg"));

    _textureShader = new PixelShaderEffect(buffer.ToArray())
    {
        Source1 = sourceImage
    };

    _blur = new GaussianBlurEffect
    {
        BlurAmount = 4,
        Source = _textureShader
    };

    _blend = new BlendEffect
    {
        Foreground = _blur,
        Background = sourceImage,
        Mode = BlendEffectMode.Color
    };
}

private void AnimatedControl_OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
    args.DrawingSession.DrawImage(_blend);
}

private void RangeBase_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    _textureShader.Properties["threshold"] = (float)e.NewValue / 100;
}

private void SourceColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["sourceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

private void TargetColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["replaceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

PIXEL SHADER:

#define D2D_INPUT_COUNT 1
#define D2D_INPUT0_SIMPLE

#include "d2d1effecthelpers.hlsli"

float3 sourceColor;
float3 replaceColor;
float threshold;

D2D_PS_ENTRY(main)
{
    float3 color = D2DGetInput(0).rgb;

    if (abs(color.r - sourceColor.r) < threshold && abs(color.g - sourceColor.g) < threshold && abs(color.b - sourceColor.b) < threshold) 
    {
        float3 newColor = color - sourceColor + replaceColor;
        return float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else 
    {
        return float4(0, 0, 0, 0);
    }
}

So my next step was to take this solution one step forward and introduce more that one color replacement simulataneously. So I changed everything to support 2 colors as such:

MY CHANGES

XAML:

<Grid>
    <xaml:CanvasAnimatedControl x:Name="AnimatedControl"
                            CreateResources="AnimatedControl_OnCreateResources"
                            Draw="AnimatedControl_OnDraw"/>

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Bottom">
        <TextBlock Text="Source Color" FontSize="32" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="256" Height="256" ColorChanged="SourceColorSpectrum_OnColorChanged"/>
        <ColorSpectrum Width="256" Height="256" ColorChanged="SourceColorSpectrum_OnColorChanged2"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom">
        <TextBlock Text="Replace Color" FontSize="32" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="256" Height="256" ColorChanged="TargetColorSpectrum_OnColorChanged"/>
        <ColorSpectrum Width="256" Height="256" ColorChanged="TargetColorSpectrum_OnColorChanged2"/>
    </StackPanel>

    <Slider Width="512" ValueChanged="RangeBase_OnValueChanged" VerticalAlignment="Center"/>
</Grid>

CODE:

private PixelShaderEffect _textureShader;
private GaussianBlurEffect _blur;
private BlendEffect _blend;

private void AnimatedControl_OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
{
    args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}

private async Task CreateResourcesAsync(CanvasAnimatedControl sender)
{
    var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Shaders/TextureTest.bin"));
    var buffer = await FileIO.ReadBufferAsync(file);

    var sourceImage = await CanvasBitmap.LoadAsync(sender, new Uri("ms-appx:///Assets/image.jpg"));

    _textureShader = new PixelShaderEffect(buffer.ToArray())
    {
        Source1 = sourceImage,
        Source2 = sourceImage
    };

    _blur = new GaussianBlurEffect
    {
        BlurAmount = 4,
        Source = _textureShader
    };

    _blend = new BlendEffect
    {
        Foreground = _blur,
        Background = sourceImage,
        Mode = BlendEffectMode.Color
    };
}

private void AnimatedControl_OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
    args.DrawingSession.DrawImage(_blend);
}

private void RangeBase_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    _textureShader.Properties["threshold"] = (float)e.NewValue / 100;
}

private void SourceColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["sourceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

private void TargetColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["replaceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

private void SourceColorSpectrum_OnColorChanged2(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["sourceColor2"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

private void TargetColorSpectrum_OnColorChanged2(ColorSpectrum sender, ColorChangedEventArgs args)
{
    _textureShader.Properties["replaceColor2"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
}

PIXEL SHADER:

#define D2D_INPUT_COUNT 2
#define D2D_INPUT0_SIMPLE
#define D2D_INPUT1_SIMPLE

#include "d2d1effecthelpers.hlsli"

float3 sourceColor;
float3 replaceColor;
float3 sourceColor2;
float3 replaceColor2;
float threshold;

D2D_PS_ENTRY(main)
{
    float3 color1 = D2DGetInput(0).rgb;
    float3 color2 = D2DGetInput(1).rgb;

    float4 result1;
    float4 result2;

    if (abs(color1.r - sourceColor.r) < threshold &&
        abs(color1.g - sourceColor.g) < threshold &&
        abs(color1.b - sourceColor.b) < threshold) 
    {
        float3 newColor = color1 - sourceColor + replaceColor;
        result1 = float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else 
    {
        result1 = float4(0, 0, 0, 0);
    }

    if (abs(color2.r - sourceColor2.r) < threshold &&
        abs(color2.g - sourceColor2.g) < threshold &&
        abs(color2.b - sourceColor2.b) < threshold)
    {
        float3 newColor = color2 - sourceColor2 + replaceColor2;
        result2 = float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else
    {
        result2 = float4(0, 0, 0, 0);
    }

    return result1 * result2;
}

So essentially I just doubled everything, in the XAML, the code, and the Pixel Shader. But for the Pixel Shader, I'm not sure if my return value is correct by multiplying both results. Am I on the right track for the ability to replace more than one color at once ?

回答1:

Ok here's your sample with 2 input colors, 2 replace colors and 2 thresholds.

XAML:

<Grid>
    <xaml:CanvasAnimatedControl x:Name="AnimatedControl"
                            CreateResources="AnimatedControl_OnCreateResources"
                            Draw="AnimatedControl_OnDraw"/>

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Bottom">
        <TextBlock Text="Source Color 2" FontSize="16" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="128" Height="128" ColorChanged="ColorSpectrum_OnColorChanged2"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom">
        <TextBlock Text="Replace Color 2" FontSize="16" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="128" Height="128" ColorChanged="ColorSpectrum_OnColorChanged3"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
        <TextBlock Text="Source Color 1" FontSize="16" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="128" Height="128" ColorChanged="ColorSpectrum_OnColorChanged"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Right" VerticalAlignment="Top">
        <TextBlock Text="Replace Color 1" FontSize="16" Foreground="White" TextAlignment="Center"/>

        <ColorSpectrum Width="128" Height="128" ColorChanged="ColorSpectrum_OnColorChanged1"/>
    </StackPanel>

    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Slider Width="512" ValueChanged="RangeBase_OnValueChanged" VerticalAlignment="Center"/>
    <Slider Width="512" ValueChanged="RangeBase1_OnValueChanged" VerticalAlignment="Center"/>
    </StackPanel>
</Grid>

Code behind:

    private PixelShaderEffect _textureShader;
    private GaussianBlurEffect _blur;
    private BlendEffect _blend;

    public Ripple()
    {
        InitializeComponent();
    }

    private void AnimatedControl_OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
    {
        args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
    }

    private async Task CreateResourcesAsync(CanvasAnimatedControl sender)
    {
        var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Shaders/TextureTest.bin"));
        var buffer = await FileIO.ReadBufferAsync(file);

        var sourceImage = await CanvasBitmap.LoadAsync(sender, new Uri("ms-appx:///Assets/image.jpg"));

        _textureShader = new PixelShaderEffect(buffer.ToArray())
        {
            Source1 = sourceImage
        };

        _blur = new GaussianBlurEffect
        {
            BlurAmount = 4,
            Source = _textureShader
        };

        _blend = new BlendEffect
        {
            Foreground = _blur,
            Background = sourceImage,
            Mode = BlendEffectMode.Color
        };
    }

    private void AnimatedControl_OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
    {
        args.DrawingSession.DrawImage(_blend);
    }

    private void RangeBase_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        _textureShader.Properties["threshold"] = (float)e.NewValue / 100;
    }

    private void ColorSpectrum_OnColorChanged(ColorSpectrum sender, ColorChangedEventArgs args)
    {
        _textureShader.Properties["sourceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
    }

    private void ColorSpectrum_OnColorChanged1(ColorSpectrum sender, ColorChangedEventArgs args)
    {
        _textureShader.Properties["replaceColor"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
    }

    private void RangeBase1_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        _textureShader.Properties["threshold2"] = (float)e.NewValue / 100;
    }

    private void ColorSpectrum_OnColorChanged2(ColorSpectrum sender, ColorChangedEventArgs args)
    {
        _textureShader.Properties["sourceColor2"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
    }

    private void ColorSpectrum_OnColorChanged3(ColorSpectrum sender, ColorChangedEventArgs args)
    {
        _textureShader.Properties["replaceColor2"] = new Vector3((float)args.NewColor.R / 255, (float)args.NewColor.G / 255, (float)args.NewColor.B / 255);
    }

HLSL shader:

#define D2D_INPUT_COUNT 1
#define D2D_INPUT0_SIMPLE

#include "d2d1effecthelpers.hlsli"

float3 sourceColor;
float3 replaceColor;
float threshold;

float3 sourceColor2;
float3 replaceColor2;
float threshold2;

D2D_PS_ENTRY(main)
{
    float3 color = D2DGetInput(0).rgb;

    if (abs(color.r - sourceColor.r) < threshold && abs(color.g - sourceColor.g) < threshold && abs(color.b - sourceColor.b) < threshold) 
    {
        float3 newColor = color - sourceColor + replaceColor;
        return float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else if (abs(color.r - sourceColor2.r) < threshold2 && abs(color.g - sourceColor2.g) < threshold2 && abs(color.b - sourceColor2.b) < threshold2)
    {
        float3 newColor = color - sourceColor2 + replaceColor2;
        return float4(newColor.r, newColor.g, newColor.b, 1);
    }
    else 
    {
        return float4(0, 0, 0, 0);
    }
}

Enjoy!



回答2:

In WPF there is no way to automatically "stack" effects. You can do it though, however, using composition. You wouldn't have to double up on anything that you are doing with the pixel shader. You'd essentially double up on ancestors of the child in question, and apply multiple pixel shader effects. For example, let's say you wanted to apply 2 color replacement pixel shader to a single Image. The following pseudo-code would get you there:

<Grid Name="Ancestor1">
    <Grid Name="Ancestor2">
        <Image Source="..." Name="Child" />
    </Grid>
</Grid>

And in the code behind, you could apply the pixel shader effects to each of the ancestors, like so:

Ancestor1.Effect = New ColorReplacementPixelShader() with { .PropertyA = someValue, ... }
Ancestor2.Effect = New ColorReplacementPixelShader() with { .PropertyA = somevalue, ... }

In doing this, you've effectively applied the same pixel shader TWICE to the child Image element, but did so by stacking the effect by using two ancestors.

Hope that helps!