views:

355

answers:

2

I'd like to recreate the effect that the UITabBarController is doing with images in the tab bar, using exactly the same images. I've futzed around with a number of ideas using masking, but I haven't come up with anything satisfactory.

Anyone have a recipe for doing this?

A: 

Three20 has a class in it for making masks over images. You want the TTMaskStyle. You can try to pull out the source code, or just copy the logic but it should work for you. You can find an example in the TTCatalog example app.

coneybeare
Thank you ... I've had some difficulty working with the 320 code, but I'll pull that apart over the weekend and see how it works (and post back here)
dpjanes
+2  A: 

The Three20 stuff didn't help too much, but after a few hours of hacking I've come up with a satisfactory solution to the problem. The two core tricks one needs to know are:

  • how to convert an image to the DeviceGray color space
  • how to mask an image

Comments are inline with the code:

+ (UIImage*) tabBarLikeIconWith:(UIImage*)tabBarIconImage
{
/*
 * 1. 
 * The output is going to be an image the exact same size as the tabBarIconImage
 */
CGSize size = CGSizeMake(tabBarIconImage.size.width, tabBarIconImage.size.height);
CGRect bounds = CGRectMake(0, 0, tabBarIconImage.size.width, tabBarIconImage.size.height);

/*
 * 2.
 * The background image is a fairly big (50x50) image used to create whatever gradient
 * effect is desired for the final image. We clip & move the image to proper size.
 *
 * This could probably be cleverly composed on the fly using Quartz functions too. 
 */
static UIImage* backgroundImage = nil;
if (backgroundImage == nil) {
 backgroundImage = [UIImage imageNamed:@"TabBarLikeTransition.png"];
}

UIGraphicsBeginImageContext(size);
[backgroundImage drawAtPoint:CGPointMake(( size.width - backgroundImage.size.width) / 2, ( size.height - backgroundImage.size.height ) / 2)];

UIImage* workingBackgroundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

/*
 * 3.
 * We cannot deal with alpha levels in the tabBarIconImage, so we composite
 * it with a white background to get a solid image.
 */
UIGraphicsBeginImageContext(size);

[[UIColor whiteColor] set];
UIRectFill(bounds);
[tabBarIconImage drawAtPoint:CGPointMake(0, 0)];

UIImage* maskImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

/*
 * 4.
 * Next we convert the maskImage to the 'DeviceGray' colorspace
 * needed by Apple's masking functions. Why this isn't done automatically
 * is something of a mystery to me.
 */
CGColorSpaceRef grayscaleColorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef bitmapContext = CGBitmapContextCreate(NULL,
           size.width,
           size.height,
           8,
           8 * size.width,
           grayscaleColorSpace,
           0
           );

CGContextDrawImage(bitmapContext, bounds, maskImage.CGImage);
CGImageRef maskImageRef = CGBitmapContextCreateImage(bitmapContext);

maskImage = [UIImage imageWithCGImage:maskImageRef];

CGContextRelease(bitmapContext);
CGImageRelease(maskImageRef);
CGColorSpaceRelease(grayscaleColorSpace);

/*
 * 5.
 * Mask the background and our new grayscale mask and we're off the races.
 * I'd probably clean up these variables names if I had free time.
 *
 * This cleverness comes from here:
 * http://iphonedevelopertips.com/cocoa/how-to-mask-an-image.html
 */
CGImageRef maskRef = maskImage.CGImage; 
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
         CGImageGetHeight(maskRef),
         CGImageGetBitsPerComponent(maskRef),
         CGImageGetBitsPerPixel(maskRef),
         CGImageGetBytesPerRow(maskRef),
         CGImageGetDataProvider(maskRef), NULL, true);

CGImageRef masked = CGImageCreateWithMask([workingBackgroundImage CGImage], mask);

CGImageRelease(mask);

return [UIImage imageWithCGImage:masked];
}
dpjanes