CIImage drawing EXC_BAD_ACCESS - cocoa

So, I have a CIImage that I'm attempting to draw in an NSView's -drawRect method.
This is the line of code that I call to draw the image:
[outputCoreImage drawInRect: [self bounds]
fromRect: originalBounds
operation: NSCompositeSourceOver
fraction: 1];
outputCoreImage, originalBounds, and [self bounds] are all non-nil, and indeed are their respective expected values. On Lion (OS X 10.7), this worked fine, however on Mountain Lion (OS X 10.8) I receive an EXC_BAD_ACCESS on this line. If I walk up the stack, I find that the internal function call that breaks is on CGLGetPixelFormat.
frame #0: 0x00007fff99c3b26d OpenGL`CGLGetPixelFormat + 27
frame #1: 0x00007fff8fa98978 CoreImage`-[CIOpenGLContextImpl createAccelContext] + 335
frame #2: 0x00007fff8fa98d30 CoreImage`-[CIOpenGLContextImpl updateContext] + 111
frame #3: 0x00007fff8fa9a865 CoreImage`-[CIOpenGLContextImpl _lockfeContext] + 25
frame #4: 0x00007fff8fa7e4ac CoreImage`-[CIContextImpl setObject:forKey:] + 120
frame #5: 0x00007fff8fa9881c CoreImage`-[CIOpenGLContextImpl setObject:forKey:] + 259
frame #6: 0x00007fff90db93e3 libCGXCoreImage.A.dylib`cgxcoreimage_instance_render + 1477
frame #7: 0x00007fff97074ac6 CoreGraphics`CGSCoreImageInstanceRender + 32
frame #8: 0x00007fff947c89cc libRIP.A.dylib`ripc_AcquireCoreImage + 1344
frame #9: 0x00007fff947b8a00 libRIP.A.dylib`ripc_DrawShading + 9680
frame #10: 0x00007fff96cb2b2b CoreGraphics`CGContextDrawShading + 51
frame #11: 0x00007fff8fa78237 CoreImage`-[CICGContextImpl render:] + 918
frame #12: 0x00007fff8fa7d76a CoreImage`-[CIContext drawImage:inRect:fromRect:] + 1855
frame #13: 0x00007fff9897b8f3 AppKit`-[CIImage(NSAppKitAdditions) drawInRect:fromRect:operation:fraction:] + 243
I have zombies, guard malloc, and log exceptions all turned on, and none of them return any useful information.
Other OpenGL Testing:
I added this chunk:
CIImage *anImage = [[CIImage alloc] init];
[[[self window] contentView] lockFocus];
{
[anImage drawInRect: [[self window] frame]
fromRect: [[self window] frame]
operation: NSCompositeSourceOver
fraction: 1];
}
[[[self window] contentView] unlockFocus];
to the -windowDidLoad method of my NSWindowController. It runs without issue.
I added this chunk:
NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString: #"http://cache.virtualtourist.com/14/684723-Red_Square_Moscow.jpg"]];
CIImage *anImage = [[CIImage alloc] initWithData: data];
[self lockFocus];
{
[anImage drawInRect: [self bounds]
fromRect: [anImage extent]
operation: NSCompositeSourceOver
fraction: 1];
}
[self unlockFocus];
to another NSView's -drawRect. I received the same error on CGLGetPixelFormat.
Radar:
The folks over at #macdev seem to believe that this is in an OS issue, and I'm not inclined to disagree. I've filed a radar (rdar://11980704) explaining the problem and linking back to this question.
Workaround:
CGContextRef contextReference = [[NSGraphicsContext currentContext] graphicsPort];
CIContext *coreImageContext = [CIContext contextWithCGContext: contextReference
options: #{kCIContextUseSoftwareRenderer : #YES}];
[coreImageContext drawImage: outputCoreImage
inRect: [self bounds]
fromRect: originalBounds];
It looks like forcing the image to draw using the software renderer "solves" the problem. It's a bit blurry and slow, but it doesn't crash.

Resolved in 10.8.1
The folks over at #macdev seem to believe that this is in an OS issue, and I'm not inclined to disagree. I've filed a radar (rdar://11980704) explaining the problem and linking back to this question.

This might be the first time your app calls into any OpenGL routine which is probably doing some first-run initialization or checks.
For an experiment, try adding a little OpenGL code earlier in your app and see if that crashes as well. Try to think about why calling OpenGL might causing a crash.

Related

How can I programmatically render fullscreen openGL at a specific resolution?

I am working on a OSX/Cocoa graphics application which (for performance reasons) I would like to render at 640x480 when the user selects "full screen" mode. For what it's worth, the content is a custom NSView which draws using openGL.
I understand that rather than actually change the user's resolution, it's preferable to change the backbuffer (as explained on another SO question here: Programmatically change resolution OS X).
Following that advice, I end up with the following two methods (see below) to toggle between fullscreen and windowed. The trouble is that when I go fullscreen, the content does indeed render at 640x480 but is not scaled (IE it appears as if we stayed at the window's resolution and "zoomed" into a 640x480 corner of the render).
I'm probably missing something obvious here - I suppose I could translate the render according to the actual screen resolution to "center" it, but that seems overcomplicated?
- (void)goFullscreen{
// Bounce if we're already fullscreen
if(_isFullscreen){return;}
// Save original size and position
NSRect frame = [self.window.contentView frame];
original_size = frame.size;
original_position = frame.origin;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO],NSFullScreenModeAllScreens,
nil];
// In lieu of changing resolution, we set the backbuffer to 640x480
GLint dim[2] = {640, 480};
CGLSetParameter([[self openGLContext] CGLContextObj], kCGLCPSurfaceBackingSize, dim);
CGLEnable ([[self openGLContext] CGLContextObj], kCGLCESurfaceBackingSize);
// Go fullscreen!
[self enterFullScreenMode:[NSScreen mainScreen] withOptions:options];
_isFullscreen=true;
}
- (void)goWindowed{
// Bounce if we're already windowed
if(!_isFullscreen){return;}
// Reset backbuffer
GLint dim[2] = {original_size.width, original_size.height};
CGLSetParameter([[self openGLContext] CGLContextObj], kCGLCPSurfaceBackingSize, dim);
CGLEnable ([[self openGLContext] CGLContextObj], kCGLCESurfaceBackingSize);
// Go windowed!
[self exitFullScreenModeWithOptions:nil];
[self.window makeFirstResponder:self];
_isFullscreen=false;
}
Update
Here's now to do something similar to datenwolf's suggestion below, but not using openGL (useful for non-gl content).
// Render into a specific size
renderDimensions = NSMakeSize(640, 480);
NSImage *drawIntoImage = [[NSImage alloc] initWithSize:renderDimensions];
[drawIntoImage lockFocus];
[self drawViewOfSize:renderDimensions];
[drawIntoImage unlockFocus];
[self syphonSendImage:drawIntoImage];
// Resize to fit preview area and draw
NSSize newSize = NSMakeSize(self.frame.size.width, self.frame.size.height);
[drawIntoImage setSize: newSize];
[[NSColor blackColor] set];
[self lockFocus];
[NSBezierPath fillRect:self.frame];
[drawIntoImage drawAtPoint:NSZeroPoint fromRect:self.frame operation:NSCompositeCopy fraction:1];
[self unlockFocus];
Use a FBO with a texture of the desired target resolution attached and render to that FBO/texture in said resolution. Then switch to the main framebuffer and draw a full screen quad using the texture rendered to just before. Use whatever magnification filter you like best. If you want to bring out the big guns you could implement a Lancosz / sinc interpolator in the fragment shader to upscaling the intermediary texture.

