18

I need to take the contents of an NSView and put them in an NSImage, for an experimental project. Is this possible? I did some Googling, tried two methods that I found - but they didn't really work. Any suggestions?

Justin Boo
  • 10,132
  • 8
  • 50
  • 71
Debashis
  • 199
  • 1
  • 1
  • 4

4 Answers4

31

From WWDC 2012 Session 245 (translated to Swift):

let viewToCapture = self.window!.contentView!
let rep = viewToCapture.bitmapImageRepForCachingDisplay(in: viewToCapture.bounds)!
viewToCapture.cacheDisplay(in: viewToCapture.bounds, to: rep)

let img = NSImage(size: viewToCapture.bounds.size)
img.addRepresentation(rep)
tobiasdm
  • 9,688
  • 1
  • 18
  • 15
Klaas
  • 22,394
  • 11
  • 96
  • 107
  • swift 3 let rep = viewToCapture.bitmapImageRepForCachingDisplay(in: viewToCapture.bounds)! viewToCapture.cacheDisplay(in: viewToCapture.bounds, to: rep) let img = NSImage(size: viewToCapture.bounds.size) img.addRepresentation(rep) – spacecash21 Jul 02 '17 at 17:56
  • Note: doesn't always work correctly. I'm having issued with a label that has a rotated coordinate system. – Ash Jul 04 '17 at 14:16
  • 3
    This was the best solution for me, unlike the pdf version, this also accounts for shadows and rounded corners. Unlike the window cap version, this only captures the current view. Obj C code: `- (NSImage *)screenCap { NSBitmapImageRep *bitmap = [self bitmapImageRepForCachingDisplayInRect:self.bounds]; [self cacheDisplayInRect:self.bounds toBitmapImageRep:bitmap]; NSImage *result = [[NSImage alloc] initWithSize:self.bounds.size]; [result addRepresentation:bitmap]; return result; } ` – codrut Oct 16 '17 at 13:36
  • I've been using this successfully in 10.14 & 10.15 (codrut's Obj C version above) but for some reason I get no image on 10.13. – w0mbat Aug 14 '20 at 17:49
  • I had the problem that subviews with `visible=NO`were still showing up in the PDF version. Using this version solved it. Still looking for a solution, though, which will also include an `AVPlayerView`'s current frame. Suggestions welcome :) – fbitterlich Sep 26 '22 at 13:31
  • I'm using an NSBox with .underPageBackgroundColor as the fillColor and this doesn't capture the color properly – Noah Nuebling Nov 19 '22 at 15:27
23
[[NSImage alloc] initWithData:[view dataWithPDFInsideRect:[view bounds]]];
Chuck
  • 234,037
  • 30
  • 302
  • 389
  • 2
    +1 It's worth noting that, although this is the right way in general, there are some cases where it will not work, eg views like `NSOpenGLView` that have their own OpenGL rendering context. In that case you need to get the pixel data directly and create a bitmap rep from it, which is a bit less neat. – walkytalky Jul 19 '10 at 01:11
  • Obs: shadows and cornerRadius will be ignored – codrut Sep 25 '17 at 08:30
  • 1
    If view is layer backed (wantsLayer = YES) this solution will ignore some layer properties, like backgroundColor, cornerRadius etc. I recommend code from another answer, using cacheDisplayInRect function, it should work in all cases. – Slyv Sep 25 '18 at 13:23
6
let dataOfView = view.dataWithPDFInsideRect(view.bounds)
let imageOfView = NSImage(data: dataOfView)
Zelko
  • 3,793
  • 3
  • 34
  • 40
0

NSView.bitmapImageRepForCachingDisplay() (mentioned in this answer) doesn't render the colors correctly on some views.

CGWindowListCreateImage() works perfectly for me.

Here's my implementation:

extension NSView {
    @objc func takeScreenshot() -> NSImage? {
        
        let screenRect = self.rectInQuartzScreenCoordinates()
        guard let window = self.window else { 
            assert(false); return nil 
        }
        let windowID = CGWindowID(window.windowNumber)
        guard let screenshot = CGWindowListCreateImage(screenRect, .optionIncludingWindow, windowID, []) else { 
            assert(false); return nil 
        }
        
        return NSImage(cgImage: screenshot, size: self.frame.size)
    }
}

This code uses a method NSView.rectInQuartzScreenCoordinates(). To implement it you'll first have to convert the bounds of your view to screenCoordinates using NSView and NSWindow methods and then you need to flip the coordinates like this.

Noah Nuebling
  • 219
  • 2
  • 11