使用Metal快速拍摄当前屏幕快照 [英] Take a snapshot of current screen with Metal in swift

查看:103
本文介绍了使用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的快照

解决方案

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

1.如果您使用MTKView:

let texture = view.currentDrawable!.texture

2.如果您不使用MTKView

这就是我要做的-我将拥有一个属性,该属性将最后一个可绘制的内容显示在屏幕上:

let lastDrawableDisplayed: CAMetalDrawable?

然后在屏幕上显示可绘制对象时,我将对其进行更新:

let commandBuffer = commandQueue.commandBuffer()
commandBuffer.addCompletedHandler({
  (buffer:MTLCommandBuffer!) -> Void 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)

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

I tried:

 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)

But the result is a empty screenshot. Any help would be nice!

Please keep in mind that i want to take a snapshot of a CAMetalLayer

解决方案

To make a screenshot, you need to get MTLTextureof the frame buffer.

1. If you use MTKView:

let texture = view.currentDrawable!.texture

2. If you don't use MTKView

Here's what I would do - I would have a property which holds last drawable presented to the screen:

let lastDrawableDisplayed: CAMetalDrawable?

And then when you present drawable to the screen, I would update it:

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

Now you whenever you need to make a screenshot, you can get texture like this:

let texture = lastDrawableDisplayed.texture

Ok, now when you have MTLTexture you can convert it to CGImage and then to UIImage or NSImage.

Here's the code for OS X playground (MetalKit.MTLTextureLoader is not available for iOS playgrounds), in which I convert MTLTexture to CGImage

I made a small extension over MTLTexture for this.

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

这篇关于使用Metal快速拍摄当前屏幕快照的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