views:

430

answers:

1

Im trying to look into using the WPF WriteableBitmap class to allow my application to apply an opacity mask to an image.

Basically I have a blue rectangle as an image, and another 100% transparent green rectangle image over the top of the blue one.

When the user moves their mouse over the green (transparent) image, I want to apply the opacity mask (perhaps using a simple ellipse) so that it looks like a green glow is occurring.

Im purposefully not doing this is XAML and standard WPF effects because I really need it to be super performant and I will eventually swap out the ellipse with a more advance blob...

Any thoughts??

Thanks!

+2  A: 

Hi Mark.

I'm sorry, I don't quite understand your intentions. Maybe if I could see the image, I could answer correctly from start, but here is my first-maybe-wrong answer.

If you say super-performant, you probably want to look at pixel shaders. They are processed by GPU, supported by WPF in a form of a custom effect and are easy to implement. Also you can apply shaders to playing video, while it's hard to do with WritableBitmap.

To write a pixel shader, you need to have FX Compiler (fxc.exe) from DirectX SDK and Shazzam tool - WYSIWYG WPF Shaders compiler by Walt Ritscher.

When you get them both, go ahead and try the following HLSL code

float X : register(C0); // Mouse cursor X position
float Y : register(C1); // Mouse cursor Y position
float4 Color : register(C2); // Mask color
float R : register(C3); // Sensitive circle radius.

sampler2D implicitInputSampler : register(S0);


float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 finalColor = tex2D(implicitInputSampler, uv);
    if ( (uv.x - X) * (uv.x - X) + (uv.y - Y) * (uv.y - Y) < R*R)
    {
        finalColor = Color; // Blend/Change/Mask it as you wish here.
    }
    return finalColor;
}

This gives you the following C# effect:

namespace Shazzam.Shaders {
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Effects;


    public class AutoGenShaderEffect : ShaderEffect {

        public static DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(AutoGenShaderEffect), 0);

        public static DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double), typeof(AutoGenShaderEffect), new System.Windows.UIPropertyMetadata(new double(), PixelShaderConstantCallback(0)));

        public static DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double), typeof(AutoGenShaderEffect), new System.Windows.UIPropertyMetadata(new double(), PixelShaderConstantCallback(1)));

        public static DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(System.Windows.Media.Color), typeof(AutoGenShaderEffect), new System.Windows.UIPropertyMetadata(new System.Windows.Media.Color(), PixelShaderConstantCallback(2)));

        public static DependencyProperty RProperty = DependencyProperty.Register("R", typeof(double), typeof(AutoGenShaderEffect), new System.Windows.UIPropertyMetadata(new double(), PixelShaderConstantCallback(3)));

        public AutoGenShaderEffect(PixelShader shader) {
            // Note: for your project you must decide how to use the generated ShaderEffect class (Choose A or B below).
            // A: Comment out the following line if you are not passing in the shader and remove the shader parameter from the constructor

            PixelShader = shader;

            // B: Uncomment the following two lines - which load the *.ps file
            // Uri u = new Uri(@"pack://application:,,,/glow.ps");
            // PixelShader = new PixelShader() { UriSource = u };

            // Must initialize each DependencyProperty that's affliated with a shader register
            // Ensures the shader initializes to the proper default value.
            this.UpdateShaderValue(InputProperty);
            this.UpdateShaderValue(XProperty);
            this.UpdateShaderValue(YProperty);
            this.UpdateShaderValue(ColorProperty);
            this.UpdateShaderValue(RProperty);
        }

        public virtual System.Windows.Media.Brush Input {
            get {
                return ((System.Windows.Media.Brush)(GetValue(InputProperty)));
            }
            set {
                SetValue(InputProperty, value);
            }
        }

        public virtual double X {
            get {
                return ((double)(GetValue(XProperty)));
            }
            set {
                SetValue(XProperty, value);
            }
        }

        public virtual double Y {
            get {
                return ((double)(GetValue(YProperty)));
            }
            set {
                SetValue(YProperty, value);
            }
        }

        public virtual System.Windows.Media.Color Color {
            get {
                return ((System.Windows.Media.Color)(GetValue(ColorProperty)));
            }
            set {
                SetValue(ColorProperty, value);
            }
        }

        public virtual double R {
            get {
                return ((double)(GetValue(RProperty)));
            }
            set {
                SetValue(RProperty, value);
            }
        }
    }
}

Now you can track mouse position, and set corresponding properties of your effect to trigger changes. One thing to note here: X and Y in HLSL code are ranged from 0 to 1. So you'll have to convert actual coordinates to percentages, before passing them to shader.

Things to read more about pixel shaders and WPF:

Hope this helps :)

Anvaka
Hey thanks Anvaka, thats a great answer! Ill give that a go and read up a bit more, if you dont mind however, Ill extend to the next part of the question. Im after the effect where I want a glow around the mouse, but I want this glow to be a opacity mask on an image in front of my background image (like a fixed texture that is exposed when you move the mouse over it). Can this be acheived using pixel shaders?
Mark
Sure, you can do this. You can modify color's alpha channel as you wish from HLSL code. It's as easy as setting finalColor.a = 0.75; This sets 75% to alpha.
Anvaka
awesome! thanks for the help, this has been incredibly interesting to me!
Mark
You are welcome :). Feel free to ask any further questions. I will be glad to help :)
Anvaka