JPG created from UIImage created from CIImage 3x too large - ios

I have a bunch of CIFilters that finally scale & crop the large image (from iPhone camera) to an 1080x1920 CIImage.
I then want to save the image as a JPG:
var outputFilter: CIFilter?
...
if let ciImage = outputFilter?.outputImage {
let outputImage = UIImage(ciImage: ciImage)
let data = outputImage?.jpegData(compressionQuality: 0.8)
...
}
The ciImage.extent is 1080x1920, outputImage.size is also 1080x1920, outputImage.scale is 1.0.
The image saved to disk however is 3x as large: 3240x5760.
What am I missing?

This will return an image based on your screen scale. If you check your screen scale it will result in 3X. What you need is to initialize your uiimage with the screen scale:
let outputImage = UIImage(ciImage: ciImage, scale: UIScreen.main.scale, orientation: .up)
To render the image you can use UIGraphicsImageRenderer:
extension CIImage {
var rendered: UIImage {
let cgImage = CIContext(options: nil).createCGImage(self, from: extent)!
let size = extent.size
let format = UIGraphicsImageRendererFormat.default()
format.opaque = false
return UIGraphicsImageRenderer(size: size, format: format).image { ctx in
var transform = CGAffineTransform(scaleX: 1, y: -1)
transform = transform.translatedBy(x: 0, y: -size.height)
ctx.cgContext.concatenate(transform)
ctx.cgContext.draw(cgImage, in: CGRect(origin: .zero, size: size))
}
}
}

I needed to do the following to both get the correct size and avoid bad rendering (as discussed in the comments of Leo Dabus' answer).
private func renderImage(ciImage: CIImage) -> UIImage?
{
var outputImage: UIImage?
UIGraphicsBeginImageContext(CGSize(width: 1080, height: 1920))
if let context = UIGraphicsGetCurrentContext()
{
context.interpolationQuality = .high
context.setShouldAntialias(true)
let inputImage = UIImage(ciImage: ciImage)
inputImage.draw(in: CGRect(x: 0, y: 0, width: 1080, height: 1920))
outputImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
return outputImage
}

Related

Screenshot a view with label and transparent background in iOS and upload it to server

I want to screenshot a view and create an UIImage from it. I want the transparency attribute of the view to be maintained in my image. I tried this method after creating an extension of UIImage but the background is not transparent in the resultant image when uploaded to the server.
Kindly help or point me if I am doing something wrong!! This means that the resultant png was not having transparency.
class func createTransparentImageFrom(label: UILabel, imageSize: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(imageSize, false, 2.0)
let currentView = UIView.init(frame: CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height))
currentView.backgroundColor = UIColor.clear
currentView.addSubview(label)
currentView.layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
This was due to the fact that when we render a view the image is JPEG/JPG, so we need to convert it to png in order to get layer information.
Try this :
extension UIImage {
class func createTransparentPNGFrom(label: UILabel, imageSize: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
let currentView = UIView.init(frame: CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height))
currentView.backgroundColor = UIColor.clear
currentView.addSubview(label)
currentView.layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let newImg = img else {
return nil
}
guard let imageData = UIImagePNGRepresentation(newImg) else {
return nil
}
return UIImage.init(data: imageData)
}
}
Swift 5 UIImage extension: screen UIView with transparent background
this is for taking a screenshot of a view which should have .backgroundColor = .clear
extension UIView {
func takeSnapshotOfView() -> UIImage? {
let size = CGSize(width: frame.size.width, height: frame.size.height)
let rect = CGRect.init(origin: .init(x: 0, y: 0), size: frame.size)
UIGraphicsBeginImageContext(size)
drawHierarchy(in: rect, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let imageData = image?.pngData() else {
return nil
}
return UIImage.init(data: imageData)
}
}
You need to put opaque value true for transparent result
Extension of UIView Of transparent screenshot
func screenShot() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, UIScreen.main.scale)
self.layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}

CIImage doubles size of resized image

I have the following code in a playground:
import UIKit
var str = "Hello, playground"
func rescaledImage(_ image: UIImage, with newSize: CGSize) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: newSize)
let rescaled = renderer.image { _ in
image.draw(in: CGRect.init(origin: CGPoint.zero, size: newSize))
}
return rescaled
}
let original = UIImage(named: "burn.jpg")!
let resized = rescaledImage(original, with: CGSize(width: 200, height: 200))
let ciImage = CIImage(image: resized)
burn.jpg is a 5000 pixel by 5000 pixel black and white jpg.
The resized image is properly 200 pixels by 200 pixels. The ciImage however, is 400 pixels by 400 pixels. In fact, no matter what I resize it to, the ciImage is always doubled.
However, if I just make ciImage out of the original:
let ciImage = CIImage(image: original)
the ciImage will be 5000 by 5000 pixels, instead of being doubled.
So what is causing this doubling? Something in the format of the resized image must be causing this, but I cannot seem to isolate it.
Note that this doubling also happens if I use UIGraphicsBeginImageContextWithOptions instead.
func imageWithImage(image:UIImage, scaledToSize newSize:CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0);
image.draw(in: CGRect(origin: CGPoint.zero, size: CGSize(width: newSize.width, height: newSize.height)))
let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}
If you check the scale of your images, you'll notice that the original.scale is probably 1, while the resized.scale is probably 2.
You can set the scale of the renderer using UIGraphicsImageRendererFormat, and see if that helps.
func rescaledImage(_ image: UIImage, with newSize: CGSize) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let renderer = UIGraphicsImageRenderer(size: newSize, format: format)
let rescaled = renderer.image { _ in
image.draw(in: CGRect.init(origin: CGPoint.zero, size: newSize))
}
return rescaled
}

