Currently I am spending some (if you ask my wife, she would probably say: a lot) of my free time writing a little app for the iPhone as a native frontend to one of our products, a deliverable management system (conjectPM).

In 1990 I got myself a NeXTStation and started to play around with NeXTStep and Objective-C. I really loved the AppKit as it was called back then. I became an iPhone developer as soon as Apple created the program and immediately got eh iPhone SDK. I have to say, programming the iPhone is as much a joy as working on NeXTStep was back then. The fundamentals haven’t changed much. The frameworks still are based on a very clear MVC pattern with lots of delegation to separate framework functionality from your own code. XCode is a nice environment but by far not as evolved as IntelliJ IDEA. A huge amount of often needed functionality is just there (like splitting a NSString containing a path into it’s components), but not having garbage collection and the arcane [object message:param]; syntax is quite cumbersome.

Because I want to mark the icons of favorites with a blue star and I don’t want to create every icon twice, I wanted to just superimpose the star onto the standard icon for the document type. It wasn’t as easy as

UIImage *result = [originalIcon compositeWith:starIcon];

Here is how to do it (based on the example in SDK documentation and helped by a hint from SkylarEC)

  1. - (UIImage *) getIconOfSize:(CGSize) size {
  2.    UIImage *icon = [[UIImage imageNamed:self.iconName] scaleImageToSize:size];
  3.    UIImage *overlay = [UIImage imageNamed:@"BlueStar.png"];
  4.    CGRect iconBoundingBox = CGRectMake (0, 0, size.width, size.height);
  5.    CGRect overlayBoundingBox = CGRectMake (size.width/2, size.height/2,
  6.                                               size.width/2, size.height/2);
  7.    CGContextRef myBitmapContext = [self createBitmapContextOfSize:size];
  8.    CGContextSetRGBFillColor (myBitmapContext, 1, 1, 1, 1);
  9.    CGContextFillRect (myBitmapContext, iconBoundingBox);
  10.    CGContextDrawImage(myBitmapContext, iconBoundingBox, icon.CGImage);
  11.    CGContextDrawImage(myBitmapContext, overlayBoundingBox, overlay.CGImage);
  12.    UIImage *result = [UIImage imageWithCGImage: CGBitmapContextCreateImage (myBitmapContext)];
  13.    CGContextRelease (myBitmapContext);
  14.    return result;
  15. }
  1. Create the CGContextRef
  2. draw both icons with the correct bounding rectangles into the CGContext
  3. Get the combined UIImage out of the CGContext with CGBitmapContextCreateImage
  4. done

Here is how to create the CGContextRef (based on the SDK example)

  1. - (CGContextRef) createBitmapContextOfSize:(CGSize) size {
  2.     CGContextRef    context = NULL;
  3.     CGColorSpaceRef colorSpace;
  4.     void *          bitmapData;
  5.     int             bitmapByteCount;
  6.     int             bitmapBytesPerRow;
  7.  
  8.     bitmapBytesPerRow   = (size.width * 4);
  9.     bitmapByteCount     = (bitmapBytesPerRow * size.height);
  10.  
  11.     colorSpace = CGColorSpaceCreateDeviceRGB();
  12.     bitmapData = malloc( bitmapByteCount );
  13.     if (bitmapData == NULL) {
  14.         fprintf (stderr, "Memory not allocated!");
  15.         return NULL;
  16.     }
  17.     context = CGBitmapContextCreate (bitmapData,
  18.                                          size.width,
  19.                                          size.height,
  20.                                          8,      // bits per component
  21.                                          bitmapBytesPerRow,
  22.                                          colorSpace,
  23.                                          kCGImageAlphaPremultipliedLast);
  24.         CGContextSetAllowsAntialiasing (context,NO);
  25.     if (context== NULL) {
  26.         free (bitmapData);
  27.         fprintf (stderr, "Context not created!");
  28.         return NULL;
  29.     }
  30.     CGColorSpaceRelease( colorSpace );
  31.     return context;
  32. }

Beware: In the original example the colorSpace on line 11 is created like this

    colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

This is deprecated. Use GColorSpaceCreateDeviceRGB() instead.

9 Comments »

  • Wow! First hit in Google and this is EXACTLY what I need to do for one of my projects. Thanks SO much!!

    Comment by iPhoneCzar — July 15, 2009 @ 5:21 pm
  • Sure thing – glad to help

    Comment by Chris — July 16, 2009 @ 2:58 pm
  • I got a white background instead of a clear overlay.

    Remove the white background:

    CGContextSetRGBFillColor (myBitmapContext, 1, 1, 1, 1);
    CGContextFillRect (myBitmapContext, iconBoundingBox);

    And instead, start with a blank slate:

    CGContextClearRect (myBitmapContext, iconBoundingBox);

    That will preserve any alpha in your icons.

    Comment by Joe — August 24, 2009 @ 11:15 pm
  • Thanks, Joe.
    I noticed that problem originally as well.
    As my icons are rendered on a white background it wasn’t a problem.
    Preserving alpha is, of course, better.

    Comment by Chris — September 7, 2009 @ 9:58 pm
  • Joe thanks for the example!

    Comment by Diter dos Santos — May 30, 2010 @ 11:21 pm
  • Thank you kindly for this snippet of code! I’m relatively new to developing on the iPhone SDK with Objective-C, and while I don’t FULLY understand what’s going on in the createBitMapContextOfSize:size function, it’s pointing me in the proper direction. Thank you for sharing!

    Comment by Mike — June 5, 2010 @ 2:13 am
  • Tried to reuse your code. Isn’t there a memory leak due to
    bitmapData = malloc( bitmapByteCount );
    never being freed when context is not NULL?

    Comment by chrizz — November 1, 2010 @ 1:33 pm
  • @chrizz, you are right.
    Starting with iOS 4.0 and on OS X 10.6 you can pass NULL if you want Quartz to allocate memory for the bitmap. According to the docs “This frees you from managing your own memory, which reduces memory leak issues.”

    So this becomes
    colorSpace = CGColorSpaceCreateDeviceRGB();
    context = CGBitmapContextCreate (NULL,
    size.width,
    size.height,
    8, // bits per component
    bitmapBytesPerRow,
    colorSpace,
    kCGImageAlphaPremultipliedLast);
    CGContextSetAllowsAntialiasing (context,NO);

    No more bitmapData, passing NULL instead to CGBitmapContextCreate()

    Comment by Chris — November 3, 2010 @ 1:03 pm
  • Fantastic – just what I needed.

    Comment by Megan McVey — December 22, 2010 @ 8:20 pm

Email this Share this on Facebook Share this on LinkedIn Tweet This! RSS feed for comments on this post. TrackBack URL

Leave a comment