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.

Max 2008 session material

As promised, here’s the pdf of my recent Max 2008 session “Optimizing Adobe AIR for Code Execution, Memory, and Rendering“. There’s a good amount of Flash VM tips and tricks in there for reference whether you’re working in the browser or AIR. Thanks to everyone who attended and gave me high marks for my session. I guess I have to start thinking about what to talk about next year. For anyone who didn’t get to see the session live, the pdf really only tells half the story, so you may want to check out the session when it shows up on http://tv.adobe.com soon.

Web app to extract torrent files out of Blizzard Downloader

Diablo 3 was announced today and like many thousands of other gamers online right now I’m trying to download the 800mb high def gameplay trailer while putting up with the very clumsy Blizzard Downloader application. For those who don’t know, the Blizzard Downloader is just a mini BitTorrent client with a single torrent file baked in. I’m not a big fan of the downloader because I already have a BitTorrent client that’s much better at bandwidth allocation and my firewall’s already set up for that client. I’d much rather download the movie with my own client instead of Blizzards so I’ve written a little flash app to help facilitate that.

To use this, just point the app at the url of any Blizzard Downloader exe file online (I’m not sure if it’ll work with the .dmg files) and hit the ‘Extract’ button. A link will pop up below that you can click to save the torrent file. The file has no name by default so you’ll have to rename it ‘whatever.torrent’ after it finishes downloading. The resulting torrent will still connect to Blizzards tracker and you’ll download the same file you would be if you were using the Blizzard Downloader app.

This should work with all browsers other then IE 7 and below, since IE doesn’t support the data:URI scheme to create dynamic files.


View Source

Example URLs:
Diablo 3 Gameplay Downloader
Starcraft 2 Gameplay Downloader

More Info:
Blizzard Downloader Wiki

Hacking away at UI development

-->