NSBitmapImageRep generated BMP can't be read on Windows

I have an NSBitmapImageRep that I am creating the following way:
+ (NSBitmapImageRep *)bitmapRepOfImage:(NSURL *)imageURL {
CIImage *anImage = [CIImage imageWithContentsOfURL:imageURL];
CGRect outputExtent = [anImage extent];
NSBitmapImageRep *theBitMapToBeSaved = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL pixelsWide:outputExtent.size.width
pixelsHigh:outputExtent.size.height bitsPerSample:8 samplesPerPixel:4
hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:0 bitsPerPixel:0];
NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:theBitMapToBeSaved];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext: nsContext];
CGPoint p = CGPointMake(0.0, 0.0);
[[nsContext CIContext] drawImage:anImage atPoint:p fromRect:outputExtent];
[NSGraphicsContext restoreGraphicsState];
return [[theBitMapToBeSaved retain] autorelease];
}
And being saved as BMP this way:
NSBitmapImageRep *original = [imageTools bitmapRepOfImage:fileURL];
NSData *converted = [original representationUsingType:NSBMPFileType properties:nil];
[converted writeToFile:filePath atomically:YES];
The thing here is that the BMP file can be read and manipulated correctly under Mac OSX, but under Windows, it just fails to load, just like in this screenshot:
screenshot http://dl.dropbox.com/u/1661304/Grab/74a6dadb770654213cdd9290f0131880.png
If the file is opened with MS Paint (yes, MS Paint can open it) and then resaved, though, it will work.
Would appreciate a hand here. :)
Thanks in advance.
I think the main reason your code is failing is that you are creating your NSBitmapImageRep with 0 bits per pixel. That means your image rep will have precisely zero information in it. You almost certainly want 32 bits per pixel.
However, your code is an unbelievably convoluted way to obtain an NSBitmapImageRep from an image file on disk. Why on earth are you using a CIImage? That is a Core Image object designed for use with Core Image filters and makes no sense here at all. You should be using an NSImage or CGImageRef.
Your method is also poorly named. It should instead be named something like +bitmapRepForImageFileAtURL: to better indicate what it is doing.
Also, this code makes no sense:
[[theBitMapToBeSaved retain] autorelease]
Calling retain and then autorelease does nothing, because all it does in increment the retain count and then decrement it again immediately.
You are responsible for releasing theBitMapToBeSaved because you created it using alloc. Since it is being returned, you should call autorelease on it. Your additional retain call just causes a leak for no reason.
Try this:
+ (NSBitmapImageRep*)bitmapRepForImageFileAtURL:(NSURL*)imageURL
{
NSImage* image = [[[NSImage alloc] initWithContentsOfURL:imageURL] autorelease];
return [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];
}
+ (NSData*)BMPDataForImageFileAtURL:(NSURL*)imageURL
{
NSBitmapImageRep* bitmap = [self bitmapRepForImageFileAtURL:imageURL];
return [bitmap representationUsingType:NSBMPFileType properties:nil];
}
You really need to review the Cocoa Drawing Guide and the Memory Management Guidelines, because it appears that you are having trouble with some basic concepts.

