Thursday, June 12, 2008

Grayscale Effect - A Pixel Shader Effect in WPF

This post will describe how Effects (pixel shaders) are used in WPF. I will give a simple step-by-step example of a grayscale effect. You must have .NET Framework 3.5 sp1 or later installed for this to work. You also need DirectX SDK installed to be able to compile the pixel shader effect.

Download Sample Project and source code here.
Download effect here (add it to your project and use a grayscale effect on your WPF components)

image  image   image

1. New Project
Start Visual Studio and create a new WPF Application. (Targeting the .NET 3.5 Framework)

image

2. Add new project. (Targeting the .NET 3.5 Framework)
In your solution, add a new project, a Class Library-project. Name your project "GrayscaleEffect". Now you will have the following view in your Solution Explorer:

image

3. Prepare the Grayscale Effect.
First add some references to the GrayscaleEffect-project:
PresentationCore
PresentationFramework
WindowsBase
Delete the autogenerated Class1.cs-file and instead create a new C# class and name it "GrayscaleEffect.cs". This will be an empty class. Now lets add the C#-part of the effect:

using System;
using System.Windows.Media.Effects;
using System.Windows;
using System.Windows.Media;

namespace GrayscaleEffect
{
public class GrayscaleEffect : ShaderEffect
{
private static PixelShader _pixelShader = new PixelShader() { UriSource = new Uri(@"pack://application:,,,/GrayscaleEffect;component/GrayscaleEffect.ps") };

public GrayscaleEffect()
{
PixelShader
= _pixelShader;

UpdateShaderValue(InputProperty);
UpdateShaderValue(DesaturationFactorProperty);
}

public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(GrayscaleEffect), 0);
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}

public static readonly DependencyProperty DesaturationFactorProperty = DependencyProperty.Register("DesaturationFactor", typeof(double), typeof(GrayscaleEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(0), CoerceDesaturationFactor));
public double DesaturationFactor
{
get { return (double)GetValue(DesaturationFactorProperty); }
set { SetValue(DesaturationFactorProperty, value); }
}

private static object CoerceDesaturationFactor(DependencyObject d, object value)
{
GrayscaleEffect effect
= (GrayscaleEffect)d;
double newFactor = (double)value;

if (newFactor < 0.0 || newFactor > 1.0)
{
return effect.DesaturationFactor;
}

return newFactor;
}
}
}


This is the C# side of the effect and will be the interface to the shader effect. This class and its properties (dependency properties) can be used directly from WPF and XAML. Note that the properties can be animated in the same way as other dependency properties. The difference is that we use a special type for the Input property and for our custom property we just refer to a register on the GPU, but we really do not need to know that ;). I really love the simplicity!



4. Add the effect files.

In the GrayscaleEffect-project, add two new files (Add -> New Item). GrayscaleEffect.fx and GrayscaleEffect.ps as Text-file. Make sure that GrayscaleEffect.ps is set as a Resource (in Build Action) in Properties. NOTE: The GrayscaleEffect.fx must be in ANSI format. Convert it to ANSI by opening the file in Notepad and save it, select ANSI format in the save dialog.



5. Now open the GrayscaleEffect.fx and add the following code (this is HLSL - High Level Shader Language):



sampler2D implicitInput : register(s0);
float factor : register(c0);

float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 color
= tex2D(implicitInput, uv);
float gray = color.r * 0.3 + color.g * 0.59 + color.b *0.11;

float4 result;
result.r
= (color.r - gray) * factor + gray;
result.g
= (color.g - gray) * factor + gray;
result.b
= (color.b - gray) * factor + gray;
result.a
= color.a;

return result;
}



This is our pixel shader. As we can see at the top implicitInput refers to one registry and a float named factor refers to another register. This is the register that we set in our C#-file (the last parameter in RegisterPixelShaderSamplerProperty(,,0) and the parameter in PixelShaderConstantCallback(0)). Below is our entry point the the shader file. We take a position as input and the first thing we do is to get the value in the "texture" at this position.


The magic below this is out grayscale alogrithm. We first create a grayscale value from the red, green and blue channel. Then we set this value in the output value depending on the factor parameter. The factor parameter is the DesaturationFactor in the C#-file and we use it to create a desaturation effect and a gradient between color and grayscale mode.



6. Compile the pixel shader.

We have created the pixel shader effect but we need to compile it to use it. Note that we can do this outside of Visual Studio, but we don't want that, so do the following. Open the project-properties for the GrayscaleEffect-project and select "Build Events". Under Pre-build event command line, add the following:


