Category Archives: Cocoa

PNG compression on iPhone/iPad

I was having a discussion with a coworker about png compression for the iphone and wanted to resolve the issue of whether we should bundle Fireworks source assets in xcode for quick editing and recompiling, or if we should export compressed versions to use for the bundling phase. I know the iPhone does it’s own compression and byte reordering on pngs, but I wasn’t sure how much compression was done, so here’s the answer.

I made a relatively complex png file for testing, the source file is a 230 kb Fireworks file, which is pretty close to some of the assets we have in our projects. A quick 24bit png export from fireworks produces a 6 kb png file with no visual fidelity loss. When the project is compiled to the device, the source image is compressed by xcode down to 19 kb, while the exported version actually increases in size to 7 kb. So far this supports the theory that manual compression can be up to 3 times better then letting xcode handle it for you.

Original Files in Xcode Project

Files compressed for iOS device build.

Additionally it seems that I suffered no visual loss by letting xcode recompress these png files, and decompression times for both pngs on the device took roughly 20 milliseconds each.

Honestly I think you could read these results either way, lazy developers may choose to just use source files in xcode and suffer a 3x penalty on images compression, for those that like to control the compression exactly on their files, you’ll be better off.

360iDev session materials on Quartz 2D

360iDev San Jose has been awesome so far and it’s not over yet, still a whole day of sessions to go. I’m putting my session materials up now during the keynote so I don’t forget after I’ve presented. Get the zip file here.

Update: I forgot to mention during my presentation, but the samples within the zip archive may contain memory leaks for the sake of code brevity. If you copy and paste the code samples into your own projects, you might need to make sure you add cleanup code to the end of it.

Creating the ‘loupe’ or magnifying glass effect on the iphone.

One of my recent projects tasked me with figuring out how to recreate the magnifying glass effect that Apple uses inside textfields on the iPhone, but in a more generic way so anything on the screen could be magnified. Getting this effect is pretty simple in any visual framework once you figure out how masking works and which api will let you draw a bitmap of a given graphics layer. While the logic was simple enough, finding the right combination of apis was the painful part but I finally figured it out and wanted to share the code with everyone else trying to accomplish this task.

First up an example of what we’re able to accomplish with this effect.

Magnifying effect on iPhone

You can magnify any UIView as well as the children of that view, images, text and 2d vectors are captured and magnified appropriately. There are couple critical pieces of this code to pay attention to.

First up, when a touch has been recorded long enough to kick off the magnifier effect, we create a new magnifier view that is the exact size of the view we want to magnify. This is important due to the way we’re going to be copying a bitmap of the original view. Once we know the view exists we call setNeedsDisplay on it which triggers drawRect inside of it.

if(loop == nil){
	loop = [[MagnifierView alloc] initWithFrame:self.bounds];
	loop.viewref = self;
	[self addSubview:loop];
}
UITouch *touch = [touches anyObject];
loop.touchPoint = [touch locationInView:self];
[loop setNeedsDisplay];

Next, when drawRect gets called inside the magnifier view we want to make a copy of the original view first. The reason the magnifier view is teh same size as the original view is because we are rendering the full context of the original view into our new context before grabbing a bitmap of it. If the magnifying view were smaller, the rendered bitmap would also be smaller. We want to cache the final bitmap so we’re not redrawing the original view every time the user moves their finger around the view. We’ll destroy that cached view and the magnifying glass when the user lets up off the screen.

- (void)drawRect:(CGRect)rect {
	if(cachedImage == nil){
		UIGraphicsBeginImageContext(self.bounds.size);
		[self.viewref.layer renderInContext:UIGraphicsGetCurrentContext()];
		cachedImage = [UIGraphicsGetImageFromCurrentImageContext() retain];
		UIGraphicsEndImageContext();
	}

Following that we need to generate a masked view for the magnified view to sit in, since the loop is a circle we have to mask out the corners and antialias the outer perimeter. This is accomplished using 2 images, the magnifying glass itself and a mask image with appropriate grayscale levels for masking.

CGImageRef imageRef = [cachedImage CGImage];
CGImageRef maskRef = [[UIImage imageNamed:@"loopmask.png"] CGImage];
CGImageRef overlay = [[UIImage imageNamed:@"loop.png"] CGImage];
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
					CGImageGetHeight(maskRef),
					CGImageGetBitsPerComponent(maskRef),
					CGImageGetBitsPerPixel(maskRef),
                                        CGImageGetBytesPerRow(maskRef),
					CGImageGetDataProvider(maskRef),
					NULL,
					true);
//Create Mask
CGImageRef subImage = CGImageCreateWithImageInRect(imageRef, CGRectMake(touchPoint.x-18, touchPoint.y-18, 36, 36));
CGImageRef xMaskedImage = CGImageCreateWithMask(subImage, mask);

Lastly, we’ll draw the magnifying glass and magnfied bitmap copy of our orginal view underneath the mask and we’re done. Since the iPhone uses a different coordinate system then other languages, we have to remember to flip the view upside down before drawing it.

CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform xform = CGAffineTransformMake(
					1.0,  0.0,
					0.0, -1.0,
					0.0,  0.0);
CGContextConcatCTM(context, xform);
CGRect area = CGRectMake(touchPoint.x-42, -touchPoint.y, 85, 85);
CGRect area2 = CGRectMake(touchPoint.x-40, -touchPoint.y+2, 80, 80);
CGContextDrawImage(context, area2, xMaskedImage);
CGContextDrawImage(context, area, overlay);

And that’s it, now we have a modular magnifying glass that can plug in to any UIView with minimal effort. If you’re looking for a way to add interactivity underneath the magnifying glass, like moving the cursor within a textfield, that’s gonna require a bit more custom code on the control you’re dealing with, and this example doesn’t really address that.

Download example: XCode magnifier example for iPhone.