使用Swift中的Metal拍摄当前屏幕快照

8
我尝试过:
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进行截图。
6个回答

15

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

1. 如果使用MTKView

let texture = view.currentDrawable!.texture

2. 如果您不使用 MTKView

这是我会做的 - 我会有一个属性来保存最后呈现到屏幕的可绘制对象:

let lastDrawableDisplayed: CAMetalDrawable?

当您将可绘制对象呈现到屏幕上时,我会更新它:

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

现在,每当您需要进行截屏时,您可以获得如下所示的纹理:

let texture = lastDrawableDisplayed.texture

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

这是用于OS X playground的代码(MetalKit.MTLTextureLoader对iOS playground不可用),其中我将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))
}

缺少至少一个 } - Klaas
1
无法捕获透明背景。 - Anessence

10

对于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
    }
}

非常感谢,因为在我的情况下这是获取图像的独特方式 https://github.com/BradLarson/GPUImage3#capturing-and-filtering-a-still-photo #GPUImage3 - WINSergey
1
对我来说不起作用:_validateGetBytes:51: 失败的断言“纹理不能是仅限帧缓冲区的纹理”。 - Alex Stone

5

我没有成功地在Swift 4 / Metal 2和XCode 9.1上的iPhone 6s中使用被接受的答案。因此,我采用了稍微不同的方法,假设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初始化器文档:

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

并且CIImage处理说明如何使用CIContext创建CGImage

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

希望这对使用Swift 4的任何人都有帮助。
编辑:此外,在我的项目中有多个覆盖的CAMetalLayer,我想将它们组合成一个单独的UIImage。 因此,需要引用每个图层的最后一个CAMetalDrawable对象。 在添加新图层之前(因此用作nextDrawable()的提供者),我只需将lastDrawableDisplayed添加到数组[CAMetalDrawable]中。 当“导出”图层时,我只需将所有UIImage依次写入基于位图的图形上下文中,并使用UIGraphicsGetImageFromCurrentImageContext()获取最终图像。
编辑:如果您遇到方向问题,请尝试以下步骤:
let uiImg = UIImage(cgImage: cgImg, scale: 1.0, orientation: UIImageOrientation.downMirrored)

使用 CIImage 的方法对我很有效,当我需要捕获具有透明背景的快照时!谢谢! - Anessence
这是一个非常出色的答案! - ZAY
这是程序相关内容的翻译,仅返回翻译后的文本:对我也不起作用: -[MTLDebugComputeCommandEncoder setTexture:atIndex:]: 373: 失败断言frameBufferOnly纹理不支持计算。需要(会有性能损失):metalView.framebufferOnly = false - Alex Stone

0

MTLTexture的toImage方法需要在释放数据回调中释放数据的内存:

let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
            data.dealloc()
            return
 }           

0

由于未释放为CGDataProvider创建的字节,存在内存泄漏问题。

这是完整的Swift 5.0版本代码。

extension MTLTexture {
    func bytes() -> UnsafeMutableRawPointer {
        let width = self.width
        let height = self.height
        let rowBytes = self.width * 4
        let p = malloc(width * height * 4)!
        getBytes(p, bytesPerRow: rowBytes, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)
        return p
    }

    func toImage() -> CGImage? {
        let p = self.bytes()
        let pColorSpace = CGColorSpaceCreateDeviceRGB()
        let rawBitmapInfo = CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
        let bitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
        
        let selftureSize = self.width * self.height * 4
        let rowBytes = self.width * 4
        if let provider = CGDataProvider(dataInfo: nil, data: p, size: selftureSize, releaseData: { _, p, _ in
            p.deallocate()
        }) {
            return CGImage(width: width,
                           height: height,
                           bitsPerComponent: 8,
                           bitsPerPixel: 32,
                           bytesPerRow: rowBytes,
                           space: pColorSpace,
                           bitmapInfo: bitmapInfo,
                           provider: provider,
                           decode: nil,
                           shouldInterpolate: true,
                           intent: CGColorRenderingIntent.defaultIntent)
        }
        return nil
    }
}

-1

Swift 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的答案的副本 - Warren Stringer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接