"$(DXSDK_DIR)Utilities\Bin\x86\fxc.exe" /T ps_2_0 /E main /Fo"$(SolutionDir)GrayscaleEffect/GrayscaleEffect.ps" "$(SolutionDir)GrayscaleEffect/GrayscaleEffect.fx"


If you now compile the project your effect should compile just fine. But we are not using it so lets use it:



7. Now in WpfApplication1.

First add a reference to the GrayscaleEffect-project from the WpfApplication1-project.


We also want to use some images that we can apply out effect on. NOTE that the effect is not limited to images, we can use any control or panel in WPF, and that's cool!!! However, we use images because they are nice ;). Add a folder the the WpfApplication1 called "images". Add a few (~4) images to this folder (Add -> Existing item), name them like img1.jpg, img2.jpg... .


Your Solution Explorer should look like this:


image 
Now add the following XAML to Window1.xaml:



<Window x:Class="WpfApplication1.Window1"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:effect
="clr-namespace:GrayscaleEffect;assembly=GrayscaleEffect"
Title
="Grayscale Effect - Dotway (www.dotway.se)" Height="400" Width="480">

<Window.Resources>

<DataTemplate x:Key="itemTemplate">
<Grid Width="225" Margin="3">
<Border BorderBrush="White" BorderThickness="2">
<Image Source="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image.Effect>
<effect:GrayscaleEffect x:Name="grayscaleEffect"/>
</Image.Effect>
<Image.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="1.0" Duration="0:0:0.3" AccelerationRatio="0.10" DecelerationRatio="0.25" Storyboard.TargetName="grayscaleEffect" Storyboard.TargetProperty="(effect:GrayscaleEffect.DesaturationFactor)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="0.0" Duration="0:0:4" AccelerationRatio="0.10" DecelerationRatio="0.25" Storyboard.TargetName="grayscaleEffect" Storyboard.TargetProperty="(effect:GrayscaleEffect.DesaturationFactor)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
</Border>
</Grid>
</DataTemplate>

</Window.Resources>

<Grid Background="Black">

<ItemsControl x:Name="myItemsControl" ItemTemplate="{StaticResource itemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

</Grid>

</Window>


We have a ItemsControl that have a WrapPanel as layout panel. We also have a template for each item in the ItemsControl. In the template two triggers have been added with animations. These animations will animate the DesaturationFactor property which will affect the shader effect.

To use the images add the following to the code-behind file:




public Window1()
{
InitializeComponent();

List
<string> images = new List<string>();
for (int i = 0; i < 4; i++)
{
images.Add(
"images/img" + i + ".jpg");
}
myItemsControl.ItemsSource
= images;
}






Now just compile and run your new effect!

BUT, how is this really useful except that it gives us "pretty images". Well, I am a fan of focus and context/master-detail implementations. It is all about putting focus to some part of my GUI and leave the rest unfocused and in a context. With the color/grayscale we can put focus to an item by giving it color and all the rest (context) is grayscale. 
This is not the end of it. In a later post I hope I can show you how to interact with the GUI after applying a pixel shader effect. The only thing you have to do is to implement EffeceMapping in you C#-file, more on that later. 
I hope this will help someone, otherwise contact the consultants at Dotway (www.dotway.se)! ;)

Wednesday, June 11, 2008

<Image Source="images/myimage.jpg" /> in WPF

I had a very annoying problem when I did a simple WPF program. I just wanted to test something and I wanted my program to display an image. This is what I did:

1. Create a new WPF application in Visual Studio.

2. Create a folder in my project with the name "images". Add an image to this folder on disk.

3. Right click the images-folder and select add existing item. The image is added and it is set to "Resource".

4. In Window1.xaml set the following code:

<Window x:Class="TestProject.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestProject"
    Title="Test" Height="500" Width="650">

    <Grid Background="Black">
        <Image Source="images/1.jpg" />        
    </Grid>

</Window>

5. When running this code it works ok, but when I want to see it in the Visual Studio Designer I get an exception saying:

Error    1    The file /images/1.jpg is not part of the project or its 'Build Action' property is not set to 'Resource'.   

Solution: Rename the image to something like img1.jpg. I do not know why this happen but it works if you rename the image-file. You could rename it to 2.jpg and it will work. Anyone?

Use Live ID to login to your Silverlight application

In this post I will give you a step by step walkthrough of how to use Windows Live ID Web Authentication for logging in to a Silverlight app.

My first attempt was to create a login directly in Silverlight. I looked at Windows Live ID Client SDK which is the WinForm version of the Live ID SDK, but it seems that this SDK can not be used with Silverlight apps. The next approach was to use ASP.NET authentication and encapsulate the Silverlight authentication in ASP.NET sites. This is exactly what I will describe below. The example is pretty much independent of Silverlight version, but it uses Silverlight 2 (Silverlight 2 Beta 2).

 

