
I recently spent two weeks converting JustLooking, my Mac OS X Image Viewing program, from NSImage to CoreImage and friends. This experience was overall much easier than I expected, and I have learned a bunch of things, some of which might have been handy to have known in advance.
The good news is that it mostly lives up to the hype. The bad news is that it’s not without tricks and traps of its own. Here are some notes and comments.
drawRect: when you first display it or tell it to change its contents. Thus, it seems as though a lot of people (myself included) have developed code that sets up a single-fire timer for some short period of time (0.1 seconds or so) to cause a redraw, which is respected.[[NSColor colorWithDeviceRed: 0 green: 0 blue: 0 alpha: 0.7] set]. In some situations, overriding - (BOOL)isOpaque might be required so you can return NO. Doing this in the NSOpenGLView, however, is a monumental effort, and something I have to figure out. I found a few examples on the Internet, but was never able to make one work. (If you have done this before, please please please send me mail or add a comment).imageWithContentsOfURL: is extremely useful for quickly loading in images, but won’t let you get at things like EXIF or other image meta-data. You’re far better off using CGImageSourceRef APIs to load in CGImages, and then pass these to the CIImage method imageWithCGImage:. These will even let you load in all the frames in an animated GIF (something which NSImage simply does not).In summary, I’m extremely glad I took the CoreImage leap. The system is extremely cool, and has pushed NSImage to a minor utility role in my programming world. If you do decide to go for it, take heart—it’s really not so bad, and internet searches will give you all the help you could want.
The other day, I was working on JustLooking, changing the appearance and the like of a couple of dialogs. As I ran the programs and went to show the dialogs (“panels” in the local terminology), nothing would show and I’d get an error in my XCode results window:
"Unknown class 'CustomCombo' in nib file. using 'NSObject' instead."
I spent the next half hour trying to figure out why Interface Builder didn’t know about this class: it was there in the class inspector, and the ui widgets were correctly set up to use that new class, and all the hookups in the UI also seemed correct.
Well, I finally figured out: when I first developed the class, I had some errors in it, but wanted to test a few things out in my program elsewhere. So I had unchecked it in XCode, telling the IDE not to compile and link it.
Thus, when Cocoa tried to load the NIB file, it couldn’t find the definition for the class, and just put in NSObject instead.
Here’s to hoping that this blog entry saves somebody that 30 minutes I spent on that one.
I recently started working on an image viewing program for Mac OS X using Cocoa, and one of the features I decided to add was the ability to rotate images in 90° increments. I did some searching on the internet, and found a few things:
Neither of the first two was exactly what I wanted—the first didn’t quite work, while the second was too complicated and, in order to support arbitrary rotation, created an NSImage object that was way too large.
So, after reading the Apple Cocoa Drawing Guide on Using Transforms, I came up with the following code to rotate an NSImage object 90°. Please note that this function ONLY supports a clockwise or counterclockwise 90° rotation. It also returns an object that must be released by the callER. I’m not 100% sure if this is standard Objective-C/Cocoa methodology, as I’m still a bit new to all this stuff.
But, without any further ado, here is the rotation function:
- (NSImage *)rotateIndividualImage: (NSImage *)image clockwise: (BOOL)clockwise
{
NSImage *existingImage = image;
NSSize existingSize;
/**
* Get the size of the original image in its raw bitmap format.
* The bestRepresentationForDevice: nil tells the NSImage to just
* give us the raw image instead of it's wacky DPI-translated version.
*/
existingSize.width = [[existingImage bestRepresentationForDevice: nil] pixelsWide];
existingSize.height = [[existingImage bestRepresentationForDevice: nil] pixelsHigh];
NSSize newSize = NSMakeSize(existingSize.height, existingSize.width);
NSImage *rotatedImage = [[NSImage alloc] initWithSize:newSize];
[rotatedImage lockFocus];
/**
* Apply the following transformations:
*
* - bring the rotation point to the centre of the image instead of
* the default lower, left corner (0,0).
* - rotate it by 90 degrees, either clock or counter clockwise.
* - re-translate the rotated image back down to the lower left corner
* so that it appears in the right place.
*/
NSAffineTransform *rotateTF = [NSAffineTransform transform];
NSPoint centerPoint = NSMakePoint(newSize.width / 2, newSize.height / 2);
[rotateTF translateXBy: centerPoint.x yBy: centerPoint.y];
[rotateTF rotateByDegrees: (clockwise) ? - 90 : 90];
[rotateTF translateXBy: -centerPoint.y yBy: -centerPoint.x];
[rotateTF concat];
/**
* We have to get the image representation to do its drawing directly,
* because otherwise the stupid NSImage DPI thingie bites us in the butt
* again.
*/
NSRect r1 = NSMakeRect(0, 0, newSize.height, newSize.width);
[[existingImage bestRepresentationForDevice: nil] drawInRect: r1];
[rotatedImage unlockFocus];
return rotatedImage;
}
The function works great and correctly rotates the incoming NSImage 90 degrees. However, People working with JPEGs should probably not save these data to disk in place of existing images, as the function works on the decompressed bits and resaving them can lose more image data. To truly solve this problem, you should use a “Lossless JPEG Image Rotation” algorithm.
User:marc: Chipmunk Ninja Technical Articles
cocoa
user:marc
rotation
transform
NSImage
rotate
NSAffineTransform