【问题标题】:Cannot create a Screenshot of a SCNView无法创建 SCNView 的屏幕截图
【发布时间】:2014-06-23 14:01:55
【问题描述】:

是否可以获取 SCNView 的屏幕截图?我正在尝试使用下面的代码,但它总是出现白色......

NSRect bounds = [window.contentView bounds];
NSImage *screenshot = [[NSImage alloc] initWithData:[window.contentView dataWithPDFInsideRect:bounds]];

当视图是标准 NSView 时,它可以正常工作...

【问题讨论】:

    标签: scenekit


    【解决方案1】:

    在 OS X v10.10 和 iOS 8 中,SCNView 添加了一个snapshot 方法,因此您可以更轻松地从中获取NSImage(或UIImage)。

    【讨论】:

    • 快照似乎也没有反映最近的更改,即,如果您在调用快照之前隐藏节点,该节点仍然会出现。这也是你的经历吗? @ErikAigner
    • 快照方法只运行正常更新/渲染循环的一部分(渲染部分)。确保对场景图的更改进入屏幕截图的最简单方法是将它们包含在 transaction 中并在调用屏幕截图之前提交。
    • 我在 Apple 文档中也看到了 snapshot() 方法,请问如何使用呢?谁能展示一些示例代码?
    【解决方案2】:

    SceneKit 使用 OpenGL 上下文进行绘制。您不能像基于 Quartz 的上下文那样轻松地将其转换为 PDF 数据(如“普通”AppKit 视图所使用的那样)。
    但是你可以从 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;
    }
    

    不要忘记链接 OpenGL.framework 和 #import "OpenGL/gl.h"

    更新
    SceneKit 似乎使用了翻转的上下文。我添加了一些代码来修复颠倒的图像。

    更新 2
    更新了代码以考虑支持比例因子(对于视网膜显示器)

    【讨论】:

    • 谢谢,它几乎可以工作了,我只看到左上象限,所以我认为这是我的相机的问题?
    • 另外,这个错字:NSInteger width = NSHeight([sceneKitView bounds]);应该是 NSInteger width = NSWidth([sceneKitView bounds]);
    • 其实不是左上角,是左下角,是倒置的
    • 感谢您的提示 - 如果修正了错字并更新了代码示例以处理翻转的上下文。
    • 谢谢@weichsel,我用过我的相机角度,但这并没有什么不同,我只得到了四分之一的图像,特别是左下角,任何想法都来自你的顶部头?
    【解决方案3】:

    我使用 SCNRenderer 编写了代码来执行此操作,因此它不依赖于屏幕内容。

    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)
    }
    

    【讨论】:

      猜你喜欢
      • 2012-12-20
      • 2014-03-18
      • 1970-01-01
      • 1970-01-01
      • 2019-02-08
      • 2012-05-10
      • 2016-08-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多