1. Register your application with Windows Live ID

First of all we need to register our application with the Windows Live ID. To be able to use the authentication functionality of Live ID the application must be registered. Go to https://msm.live.com/app/default.aspx and select "Register an Application".

image

Note: If you want to use localhost in development and testing you must register this as an application. If you then have another server for publishing the application, you also need to register that one as another application. In this example I will use localhost.

image

E.g.:
Application Name: MySilverlightLoginTestDev
Return URL: http://localhost:5501/LoginSampleWeb/authresponse.aspx
Domain Name: [Leave this empty for localhost registration]
Secret Key: Some7ingVery_1ong_AnD_S7ran9e
Application verifier required: 0

Remember the port that is registered as return URL, this will later be set in Visual Studio. When the information has been submitted you find information about it under "Manage My Application". You also find an Application ID here that you need later in Visual Studio.

Now you have registered an application with Live ID and it is time for code and Visual Studio.

 

2. Sample Project

<SourceCode>Download sample project here.</SourceCode>

First download, unzip and open the Visual Studio sample. You will see something like this in the Solution Explorer:

image

1. Open web.config, you find this block in the config file. Copy the information that you entered previously in the registration.

<appSettings>
   <add key="wll_appid" value="YourAppIdHere e.g. 00161AA1111111A"/>
   <add key="wll_secret" value="YourSecretKeyHere e.g. Some7ingVery_1ong_AnD_S7ran9e"/>
   <add key="wll_securityalgorithm" value="wsignin1.0"/>
   <add key="loginpage" value="Default.aspx"/>
   <add key="loggedinpage" value="LoginSampleTestPage.aspx"/>
   <add key="logincookie" value="webauthtoken"/>
</appSettings>

2. Now set the right port number. Select the web project in Solution Explorer (LoginSampleWeb) and then select properties.

image image

In the properties view, set Use dynamic ports to "False" and then set the port number to the same as you entered after localhost in the registration (localhost:5501).

3. Run the example.

image  image

3. How does it work - Overview

When you start the sample project Default.aspx will open. This is a very simple page that only displays some text and a "Sign in" link.

<body>
    <h1>Silverlight login using Windows Live&trade; ID Web Authentication SDK</h1>   

    <iframe
       id="WebAuthControl"
       name="WebAuthControl"
       src="http://login.live.com/controls/WebAuth.htm?appid=<%=_applicationId%>&style=font-size%3A+10pt%3B+font-family%3A+verdana%3B+background%3A+white%3B"
       width="80px"
       height="20px"
       marginwidth="0"
       marginheight="0"
       align="middle"
       frameborder="0"
       scrolling="no">
   </iframe>   
</body>

When the user clicks the link he/she will be redirected to a Live login page. If the login is successful the Live ID login page will return to the page that was specified in the registration, e.g. authresponse.aspx. In authresponse.aspx a cookie will be set with some settings to determine whether a user is logged in or not. This will also redirect the user to the right target page (LoginSampleTestPage.aspx). You can also get a specific token ("stoken") for the logged in user that you can use to identify your user and connect to user specific settings.

In LoginSampleTestPage.aspx we check that the user really is logged in. If he/she is not, we redirect them to the login page. If they are logged in we continue to load the page which in this case contains a Silverlight control.

    private static WindowsLiveLogin _windowsLiveLogin = new WindowsLiveLogin(true);
    protected static string _applicationId = _windowsLiveLogin.AppId;

    protected void Page_Load(object sender, EventArgs e)
    {
        HttpRequest request = HttpContext.Current.Request;
        HttpCookie loginCookie = request.Cookies[_windowsLiveLogin.LoginCookie];

        if (loginCookie == null)
        {
            RedirectToLoginPage();
        }

        string token = loginCookie.Value;
        if (string.IsNullOrEmpty(token))
        {
            RedirectToLoginPage();
        }

        WindowsLiveLogin.User user = _windowsLiveLogin.ProcessToken(token);
        if (user == null)
        {
            RedirectToLoginPage();
        }
    }

4. Summary

This is in fact only a encapsulation of a login to Silverlight. We use ASP.NET and server authentication for the login procedure and then give access to the Silverlight application. It is very easy to implement and we let the Live services handle the user registration and so on. Someone also mentioned that Silverlight will get some authentication controls in future versions but I do not know if that is true or not?! If such a control will come, then we can do client side authentication directly in Silverlight.

 

This example uses a modified version of WindowsLiveLogin.cs. The original file can be downloaded from the Windows Live ID Authentication SDK. You also find documentation and useful links at that location.

Thursday, May 22, 2008

Best scrollbar ever!

