Quirk of the day - NSImage and @2x

Today's quirk is to do with NSImage behavior depending on the name of an image file.

iOS uses a file naming convention to differentiate between retina and non-retina versions of the same image. If the two versions of the image are named: . and @2x. then the UIImage imageNamed: method will use the appropriate version for the platform being used (eg the @2x on the new iPad and the other on the original iPad and iPad 2). This behavior is well documented and should be no surprise.

The unexpected thing (at least to me) is that if you happen to name an image file using the @2x format and explicitly access it on a Mac using the NSImage initWithContentsOfFile: method, the resulting image will have a size in points equal to half the size in pixels.

For example, a 30x30 pixel image will draw in a 15x15 point rectangle. If the scale factor of the graphics context you are using is 2, I assume that you have your image in all it's glory (ie all 30x30 pixels are displayed). If the scale factor is 1, the image is now only 15x15 pixels!

What makes this unexpected is that unlike the UIImage case, you have not asked NSImage to load whichever version is appropriate. You have explicitly asked it to open a specific file. It has then set the scale factor for the image based purely on the format of the file name.

Imagine you have a piece of code that loads an image like this and draws it on a graphics context configured with a scale factor of one pixel per point. The image will appear at a quarter of the expected size. If you then simply change the file name (eg to -HD.) and re-run the code the resulting image will be the expected size.

This may sound like a contrived example, but it is exactly what happened to me. I had written an OS X utility to create images (to be used as resources in an iPad app) from multiple smaller component images. Composing the images from the components was done by drawing to an offline graphics context (created with an NSBitmapImageRep object). Since there was no actual display device involved in this I was thinking in pixels and, as far as I can see, the NSBitmapImageRep and NSGraphicsContext objects don't even have scale factors associated with them (in contrast to the iOS UIGraphicsBeginImageContextWithOptions function which takes the scale factor as an argument). It took me a while to understand why some of the components were being drawn at a quarter of the size I expected.

Once you see what is going on it is easy to deal with, but I could not find anything in the Apple dev documentation that covers this.