objective-cmacoscocoacore-graphicsquartz-2d

Mac OS X: Drawing into an offscreen NSGraphicsContext using CGContextRef C functions has no effect. Why?


Mac OS X 10.7.4

I am drawing into an offscreen graphics context created via +[NSGraphicsContext graphicsContextWithBitmapImageRep:].

When I draw into this graphics context using the NSBezierPath class, everything works as expected.

However, when I draw into this graphics context using the CGContextRef C functions, I see no results of my drawing. Nothing works.

For reasons I won't get into, I really need to draw using the CGContextRef functions (rather than the Cocoa NSBezierPath class).

My code sample is listed below. I am attempting to draw a simple "X". One stroke using NSBezierPath, one stroke using CGContextRef C functions. The first stroke works, the second does not. What am I doing wrong?

NSRect imgRect = NSMakeRect(0.0, 0.0, 100.0, 100.0);
NSSize imgSize = imgRect.size;

NSBitmapImageRep *offscreenRep = [[[NSBitmapImageRep alloc]
   initWithBitmapDataPlanes:NULL
   pixelsWide:imgSize.width
   pixelsHigh:imgSize.height
   bitsPerSample:8
   samplesPerPixel:4
   hasAlpha:YES
   isPlanar:NO
   colorSpaceName:NSDeviceRGBColorSpace
   bitmapFormat:NSAlphaFirstBitmapFormat
   bytesPerRow:0
   bitsPerPixel:0] autorelease];

// set offscreen context
NSGraphicsContext *g = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
[NSGraphicsContext setCurrentContext:g];

NSImage *img = [[[NSImage alloc] initWithSize:imgSize] autorelease];

CGContextRef ctx = [g graphicsPort];

// lock and draw
[img lockFocus];

// draw first stroke with Cocoa. this works!
NSPoint p1 = NSMakePoint(NSMaxX(imgRect), NSMinY(imgRect));
NSPoint p2 = NSMakePoint(NSMinX(imgRect), NSMaxY(imgRect));
[NSBezierPath strokeLineFromPoint:p1 toPoint:p2];

// draw second stroke with Core Graphics. This doesn't work!
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 0.0, 0.0);
CGContextAddLineToPoint(ctx, imgSize.width, imgSize.height);
CGContextClosePath(ctx);
CGContextStrokePath(ctx);

[img unlockFocus];

Solution

  • You don't specify how you are looking at the results. I assume you are looking at the NSImage img and not the NSBitmapImageRep offscreenRep.

    When you call [img lockFocus], you are changing the current NSGraphicsContext to be a context to draw into img. So, the NSBezierPath drawing goes into img and that's what you see. The CG drawing goes into offscreenRep which you aren't looking at.

    Instead of locking focus onto an NSImage and drawing into it, create an NSImage and add the offscreenRep as one of its reps.

    NSRect imgRect = NSMakeRect(0.0, 0.0, 100.0, 100.0);
    NSSize imgSize = imgRect.size;
    
    NSBitmapImageRep *offscreenRep = [[[NSBitmapImageRep alloc]
       initWithBitmapDataPlanes:NULL
       pixelsWide:imgSize.width
       pixelsHigh:imgSize.height
       bitsPerSample:8
       samplesPerPixel:4
       hasAlpha:YES
       isPlanar:NO
       colorSpaceName:NSDeviceRGBColorSpace
       bitmapFormat:NSAlphaFirstBitmapFormat
       bytesPerRow:0
       bitsPerPixel:0] autorelease];
    
    // set offscreen context
    NSGraphicsContext *g = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext:g];
    
    // draw first stroke with Cocoa
    NSPoint p1 = NSMakePoint(NSMaxX(imgRect), NSMinY(imgRect));
    NSPoint p2 = NSMakePoint(NSMinX(imgRect), NSMaxY(imgRect));
    [NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
    
    // draw second stroke with Core Graphics
    CGContextRef ctx = [g graphicsPort];    
    CGContextBeginPath(ctx);
    CGContextMoveToPoint(ctx, 0.0, 0.0);
    CGContextAddLineToPoint(ctx, imgSize.width, imgSize.height);
    CGContextClosePath(ctx);
    CGContextStrokePath(ctx);
    
    // done drawing, so set the current context back to what it was
    [NSGraphicsContext restoreGraphicsState];
    
    // create an NSImage and add the rep to it    
    NSImage *img = [[[NSImage alloc] initWithSize:imgSize] autorelease];
    [img addRepresentation:offscreenRep];
    
    // then go on to save or view the NSImage