» tagged pages
» logout

sorted by: recent | see : popular
Content Tagged with User:marc + cocoa

Things I've learned about CoreImage (and Quartz, and OpenGL) in two weeks

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.

  1. Once set up, properly configured, coded, and tuned, CoreImage rocks. Whereas before 10fps NSImage transitions between medium images dragged on an Intel Mac, 30fps transitions on 1GHz G4s are smooth like butter.
  2. Proper configuration is a must. I first just converted my various NSImage calls to their CoreImage counterparts (AppKit makes it easy to put CoreImage into your program), and saw maybe a 10-15% performance improvement.
  3. Using CoreImage pretty much requires you to start getting comfy with OpenGL and its usage in Mac Applications. Instead of NSView classes, you need to use NSOpenGLView. Going to do drawing in the view along with your CoreImage? You’re going to need to learn how to do this in OpenGL (or, if you’re like me, find some samples that do the same thing you’re doing and template heavily).
  4. This one took me the longest time to figure out: In Interface Builder, if your window has a subclassed NSView called MyNSView, simply changing MyNSView’s superclass to be NSOpenGLView instead of NSView and changing your code files likewise is not enough. My views were drawing all sorts of weird crap all over the place and it was driving me crazy. The part I was missing? You have to go back to IB
    • Delete your NSView from the window.
    • Drag on a new NSOpenGLView
    • And finally mark its subclas as being MyNSView (which now inherits from NSOpenGLView instead of NSView).
  5. NSOpenGLView is quirky. The weirdest of them all: It often doesn’t call your 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.
  6. I still cannot get an NSOpenGLView to be semi-transparent. It’s trivial to do this with an NSView—just call [[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).
  7. Any given CIImage instance you have is probably not an actual image. Instead, it’s a collection of instructions that the CoreImage code will later use to figure out the best way to render your final image to the screen, using all sorts of cool and clever optimisations. Applying resizing, scaling, and rotating transformations are all done at once, as none are actually computed until rendering time.
  8. The filters you can use with CoreImage images are just awesome. From the banal like regular NSAffineTransforms, to impressive scaling filters, to all sorts of colour and distorting filters, you might be forgiven for thinking you could write you own little Photoshop clone in a few hours. The CILanczosScaleTransform filter, in particular, provides excellent results (sadly, it isn’t quite fast enough to use in real time).
  9. The transitions available in CoreImage are all quite cool, and reasonably easy to use. Most simply require the setting of a few params and you’re good to go—one or two might require a mask image. The one thing the docs do not explain very well, however, is how to use the transitions. Oh sure, there’s a full page in the Programmer’s Guide, but it blithely leaves out a few of the key steps, leaving you scratching your head. Which brings me to my next point:
  10. If you’re going to use a CoreImage transition, download and learn to love the CITransitionSelectorSample2 sample. Until you start playing with CoreImage (or have read this article), you might not have noticed that it uses an NSOpenGLView, that it fires a timer to make sure that NSOpenGLView always paints correctly, and more. And It shows very clearly how to use all of pre-canned transitions in Tiger.
  11. The CoreImage classes are not without quirks, possibly bugs (although I’m such a newbie at this that I’m still willing to believe they were faults of my own). In my program I simultaneously do things like rotations, translations (so that images remain centred in the window), and scales to draw images on the screen. On a few occasions, I found that CoreImage would draw the images correctly for a few frames in a transition, and then suddenly start drawing them in unexpected places. Only by eliminating one of the transforms was I able to eliminate these problems.
  12. The CIImage method 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).
  13. For thoses cases where you do want to use an NSView, such as printing, you’re still more than welcome to use CIImage objects. JustLooking does its printing in a regular NSView, and just uses AppKit extensions to the CIImage class to do the drawing. Extremely convenient!
  14. This isn’t so much a CoreImage observation as much as an artifact of design changes I made in order to best use CoreImage: Drawing text in an NSOpenGLView is a monstrous pain in the butt. There are various classes and tutorials on how to do this, which is quite disheartening. I avoided tackling this issue and got around this by just using other NSViews.

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.

User:marc: Chipmunk Ninja Technical Articles

Weird Cocoa Errors 101

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.

User:marc: Chipmunk Ninja Technical Articles

Rotating an NSImage object in Cocoa

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