PETZOLD BOOK BLOG

Charles Petzold on writing books, reading books, and exercising the internal UTM


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

Random Rectangles in Silverlight (using WriteableBitmap)

October 30, 2009
New York, N.Y.

I remember going to COMDEX in Las Vegas sometime in the early 90s when Microsoft Windows had just reached some kind of tipping point (at least among manufacturers if not users) and the floor of the Convention Center was ablaze with Windows machines, most of them running random-rectangle programs.

The traditional random-rectangle program is short, simple, silly, and ridiculously hypnotic as it covers the display with an ever increasing number of overlapping rectangles of random sizes and colors. Some of those early random-rectangle programs worked off the Windows timer, but the really fast ones replaced the normal message loop with a special PeekMessage loop and used all available CPU cycles for the magnificant splattering of color.

Back when I was writing my book on the Windows Presentation Foundation, Applications = Code + Markup, I wanted to include a WPF random-rectangle program for old-times' sake. I even created a new project named RandomRectangles, but as I started to type the code, I stopped dead in my tracks.

It can't be done.

Well, that's not entirely true. But I realized that any kind of simple random-rectangle program in WPF would just be wrong, wrong, wrong.

Here's the problem: When programming for the Windows API and frameworks prior to WPF, drawing a rectangle involves an API call that goes through the video device driver to render pixels on the display in the form of a rectangle. By the time the call returns to the application, the rectangle is on the screen. But WPF is a retained-mode graphics system. In WPF, the video display is maintained in a visual-composition layer that retains all the visual objects. To draw a rectangle, you actually create an object derived from UIElement, and you typically display that object by adding it to a panel. The object itself remains alive.

A random-rectangle program for WPF (or Silverlight, for that matter) would create thousands of rectangle objects every few minutes. All these rectangles would remain alive while the program continued running, requiring more and more memory every second!

Well, I wouldn't be writing this blog entry if I didn't have a solution. Let's switch to Silverlight for the remainder of this discussion.

With the knowledge that the simple approach is not the way to write a Silverlight random-rectangle program, let's do so anyway. You can download the BadRandomRectangle project or just follow along with my discussion. The MainPage.xaml file has just one item in the main Grid:

<Grid x:Name="LayoutRoot">
    <TextBlock Name="txt" Canvas.ZIndex="1" />
</Grid>

This TextBlock is for a counter displayed at the upper-left corner. The value of the ZIndex just ensures that it stays on top of everything else (which will have a default ZIndex of 0). The code part of the MainPage class looks like this:

public partial class MainPage : UserControl
{
    Random rand = new Random();
    ulong count = 0;

    public MainPage()
    {
        InitializeComponent();
        CompositionTarget.Rendering += OnRendering;
    }

    void OnRendering(object sender, EventArgs args)
    {
        int width = (int)LayoutRoot.ActualWidth;
        int height = (int)LayoutRoot.ActualHeight;

        if (width == 0 || height == 0)
            return;

        int x1 = rand.Next(width);
        int x2 = rand.Next(width);
        int y1 = rand.Next(height);
        int y2 = rand.Next(height);

        byte[] bytes = new byte[4];
        rand.NextBytes(bytes);
        Color clr = Color.FromArgb(bytes[0], bytes[1], 
                                   bytes[2], bytes[3]);
        Path path = new Path()
        {
            Fill = new SolidColorBrush(clr),
            Data = new RectangleGeometry()
            {
                Rect = new Rect(Math.Min(x1, x2), 
                                Math.Min(y1, y2),
                                Math.Abs(x1 - x2), 
                                Math.Abs(y1 - y2))
            }
        };

        LayoutRoot.Children.Add(path);
        txt.Text = (++count).ToString();
    }
}

I use the CompositionTarget.Rendering event for creating each rectangle. This event is based on the frame rate of the video display, so it's pretty fast and a good choice for visuals. On receipt of that event, the program creates a Path object with a random color, a random transparency value, and random dimensions. This is added to the Grid named LayoutRoot.

You can run the program from here:


BadRandomRectangles.html

At first it seems to work fine, but it doesn't take long before it begins to slow down dramatically. Every time a new Path element is added to the Grid, it forces a whole new layout cycle. Grid will then iterate through all its children in measure and arrange passes. Obviously as the number of children gets larger, this process becomes slower. At some point, I suspect some exception will be raised, but I don't know what that exception is, or if it's worthwhile waiting until it happens.

So that's obviously the bad way to write a Silverlight random-rectangle program. What can be done to fix it?

