使用金属快速拍摄当前屏幕的快照

时间:2015-11-21 13:53:40

标签: swift metal

我试过了:

 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的快照

4 个答案:

答案 0 :(得分:7)

要制作屏幕截图,您需要获得帧缓冲区MTLTexture

<强> 1。如果您使用MTKView

let texture = view.currentDrawable!.texture

<强> 2。如果您不使用MTKView

这就是我要做的事情 - 我会有一个属性,其中包含最后一个可绘制的属性:

let lastDrawableDisplayed: CAMetalDrawable?

然后当你将drawable呈现给屏幕时,我会更新它:

let commandBuffer = commandQueue.commandBuffer()
commandBuffer.addCompletedHandler({
  (buffer:MTLCommandBuffer!) -> Void in
  self.lastDrawableDisplayed = drawable
})

现在,无论何时需要制作屏幕截图,都可以获得如下纹理:

let texture = lastDrawableDisplayed.texture

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

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

我为MTLTexture做了一个小扩展。

import Metal
import MetalKit
import Cocoa

let device = MTLCreateSystemDefaultDevice()!
let textureLoader = MTKTextureLoader(device: device)

// PATH TO YOUR IMAGE FILE
let path = "/Users/haawa799/Desktop/Metal_Snapshot.playground/Resources/q.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))

答案 1 :(得分: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
    }
}

答案 2 :(得分:0)

我无法在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:)使用Metal纹理提供的数据初始化图像对象。

CIImage Processing,其中介绍了如何使用CGImage创建CIContext

  

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

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

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

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

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

答案 3 :(得分:0)

迅速4.2

扩展名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
}

}