As Beast Boxing 3D wraps up, I’ve been brushing up my iOS SDK chops by working through the Big Nerd Ranch Guide to iPhone Programming, a great book that takes a fundamentals-based approach to learning how to operate well within Apple’s SDKs. In Chapter 16, there’s a cool challenge where they ask you to take a relatively simple method of converting an image to a thumbnail, and make it prettier. I spent some time researching it and ended up with a method I’m pretty satisfied with that uses CoreGraphics clipping paths and gradients to do the job. As I’m working on iPad apps next, iOS 3.2 is my target, so I take advantage of UIBezierPath, an iOS 3.2 addition, in this sample. Without further ado, here’s the code. Enjoy!
- (void)setThumbnailDataFromImage:(UIImage *)image
{
// Release old data
[thumbnailData release];
[thumbnail release];
// Create an empty image of size 70 x 70
CGRect imageRect = CGRectMake(0, 0, 70, 70);
// Create an offscreen graphics context to draw into
UIGraphicsBeginImageContext(imageRect.size);
CGContextRef c = UIGraphicsGetCurrentContext();
UIBezierPath *bp = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:10.0];
CGPathRef path = [bp CGPath];
CGContextAddPath(c, path);
CGContextClip(c);
// Render the big image onto the image context
[image drawInRect:imageRect];
// Render a glossy gradient on top!
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorSpace;
size_t num_locations = 2;
CGFloat locations[2] = {
0.0, 1.0
};
CGFloat components[8] = {
1.0, 1.0, 1.0, 0.85, // Start color
1.0, 1.0, 1.0, 0.05 // End color
};
rgbColorSpace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorSpace, components, locations, num_locations);
CGPoint topCenter = CGPointMake(CGRectGetMidX(imageRect), 0.0);
CGPoint topQuarterBoundary = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height * 0.35);
CGContextDrawLinearGradient(c, glossGradient, topCenter, topQuarterBoundary, 0);
CGGradientRelease(glossGradient);
// Render a shadow gradient down low
CGGradientRef shadowGradient;
CGFloat shadowLocations[2] = {
0.0, 1.0
};
CGFloat shadowComponents[8] = {
0.0, 0.0, 0.0, 0.65, // Start color
0.0, 0.0, 0.0, 0.00 // End color
};
rgbColorSpace = CGColorSpaceCreateDeviceRGB();
shadowGradient = CGGradientCreateWithColorComponents(rgbColorSpace, shadowComponents, shadowLocations, num_locations);
CGPoint bottomCenter = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height);
CGPoint bottomQuarterBoundary = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height * 0.85);
CGContextDrawLinearGradient(c, shadowGradient, bottomCenter, bottomQuarterBoundary, 0);
CGGradientRelease(shadowGradient);
CGColorSpaceRelease(rgbColorSpace);
// Make a new one from the image context
thumbnail = UIGraphicsGetImageFromCurrentImageContext();
// Retain the new one
[thumbnail retain];
// Clean up image context resources
UIGraphicsEndImageContext();
// Make a new data object from the image
thumbnailData = UIImageJPEGRepresentation(thumbnail, 0.5);
// You may get malloc warnings on the simulator from this line,
// it is a bug in the simulator
// Retain it
[thumbnailData retain];
}
{
// Release old data
[thumbnailData release];
[thumbnail release];
// Create an empty image of size 70 x 70
CGRect imageRect = CGRectMake(0, 0, 70, 70);
// Create an offscreen graphics context to draw into
UIGraphicsBeginImageContext(imageRect.size);
CGContextRef c = UIGraphicsGetCurrentContext();
UIBezierPath *bp = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:10.0];
CGPathRef path = [bp CGPath];
CGContextAddPath(c, path);
CGContextClip(c);
// Render the big image onto the image context
[image drawInRect:imageRect];
// Render a glossy gradient on top!
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorSpace;
size_t num_locations = 2;
CGFloat locations[2] = {
0.0, 1.0
};
CGFloat components[8] = {
1.0, 1.0, 1.0, 0.85, // Start color
1.0, 1.0, 1.0, 0.05 // End color
};
rgbColorSpace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorSpace, components, locations, num_locations);
CGPoint topCenter = CGPointMake(CGRectGetMidX(imageRect), 0.0);
CGPoint topQuarterBoundary = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height * 0.35);
CGContextDrawLinearGradient(c, glossGradient, topCenter, topQuarterBoundary, 0);
CGGradientRelease(glossGradient);
// Render a shadow gradient down low
CGGradientRef shadowGradient;
CGFloat shadowLocations[2] = {
0.0, 1.0
};
CGFloat shadowComponents[8] = {
0.0, 0.0, 0.0, 0.65, // Start color
0.0, 0.0, 0.0, 0.00 // End color
};
rgbColorSpace = CGColorSpaceCreateDeviceRGB();
shadowGradient = CGGradientCreateWithColorComponents(rgbColorSpace, shadowComponents, shadowLocations, num_locations);
CGPoint bottomCenter = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height);
CGPoint bottomQuarterBoundary = CGPointMake(CGRectGetMidX(imageRect), imageRect.size.height * 0.85);
CGContextDrawLinearGradient(c, shadowGradient, bottomCenter, bottomQuarterBoundary, 0);
CGGradientRelease(shadowGradient);
CGColorSpaceRelease(rgbColorSpace);
// Make a new one from the image context
thumbnail = UIGraphicsGetImageFromCurrentImageContext();
// Retain the new one
[thumbnail retain];
// Clean up image context resources
UIGraphicsEndImageContext();
// Make a new data object from the image
thumbnailData = UIImageJPEGRepresentation(thumbnail, 0.5);
// You may get malloc warnings on the simulator from this line,
// it is a bug in the simulator
// Retain it
[thumbnailData retain];
}
Hi,
Nice work!
But I was wondering why is the retain necessary at the end of:
UIBezierPath *bp = [[UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:10.0] retain];
It’s true it doesn’t work without it, but I can’t see why the retain count must be incremented here…
Thanks!
Hi Victor,
Good catch, the retain is unnecessary because the bezierPathWithRoundedRect:imageRect:cornerRadius call is a convenience function, and should return an autoreleased UIBezierPath object. I’ll update the code to remove that retain call as well as the corresponding release further down. Since I don’t use the path outside of this function, the retain/release is unnecessary, as it will be cleaned up the next time through the run loop.
-Gordon
Hi thank you for great piece of code but I am newbie and cannot understand what is :thumbnailData?