image scaling performance in Quartz/Cocoa vs Qt4

I wrote a test application in Qt4 which uses QImage.scaled() or QPixmap.scaled() methods that turned to be very slow. Even a perspective transform is faster, while a scaling transform is the same slow.
[I tried to scale directly a QPainter but I do not master paintEvent() so I always get "painter not active" or paintEvent() is not called at all. So I do not know the painter scaling performaces.]
I ask here if the same issue is known for Quartz/Cocoa or instead their performances for similar tasks are better. I am particularly interested in native Quartz pdf rendering capability and subsequent image scaling.
NIRTimer *timer = [NIRTimer timer];
[timer start];
NSImage *image = [[NSImage alloc]initWithContentsOfFile:#"filename"];
NSImage *scaledImage = [[NSImage alloc]initWithSize:NSMakeSize(720, 480)];
[scaledImage lockFocus];
[image drawInRect:NSMakeRect(0, 0, 720, 480) fromRect:NSZeroRect operation:NSCompositeSourceAtop fraction:1];
[scaledImage unlockFocus];
[image release];
[scaledImage release];
NSLog(#"time: %ld", [timer microseconds]);
This is how to scale an image in Cocoa, and it takes 80000 microseconds (0.08 seconds).

Getting a CGIImageRef from an NSImage in Cocoa on Mac OS X

I need to get a CGIImageRef from an NSImage. Is there an easy way to do this in Cocoa for Mac OS X?
Pretty hard to miss:
-[NSImage CGImageForProposedRect:context:hints:]
If you need to target Mac OS X 10.5 or any other previous release, use the following snippet instead. If you don’t, then NSD’s answer is the right way to go.
CGImageRef CGImageCreateWithNSImage(NSImage *image) {
NSSize imageSize = [image size];
CGContextRef bitmapContext = CGBitmapContextCreate(NULL, imageSize.width, imageSize.height, 8, 0, [[NSColorSpace genericRGBColorSpace] CGColorSpace], kCGBitmapByteOrder32Host|kCGImageAlphaPremultipliedFirst);
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:bitmapContext flipped:NO]];
[image drawInRect:NSMakeRect(0, 0, imageSize.width, imageSize.height) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
CGContextRelease(bitmapContext);
return cgImage;
}
If your image comes from a file you may be better off using an image source to load the data directly into a CGImageRef.

Printing an NSImage

I'm migrating Cocoa-Java code to Cocoa + JNI since Cocoa-Java is deprecated. The code prints an image stored in a file. The new Cocoa code is basically:
NSImage *image = [[NSImage alloc] initWithContentsOfFile:spoolFile];
if ( [image isValid] ) {
NSImageView *view = [[NSImageView alloc] init];
[view setImage:image];
[view setImageScaling:NSScaleProportionally];
NSPoint p;
NSSize s;
p.x = static_cast<float>( boundsX );
p.y = static_cast<float>( boundsY );
[view setBoundsOrigin:p];
s.width = static_cast<float>( boundsWidth );
s.height = static_cast<float>( boundsHeight );
[view setBoundsSize:s];
NSPrintInfo *info = [NSPrintInfo sharedPrintInfo];
[info setHorizontalPagination:NSClipPagination];
[info setVerticalPagination:NSClipPagination];
[info setHorizontallyCentered:NO];
[info setVerticallyCentered:NO];
p.x = static_cast<float>( boundsX );
p.y = static_cast<float>( [info paperSize].height - boundsHeight - boundsY );
[view translateOriginToPoint:p];
NSPrintOperation *printOp =
[NSPrintOperation printOperationWithView:view printInfo:info];
[printOp setShowsPrintPanel:NO];
[printOp runOperation];
}
Running this code eventually crashes with:
Thread 0 Crashed:
0 com.apple.AppKit 0x9484ac75 -[NSConcretePrintOperation(NSInternal) _tryToSetCurrentPageNumber:] + 345
1 com.apple.AppKit 0x948d88cf -[NSView(NSPrintingInternal) _printForCurrentOperation] + 524
2 com.apple.AppKit 0x948d85c5 -[NSConcretePrintOperation _renderView] + 358
3 com.apple.AppKit 0x9491f0c0 -[NSConcretePrintOperation runOperation] + 362
Why? How can I simply print an image that's stored in a file?
NSImageView *view = [[NSImageView alloc] init];
That's invalid. You need to use initWithFrame: to initialize a view. You'll probably want to pass a frame consisting of NSZeroPoint and the image's size.
As for the use of setBoundsOrigin: and setBoundsSize:: I'm not sure those will work, assuming you mean to crop the image. You can try them (after fixing the above problem), but I would feel safer to create a new image from the desired section of the old one. You would do this by creating an empty image of the desired size, locking focus on it, drawing the correct section of the old image at the origin in the new image, and unlocking focus on the new image, then giving the new image instead of the old to the image view.
Is that a typo the second time you assign to p.y? It doesn't look like you define info until 2 lines later...
Also, wouldn't it be simpler to use NSMakePoint() and NSMakeSize() by passing ints, instead of constructing them by hand and using static_cast<float>? That seems like a very C++ approach...
For example, something like this could work?
NSImage *image = [[NSImage alloc] initWithContentsOfFile:spoolFile];
if ([image isValid]) {
NSPrintInfo *info = [NSPrintInfo sharedPrintInfo];
[info setHorizontalPagination:NSClipPagination];
[info setVerticalPagination:NSClipPagination];
[info setHorizontallyCentered:NO];
[info setVerticallyCentered:NO];
NSImageView *view = [[NSImageView alloc] init];
[view setImage:image];
[view setImageScaling:NSScaleProportionally];
[view setBoundsOrigin:NSMakePoint(boundsX, boundsY)];
[view setBoundsSize:NSMakeSize(boundsWidth, boundsHeight)];
[view translateOriginToPoint:NSMakePoint(boundsX, [info paperSize].height -
boundsHeight - boundsY)];
NSPrintOperation *printOp = [NSPrintOperation printOperationWithView:view printInfo:info];
[printOp setShowsPrintPanel:NO];
[printOp runOperation];
}

Resources