At first I thought I could write some logic to remove rectangles from the Grid when they are sufficiently covered up so they are no longer visible. That might work for completely opaque rectangles, but when rectangles are partially transparent — and in WPF and Silverlight you really don't want to display boring opaque rectangles — the entire concept of a rectangle becoming completely covered up is no longer quite viable.

In WPF, you could always write the random-rectangle logic as a Win32 or WinForms control, and then host that control in the WPF application, but that approach doesn't interest me very much.

The really interesting solution involves a WPF class named RenderTargetBitmap, and the similar class new in Silverlight 3 called WriteableBitmap. (Just to make things confusing for the dual-platform programmer, WPF also has a class named WriteableBitmap but it's different from Silverlight's WriteableBitmap.)

If you're familiar with the Windows API, Silverlight's WriteableBitmap is conceptually similar to the memory DC, in that it allows you to draw actual graphics on the surface of a bitmap. With the old memory DC, you used GDI drawing commands; with the WriteableBitmap the image plastered on the bitmap is an object derived from UIElement. Once the visual appearance of that UIElement has been transferred to the surface of the bitmap, the UIElement itself is no longer needed and can be eliminated by the garbage collector.

Here's another way to think about it: A WriteableBitmap is just like the video display surface of an old-fashioned graphical environment before anybody thought about retained-mode graphics and composition layers. Just draw on it and throw the paintbrush away (metaphorically speaking).

I discussed the WPF versions of RenderTargetBitmap and WriteableBitmap in my MSDN Magazine article "Bitmaps and Pixel Bits". Jeff Prosise has written about the Silverlight 3 WriteableBitmap here, here, and here, and you'll find more examples of WriteableBitmap if you bingle it.

A random-rectangle program for Silverlight can consist of an Image element containing a big WriteableBitmap. During each timer tick, a new rectangle can be rendered on this bitmap without any buildup of thousands of rectangle elements.

You can download the BetterRandomRectangles project or follow along here. The XAML file puts both an Image and a TextBlock in the Grid:

<Grid x:Name="LayoutRoot"
      SizeChanged="OnGridSizeChanged">
    <Image Name="img" />
    <TextBlock Name="txt" />
</Grid>

The code section includes a field of type WriteableBitmap field and creates a new one every time the size of the Grid changes. This WritableBitmap is displayed by the sole Image element:

public partial class MainPage : UserControl
{
    Random rand = new Random();
    ulong count = 0;
    WriteableBitmap bitmap;

    public MainPage()
    {
        InitializeComponent();
        CompositionTarget.Rendering += OnRendering;
    }

    void OnGridSizeChanged(object sender, SizeChangedEventArgs args)
    {
        if (args.NewSize.IsEmpty)
            return;

        bitmap = new WriteableBitmap((int)args.NewSize.Width, 
                                     (int)args.NewSize.Height);
        img.Source = bitmap;
        count = 0;
    }

I thought about copying the contents of the existing WriteableBitmap to the new WriteableBitmap, but I decided to keep the code simple. The OnRendering event handler is very similar to the earlier one, except that the Path object is rendered on the WriteableBitmap:

    void OnRendering(object sender, EventArgs args)
    {
        if (bitmap == null)
            return;

        int width = bitmap.PixelWidth;
        int height = bitmap.PixelHeight;

        int x1 = rand.Next(width);
        int x2 = rand.Next(width);
        int y1 = rand.Next(height);
        int y2 = rand.Next(height);

        byte[] bytes = new byte[4];
        rand.NextBytes(bytes);
        Color clr = Color.FromArgb(bytes[0], bytes[1], 
                                   bytes[2], bytes[3]);
        Path path = new Path()
        {
            Fill = new SolidColorBrush(clr),
            Data = new RectangleGeometry()
            {
                Rect = new Rect(Math.Min(x1, x2), 
                                Math.Min(y1, y2),
                                Math.Abs(x1 - x2), 
                                Math.Abs(y1 - y2))
            }
        };

        path.Measure(new Size(width, height));
        path.Arrange(new Rect(new Point(), path.DesiredSize));

        bitmap.Render(path, null);
        bitmap.Invalidate();
        txt.Text = (++count).ToString();
    }
}

Notice that the program calls Measure and Arrangle on the Path element to give it a finite size. The documentation indicates that these calls are necessary for any element that is not part of an existing visual tree, but if you comment them out, the program still works. The Path is rendered on the WriteableBitmap with a call to Render. To update the bitmap as it appears in the Image element, a call to Invalidate is also required. You can run the program from here:


BetterRandomRectangles.html

I have insufficient hubris to call this program BestRandomRectangles, but I really haven't been able to think of a better approach.


Comments:

