NSImage: Deceivingly Simple or Just Deceiving?

Several weeks ago, Daniel Jalkut posted an entry about NSString, and it’s deceivingly simple ability to handle Unicode strings with little or no knowledge of Unicode, ASCII, or their details from the developer. That got me thinking… there are quite a few deceivingly simple Cocoa classes that are dead simple to use, but mask a ton of complexity. The first one that came to mind was NSImage. Without any help from the developer, NSImage can read images from disk or a URL in any of these formats: PDF, EPS, JPEG, PNG, TIFF, BMP, ICO, ICNS, GIF, DIB, and others. And it can composite them to the screen with either of these calls:

    -drawInRect:fromRect:operation:fraction:
    -drawAtPoint:fromRect:operation:fraction:

The developer doesn’t have to care about whether the image data is vector or bitmap, what its resolution is, or even whether the file is local or on a website somewhere. NSImage has quite a few other capabilities: You can draw to an NSImage object just like you would an NSView by locking focus on it and an NSImage knows how to draw itself into a flipped view just by calling -setFlipped:

Clearly, for the tasks that involve taking image data and getting it onto the screen, NSImage makes life incredibly simple. But, sometimes when you get up close, NSImage gets a little uglier…

Like Impressionist Art

Do a Google search for “NSImage troubleshoot”, or search CocoaDev.com or CocoaBuilder.com and you will find lots of postings about NSImage. Most of them will include people fiddling around with NSImageRep classes, and that’s where NSImage’s simplicity starts to break down. Unlike NSString, NSImage isn’t a lone class - it’s is an example of the Façade design pattern. From Apple’s Cocoa Design Patterns chapter of Cocoa Fundamentals:

“A Façade defines a higher-level interface that makes the subsystem easier to use by reducing complexity and hiding the communication dependencies between subsystems.”

And specifically as it applies to NSImage:

“NSImage can keep more than one representation of the same image; each representation is a kind of NSIImageRep object. NSImage automates the choice of the representation that is appropriate for a particular type of data and for a given display device. It also hides the details of image manipulation and selection so that the client can use many different underlying representations interchangeably.”

So the NSImage class is really just a coordinating front for NSImageRep objects that actually store and manipulate the data. Knowing that can solve a couple common NSImage problems.

Like a Kid Too Smart For His Own Good

One of the most common NSImage problems is reading in a large image file, but drawing it to the screen and the result is much smaller than the original. Knowing a little about NSImage under the hood will help here: Only the underlying NSImageRep objects know about actual pixel dimensions. When you ask for an NSImage object’s size, it looks at it’s underlying NSImageRep’s pixel size and resolution, and then uses both to calculate its dimensions in user space (which is usually, but may not always be 72-dpi). Because the underlying image resolution may be higher than user space screen resolution, the size that NSImage reports may be smaller than the underlying pixel size. Here’s some sample code (taken from Mugshot) that reads images from disk and draws them to a view:

NSImage *thumbnailImage = [[NSImage alloc] initWithData:imageData];
/* define the rect I'm drawing to in my view and assign it to drawingRect */
imageRect = NSMakeRect(0,0,[thumbnailImage size].width, [thumbnailImage size].height);
[thumbnailImage drawInRect:drawingRect fromRect:imageRect operation:NSCompositeCopy fraction:1.0];

The image below shows the drawing results. Each of these images should be approximately 100 pixels by 75 pixels:

But, if we use just a little bit of knowledge that NSImage is using NSImageRep objects behind-the-scenes, we can tell NSImage to stop accounting for resolution and to set its size to the actual pixel dimensions. Important note: We don’t have to know about which image representation NSImage is using, or how many it has has, or what their specific class is - We just have to ask the NSImage for the best one for the current device is. We do that by passing nil to -bestRepresentationForDevice:

NSImage *thumbnailImage = [[NSImage alloc] initWithData:imageData];
NSSize imageSize;
imageSize.width = [[thumbnailImage bestRepresentationForDevice:nil] pixelsWide];
imageSize.height = [[thumbnailImage bestRepresentationForDevice:nil] pixelsHigh];
[thumbnailImage setScalesWhenResized:YES];
[thumbnailImage setSize:imageSize];
/* cut out some code where I define drawingRect and imageRect - adjusting the size of these does NOT fix the problem */
[thumbnailImage drawInRect:drawingRect fromRect:imageRect operation:NSCompositeCopy fraction:1.0];

And here’s the new drawing. Note the image sizes versus what’s above:

Compared to just reading in a file from disk and drawing it to the screen in two lines of code, we traded in some of NSImage’s simplicity for a little bit of behind-the-scenes knowledge to synchronize the object’s dimensions with the pixel dimensions. I think it was a fair trade.

Do Not Look Behind The Curtain

