“Universal” Mandelbrot

September 10, 2014

one comment

Those of you following my blog may have noticed a somewhat “obsessiveness” with the Mandelbrot Set. In the past, I’ve created a WPF version and a C++ AMP version (and privately a few more versions). I thought it was high time to write yet another version as a “Universal” app, running on Windows 8.1 and Windows Phone 8.1, while sharing as much code and XAML as possible. My approach was to port the WPF version to universal.

First, I created a blank new Universal App project in C#:

clip_image002[4]

This results in 3 projects being created – Mandelbrot.Windows, Mandelbrot.WindowsPhone and Mandelbrot.Shared.

By default, the Shared project only shares App.Xaml (and App.Xaml.cs).

The bulk of the implementation uses a user control named Mandelbrot that’s added to the Shared project. This control has a pretty simple XAML, very similar to the WPF version:

    <Grid>

        <Image x:Name=”_image” Stretch=”Fill”

PointerPressed=”OnPressed” PointerReleased=”OnReleased”

PointerMoved=”OnMoved” />

        <Path x:Name=”_selection” Stroke=”Red” StrokeThickness=”1″>

            <Path.Data>

                <RectangleGeometry x:Name=”_rect” />

            </Path.Data>

        </Path>

    </Grid>

 

The Image element will be tied to a WriteableBitmap that holds the actual pixel data, while the Path is used for the rectangle drawn while the user selects a region to zoom into.

As we can see, one difference from the WPF version is the set of events that we use to handle the selection rectangle. WPF uses the MouseDown/Up/Move events, but WinRT doesn’t have those, since mouse is just one possible input device; this could be a pen or a finger as well. That’s why the event names are different (PointerPressed/Released/Moved). Of course, this is not just name changes; the arguments to the events have additional information that allow (for example) to handle multiple touch points and be able to track each touch as it changes. In the Mandelbrot case this is not used, as just a single touch point is enough, but in other cases this information is critical (by the way, WPF has a separate set of events for touch handling, such as TouchDown/Move/Up/Enter/Leave, etc.).

Calculating the Set

The Mandelbrot Set is calculated on the complex plane, so it’s convenient to use the System.Numerics.Complex value type. Fortunately, it’s also available for WinRT applications.

The color gradient I used in the WPF version is built around a ColorRainbow class that provides the smooth colors. Since it’s pretty neutral, I could use it almost without changes. The only thing to change was the Color.FromRgb WPF method which doesn’t exist in WinRT to Color.FromArgb, while setting the alpha component to a constant 255 value (fully opaque). Of course the System.Windows.Media.Color WPF type is very different internally from the Windows.UI.Color WinRT type, but the external difference for most purposes is minimal.

The parameters for the gradient were stored in an XML file that I added to the project under a “Data” project folder. The code reads the file and builds the gradient when initializing like so:

var file = await StorageFile.GetFileFromApplicationUriAsync(

new Uri(“ms-appx:///Data/nice.xml”));

using (var stm = await file.OpenReadAsync()) {

       _rainbow = ColorGradientPersist.Read(stm.AsStreamForRead()).GenerateColors(512);

}

 

The “ms-appx:” scheme is the simplest way to get to a file that is distributed as part of the application package. The ColorGradientPersist class is a helper class that does the reading and writing of the rainbow XML using LINQ to XML. The only change I had to make here is call the extension method AsStreamForRead to turn a WinRT stream interface into a .NET Stream object so that the ColorGradientPersist.Read method would be able to do its job, since StorageFile.OpenReadAsync is a WinRT method and as such returns a WinRT stream interface and has no notion of a .NET Stream class. These kinds of adapters make it much easier to move from a WinRT “environment” to the, perhaps more familiar, .NET environment.

The set itself is calculated in the method named RunAsync:

public async Task RunAsync(Complex from, Complex to) {

       _from = from; _to = to;

       int width = _bmp.PixelWidth, height = _bmp.PixelHeight;

       double deltax = (to.Real – from.Real) / width;

       double deltay = (to.Imaginary – from.Imaginary) / height;

       byte[] bytes = new byte[width * 4];

       var buffer = _bmp.PixelBuffer.AsStream();

       for(int y = 0; y < height; y++) {

              await Task.Run(() => {

                     Parallel.For(0, width, x => {

                           int pixel = MandelbrotColor(from + new Complex(x * deltax, y * deltay));

                           int pos = x * 4;

                           bytes[pos] = (byte)(pixel & 0xff);

                           bytes[pos + 1] = (byte)((pixel >> 8) & 0xff);

                           bytes[pos + 2] = (byte)((pixel >> 16) & 0xff);

                           bytes[pos + 3] = 255;

                     });

                     buffer.Write(bytes, 0, bytes.Length);

              });

              _bmp.Invalidate();

       }

}

 

This method has important changes from the WPF version. The most important one is the way to access the pixel data inside the WriteableBitmap (named _bmp in the code). In WinRT’s version, the pixel buffer is available with the PixelBuffer property. Unfortunately, it’s typed as IBuffer, which in itself is a pretty useless interface, hosting just a Length and Capacity properties. The object implementing the interface has more functionality but it’s not obvious how to get to it. In C++ for instance, you would have to query for another interface named IBufferByteAccess, which is not a WinRT interface, but a regular COM interface (that is, it inherits from IUnknown and not IInspectable). In C#, the easiest way to get to something richer is to use an extension method on IBuffer – AsStream, which turns it into a .NET Stream that is much easier to work with. With this Stream in hand, the code uses the normal Write method to store the color values returned from the Mandelbrot method that does the actual calculation for a particular point.

The MainPage.xaml file remains separated between the Windows and Windows Phone versions, because I wanted to add a command bar option to reset the Mandelbrot Set to its initial state, but wanted the freedom to use primary command for Windows and secondary command for the phone (so that’s it’s hidden by default and just shows the ellipsis). Since the real workhorse is the shared user control, the main page is fairly simple. Here’s the important part of the Windows version:

    <Grid Background=”{ThemeResource ApplicationPageBackgroundThemeBrush}”>

        <c:Mandelbrot x:Name=”_mandelbrot”/>

    </Grid>

    <Page.BottomAppBar>

        <CommandBar >

            <AppBarButton Icon=”Clear” Label=”Reset” Click=”OnReset” />

        </CommandBar>

    </Page.BottomAppBar>

 

And the Phone version:

    <Grid>

        <c:Mandelbrot x:Name=”_mandelbrot”/>

    </Grid>

    <Page.BottomAppBar>

        <CommandBar >

            <CommandBar.SecondaryCommands>

                <AppBarButton Icon=”Clear” Label=”Reset” Click=”OnReset” />

            </CommandBar.SecondaryCommands>

        </CommandBar>

    </Page.BottomAppBar>

 

Here is a screenshot of the app in action on the Windows simulator:

clip_image004[4]

And the Phone:

clip_image006[4]

The code can be downloaded here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

one comment

  1. Pingback: Mandelbrot Set with Xamarin Forms (part 1) | Pavel's Blog