Cropping UIImage - change background color

I have a function that crops a UIImage to the dimensions of a given rectangle. Sometimes part of the rectangle falls outside the image. After cropping, the default color of this extra part is white. Is it possible to change this color?
func crop(mainImage:UIImage, rectangle:CGRect) -> UIImage?{
let rectSize = CGSize(width: floor(rectangle.width), height: floor(rectangle.height))
UIGraphicsBeginImageContextWithOptions(rectSize, false, mainImage.scale)
let context:CGContext = UIGraphicsGetCurrentContext()!;
mainImage.draw(at: CGPoint(x: -rectangle.origin.x, y: -rectangle.origin.y))
let croppedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return croppedImage
}
You can use extension
extension UIImage {
func crop( rect: CGRect) -> UIImage {
var rect = rect
rect.origin.x*=self.scale
rect.origin.y*=self.scale
rect.size.width*=self.scale
rect.size.height*=self.scale
let imageRef = self.cgImage!.cropping(to: rect)
let image = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
return image
}
}
either use method like below
/**
Extracts image at a given rect from an image
- parameter rect : rect for which UIImage should be extracted within
- returns : new UIImage created from rect value supplied
*/
func imageByCroppingAtRect(_ rect: CGRect) -> UIImage? {
let imageRef: CGImage? = cgImage?.cropping(to: rect)
if imageRef == nil {
return nil
} else {
let subImage: UIImage = UIImage(cgImage: imageRef!)
return subImage
}
}

How to customize the camera view and capture the part in the overlay?

I need to build a custom camera view similar to this: example image
I've used AVFoundation and placed an UIImageView over the AVCaptureVideoPreviewLayer and it look almost the same (although I'm not sure if this is the right way, thats way I wrote the title like this). I'm capturing the image and saving it to the gallery, but I need only the image in the rectangle in the middle.
Any suggestions how to do it?
Thanks in advance!
Actually the answer from Nishant Bhindi needs some corrections. The code below will do the work:
func cropToBounds(image: UIImage) -> UIImage
{
let contextImage: UIImage = UIImage(cgImage: image.cgImage!)
let contextSize: CGSize = contextImage.size
let widthRatio = contextSize.height/UIScreen.main.bounds.size.height
let heightRatio = contextSize.width/UIScreen.main.bounds.size.width
let width = (self.imgOverlay?.frame.size.width)!*widthRatio
let height = (self.imgOverlay?.frame.size.height)!*heightRatio
let x = (contextSize.width/2) - width/2
let y = (contextSize.height/2) - height/2
let rect = CGRect(x: x, y: y, width: width, height: height)
let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
let image: UIImage = UIImage(cgImage: imageRef, scale: 0, orientation: image.imageOrientation)
return image
}
You need to crop the image in context of overlay imageView.Pass the captured image in below function that may help you.
func cropToBounds(image: UIImage) -> UIImage
{
let contextImage: UIImage = UIImage(cgImage: image.cgImage!)
let contextSize: CGSize = contextImage.size
let widthRatio = contextSize.height/UIScreen.main.bounds.size.width
let heightRatio = contextSize.width/UIScreen.main.bounds.size.height
let width = (self.imgOverlay?.frame.size.width)!*widthRatio
let height = (self.imgOverlay?.frame.size.height)!*heightRatio
let x = ((self.imgOverlay?.frame.origin.x)!)*widthRatio
let y = (self.imgOverlay?.frame.origin.y)!*heightRatio
let rect = CGRect(x: x, y: y, width: height, height: width)
let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
let image: UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
return image
}

Create a universal vector image programmatically

We can now create universal vector images in Xcode by adding them into image assets folder and then changing the scale factors to single vector. I would like to know how to do this programmatically. I've tried the following but no luck :(
func resizeImage(image: UIImage, newSize: CGSize) -> (UIImage) {
let newRect = CGRectIntegral(CGRectMake(0,0, newSize.width, newSize.height))
let imageRef = image.CGImage
let scaleFactor = UIScreen.mainScreen().scale
UIGraphicsBeginImageContextWithOptions(newSize, false, scaleFactor)
// UIGraphicsBeginImageContextWithOptions(newSize, false, 0.5)
let context = UIGraphicsGetCurrentContext()
// Set the quality level to use when rescaling
CGContextSetInterpolationQuality(context, CGInterpolationQuality.High)
let flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height)
CGContextConcatCTM(context, flipVertical)
// Draw into the context; this scales the image
CGContextDrawImage(context, newRect, imageRef)
let newImageRef = CGBitmapContextCreateImage(context)! as CGImage
let newImage = UIImage(CGImage: newImageRef)
// Get the resized image from the context and a UIImage
UIGraphicsEndImageContext()
return newImage
}
This should do it. Yea...Apple kind of gears this towards setting it in storyboard by ticking the preserve vector data. I'm not sure how to preserve vector data programmatically.
public class var add: UIImage {
let path = bundle.url(forResource: "plusReal", withExtension: "pdf")
return drawPDFFromURL(url: path!)!
}
class func drawPDFFromURL(url: URL) -> UIImage? {
guard let document = CGPDFDocument(url as CFURL),
let page = document.page(at: 1) else {return nil }
let pageRect = page.getBoxRect(.mediaBox)
let renderer = UIGraphicsImageRenderer(size: pageRect.size)
let img = renderer.image { ctx in
UIColor.white.set()
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)
}
return img
}

Resources