So you can draw many common formats to the screen with a couple simple calls, and you can adjust for image resolution by using a little bit of NSImageRep knowledge, but without knowing which NSImageRep to choose, or what its type is. But some situations call for ever more knowledge. For example, what if you want to get image data back out of an NSImage object? Apple says:

“Treat NSImage and its image representations as immutable objects. The goal of NSImage is to provide an efficient way to display images on the target canvas. Avoid manipulating the data of an image representation directly, especially if there are alternatives to manipulating the data such as compositing the image and some other content into a new image object.”

OK, so we shouldn’t try to manipulate the actual image data stored in the NSImageRep. But since we can -lockFocus on an NSImage and perform any compositing or drawing we want while NSImage manages the representations behind the scenes, and since NSBitmapImageRep has a method that gives us the raw NSData version of itself, those methods should be enough to read an image in, modify it or draw on it, then turn it back into data that we can save to disk. But here again, NSImage’s smarts get in our way. If you create an NSImage by reading a JPEG file from disk, the object returned to you has a single representation: an NSBitmapImageRep. But, as soon as you call -lockFocus on the image and draw to it, NSImage discards the NSBitmapImageRep and creates an NSCachedImageRep from the data. This is one of NSImage’s optimizations to achieve its goal of getting image data to the screen quickly - even though it interferes with getting the data you want back out to disk. The solution is to create your own NSBitmapImage rep instead of relying on NSImage to maintain the original one for you:

[image lockFocus];
/* do your drawing and/or compositing here */
[image unlockFocus];
/* turn your NSImage into an NSBitmapImageRep and get its underlying data */
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor];
photoData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];
/* save your file! */
[photoData writeToFile:filename atomically:NO];

That gets us a customized image in JPEG format. Where does that leave us? So far, NSImage allows you to read and draw many formats with almost zero knowledge about its internals, it allows you to adjust for higher-resolution images with a little bit of knowledge, and it allows you to modify an image and re-save it with a bit more knowledge. The caching features can be confusing, but other than that, I think that’s pretty slick. And certainly easier than having to worry about reading the formats yourself. (Yes, you can turn off caching, and also control how NSImage retains or discards different representations - but each of those has performance tradeoffs that may not always be worth it.)

Tiger, Tiger, Burning Bright

Not only does Tiger add a ton of supported image formats to NSImage (Adobe Digital Negative, Canon 2 RAW, Canon RAW, FlashPix, Fuji RAW, Kodak RAW, MacPaint(!), Minolta RAW, Minolta RAW, Nikon RAW, Olympus RAW, OpenEXR, Photoshop .psd, QuickTime Import Format, Radiance, SGI, Sony RAW, Targa, WindowsCursor, XWindow Bitmap… whew, that’s a lot!), but it also allows you to shortcut some of the NSCachedImageRep steps above: Tiger allows you to create a bitmap image representation and draw to it directly - without even dealing with an NSImage object. Apple provides sample code for doing this in the Cocoa Drawing Guide. Drawing directly to an NSBitmapImageRep allows you to save the data to an NSData object or add the modified NSBitmapImageRep to an NSImage object without worrying about what NSImage is doing behind the scenes while you draw. Effectively, these features bypass the optimizations that NSImage imposes, by letting us draw in one place. Tiger also allows you to capture the contents of the screen or an NSView directly to an NSBitmapImageRep object with -bitmapImageRepForCachingDisplayInRect: and -cacheDisplayInRect:toBitmapImageRep:. These Tiger features make it much easier to work with image bitmaps without worrying about NSImage’s smarts.

I Wash My Hands of it!

Some developers prefer to bypass NSImage altogether and instead use Quartz CGImage objects instead. CGImage bypasses all the funky caching and uncertainty of what NSImage is doing with NSImageRep objects, but also loses the simplicity of reading a file and drawing it to the screen with just a couple method calls. Your mileage may vary - and there’s worthwhile fine-grained control that you get with CGImages. Check out Apple’s CGImage docs or even better, take a look at Programming in Quartz, an excellent book written by a couple Apple employees. It goes into detail about CGImage and the rest of Quartz.

And beyond the NSImage documentation (which does shed some light on all of the caching behaviors), Apple’s Cocoa Drawing Guide provides even more explanation about caching, resolution, sizing, dealing with vector images, and more. Definitely check it out - especially the section on images.

So Wait… What Was the Point?

So after all that, my point was just to mention that NSImage hides a lot image format complexity from the developer, even if it involves relatively more complexity than NSString. You can perform very powerful drawing of the most common image formats without knowing anything about the format details, and get those images to screen with only a couple lines of code. However, there are also a few common issues which you can handle if you understand some details about the NSImageRep classes behind NSImage. As simple as Daniel’s NSString? Maybe not. But better than handling the image format details? Heck yes! Go forth and draw!

