Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

Monotouch: drawing an image pixel by pixel

I have been exploring Monotouch, a C# development environment for the iPhone/iPad,  for some time now. And I really do like it. The learning curve is somewhat steep, but after getting familiar with the (rather strict, but not bad at all) philosophy of iOS application architecture it makes my C# skills really shine. In this post I will describe the essence of drawing (a lot of) individual pixels in an image. We will meet the iOS Api as it is wrapped up in the Monotouch framework, working with managed and unmanaged resources and see how the result is used in an iOS app. All in plain C# code.

The problem

The graphical API does have a lot of options to draw shapes and colors. But, just like the Windows API, there is no way to write individual pixels. There are tricks like drawing a one by one square, but these are far too expensive to use. The best way is to write directly to the video memory, the iOS API has very useful options to do this. A hurdle is that the encoding of pixel colors is quite different between the iOS api and the video memory.

Color

An iOS color is wrapped up in the Monotouch UIColor class. In these color objects the Red, Green, Blue and Alpha value are encoded as floats, with a range between 0 and 1. In the iPhone’s video memory these RGBA values are encoded as bytes, just like the Color structure in the  .net Framework windows style. This helper Color class bridges these differences. It describes a color in RGB bytes and has a property to translate its value to and from a Monotouch UIcolor.

public class Color

{

    public Color (byte r, byte g, byte b)

    {

        R = r;

        G = g;

        B = b;

    }

 

    public byte R { get; set;}

    public byte G { get; set;}

    public byte B { get; set;}

 

    public UIColor AsUIColor

    {

        get

        {

            float r = (float) R / (float) 255;

            float g = (float) G / (float) 255;

            float b = (float) B / (float) 255;

            return new UIColor(r, g, b, 1);

        }

        set

        {

            float r, g, b, a;

            value.GetRGBA(out r, out g, out b, out a);

            R = (byte) (r * 255);

            G = (byte) (g * 255);

            B = (byte) (b * 255);

        }

    }

}

 

The AsUIColor property does the translation.

Drawing the image

We are going to build an image, pixel by pixel from a 2-dimensional array of objects having a Color property. Monotouch, in fact the wrapped up iOS API itself, is based on a MVC model. To work with an Image you need an an UIImageView object. The CustomImageView class presented here is a subclass of UIImageView. The View class has an Image property, typed UIImage, which contains the image itself. In the constructor of the custom class the array with the Pixels is passed in. The DrawImage method does build the Image property from this array.

public class CustomImageView : UIImageView

{

    public CustomImageView (Cell[][] cells) : base(new RectangleF(0f, 0f, cells[0].Length, cells.Length))

    {

        Cells = cells;

    }

 

    private Cell[][] Cells { get; set; }   

 

    public void DrawImage ()

    {

 

        IntPtr buffer = IntPtr.Zero;

        CGBitmapContext context = null;

        try {

            var width = Cells[0].Length;

            buffer = Marshal.AllocHGlobal (Cells.Length * width * 4);

            var colorSpace = CGColorSpace.CreateDeviceRGB ();

            context = new CGBitmapContext (buffer, width, Cells.Length, 8, 4 * width, colorSpace, CGImageAlphaInfo.NoneSkipFirst);

            colorSpace.Dispose ();

            context.InterpolationQuality = CGInterpolationQuality.None;

            context.SetAllowsAntialiasing (false);

 

            unsafe {

 

                var bufPtr = (byte*)((void*)buffer);

 

                for (int i = 0; i < Cells.Length; i++) {

                    var ofset = i * width;

                    for (int j = 0; j < width; j++) {

                        var cell = Cells[i][j];

                        var bc = 4 * (ofset + j);

                        if (cell.State == CellState.Alive) {

                            bufPtr[bc + 1] = cell.Color.R;

                            bufPtr[bc + 2] = cell.Color.G;

                            bufPtr[bc + 3] = cell.Color.B;

                        }

                        else

                        {

                            bufPtr[bc + 1] = 0;

                            bufPtr[bc + 2] = 0;

                            bufPtr[bc + 3] = 0;

                        }

                    }

                }

 

 

            }

            Image = UIImage.FromImage (context.ToImage ());

 

        } finally {

            if (context != null)

                context.Dispose ();

            Marshal.FreeHGlobal (buffer);

        }

    }

 

}

In the constructor the image is passed in. The constructor invokes the base constructor of the UIImageView class to set the view’s size. All work is done in DrawImage

 

  • Declare a bitmap context

  • Allocate a buffer of 4 bytes per pixel

  • Create a colorspace. Here it is a plain RGB colorspace

  • Initialize the bitmapcontext. Some of the parameters are somewhat misty or may seem redundant. The 8 stands for 8 bits per component. Besides the width the number of bytes per row is required as well. The parameterlist is more or less self describing, else just check the documentation.

  • Dispose the colorcontext. It is an unmanaged resource.

  • Switch of interpolation and antialiasing for the sake of speed. All pixels will be set explicitly anyhow

