Thursday, January 28, 2010

Rounder, Faster, Better - WriteableBitmapEx

A lot of things happened since the release of the last WriteableBitmapEx version. I used the time I have to wait for a third party [spoiler] until I could release my Silverlight Augmented Reality framework SLARToolkit [/spoiler] to work on the WriteableBitmapEx project. Beside some optimizations and smaller extensions, the main addition in the new version is the support for parametric curves. I implemented the cubic Beziér spline and the Cardinal spline. The Cardinal spline is not part of Silverlight's and WPF's Geometry nor shape classes, but the DrawCurve method is part of GDI+ and many people appreciate the characteristics of cardinal splines. Fortunately the WriteableBitmapEx library fills this gap now for the WriteableBitmap.

New features
  • Draw methods for parametric curves: Cubic Beziér splines and the Cardinal splines similar to the GDI+ API. 
  • Clone method to copy the whole WriteableBitmap to a new instance.
  • Boundary check for the Draw* (Shape) methods. The coordinates are now clipped to the WriteableBitmap's size.
  • A new, even faster DrawLine method overload that uses the bitmap's pixels int array, width and height directly as parameters. This method should be used if many lines are drawn to the same WriteableBitmap.
  • Optimization of the byte array conversion methods using the Buffer.BlockCopy method. This improvement was suggested in this comment.
  • WriteTga method to write a WriteableBitmap as a TGA image to a stream. The original method was provided in this blog post.
  • Fast path for Blitting using the Buffer.BlockCopy method if the BlendMode is None and the bitmap should not be tinted. The result is a performance boost by factor 3.5 compared to traditional loop iteration. This improvement was suggested in this CodePlex discussion
  • Adam Kinney contributed two new blend modes for the Blit method: Multiply and Mask. He used these in his cool torn photo effect.
  • A new method that takes a function as parameter and iterates over each pixel and sets its color. The method is called ForEach and has two overloads. Usage example:
    writeableBmp.ForEach((x, y, color) => Color.FromArgb(color.A, color.R / 2, (byte)(x * y), 100));
    The method was suggested in this CodePlex discussion
The code listing on the project's CodePlex site was updated to show the usage of the new methods.
    I also wrote a new sample application that shows the spline methods in action. The sample starts with a demo animation that uses the Cardinal spline DrawCurve method to draw an artificial plant that grows procedurally. The other part of the sample is interactive and allows to draw and manipulate Beziér and Cardinal splines with the mouse.

    Uncheck the "Growing plant demo" Checkbox to change to the interactive mode where you can add new or move curve control points with the left mouse button. Press the DEL key while a control point is selected to remove it. The used curve type can be changed with the Radio Buttons. A cardinal spline needs at least 3 points and a cubic Beziér 4 points, a start point, the control point 1, the control point 2 and the end point.The Slider controls the tension of the Cardinal spline. The tension defines the shape of the curve and is usually between 0 and 1. A value of 0 would be a straight line.
    Uncheck the "Points" Checkbox to hide the control points and press the Clear Button to remove all control points. Click the Save Button to write the WriteableBitmap to a TGA image file.
    The other controls should be self-explaining.

    Get it while it's hot
    The WriteableBitmapEx library is hosted at CodePlex. You can find the new binary release here.


      1. Great work! When I saw the blendmodes I was immediatly hooked. One thing I'd like to know is if you have any ideas how to make it more pluggable when I want to add more blendmodes. If i want to add 20 blendmodes then the code becomes a bit silly.

      2. Thanks.
        I know what you mean. And one might consider to add an overload that uses a delegate like this:
        Blit(this WriteableBitmap bmp, Rect destRect, WriteableBitmap source, Rect sourceRect, Color color, Func<int, int, int> BlendFunction)
        The func would get the source and the destination color as input and should return the new destination color as int.

        But, the main goal of the library is performance and calling a func for each pixel would have a certain impact on it. And, do you really need 20 blend modes?

      3. 20 blend modes? yes, yes, Of course I do :) for a sort of Photoshop / layer experiment application.
        Would the calling of a function not even out against all the if statements? I'll give it a try

      4. That's great. Please keep me updated on your progress or if you need some help.
        The CodePlex project's site discussion is a good place:

        The method needs a bit of redesign to work with a BlendFunction. You can leave the different hierarchies of the if-statements out. If you look closely at them there are some tests for the alpha values to optimize things a bit.
        In the first step I would rewrite the if-statements so the BlendMode checks are all in one hierarchy. After that I would extract each BlendMode in a separate method in a class called BlendFunctions or something.

      5. @Rene:
        Great job! I liked the growing tree and curved sample a lot! Also perf looks quite nice :)

        @Michaud: if you're looking for layer (complete bitmap) blending, maybe you could try blending in pixel shaders after you draw in the current layer. I've been experimenting with this a bit now. Here's a sample code to do overlay blend in pixel shaders. Since overlay blend is one of the more complex ones, there's also a multiply and additive one:
        // overlay blend
        float4 backColor = tex2D(input, uv);
        float4 colorM = color*backColor; // multiply blending
        float4 colorS = 1 - (1-color)*(1-backColor); // screen blending
        float4 colorO = backColor * colorS + (1-backColor)*colorM; // overlay blending

        // mix with original
        return colorO*amount+backColor*(1-amount);

      6. Just found that - all Photoshop blend modes in pixel shaders:

        looks very interesting! :)

      7. Thanks Nikola and great finding with the Photoshop blend modes.
        Good that I was able to convince you that pixel shaders are faster than Bitmap operations in Silverlight, although they run on the CPU. :) But as you know hey're implemented efficiently using SSE. I like your idea with the Shader effects in your Image editor, which is awesome. Kudos my friend.
        It would be great if we were able to apply shaders directly to a WriteableBitmap without the additional Image and Effect classes.

      8. Hello Rene,

        Clear would be somewhat better like this:

        public static void Clear(this WriteableBitmap bmp, Color color)
        int col = (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B;
        int[] pixels = bmp.Pixels;
        int w = bmp.PixelWidth;
        int h = bmp.PixelHeight;
        for (int i = 0; i < w; i++)
        pixels[i] = col;
        for (int j = 1; j < h; j++)
        Buffer.BlockCopy(pixels, 0, pixels, j * w * SizeOfARGB, w * SizeOfARGB);

        Cheers, Balint

      9. Hi Balint,

        thanks for the suggestion. I somehow missed this when I optimized the methods with Buffer.BlockCopy. But I use the even faster Clear() method most of the time.
        Thanks again.