A Magnifying Glass for Cocos2D
UPDATED VERSION 12/5/2012
During development of our new iOS game, using Cocos2D, I needed a magnifying glass that you could move over the screen and enlarge the portions below it. I did something similar before with UIKit, and that wasn’t a big deal at all, however with OpenGL it is a different story. I experimented with CCLens3D effects, but wasn’t satisfied with it. Surfing around didn’t reveal much, except that there a) is a need, and b) no real complete solution around. At the end of this blog, you can download everything you need for a great magnifying glass Cocos2D solution!
Based on how to grab a screenshot to get the data that is currently shown (discussed e.g. here in the Cocos2D forum), I came up with a solution to simply take the UIKit technique I knew already: grab the data (in this case from the OpenGL buffer, but just the piece that I need), redraw it enlarged into an UIView (depending on a variable magnification scale) and then let that view hover on top of the OpenGL view. This solves at the same time the issue that any loupe graphics you use in Cocos2D would be part of the contents of the current OpenGL buffer and be enlarged as well, ending up with a funny mirror-in-mirror effect.
The UIView will be wrapped by a CCNode-derived class, so you can use it like other nodes within your scene. The solution is surprisingly lean and easy to use, does not eat much of your precious memory and is fast. In contrast to the well-known textedit loupe, I chose to not enlarge the area below your finger, but that below the center of the loupe. This turned out to be much more intuitive for my use-case, but of course you can do it differently by simply changing the grabbed rectangle coordinates.
The loupe will hover quite a bit above your finger, which is determined by setting the anchor position in the initialization. You can choose a different setting here, or make it even dynamic by adapting the anchor in setPosition (so the loupe will move around your finger when approaching the lower screen border — pretty nice effect).
For the loupe frame itself you can create whatever graphics you like. My example provides a very thin and simple rounded frame with a little 3D effect and shadow, but you can design whatever you like. For simplicity I chose a centered inner area, which makes rendering the magnified area very easy. If you need it, however, you can also go for a masked approach and mask out the inner part by providing an according masking image (and the required coding for it, of course. Ray Wenderlich provides some great samples for masking in his blog!).
Update 5/12/2012: For Cocos2D V2.0 it is no longer required to explicitly rotate the contents according to device orientation. The coding to download below now checks the Cocos-Version and omits the rotation now automatically.
If you create your own loupe image with different proportions, you need to change the percentage of the inner part. You can compute that simply by dividing the diameter of the inner part by the total side-length of your image. Included you’ll find all required graphics for iPhone & iPad, ready & tested for standard and retina resolutions. I chose to have a relatively larger loupe for the iPhone/iPod, since magnification was even more required here than on the iPad, but it’s up to you what size of graphics you provide.
Once you imported the Magnifier class and the images into your project, you set the loupe up like that somewhere in your scene:
Magnifier *magnifier = [[[Magnifier alloc] initWithMagnification: 2.0] autorelease]; magnifier.position = <some position, e.g. touchLocation in ccTouchesBegan/Moved>; [self addChild:magnifier];
and there you go. Whenever you change the position, the magnified area will be updated as well automatically. If you want the magnified content to change dynamically also while you don’t move the loupe, you need to call the magnify method either periodically from a timer method, or at points in time where you know that content has changed. From my experience, though, having it updated on position changes is completely sufficient.
Update: Fix for iOS6 and above
There is nothing else to do for all iOS versions up to iOS 5.1. For newer versions of iOS in the future, however, you will need to tweak the way OpenGL handles graphics buffering, otherwise you will see a unicolor area only in the loupe. Fortunately, this is pretty easy to do: just replace this standard EAGLView initialization in your app delegate:
EAGLView *glView = [EAGLView viewWithFrame:[window bounds] pixelFormat:kEAGLColorFormatRGBA8 // or whatever you use... depthFormat:0];
with this one to make sure that OpenGL will retain it’s backbuffers:
EAGLView *glView = [EAGLView viewWithFrame:[window bounds] pixelFormat:kEAGLColorFormatRGBA8 // or whatever you use... depthFormat:0 preserveBackbuffer:YES sharegroup:nil multiSampling:NO numberOfSamples:0];
The loupe graphics and Magnifier class ready for consumption can be downloaded here.