Next comes the unsafe part. Here we step right into the deepness of the iPhone. The good thing is that C# has the unsafe keyword for isolating unsafe code. It requires setting the “allow unsafe code” option in the project’s property.

  •  
  • bufPtr is pointer to the buffer of the bitmapcontext.

  • In this buffer every 4 bytes encode one pixel.

  • The “meaning” of these bytes was determined by the colorInfo and CGImageAlphaInfo parameters passed to the constructor of the bitmap-context. In this case it is ARGB.

  • The color bytes form the color are copied into the buffer.

  • Note that you need to set every pixel. There is no guarantee whatsoever that the buffer is clean. In case pixels are not set your image will be filled with a lot of noise. In my case I set the RGB values for pixels whose Cell is not Alive (Yes it is the game of life, but not as you know it :)) explicitly to 0.

  • Having filled the buffer the Image is created by invoking context.ToImage()

  • Finally you have to clean up all unmanaged resources. The context is disposed and the buffer is freed. The try finally construct makes sure that the resources are always discarded, also when en error is hit..

 

Using the view

 

This custom built imageview can be displayed by adding it as a subview to any other view. When you have the array of pixels ready that’s straightforward. Like here in main()

// The name AppDelegate is referenced in the MainWindow.xib file.

public partial class AppDelegate : UIApplicationDelegate

{

    // This method is invoked when the application has loaded its UI and its ready to run

    public override bool FinishedLaunching (UIApplication app, NSDictionary options)

    {

        // If you have defined a view, add it here:

        // window.AddSubview (navigationController.View);

        var LifesGrid = MyGrid();

        var LifesImage = new CustomImageView (LifesGrid);

        window.AddSubview(LifesImage);           

        window.MakeKeyAndVisible ();

        */

        LifesImage.DrawImage ();

        return true;

    }

 

    // This method is required in iPhoneOS 3.0

    public override void OnActivated (UIApplication application)

    {

    }

}

The customview is now a part of your app. After manipulating the content of the array updating the image requires a new call to DrawImage.

Winding down

In this example we have seen C# and the Monotouch .net framework really shine. No refcounting issues at all, just one pointer. When it comes to working with unmanaged resources the combination of try finally and Idisposable does its work in a clear manner. And when it comes to banging deep inside memory itself unsafe makes that clear code as well.

This way building iPhone apps is plain pleasure. Which would be complete having Resharper for MonoDevelop. As I mentioned before the latter does have some refactoring and code-analysis, but they completely pale compared what I was used to.

This entry was posted in Coding, Mobile. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • arronlee

    Hi, Bob.

    I am testing about the related image drawing toolkits these days. I want to look for a professional one whose way of processing is simple and fast to help me with the related work. It will be better if it is totally manual and can be customized by users according to our own favors. Do you have any ideas about it? Or any good suggestion? Thanks in advance.

    Best regards,
    Arron

  • Anonymous

    The View on which you draw should be in front. In your case it sounds that there is another window on top of it.

  • Geert

    Just what I was looking for. However, when I try the code, the image seems te be drawn “behind” the window (I see a glimps of it when I push the button, and the app “minimizes”). 

  • http://twitter.com/ryanwoodings Ryan Woodings

    Thanks for the example code, I’ve been searching for hours trying to figure out how to replace System.Drawing.SetPixel() in MonoTouch!

  • Anonymous

    :)
    Nice you can still do that.
    OTOH the idea of bit-banging the graphic transitions iOS has to offer would be a technological nightmare. Good to have that abstracted away into some good methods.
    On the whole Monotouch and iOS shine in offering different ways to draw the picture.

  • Wouter

    What happened? 20 years ago, drawing a pixel directly in video memory would be as simple as this:

    Mem [$A000:X+(Y*BufferWidth)]:=Col;

    I can hear some people thinking: no worries, we can add yet another abstraction layer on top of the existing ones to fix that! :)

  • Anonymous

    MyGrid results in a 2dimensional array, an element for every pixel. In my case every element is an object which has a Color object. Color as the class described. It describes the color of the pixel to draw.

  • James

    Hi Peter,

    I am trying to do some pixel by pixel drawing as well. You are clearly far more advanced on this stuff than me! I understand your code to the point where you use the DrawImage method in the main function. I MyGrid() returns the array of pixels? How does this work? I am missing something and can’t get my head around it!

    Thanks,
    James

  • Bob

    Are there more monotouch examples? Are people really using it to create iPhone apps?