【问题标题】:Take a snapshot of current screen with Metal in swift在 swift 中使用 Metal 拍摄当前屏幕的快照
【发布时间】:2015-11-21 13:53:40
【问题描述】:

我试过了:

let scale = UIScreen.mainScreen().scale        
UIGraphicsBeginImageContextWithOptions(metalLayer.bounds.size, false, scale)
            
// metalLayer.renderInContext(UIGraphicsGetCurrentContext()!)

// self.view.layer ...

metalLayer.presentationLayer()!.renderInContext(UIGraphicsGetCurrentContext()!)
            
let image = UIGraphicsGetImageFromCurrentImageContext()           
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)

但结果是一个空的屏幕截图。任何帮助都会很好!

请记住,我想为CAMetalLayer 拍摄快照。

【问题讨论】:

    标签: swift metal


    【解决方案1】:

    要截屏,需要获取帧缓冲区的MTLTexture

    1.如果你使用MTKView

    let texture = view.currentDrawable!.texture
    

    2。如果你不使用MTKView

    这就是我要做的——我会有一个属性来保存最后一个呈现在屏幕上的可绘制对象:

    let lastDrawableDisplayed: CAMetalDrawable?
    

    然后当你在屏幕上展示drawable时,我会更新它:

    let commandBuffer = commandQueue.commandBuffer()
    commandBuffer.addCompletedHandler { buffer in
      self.lastDrawableDisplayed = drawable
    }
    

    现在你只要需要截图,就可以得到这样的纹理:

    let texture = lastDrawableDisplayed.texture
    

    好的,现在当您拥有MTLTexture 时,您可以将其转换为CGImage,然后再转换为UIImageNSImage

    这是 OS X 游乐场的代码(MetalKit.MTLTextureLoader 不适用于 iOS 游乐场),其中我将 MTLTexture 转换为 CGImage

    为此,我在MTLTexture 上做了一个小扩展。

    import Metal
    import MetalKit
    import Cocoa
    
    let device = MTLCreateSystemDefaultDevice()!
    let textureLoader = MTKTextureLoader(device: device)
    
    let path = "path/to/your/image.jpg"
    let data = NSData(contentsOfFile: path)!
    
    let texture = try! textureLoader.newTextureWithData(data, options: nil)
    
    extension MTLTexture {
      
      func bytes() -> UnsafeMutablePointer<Void> {
        let width = self.width
        let height = self.height
        let rowBytes = self.width * 4
        let p = malloc(width * height * 4)
        
        self.getBytes(p, bytesPerRow: rowBytes, fromRegion: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)
        
        return p
      }
      
      func toImage() -> CGImage? {
        let p = bytes()
        
        let pColorSpace = CGColorSpaceCreateDeviceRGB()
        
        let rawBitmapInfo = CGImageAlphaInfo.NoneSkipFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue
        let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
        
        let selftureSize = self.width * self.height * 4
        let rowBytes = self.width * 4
        let provider = CGDataProviderCreateWithData(nil, p, selftureSize, nil)
        let cgImageRef = CGImageCreate(self.width, self.height, 8, 32, rowBytes, pColorSpace, bitmapInfo, provider, nil, true, CGColorRenderingIntent.RenderingIntentDefault)!
        
        return cgImageRef
      }
    }
    
    if let imageRef = texture.toImage() {
      let image = NSImage(CGImage: imageRef, size: NSSize(width: texture.width, height: texture.height))
    }
    

    【讨论】:

    • 至少缺少一个}
    • 不捕捉透明背景。
    【解决方案2】:

    对于 Swift 4.0, 只是转换haawa提供的代码

    let lastDrawableDisplayed = metalView?.currentDrawable?.texture
    
    if let imageRef = lastDrawableDisplayed?.toImage() {
        let uiImage:UIImage = UIImage.init(cgImage: imageRef)
    }
    
    extension MTLTexture {
    
        func bytes() -> UnsafeMutableRawPointer {
            let width = self.width
            let height   = self.height
            let rowBytes = self.width * 4
            let p = malloc(width * height * 4)
    
            self.getBytes(p!, bytesPerRow: rowBytes, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)
    
            return p!
        }
    
        func toImage() -> CGImage? {
            let p = bytes()
    
            let pColorSpace = CGColorSpaceCreateDeviceRGB()
    
            let rawBitmapInfo = CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
            let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
    
            let selftureSize = self.width * self.height * 4
            let rowBytes = self.width * 4
            let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
                return
            }
            let provider = CGDataProvider(dataInfo: nil, data: p, size: selftureSize, releaseData: releaseMaskImagePixelData)
            let cgImageRef = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes, space: pColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)!
    
            return cgImageRef
        }
    }
    

    【讨论】:

    • 非常感谢,因为在我的情况下,这是获取图像的独特方式github.com/BradLarson/…#GPUImage3
    • 对我不起作用:_validateGetBytes:51: 断言失败 `texture must not be a framebufferOnly texture.'
    【解决方案3】:

    在 iPhone 6s 上使用 XCode 9.1 在 Swift 4 / Metal 2 中工作时,我没有得到公认的答案。因此,我使用了一种稍微不同的方法,假设 lastDrawableDisplayed 已按照接受的答案中的描述保存。快速而肮脏且没有任何异常处理:

    let context = CIContext()
    let texture = self.lastDrawableDisplayed!.texture
    let cImg = CIImage(mtlTexture: texture, options: nil)!
    let cgImg = context.createCGImage(cImg, from: cImg.extent)!
    let uiImg = UIImage(cgImage: cgImg)
    

    这是基于使用的CIImage Initializer的文档:

    init(mtlTexture:options:) 使用金属纹理提供的数据初始化图像对象。

    CIImage Processing,它描述了如何使用CIContext创建CGImage

    CIContext() 创建[s] CIContext 对象(使用默认选项)[...] context.createCGImage 将输出图像渲染[s] 为您可以显示或保存到文件的 Core Graphics 图像。

    希望对任何使用 Swift 4 的人有所帮助。

    编辑:此外,我的项目中有多个覆盖CAMetalLayer,并希望将它们组合成一个UIImage。因此需要引用每一层的最后一个CAMetalDrawable 对象。在添加新层(因此用作nextDrawable() 的提供者)之前,我只需将lastDrawableDisplayed 添加到数组[CAMetalDrawable] 中。当“导出”图层时,我只需将所有 UIImages 随后写入基于位图的图形上下文中,并使用UIGraphicsGetImageFromCurrentImageContext() 获得最终图像。

    编辑:如果您在定位方面遇到问题,请尝试以下操作:

    let uiImg = UIImage(cgImage: cgImg, scale: 1.0, orientation: UIImageOrientation.downMirrored)
    

    【讨论】:

    • 当我需要捕获具有透明背景的快照时,使用CIImage 的方法对我有用!谢谢!
    • 这是一个绝妙的答案!
    • 对我也不起作用:-[MTLDebugComputeCommandEncoder setTexture:atIndex:]:373: failed assertion frameBufferOnly texture not supported for compute. 需要(性能受到影响):metalView.framebufferOnly = false
    【解决方案4】:

    斯威夫特 4.2

    extension MTLTexture {
        
        func bytes() -> UnsafeMutableRawPointer {
            let width = self.width
            let height   = self.height
            let rowBytes = self.width * 4
            let p = malloc(width * height * 4)
            
            self.getBytes(p!, bytesPerRow: rowBytes, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)
            
            return p!
        }
        
        func toImage() -> CGImage? {
            let p = bytes()
            
            let pColorSpace = CGColorSpaceCreateDeviceRGB()
            
            let rawBitmapInfo = CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
            let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
            
            let selftureSize = self.width * self.height * 4
            let rowBytes = self.width * 4
            let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
                return
            }
            let provider = CGDataProvider(dataInfo: nil, data: p, size: selftureSize, releaseData: releaseMaskImagePixelData)
            let cgImageRef = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes, space: pColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)!
            
            return cgImageRef
        }
    }
    

    【讨论】:

    • @Ankit 的回答重复
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-03
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 2016-08-26
    相关资源
    最近更新 更多