C# Tips & Tricks: Non-blocking Per-Pixel Painting in Windows Presentation Foundation
In a previous tip i've shown how to implement and use a SetPixel method for a Windows Forms control. This time i'll show how to do the same in WPF.
While WPF has some great advantages over WinForms, like the speed it gains by utilizing the hardware through DirectX, and its high flexibility, it has also become a bit more complex and complicated then WinForms.
While WinForms gives us no SetPixel method on a control, it at least gives us one on the Bitmap class, which controls can use. In WPF we don't even have that luxury, and need to build our SetPixel method from the ground up.
Like in the WinForms article, we will make our sample application multithreaded, so that the user interface stays responsive even if our Render function is very slow.
First, create a new Windows Presentation Foundation project, and add an Image control (named image1) and a Button (named button1) to the form, and add an event handler for Button.Click and Window.Closed.
We will be using the following two namespaces in addition to the ones added by default:
using System.Windows.Threading;To the main window class we add the following data members:
BitmapSource bitmap;First we need a BitmapSource object, which is what our Image control will use to paint itself with.
Then we have to define a PixelFormat that we will use when painting. I'm using 24 bit RGB format here. After this we declare variables to hold the width and height of our Image control. pixelData is a byte array from which the BitmapSource will be created, and since the array is one-dimensional, we need rawStride to hold the length (in bytes) of each horizontal scan line of the image we will be rendering into pixelData.
Here is how we initialize these variables:
void Init ()width and height are the width and height in pixels taken from the Image control. rawStride is computed as the width times the bytes per pixel, padded to the next multiple of 8.
Just a quick word about width and height: you might be inclined to reduce the number of class variables, by simply omitting width and height, and substituting image1.Width/Height wherever they occur. Unfortunately, this is not possible if we want to call our drawing functions from a background thread, since WPF is very strict in the fact that only the thread that created a control has access to it. Thus using image1.Width in one of the methods we call from a background thread would produce an InvalidOperationException and crash our program. A way around this would be to read the value through a delegate passed to image1.Dispatcher.Invoke(), but this would incur a performance hit we can easily avoid. A better way would be to pass image1.Height/Width as delegate arguments when we create the Render thread, but for clarity's sake i make them class members here.
Now that we have the necessary data members, we can write our SetPixel method:
void SetPixel (int x, int y, Color c, byte buffer, int rawStride)Our incoming x and y variables are pixel coordinates, which we have to transform into the correct byte-coordinates within our one-dimensional data array. Each pixel will occupy a number of bytes depending on the PixelFormat we use. We multiply x by that number to get an x index. Each scanline occupies 'rawStride' bytes, so the final index in our array is (number of bytes per pixel) * x + (number of bytes per line) * y.
Since we are using the RGB format, we have 3 bytes per pixel, which we then fill with the appropriate color values.
With the above SetPixel method, our Render method is very straightforward:
void Render()Here we can simply walk through each pixel and set a color to it.
Note that for more performance we could consolidate our Render and SetPixel method to save some method calls and computations:
void Render()To run our Render method in a background thread, we use the following code:
void button1_Click(object sender, RoutedEventArgs e)After initializing our variables, we create a new Task which will execute our Render method (of course you can also use Thread or WaitCallbacks here instead of Task, but Task should be your first choice).
Then we create a Timer, set it to tick 10 times per second, add UpdateScreen to its Tick event, and start it.
UpdateScreen is here:
void UpdateScreen(object o, EventArgs e)This method does the actual drawing to the screen: it generates a new BitmapSource from our pixel buffer and feeds it to our Image control.
Now if you remember, i wrote that WPF doesn't let you access a control from a thread it wasn't created on, and here i'm setting its Source property from within a Timer event that runs in the Timer's thread.
But what System.Windows.Forms.Timer (unlike System.Threading.Timer) is for WinForms, DispatcherTimer is for WPF: the thread that the timer's Tick event is run on has safe access to WPF controls, so we can do this here without raising an exception.
The last thing to do now is some cleanup in the window's close event:
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)And we're done. If you run the application and hit the button now, the image control will propably be blue immediately, which shows how fast WPF is. To see how the image is actually updated blockwise on timer ticks, you can add a Thread.Sleep call inside the render method.
Andreas Hartl, 2010-2011