CATransformation3D Transformed Label not saving on image - swift

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

Take snapshot from UIView with lower resolution

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

Why do these 2 UIGraphics drawing operations produce images of different sizes?

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.

How to get entire snapshot of scrollview in iOS 13?

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

how to save a cropped uiview screen shot using a extension market

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

ScreenShot issue in ARKit in swift

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