Search code examples
iphoneiosuikitbitmapcgimageref

How would I draw to a mutable in-memory bitmap?


I'm trying to write a simple painting app for iOS as a first non-trivial project. Basically, on every touch event, I need to open a graphics context on the bitmap, draw something over top of where the user left off, and close it.

UIImage is immutable, so it's not exactly suitable for my purposes; I'd have to build a new bitmap and draw the old one into the new one. I can't imagine that performing well. Is there any sort of mutable bitmap class in UIKit, or am I going to have to go down to CGImageRef?


Solution

  • If you're willing to venture away from cocoa, I would strongly recommend using OpenGL for this purpose. Apple provide a great sample app (GLPaint) that demonstrates this. Tackling the learning curve of OpenGL will certainly pay off in terms of appearance, performance, and sheer power & flexibility.

    However, if you're not up for that then another approach is to create a new CALayer subclass overriding drawInContext:, and store each drawing stroke (path and line properties) there. You can then add each 'strokeLayer' to the drawing view's layer hierarchy, and force a redraw each frame. CGLayers can also be used to increase performance (which is likely to become a big issue - when a user paints a long stroke you will see frame rates drop off very rapidly). In fact you will likely end up using a CGLayer to draw to in any case. Here is a bit of code for a drawRect: method which might help illustrate this approach:

    - (void)drawRect:(CGRect)rect {
        // Setup the layer and it's context to use as a drawing buffer.
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGLayerRef drawingBuffer = CGLayerCreateWithContext(context, self.bounds.size, NULL);
        CGContextRef bufferContext = CGLayerGetContext(drawingBuffer);
    
        // Draw all sublayers into the drawing buffer, and display the buffer.
        [self.layer renderInContext:bufferContext];
        CGContextDrawLayerAtPoint(context, CGPointZero, drawingBuffer);
        CGLayerRelease(drawingBuffer);
    }
    

    As far as mutability goes, the most obvious thing to do would be to draw the background colour over the painting strokes. This way an eraser stroke would be exactly the same as a painting stroke, just a different colour.

    You mentioned using a bitmap image, and this is really beginning to hint at OpenGL render-to-texture, where a series of point sprites (forming a line) can be drawn onto a mutable texture at very high framerates. I don't want to put a damper on things, but you will inevitably hit a performance bottleneck using Core Graphics / Quartz to do your drawing in this fashion.

    I hope this helps.