Cannot create a Screenshot of a SCNView

后端 未结 3 599
南笙
南笙 2020-12-17 03:46

Is it possible to get a screenshot of an SCNView? I\'m trying with the below code, but it always comes out white...

NSRect bounds = [window.contentView bound         


        
相关标签:
3条回答
  • 2020-12-17 04:09

    I wrote code to do this with an SCNRenderer so it’s not dependent on the screen contents.

    public extension SCNRenderer {
    
        public func renderToImageSize(size: CGSize, floatComponents: Bool, atTime time: NSTimeInterval) -> CGImage? {
    
            var thumbnailCGImage: CGImage?
    
            let width = GLsizei(size.width), height = GLsizei(size.height)
            let samplesPerPixel = 4
    
            #if os(iOS)
                let oldGLContext = EAGLContext.currentContext()
                let glContext = unsafeBitCast(context, EAGLContext.self)
    
                EAGLContext.setCurrentContext(glContext)
                objc_sync_enter(glContext)
            #elseif os(OSX)
                let oldGLContext = CGLGetCurrentContext()
                let glContext = unsafeBitCast(context, CGLContextObj.self)
    
                CGLSetCurrentContext(glContext)
                CGLLockContext(glContext)
            #endif
    
            // set up the OpenGL buffers
            var thumbnailFramebuffer: GLuint = 0
            glGenFramebuffers(1, &thumbnailFramebuffer)
            glBindFramebuffer(GLenum(GL_FRAMEBUFFER), thumbnailFramebuffer); checkGLErrors()
    
            var colorRenderbuffer: GLuint = 0
            glGenRenderbuffers(1, &colorRenderbuffer)
            glBindRenderbuffer(GLenum(GL_RENDERBUFFER), colorRenderbuffer)
            if floatComponents {
                glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_RGBA16F), width, height)
            } else {
                glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_RGBA8), width, height)
            }
            glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), colorRenderbuffer); checkGLErrors()
    
            var depthRenderbuffer: GLuint = 0
            glGenRenderbuffers(1, &depthRenderbuffer)
            glBindRenderbuffer(GLenum(GL_RENDERBUFFER), depthRenderbuffer)
            glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_DEPTH_COMPONENT24), width, height)
            glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_DEPTH_ATTACHMENT), GLenum(GL_RENDERBUFFER), depthRenderbuffer); checkGLErrors()
    
            let framebufferStatus = Int32(glCheckFramebufferStatus(GLenum(GL_FRAMEBUFFER)))
            assert(framebufferStatus == GL_FRAMEBUFFER_COMPLETE)
            if framebufferStatus != GL_FRAMEBUFFER_COMPLETE {
                return nil
            }
    
            // clear buffer
            glViewport(0, 0, width, height)
            glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); checkGLErrors()
    
            // render
            renderAtTime(time); checkGLErrors()
    
            // create the image
            if floatComponents { // float components (16-bits of actual precision)
    
                // slurp bytes out of OpenGL
                typealias ComponentType = Float
    
                var imageRawBuffer = [ComponentType](count: Int(width * height) * samplesPerPixel * sizeof(ComponentType), repeatedValue: 0)
                glReadPixels(GLint(0), GLint(0), width, height, GLenum(GL_RGBA), GLenum(GL_FLOAT), &imageRawBuffer)
    
                // flip image vertically — OpenGL has a different 'up' than CoreGraphics
                let rowLength = Int(width) * samplesPerPixel
                for rowIndex in 0..<(Int(height) / 2) {
                    let baseIndex = rowIndex * rowLength
                    let destinationIndex = (Int(height) - 1 - rowIndex) * rowLength
    
                    swap(&imageRawBuffer[baseIndex..<(baseIndex + rowLength)], &imageRawBuffer[destinationIndex..<(destinationIndex + rowLength)])
                }
    
                // make the CGImage
                var imageBuffer = vImage_Buffer(
                    data: UnsafeMutablePointer<Float>(imageRawBuffer),
                    height: vImagePixelCount(height),
                    width: vImagePixelCount(width),
                    rowBytes: Int(width) * sizeof(ComponentType) * samplesPerPixel)
    
                var format = vImage_CGImageFormat(
                    bitsPerComponent: UInt32(sizeof(ComponentType) * 8),
                    bitsPerPixel: UInt32(sizeof(ComponentType) * samplesPerPixel * 8),
                    colorSpace: nil, // defaults to sRGB
                    bitmapInfo: CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue | CGBitmapInfo.FloatComponents.rawValue),
                    version: UInt32(0),
                    decode: nil,
                    renderingIntent: kCGRenderingIntentDefault)
    
                var error: vImage_Error = 0
                thumbnailCGImage = vImageCreateCGImageFromBuffer(&imageBuffer, &format, nil, nil, vImage_Flags(kvImagePrintDiagnosticsToConsole), &error)!.takeRetainedValue()
    
            } else { // byte components
    
                // slurp bytes out of OpenGL
                typealias ComponentType = UInt8
    
                var imageRawBuffer = [ComponentType](count: Int(width * height) * samplesPerPixel * sizeof(ComponentType), repeatedValue: 0)
                glReadPixels(GLint(0), GLint(0), width, height, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), &imageRawBuffer)
    
                // flip image vertically — OpenGL has a different 'up' than CoreGraphics
                let rowLength = Int(width) * samplesPerPixel
                for rowIndex in 0..<(Int(height) / 2) {
                    let baseIndex = rowIndex * rowLength
                    let destinationIndex = (Int(height) - 1 - rowIndex) * rowLength
    
                    swap(&imageRawBuffer[baseIndex..<(baseIndex + rowLength)], &imageRawBuffer[destinationIndex..<(destinationIndex + rowLength)])
                }
    
                // make the CGImage
                var imageBuffer = vImage_Buffer(
                    data: UnsafeMutablePointer<Float>(imageRawBuffer),
                    height: vImagePixelCount(height),
                    width: vImagePixelCount(width),
                    rowBytes: Int(width) * sizeof(ComponentType) * samplesPerPixel)
    
                var format = vImage_CGImageFormat(
                    bitsPerComponent: UInt32(sizeof(ComponentType) * 8),
                    bitsPerPixel: UInt32(sizeof(ComponentType) * samplesPerPixel * 8),
                    colorSpace: nil, // defaults to sRGB
                    bitmapInfo: CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Big.rawValue),
                    version: UInt32(0),
                    decode: nil,
                    renderingIntent: kCGRenderingIntentDefault)
    
                var error: vImage_Error = 0
                thumbnailCGImage = vImageCreateCGImageFromBuffer(&imageBuffer, &format, nil, nil, vImage_Flags(kvImagePrintDiagnosticsToConsole), &error)!.takeRetainedValue()
            }
    
            #if os(iOS)
                objc_sync_exit(glContext)
                if oldGLContext != nil {
                    EAGLContext.setCurrentContext(oldGLContext)
                }
            #elseif os(OSX)
                CGLUnlockContext(glContext)
                if oldGLContext != nil {
                    CGLSetCurrentContext(oldGLContext)
                }
            #endif
    
            return thumbnailCGImage
        }
    }
    
    
    func checkGLErrors() {
        var glError: GLenum
        var hadError = false
        do {
            glError = glGetError()
            if glError != 0 {
                println(String(format: "OpenGL error %#x", glError))
                hadError = true
            }
        } while glError != 0
        assert(!hadError)
    }
    
    0 讨论(0)
  • 2020-12-17 04:23

    In OS X v10.10 and iOS 8, SCNView adds a snapshot method, so you can get an NSImage (or UIImage) out of it much more easily.

    0 讨论(0)
  • 2020-12-17 04:24

    SceneKit uses an OpenGL context to draw. You can't turn that into PDF data as easily as a Quartz based context (as used by "normal" AppKit views).
    But you can grab the rasterized bitmap data from OpenGL:

    - (IBAction)takeShot:(id)sender
    {
        NSString* path = @"/Users/weichsel/Desktop/test.tiff";
        NSImage* image = [self imageFromSceneKitView:self.scene];
        BOOL didWrite = [[image TIFFRepresentation] writeToFile:path atomically:YES];
        NSLog(@"Did write:%d", didWrite);
    }
    
    - (NSImage*)imageFromSceneKitView:(SCNView*)sceneKitView
    {
        NSInteger width = sceneKitView.bounds.size.width * self.scene.window.backingScaleFactor;
        NSInteger height = sceneKitView.bounds.size.height * self.scene.window.backingScaleFactor;
        NSBitmapImageRep* imageRep=[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
                                                                           pixelsWide:width
                                                                           pixelsHigh:height
                                                                        bitsPerSample:8
                                                                      samplesPerPixel:4
                                                                             hasAlpha:YES
                                                                             isPlanar:NO
                                                                       colorSpaceName:NSCalibratedRGBColorSpace
                                                                          bytesPerRow:width*4
                                                                         bitsPerPixel:4*8];
        [[sceneKitView openGLContext] makeCurrentContext];
        glReadPixels(0, 0, (int)width, (int)height, GL_RGBA, GL_UNSIGNED_BYTE, [imageRep bitmapData]);
        [NSOpenGLContext clearCurrentContext];
        NSImage* outputImage = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
        [outputImage addRepresentation:imageRep];
        NSImage* flippedImage = [NSImage imageWithSize:NSMakeSize(width, height) flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
            [imageRep drawInRect:dstRect];
            return YES;
        }];
        return flippedImage;
    }
    

    Don't forget to link OpenGL.framework and #import "OpenGL/gl.h"

    Update
    SceneKit seems to use a flipped context. I added some code to fix the upside-down image.

    Update 2
    Updated code to take the backing scale factor into account (for retina displays)

    0 讨论(0)
提交回复
热议问题