Вопрос по macos, cocoa, objective-c – Mac OS X: рисование в NSGraphicsContext вне экрана с использованием функций CGContextRef C не имеет никакого эффекта. Зачем?

5

Mac OS X 10.7.4

Я рисую в контекст вне экрана, созданный с помощью+[NSGraphicsContext graphicsContextWithBitmapImageRep:].

Когда я рисую в этом графическом контексте, используяNSBezierPath classвсе работает как положено.

Тем не менее, когда я рисую в этом графическом контексте, используяCGContextRef C functionsЯ не вижу результатов моего рисунка. Ничего не работает

По причинам, в которые я не могу войти, мне действительно нужно рисовать, используяCGContextRef функции (а не какаоNSBezierPath учебный класс).

Мой пример кода приведен ниже. Я пытаюсь нарисовать простое "X". Один ход с использованиемNSBezierPath, одним ударом используяCGContextRef С функции. Первый удар работает, второй нет. Что я делаю неправильно?

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];

Ваш Ответ

4   ответа
1

Вот 3 способа рисования одного и того же изображения (Swift 4).

Метод, предложенный @Mecki, создает изображение безblurring артефакты (например, размытые кривые). Но это можно исправить, настроив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)
   }
}
0

Swift 4: Я использую этот код, который копирует удобный API из UIKit (но работает на macOS):

public class UIGraphicsImageRenderer {
    let size: CGSize

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

    func image(actions: (CGContext) -> Void) -> UIImage {
        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
}
Error: User Rate Limit Exceeded
25

Вы не указываете, как вы смотрите на результаты. Я полагаю, вы смотрите наNSImage img а неNSBitmapImageRep offscreenRep.

Когда вы звоните[img lockFocus]Вы меняете текущийNSGraphicsContext быть контекстом, чтобы привлечь вimg, ИтакNSBezierPath рисунок идет вimg и это то, что вы видите. Рисунок CG входит вoffscreenRep на которую вы не смотрите.

Вместо того, чтобы фокусироваться на NSImage и рисовать в нем, создайте NSImage и добавьте offscreenRep в качестве одного из его повторений.

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
Error: User Rate Limit ExceededimgError: User Rate Limit Exceeded Todd Ditchendorf
6

Интересно, почему все пишут такой сложный код для рисования на изображении? Если вы не заботитесь о точном растровом представлении изображения (и обычно это не так!), Создавать его не нужно. Вы можете просто создать пустое изображение и напрямую рисовать на нем. В этом случае система создаст соответствующее растровое представление (или, возможно, представление в формате PDF или что-то еще, что система считает более подходящим для рисования).

Документация по методу init

- (instancetype)initWithSize:(NSSize)aSize

которая существует с MacOS 10.0 и до сих пор не устарела, ясно говорит:

After using this method to initialize an image object, you are expected to provide the image contents before trying to draw the image. You might lock focus on the image and draw to the image or you might explicitly add an image representation that you created.

Вот как я написал бы этот код:

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 

и задокументировано как

The low-level, platform-specific graphics context represented by the graphic port.

Который может быть в значительной степени все, но в заключительной ноте говорится

In OS X, this is the Core Graphics context, a CGContextRef object (opaque type).

Это свойство было объявлено устаревшим в 10.10 в пользу нового имущества

@property (readonly) CGContextRef CGContext

который доступен только в 10.10 и позже. Если вам необходимо поддерживать более старые системы, все еще можно использоватьgraphicsPort.

Похожие вопросы