Update: A few changes to grammar and spelling. Also a few changes to describe the images better and to make a few clarifications. Clearly, I published before making one last clarity check. Please let me know if I’ve missed anything on this pass!

10 Responses to “NSImage: Deceivingly Simple or Just Deceiving?”


  1. 1 Troy Stephens Mar 31st, 2006 at 5:51 pm

    Interesting to see the kinds of issues people run into with NSImage. As you rightly point out, it’s a complex body of code that treads the line between abstraction and exposure of internal parts, and partly as a result its behaviors can unfortunately be a bit obscure at times.

    For obtaining an NSBitmapImageRep from an arbitrary NSImage on pre-Tiger systems, you might find NSBitmapImageRep’s often-overlooked -initWithFocusedViewRect: initializer useful. It’s available all the way back to 10.0, and saves you the unnecessary overhead of encoding to TIFF, then unpacking again, then re-encoding to JPEG as your second code clipping above does. Use it after you complete your drawing, while focus is still locked on your NSImage (or anytime later, after locking focus on the image again), and it will extract the pixel data for the requested rect right out of the NSImage’s offscreen window backing store. You then have an NSBitmapImageRep that you can encode however you want.

    There’s an example of doing this in the “Reducer” code sample from WWDC ‘05. Look for the BitmapImageRepFromNSImage() function at the bottom of ImageReducer.m:

    http://developer.apple.com/samplecode/Reducer/listing16.html

    Hope this helps, and thanks for the interesting post!

  2. 2 Vipin Thazhissery May 15th, 2006 at 11:43 pm

    read the article. very useful. but, my doubt is, how can i convert NSImage to DIB (Device Independantant Bitmap) format. Actually, i am in new one in Cocoa. Can I get some help from any one ?

  3. 3 blake May 16th, 2006 at 8:56 am

    I’m not familiar with the DIB format, so I can’t say for sure. However, if it’s a format that Quicktime supports, you can use QT methods to do the conversion. Or, if you really understand the format, I’m sure you could get access to the bits inside an NSImage, and do the conversion “by hand” - although, I’m sure that doesn’t sound appealing in the least!

  4. 4 Mr Pedanto Nov 14th, 2006 at 9:22 pm

    I believe the title of this article should be:

    NSImage: Deceptively Simple or Just Deceptive?

  5. 5 Shamyl Zakariya Nov 15th, 2006 at 8:54 am

    My biggest gripe with NSImage — and this seems to apply to CoreGraphics’s CGImage as well — is that if you load a PNG ( or some other file type ) with an alpha channel, you get premultiplied bitmap data. Oddly, if you read the CGBitmapContextCreate docs, there are constants you can pass to request non-premultiplied data. However, and this was corroborated by folks on the mac-opengl and cocoa-dev list — those constants are ignored, and you get a null bitmap context.

    I know that’s super useful for compositing, but frankly, sometimes you want your image data unmodified. In my case, I wanted the straight up image data for opengl texturing, and while I know you can swizzle the blend function in gl to work nicely with premultiplied data, I also wanted to use the alpha channel in textures as a lookup table for the other three channels ( for terrain texturing ).

    As far as I can tell, Cocoa/Quartz simply don’t let you get this data, so I had to use libPNG directly. How annoying!

  6. 6 Jonathon Mah Jan 3rd, 2007 at 11:57 pm

    Helpful post Blake, thanks!

    “NSString, and it’s deceivingly simple”, and
    “it looks at it’s underlying NSImageRep” both to “its”

  7. 7 Jim Hillhouse Feb 27th, 2007 at 1:22 am

    This was a great posting. What has gotten me about NSImage is its limitations. For example, let’s say I want to both draw an image from the center of the view as well as scale that image down so that it fits the smallest dimension of the rect that the image will be drawn to.

    One call to CGRectApplyAffineTransform, plus some other code, and you’re in business. Sadly, I have not found an equivalent call for NSImage, NSAffineTransform, etc.

    I started on Cocoa to be a Cocoa programmer. But sometimes I’m forced by NSImage’s, and perhaps my own, limitations reluctantly back into the arms of Quartz. Yuck!

    BTW, if anyone has some suggestions on solving the above mentioned issue in NSImage, I’m open for it.

  8. 8 blake Feb 27th, 2007 at 5:28 pm

    You can calculate the scaled rect size in three lines or so, and then draw into that rect - is that what you’re trying to do? Calculating the offset to center it is fairly easy as well - but maybe I’m not understanding what you’re trying to do.

  1. 1 Red Sweater Links » Blog Archive » Deceptively Simple NSImage Pingback on Mar 31st, 2006 at 4:52 pm
  2. 2 Bloody Fingers | Aperture, Fireballs and Mugshot Pingback on Apr 30th, 2006 at 2:20 am
Comments are currently closed.