If you'd like to examine some antique random-rectangle programs, here's the source code from the first edition of Programming Windows but no executable. Executables are available from Programming Windows 3.0 and the Programming Windows 3.1, but you can only run these under 32-bit Windows. If you have 64-bit Windows, you'll need to run the Programming Windows 95 PeekMessage version or the Programming Windows 95 multi-threaded version. — Charles

Hey Charles,

have you seen the new "Cached Composition" feature of .NET 4.0 for WPF -- UIElement.CacheMode = new BitmapCache();?

Scottgu says "Cached Composition

Massive performance wins are possible with the new Cached Composition feature in WPF 4, which allows applications to cache arbitrary content including live and fully-interactive controls, vector geometry, etc. as bitmaps which persist in video memory. Once cached, these elements can be arbitrarily transformed, animated, manipulated, and can have Effects applied, all without having to re-render the cached element.

This spares both the CPU and the GPU the cost of re-rendering content, and instead allows the GPU to render straight from the cache. The cache(s) understand dirty regions, so a blinking cursor in a cached textblock, for example, will only need to re-render the cursor between frames. There's even a new Brush which specifically uses these intelligent caches — effectively a VisualBrush with vastly better performance."

It doesn't seem as 'low level' as WriteableBitmap, since it sounds like the 'objects' are still in memory, just not repeatedly re-rendered; so presumably memory usage will continue to grow even if Cached Composition keeps drawing time relatively snappy (assuming it _does_ improve rendering speed)?

I am downloading beta 2 to have a play...

CraigD, Sat, 31 Oct 2009 23:04:39 -0400

OK, more stuff to check out. Thanks! — Charles

Hi Mr Charles Petzold

I read a comment somewhere that you are writing a book on silverlight 4.0 is that true? plus what happen to you book programming windows , will it be available for windows 7 / windows server 2008?

Bye,

— Momoa, Mon, 2 Nov 2009 05:21:12 -0500

That sounds like fun. Unfortunately my last programming book (3D Programming for Windows) only sold 4,000 copies, which is a total disaster. The sales of that book are so bad that it is now unlikely that I'll be able to write any more programming books in the future. — Charles

I have read some your books,include the windows programming Bible "programming windows", "Code: The Hidden Language of Computer Hardware and Software" and a part of "The Annotated Turing". I have great respect for you because of your skills, not only your program skills but also your writing skills which can make other body understand what you are saying easily. I like your writing style. Your powerful programmable ability and the ability which you always can understand the essense of the things with make the books you write more readable.

As it were, I am your believer (^_^).

I have something else need to talk about with you.

So I beg for your Email.

I konw you are busy for your work and study, and I know the value of your time. I promise I don't send a mail to you because of some little things. If you worry about that it will bring trouble to you if you make your Email address know to public, you can send your Email address to me in mail.

This is my Email: billbonaparte110@hotmail.com

I'm looking forward to hear from you.(^_^)

Wish your work going well and have a good mood.

Bill Bonaparte, Thu, 26 Nov 2009 09:15:57 -0500

My email address is on the home page of my web site: cp@charlespetzold.com — Charles

Hi Mr Charles Petzold,

You are a great programmer.I love you and your books! And It's easy to know that you like writing books for windows.

I want to know whether you could write some books which could lead a primary programmer to a great one. That may be a series of books that can include essential knowledge of OS,Principles of Computer Composition,data organization,algorithm and so on.And the name of the book could be like "A program of primary programmers growing up". I think that must be cool. Many primary programmers get confused and whiny. They don't know how to learn programming. Some even don't konw what books to learn. They need the guide. Some books are necessary and the author could be noone but you. Because you are the unique people who has the ability to complete this massive project in my heart.

I hope you can think it over.Thank you!

— Joe Icewind, Fri, 27 Nov 2009 05:11:23 -0500

Ah this reminds me of ye olde days, doing random ansi generation using pascal ;) In fact my first software product was a menu generator for BBS's, every time a menu loaded, my app would generate a unique semi-random colored ansi background for the menu you were on. Wonder if I still have that thing around somewhere...

Setiri, Tue, 2 Feb 2010 22:06:47 -0500

Re your book publishing. You have written some exceptional books in the past, but what I think you need is a publisher that understands the market you're in. For example, I didn't think much of your Code = Application + Markup because in a graphical environment, it didn't have any pictures. I bought that book on the back of your previous reputation but was sorely disappointed. 3D programming is simply too niche. So a Silverlight book, provided it was in colour and has pictures, would be a perfect choice for you.

— Tone, Mon, 28 Jun 2010 19:54:24 -0400


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

(c) Copyright Charles Petzold
www.charlespetzold.com