Dynamic Pattern Images with Core Graphics

The image handling capabilities available in iOS (and OS X for that matter) are pretty spectacular. Using only high level APIs, you can work with images quite easily. But lurking very close to the high level APIs is Core Graphics, where in the real power lies.

Background

With the recent release of iOS6 and more importantly the iPhone 5, I began updating one of my apps to take advantage of the large screen. This immediately presented some challenges, because now I had to support two screen sizes instead of one. Not a big deal, but in the case of this particular app, it uses a full-screen image overlay to provide a crucial visual effect.

The obvious solution (although not the best solution) was simply to provide two additional full-screen images for the iPhone 5 (one at 320×568, one at 640×1136). Those were easily created, but presented a couple new issues:

  1. The app would now need to conditionally select the correct overlay image based on screen size, and
  2. The layout of the pattern on the overlay didn’t “break” nicely where I needed it to break (the repeated image was cut in half in some cases), and
  3. Adding more full-screen overlay images was only going to add to the size of the app, which is already large.

What to do?

Pattern images to the rescue!

I immediately considered creating images that represented a single copy of the pattern I wanted to use and including those in the app bundle. That would then allow me to write code like this:

myView.backgroundColor = [UIColor colorWithPatternImage:overlayPattern];

That works and solves (1) and (3), but not (2). The solution to (2) really requires creating a pattern image dynamically that, when repeated, fills the view without any partial patterns appearing.

Enter Core Graphics

Core Graphics is incredibly powerful and I will not attempt to cover it all here. Heck, I don’t even purport to be a Core Graphics expert. But I did figure out how to do something that is pretty useful in creating images dynamically for use as pattern images.

To start, we have to understand something basic about Core Graphics: Everything you do requires a context, and there are at least a few different kinds of contexts you can work with. For our purposes, we need a bitmap context. We’ll draw in this context, and then ask Core Graphics to give us something we can work with more easily: a UIImage object.

Creating a bitmap context requires one C-style API call. (Yes, Core Graphics at this level uses all C style APIs.) I wrote a wrapper function:

CGContextRef CreateBitmapContext(int width, int height, CGColorSpaceRef colorSpace, CGImageAlphaInfo alpha)
{
    CGContextRef    context = NULL;
    int bitmapBytesPerRow   = (width * 4);

    context = CGBitmapContextCreate (NULL, //bitmapData
                                     width,
                                     height,
                                     8,      // bits per component
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     alpha);

    return context;
}

And I call it this way:

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef myBitmapContext = CreateBitmapContext(myWidth, myHeight, colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);

The arguments myWidth and myHeight are the size of the image I ultimately want, so we need a context with a matching size. The color space and alpha arguments come into play later, but to draw in color, you need both an RGB color space and an alpha channel.

Now we can work with the context. We can fill it with a solid color:

    // Fill the square with black.
    CGContextSetRGBFillColor (myBitmapContext, 0, 0, 0, 1);
    CGContextFillRect (myBitmapContext, CGRectMake (0, 0, myWidth, myHeight ));
    CGImageRef image = CGBitmapContextCreateImage (myBitmapContext);
    CGContextRelease (myBitmapContext);

And we can draw in it (with a different color):

    // Set the fill color to white
    CGContextSetRGBFillColor(myBitmapContext, 1, 1, 1, 1);
    // Draw a diamond
    CGContextBeginPath(myBitmapContext);
    CGContextMoveToPoint(myBitmapContext, myWidth/2.0, 2);
    CGContextAddLineToPoint(myBitmapContext, myWidth-2.0, myHeight/2.0);
    CGContextAddLineToPoint(myBitmapContext, myWidth/2.0, myHeight-2.0);
    CGContextAddLineToPoint(myBitmapContext, 2, myHeight/2.0);
    CGContextAddLineToPoint(myBitmapContext, myWidth/2.0, 2);
    CGContextClosePath(myBitmapContext);
    // Fill the diamond
    CGContextFillPath(myBitmapContext);

I’ve put all this together in a very short demo project, available on Github. On an iPhone 4, it creates the following screen:

Nifty, eh?

Next time, I’ll expand this project to create patterns with holes in them using image masks.

Posted in: iDevBlogADay

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove you are human: *