Mac OS X: 使用CGContextRef C函数绘制到离屏NSGraphicsContext没有效果。为什么?

10

Mac OS X 10.7.4

我使用+[NSGraphicsContext graphicsContextWithBitmapImageRep:]方法创建了一个离屏图形上下文,并在其中绘制。

如果我使用NSBezierPath类来绘制,所有的东西都会按照预期工作。

但是,如果我使用CGContextRef C函数来绘制,我的绘制没有任何效果,什么也没有画出来。

出于某些原因,我非常需要使用CGContextRef函数进行绘制(而不是Cocoa的NSBezierPath类)。

下面是我的代码示例。我试图绘制一个简单的“X”。一条线用NSBezierPath实现,另一条线用CGContextRef C函数实现。第一条线可以正常显示,但第二条线却无法显示。我哪里做错了呢?

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];
5个回答

31
你没有说明你是如何查看结果的。我假设你正在查看NSImage img而不是NSBitmapImageRep offscreenRep

当你调用[img lockFocus]时,你正在将当前NSGraphicsContext更改为上下文以绘制到img中。因此,NSBezierPath绘图将进入img,这就是你所看到的。CG绘图进入了offscreenRep,而你没有查看它。

与其锁定对NSImage的焦点并在其中进行绘制,不如创建一个NSImage,并将offscreenRep作为其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

谢谢Kurt。是的,你的假设是正确的,我正在尝试显示img。另外,你的修复方法是正确的并且解决了这个问题。感谢你的澄清! - Todd Ditchendorf

7

@Robin Stewart提供的解决方案对我很有效。我能够将其压缩为NSImage扩展程序。

extension NSImage {
    convenience init(size: CGSize, actions: (CGContext) -> Void) {
        self.init(size: size)
        lockFocusFlipped(false)
        actions(NSGraphicsContext.current!.cgContext)
        unlockFocus()
    }
}

使用方法:

let image = NSImage(size: CGSize(width: 100, height: 100), actions: { ctx in
    // Drawing commands here for example:
    // ctx.setFillColor(.white)
    // ctx.fill(pageRect)
})

6
我想知道为什么每个人都写那么复杂的代码来绘制图像。除非您关心图像的精确位图表示(通常不需要!),否则没有必要创建一个。您只需创建一个空白图像并直接在其上绘制即可。在这种情况下,系统将创建一个适当的位图表示(或者可能是PDF表示或任何系统认为更适合绘制的内容)。
初始化方法的文档
- (instancetype)initWithSize:(NSSize)aSize

自MacOS 10.0以来一直存在且仍未被弃用的方法明确说明:
使用此方法初始化图像对象后,您应该在尝试绘制图像之前提供图像内容。您可以锁定图像上的焦点并向图像绘制,也可以显式添加您创建的图像表示形式。
因此,以下是我如何编写该代码:
NSRect imgRect = NSMakeRect(0.0, 0.0, 100.0, 100.0);
NSImage * image = [[NSImage alloc] initWithSize:imgRect.size];

[image lockFocus];
// 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 = [[NSGraphicsContext currentContext] graphicsPort];
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 0.0, 0.0);
CGContextAddLineToPoint(ctx, imgRect.size.width, imgRect.size.height);
CGContextClosePath(ctx);
CGContextStrokePath(ctx);
[image unlockFocus];

这就是全部内容。

graphicsPort 实际上是一个 void * 类型:

@property (readonly) void * graphicsPort 

并记录为

由图形端口表示的底层平台特定的图形上下文。

这可能几乎包括所有内容,但最后注意事项指出

在OS X中,这是Core Graphics上下文,一个CGContextRef对象(不透明类型)。

此属性已在10.10中被弃用,而改用新属性。

@property (readonly) CGContextRef CGContext

这个功能只能在10.10及以上的版本中使用。如果你需要支持旧系统,仍然可以使用graphicsPort


1

Swift 4:我使用这段代码,它复制了UIKit中方便的API(但在macOS上运行):

public class UIGraphicsImageRenderer {
    let size: CGSize

    init(size: CGSize) {
        self.size = size
    }

    func image(actions: (CGContext) -> Void) -> NSImage {
        let image = NSImage(size: size)
        image.lockFocusFlipped(true)
        actions(NSGraphicsContext.current!.cgContext)
        image.unlockFocus()
        return image
    }
}

使用方法:

let renderer = UIGraphicsImageRenderer(size: imageSize)
let image = renderer.image { ctx in
    // Drawing commands here
}

@ingconti 谢谢,现在已经修复了。这在我的项目中使用UIImage时有效,因为我还有:typealias UIImage = NSImage。 - Robin Stewart

1
以下是3种绘制相同图像的方法(Swift 4)。
@Mecki建议的方法可以生成没有模糊伪影的图像(如模糊的曲线)。但是,可以通过调整CGContext设置来解决这个问题(本示例未包含此内容)。
public struct ImageFactory {

   public static func image(size: CGSize, fillColor: NSColor, rounded: Bool = false) -> NSImage? {
      let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
      return drawImage(size: size) { context in
         if rounded {
            let radius = min(size.height, size.width)
            let path = NSBezierPath(roundedRect: rect, xRadius: 0.5 * radius, yRadius: 0.5 * radius).cgPath
            context.addPath(path)
            context.clip()
         }
         context.setFillColor(fillColor.cgColor)
         context.fill(rect)
      }
   }

}

extension ImageFactory {

   private static func drawImage(size: CGSize, drawingCalls: (CGContext) -> Void) -> NSImage? {
      return drawImageInLockedImageContext(size: size, drawingCalls: drawingCalls)
   }

   private static func drawImageInLockedImageContext(size: CGSize, drawingCalls: (CGContext) -> Void) -> NSImage? {
      let image = NSImage(size: size)
      image.lockFocus()
      guard let context = NSGraphicsContext.current else {
         image.unlockFocus()
         return nil
      }
      drawingCalls(context.cgContext)
      image.unlockFocus()
      return image
   }

   // Has scalling or antialiasing issues, like blurred curves.
   private static func drawImageInBitmapImageContext(size: CGSize, drawingCalls: (CGContext) -> Void) -> NSImage? {
      guard let offscreenRep = NSBitmapImageRep(pixelsWide: Int(size.width), pixelsHigh: Int(size.height),
                                                bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true,
                                                isPlanar: false, colorSpaceName: .deviceRGB) else {
                                                   return nil
      }
      guard let context = NSGraphicsContext(bitmapImageRep: offscreenRep) else {
         return nil
      }
      NSGraphicsContext.saveGraphicsState()
      NSGraphicsContext.current = context
      drawingCalls(context.cgContext)
      NSGraphicsContext.restoreGraphicsState()
      let img = NSImage(size: size)
      img.addRepresentation(offscreenRep)
      return img
   }

   // Has scalling or antialiasing issues, like blurred curves.
   private static func drawImageInCGContext(size: CGSize, drawingCalls: (CGContext) -> Void) -> NSImage? {
      let colorSpace = CGColorSpaceCreateDeviceRGB()
      let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
      guard let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8,
                                    bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
         return nil
      }
      drawingCalls(context)
      guard let image = context.makeImage() else {
         return nil
      }
      return NSImage(cgImage: image, size: size)
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接