Last week I installed an addon to Visual Studio that replaces the old, gray scrollbar. RockScroll gives you a preview of your current file an it really rocks! :)

http://www.hanselman.com/blog/IntroducingRockScroll.aspx

RockScroll

Wednesday, May 21, 2008

I miss some interfaces

I don't think I'm the only one that misses some interfaces in the .NET framework. A while ago I worked with a graphics framework written in .NET and I wanted to create a generic class that would handle different types of data items collection. The type would be byte, int, double, Decimal and so on. The class should then be able to perform some calculations.

Now, the problem with generic classes is that it is generic. You should be able to put in any type and the class will work. But, one thing you can do is set some constraint on your generic class and only allow types implementing a specific interface or only value type/reference type. You do this by setting:

public class MyClass<T> where T : IMyInteface

Now the problem in my situation was that I wanted do perform calculations using standard value types. Ok, I could set that the generic type must only be a value type, but that wouldn't solve any of my problems. The real problem is that there is no interface for addition, subtraction... This means that you can not use generic classes and calculation :/. I had to limit my data type to double (or copy the class and implement the same class for byte, int).

My question is, will we see an interface for mathematical operations in the .NET framework?

Bilden “http://www.multisensory.biz/catalog/images/small%20maths%20symbols.jpg” kan inte visas, då den innehåller fel.

Thursday, May 15, 2008

Use DirectX in WPF

Yesterday I wrote about hardware accelerated effects in WPF 2D. Today I saw this video (http://channel9.msdn.com/Showpost.aspx?postid=403854) that show us that DirectX can be used from WPF. There are some (a lot!) differences between DirectX and WPF 3D and you might reach some point where WPF 3D isn't enough, you need the power of DirectX. Instead of a huge rewrite of WPF to DirectX, you can now combine these two and that is just soooo cool! I love it!

 

image

9 different DirectX sample apps running on a cube each. Displayed in WPF 3D!

Wednesday, May 14, 2008

WPF Effect on the GPU

This is just so cool! Now with .NET 3.5 Sp1, visual effects running on the GPU can be created and applied to WPF-components. There will be a set of effects provided for you like shadow and blur, or you can create your own with HLSL. The hittesting will still work and if the target computer can't handle shader language 2.0 or higher, it will automatically switch to software rendering.

To apply an effect you only need to write:
<Button ... >
    <
Button.Effect>
        <
DropShadowEffect />
    </
Button.Effect>
   
Hello
</Button>

Read more about WPF and GPU Effect here (it is a very good series of post): http://blogs.msdn.com/greg_schechter/archive/2008/05/12/a-series-on-gpu-based-effects-for-wpf.aspx

 

Motion blur:

image

image

Friday, May 9, 2008

Dotway Seminar: Painless migration to Silverlight 2.0

This summer the next highly awaited version of Silverlight will be released. New functionality, new languages and new ways to interact with the web. We will explain the most interesting news and show, with concrete examples in Silverlight and ASP.NET, what is needed to reap the benefits of these features. We will also show how to handle the transition to Silverlight 2.0 in a straightforward and painless way.

  • The news in Silverlight 2.0
  • The integration with ASP.NET and other techniques
  • The advantages with using Silverlight compared to other web client frameworks
  • The painless transition to Silverlight 2.0
    • For the customer
    • For the developer

 

Speakers:
Carl Kenne
Lars Sjögreen

Time:
2008-05-14 (Wednesday)
Kl. 17.00 - 18.30

Location:
Kompetenscenter Stockholm, Vasagatan 8-10

More info and Registration:
The seminar will be presented in Swedish.
www.dotway.se

Price:
Free

Programming languages:
C#
JavaScript
HTML

Technologies:
.NET
WPF
AJAX
Silverlight

image

Thursday, April 24, 2008

ScrumMaster

Now I have become a certified ScrumMaster after a course by Jeff Sutherland. I really like scrum since it makes development fun and is a tool that helps the team communicate. I believe that scrum can make a difference for development teams but at the same time it is not a silver bullet that will write your code. Think of it as a very simple tool for you and your team that makes it fun to work and also will help you increase your development velocity. I am looking forward to work with scrum and would really like to build an expert team that will reach the hyper speed of development and deliver, deliver, deliver :).

Tuesday, March 4, 2008

WPF Cursors

If you want to see or test the different mouse cursors in WPF (Windows Presentation Foundation) I've created a simple sample project to do so. Note that it is very easy to set the cursor on a FrameworkElement, all you have to do is to set the Cursor property:

<Border Cursor="Hand">
    <Button Content="My Button" Cursor="Help" />
</Border>

Some default Vista cursors:

Download sample project here.