I am currently working on an Image app where I can put a Text Sticker that made with a UIView. I Applied CATransform3D to change the Perspective of the UIView. When I change the Values, it works properly and changes the Perspective View of the UIView. But when I try to Save the Image It goes to it's default position.
Here is the Code
Transformation
func transformation ()
{
var transform = CATransform3DIdentity;
transform.m34 = 1.0 / -200
transform = CATransform3DRotate(transform, CGFloat(sliderX.value), 0, 1, 0)
transform = CATransform3DRotate(transform, CGFloat(sliderY.value), 1, 0, 0)
textStickerView.currentlyEditingLabel.layer.transform = transform
}
Saving Function
var imgSize = CGSize(width: mainImageView.bounds.size.width, height: mainImageView.bounds.size.height)
UIGraphicsBeginImageContextWithOptions(imgSize, false, UIScreen.main.scale)
mainImageView.addSubview(textStickerView)
mainImageView.layer.render(in: UIGraphicsGetCurrentContext()!)
let rowImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Use drawHierarchy(in: bounds, afterScreenUpdates: true) instead of mainImageView.layer.render(in: UIGraphicsGetCurrentContext()!)
extension UIView {
func asImage() -> UIImage {
if #available(iOS 10.0, *) {
let format = UIGraphicsImageRendererFormat()
format.opaque = isOpaque
format.scale = 1.0
let renderer = UIGraphicsImageRenderer(bounds: bounds, format: format)
return renderer.image { rendererContext in
//layer.render(in: rendererContext.cgContext) // can not save transform3D
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
} else {
UIGraphicsBeginImageContextWithOptions(bounds.size, true, 1.0)
//self.layer.render(in:UIGraphicsGetCurrentContext()!) // can not save transform3D
drawHierarchy(in: frame, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image ?? UIImage()
}
}
}
Related
I'm taking snapshot from a PDFView in PDFKit for streaming (20 times per sec), and I use this extesnsion
extension UIView {
func asImageBackground(viewLayer: CALayer, viewBounds: CGRect) -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: viewBounds)
return renderer.image { rendererContext in
viewLayer.render(in: rendererContext.cgContext)
}
}
}
But the output UIImage from this extension has a high resolution which make it difficult to stream. I can reduce it by this extension
extension UIImage {
func resize(_ max_size: CGFloat) -> UIImage {
// adjust for device pixel density
let max_size_pixels = max_size / UIScreen.main.scale
// work out aspect ratio
let aspectRatio = size.width/size.height
// variables for storing calculated data
var width: CGFloat
var height: CGFloat
var newImage: UIImage
if aspectRatio > 1 {
// landscape
width = max_size_pixels
height = max_size_pixels / aspectRatio
} else {
// portrait
height = max_size_pixels
width = max_size_pixels * aspectRatio
}
// create an image renderer of the correct size
let renderer = UIGraphicsImageRenderer(size: CGSize(width: width, height: height), format: UIGraphicsImageRendererFormat.default())
// render the image
newImage = renderer.image {
(context) in
self.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
}
// return the image
return newImage
}
}
but it add an additional workload which make the process even worse. Is there any better way?
Thanks
You can downsample it using ImageIO which is recommended by Apple:
extension UIImage {
func downsample(to resolution: CGSize) -> UIImage? {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let data = self.jpegData(compressionQuality: 0.75) as? CFData, let imageSource = CGImageSourceCreateWithData(data, imageSourceOptions) else {
return nil
}
let maxDimensionInPixels = Swift.max(resolution.width, resolution.height) * 3
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
return nil
}
return UIImage(cgImage: downsampledImage)
}
}
To convert CGPDFPage to UIImage, I tested these 2 functions:
private func convertToImg(page: CGPDFPage) -> UIImage {
let pageRect = page.getBoxRect(.mediaBox)
let format = UIGraphicsImageRendererFormat()
format.scale = 1 // Set scale to 1 to prevent image to be drawn bigger than the original
let renderer = UIGraphicsImageRenderer(size: pageRect.size, format: format)
let img = renderer.image { ctx in
UIColor.white.set() // White Background
ctx.fill(pageRect)
ctx.cgContext.translateBy(x: 0.0, y: pageRect.size.height)
ctx.cgContext.scaleBy(x: 1.0, y: -1.0)
ctx.cgContext.drawPDFPage(page)
}
DLog("Image has \(String(describing: img.jpegData(compressionQuality: 1.0)?.count)) size")
return img
}
private func convertToImg2(page: CGPDFPage) -> UIImage {
let pageRect = page.getBoxRect(.mediaBox)
UIGraphicsBeginImageContext(pageRect.size)
let context:CGContext = UIGraphicsGetCurrentContext()!
context.saveGState()
UIColor.white.set() // White Background
context.fill(pageRect)
context.translateBy(x: 0.0, y: pageRect.size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.concatenate(page.getDrawingTransform(.mediaBox, rect: pageRect, rotate: 0, preserveAspectRatio: true))
context.drawPDFPage(page)
context.restoreGState()
let pdfImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let pdfImage = pdfImage {
DLog("Image has \(String(describing: pdfImage.jpegData(compressionQuality: 1.0)?.count)) size")
}
return pdfImage ?? UIImage()
}
Test procedure:
PDF as input, with 1 single-page with resolution = 300 × 2108
PDF File size: 320kB
Test results:
Output function 1 (UIImage):
resolution = 300 × 2108
Image size: 607kB
Output function 2 (UIImage):
resolution = 300 × 2108
Image size: 344 kB
Why does the first function outputs an image with a bigger file size?
Found it. The trick was to set
format.preferredRange = .standard
where format is of class UIGraphicsImageRendererFormat
It limits the color range used and therefore limits the output file size.
Note: iOS 13
For below code:
extension UIScrollView {
var snapshot: UIImage? {
UIGraphicsBeginImageContextWithOptions(contentSize, false, 0)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext() else { return nil }
let previousFrame = frame
let previousOffset = contentOffset
frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
contentOffset = CGPoint.zero
layer.render(in: context)
frame = previousFrame
contentOffset = previousOffset
return UIGraphicsGetImageFromCurrentImageContext()
}
}
It did work perfect in iOS 12, however, only snapshot part of (visible area) scrollView in iOS 13.
I want to snapshot total scrollView.Any way to solve it?
The key to solving the problem is to removeFromSuperview at first :
private func snapshot() -> UIImage? {
// remove first
scrollView.removeFromSuperview()
UIGraphicsBeginImageContextWithOptions(scrollView.contentSize, false, 0)
let savedContentOffset = scrollView.contentOffset
let savedFrame = scrollView.frame
scrollView.contentOffset = CGPoint.zero
scrollView.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
scrollView.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
scrollView.contentOffset = savedContentOffset
scrollView.frame = savedFrame
UIGraphicsEndImageContext()
// add again
self.view.addSubview(scrollView)
scrollView.snp.remakeConstraints { (make) in
make.left.right.equalToSuperview()
make.top.equalTo(naviHeight)
make.bottom.equalTo(-safeBottomHeight-105)
}
return image
}
Interesting...
My extension method right now takes a screenshot of the entire uiview inside of the view controller. I would like to use the same function to do the same thing only take a exact area of of the uiview instead of the whole view. Specifically I would like to capture x:0,y:0,length 200,Height 200,
func screenshot() -> UIImage {
let imageSize = UIScreen.main.bounds.size as CGSize;
UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
let context = UIGraphicsGetCurrentContext()
for obj : AnyObject in UIApplication.shared.windows {
if let window = obj as? UIWindow {
if window.responds(to: #selector(getter: UIWindow.screen)) || window.screen == UIScreen.main {
// so we must first apply the layer's geometry to the graphics context
context!.saveGState();
// Center the context around the window's anchor point
context!.translateBy(x: window.center.x, y: window.center
.y);
// Apply the window's transform about the anchor point
context!.concatenate(window.transform);
// Offset by the portion of the bounds left of and above the anchor point
context!.translateBy(x: -window.bounds.size.width * window.layer.anchorPoint.x,
y: -window.bounds.size.height * window.layer.anchorPoint.y);
// Render the layer hierarchy to the current context
window.layer.render(in: context!)
// Restore the context
context!.restoreGState();
}
}
}
let image = UIGraphicsGetImageFromCurrentImageContext();
return image!
}
How about:
extension UIView {
func screenshot(for rect: CGRect) -> UIImage {
return UIGraphicsImageRenderer(bounds: rect).image { _ in
drawHierarchy(in: CGRect(origin: .zero, size: bounds.size), afterScreenUpdates: true)
}
}
}
This makes it a bit more reusable, but you can change it to be a hardcoded value if you want.
let image = self.view.screenshot(for: CGRect(x: 0, y: 0, width: 200, height: 200))
i have application that uses ARSCNView. i'm trying to take a screenshot on click of a button and saved that image in the gallery. But when i take a screenshot it does not show the content on that screen. Just show that image, i have some labels on it but it does not show that in an image. This is my code,
#IBAction func captureImage(_ sender: Any) {
image = sceneView.snapshot()
UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
}
How can i show that labels and buttons on ARSCView in a screenshot?
snapshot() will only take screenshot of Scene.
To take screenshot of Scene with Labels and Buttons use below method:
func snapshot(of rect: CGRect? = nil) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, self.view.isOpaque, 0)
self.view.drawHierarchy(in: self.view.bounds, afterScreenUpdates: true)
let fullImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let image = fullImage, let rect = rect else { return fullImage }
let scale = image.scale
let scaledRect = CGRect(x: rect.origin.x * scale, y: rect.origin.y * scale, width: rect.size.width * scale, height: rect.size.height * scale)
guard let cgImage = image.cgImage?.cropping(to: scaledRect) else { return nil }
return UIImage(cgImage: cgImage, scale: scale, orientation: .up)
}
To Take Screenshot:
#IBAction func takeScreenShotTapped(_ sender: Any) {
let screenShot = snapshot(of: CGRect(x: 80, y: 80, width: 100, height: 100))
}
If you want to take screenshot of fullscreen then just call snapshot().
Hope this will help you :)