Take a Snapshot of Current Screen with Metal in Swift

Take a snapshot of current screen with Metal in swift

To make a screenshot, you need to get MTLTexture of 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 in
self.lastDrawableDisplayed = drawable
}

Now you whenever you need to take a screenshot, you can get a 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)

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

How to get a snapshot of current drawable with metal on mac?

The storage mode of the drawable's texture is managed. You need to use a blit command encoder to encode a -synchronize... command. Otherwise, the data isn't guaranteed to be available to the CPU.

How can I make screen shot image from MTKView in iOS?

Self solved.

I found a similar topic.

And I write code for the following points.

1: using Objective-C

2: no using extension MTLTexture

- (IBAction)onCapture:(id)sender
{
id<MTLTexture> lastDrawableDisplayed = [metalView.currentDrawable texture];

int width = (int)[lastDrawableDisplayed width];
int height = (int)[lastDrawableDisplayed height];
int rowBytes = width * 4;
int selfturesize = width * height * 4;

void *p = malloc(selfturesize);

[lastDrawableDisplayed getBytes:p bytesPerRow:rowBytes fromRegion:MTLRegionMake2D(0, 0, width, height) mipmapLevel:0];

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaFirst;

CGDataProviderRef provider = CGDataProviderCreateWithData(nil, p, selfturesize, nil);
CGImageRef cgImageRef = CGImageCreate(width, height, 8, 32, rowBytes, colorSpace, bitmapInfo, provider, nil, true, (CGColorRenderingIntent)kCGRenderingIntentDefault);

UIImage *getImage = [UIImage imageWithCGImage:cgImageRef];
CFRelease(cgImageRef);
free(p);

NSData *pngData = UIImagePNGRepresentation(getImage);
UIImage *pngImage = [UIImage imageWithData:pngData];
UIImageWriteToSavedPhotosAlbum(pngImage, self,
@selector(completeSavedImage:didFinishSavingWithError:contextInfo:), nil);
}

But, I get the following exec error in getBytes method.

failed assertion `texture must not be a framebufferOnly texture.'

This error has been fixed in the following fix.
So, I change setting for MTKView.

metalView.framebufferOnly = false;

Thanks.

How Do I Take a Screen Shot of a UIView?

I think you may want renderInContext, not drawInContext. drawInContext is more a method you would override...

Note that it may not work in all views, specifically a year or so ago when I tried to use this with the live camera view it did not work.

How to take screenshot of portion of UIView?

The standard snapshot technique is drawHierarchy(in:afterScreenUpdates:), drawing that to an image context. In iOS 10 and later, you can use UIGraphicsImageRenderer:

extension UIView {

/// Create image snapshot of view.
///
/// - Parameters:
/// - rect: The coordinates (in the view's own coordinate space) to be captured. If omitted, the entire `bounds` will be captured.
/// - afterScreenUpdates: A Boolean value that indicates whether the snapshot should be rendered after recent changes have been incorporated. Specify the value false if you want to render a snapshot in the view hierarchy’s current state, which might not include recent changes. Defaults to `true`.
///
/// - Returns: The `UIImage` snapshot.

func snapshot(of rect: CGRect? = nil, afterScreenUpdates: Bool = true) -> UIImage {
return UIGraphicsImageRenderer(bounds: rect ?? bounds).image { _ in
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
}
}
}

And you’d use that like so:

let image = webView.snapshot(of: rect)

Prior to iOS 10, you would to get portion of an image, you can use CGImage method cropping(to:). E.g.:

extension UIView {

/// Create snapshot
///
/// - Parameters:
/// - rect: The coordinates (in the view's own coordinate space) to be captured. If omitted, the entire `bounds` will be captured.
/// - afterScreenUpdates: A Boolean value that indicates whether the snapshot should be rendered after recent changes have been incorporated. Specify the value false if you want to render a snapshot in the view hierarchy’s current state, which might not include recent changes. Defaults to `true`.
///
/// - Returns: Returns `UIImage` of the specified portion of the view.

func snapshot(of rect: CGRect? = nil, afterScreenUpdates: Bool = true) -> UIImage? {
// snapshot entire view

UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
let wholeImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

// if no `rect` provided, return image of whole view

guard let image = wholeImage, let rect = rect else { return wholeImage }

// otherwise, grab specified `rect` of image

guard let cgImage = image.cgImage?.cropping(to: rect * image.scale) else { return nil }
return UIImage(cgImage: cgImage, scale: image.scale, orientation: .up)
}

}

Which uses this little convenient operator:

extension CGRect {
static func * (lhs: CGRect, rhs: CGFloat) -> CGRect {
return CGRect(x: lhs.minX * rhs, y: lhs.minY * rhs, width: lhs.width * rhs, height: lhs.height * rhs)
}
}

And to use it, you can do:

if let image = webView.snapshot(of: rect) {
// do something with `image` here
}

For Swift 2 rendition, see previous revision of this answer.

How to take a screenshot programmatically on iOS

I'm answering this question as it's a highly viewed, and there are many answers out there plus there's Swift and Obj-C.

Disclaimer This is not my code, nor my answers, this is only to help people that land here find a quick answer. There are links to the original answers to give credit where credit is due!! Please honor the original answers with a +1 if you use their answer!


Using QuartzCore

#import <QuartzCore/QuartzCore.h> 

if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
UIGraphicsBeginImageContextWithOptions(self.window.bounds.size, NO, [UIScreen mainScreen].scale);
} else {
UIGraphicsBeginImageContext(self.window.bounds.size);
}

[self.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData *imageData = UIImagePNGRepresentation(image);
if (imageData) {
[imageData writeToFile:@"screenshot.png" atomically:YES];
} else {
NSLog(@"error while taking screenshot");
}

In Swift

func captureScreen() -> UIImage
{

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, false, 0);

self.view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)

let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()

return image
}

Note: As the nature with programming, updates may need to be done so please edit or let me know! *Also if I failed to include an answer/method worth including feel free to let me know as well!



Related Topics



Leave a reply



Submit