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
Finish Asynchronous Task in Firebase With Swift
In Swift 3, What Is a Way to Compare Two Closures
Add "For In" Support to Iterate Over Swift Custom Classes
Swift Repl: How to Import, Load, Evaluate, or Require a .Swift File
Swiftui: Can't Get the Transition of a Detailview to a Zstack in the Mainview to Work
What Is the Swift Syntax " .Bar" Called
Arkit - How to Export Obj from Iphone/iPad with Lidar
Embedding Videos in a Tableview Cell
Self' Used Before All Stored Properties Are Initialized
Xcode 7.3.1 Hangs on "Copying Swift Standard Libraries"
Convert a Swift Array of String to a to a C String Array Pointer
Any Way to Iterate a Tuple in Swift
Filter Array of [Anyobject] in Swift
Swift 2.0: Protocol Extensions: Two Protocols with the Same Function Signature Compile Error
Split a String Without Removing the Delimiter in Swift
How to Cycle Through the Entire Alphabet with Swift While Assigning Values