I'm facing an image orientation issue when cropping a square portion of an image out of a rectangular original image. When image is in landscape, it's fine. But when it is in portrait, it seems that the image orientation is not preserved, which result in an image with wrong orientation AND bad crop:
func cropImage(cropRectangleCoordinates: CGRect) {
let croppedImage = originalImage
let finalCroppedImage : CGImageRef = CGImageCreateWithImageInRect(croppedImage.CGImage, cropRectangleCoordinates)
finalImage = UIImage(CGImage: finalCroppedImage)!
}
I think the problem is with croppedImage.CGImage. Here the image gets converted to CGImage, but it seems not to preserve the orientation.
It's easy to preserve orientation by using UIImage only, but to make the crop, image needs to be temporarily CGImage and this is the problem. Even if I reorient the image when converting back to UIImage, it might be in the correct orientation but the damage is already done when cropping CGImage.
This is a swift question, so please answer in swift, as the solution can be different in Objective-C.
SWIFT 3:
convert rotated cgImage to UIImage by this method
UIImage(cgImage:croppedCGImage, scale:originalImage.scale, orientation:originalImage.imageOrientation)
Source #David Berry answer
Here's a UIImage extension I wrote after looking after looking at several older pieces of code written by others. It's written in Swift 3 and uses the iOS orientation property plus CGAffineTransform to re-draw the image in proper orientation.
SWIFT 3:
public extension UIImage {
/// Extension to fix orientation of an UIImage without EXIF
func fixOrientation() -> UIImage {
guard let cgImage = cgImage else { return self }
if imageOrientation == .up { return self }
var transform = CGAffineTransform.identity
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat(M_PI))
case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat(M_PI_2))
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat(-M_PI_2))
case .up, .upMirrored:
break
}
switch imageOrientation {
case .upMirrored, .downMirrored:
transform.translatedBy(x: size.width, y: 0)
transform.scaledBy(x: -1, y: 1)
case .leftMirrored, .rightMirrored:
transform.translatedBy(x: size.height, y: 0)
transform.scaledBy(x: -1, y: 1)
case .up, .down, .left, .right:
break
}
if let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: cgImage.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) {
ctx.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
default:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
}
if let finalImage = ctx.makeImage() {
return (UIImage(cgImage: finalImage))
}
}
// something failed -- return original
return self
}
}
I found a solution.... time will tell if it's robust enough, but it seems to work in all situations. That was a vicious bug to fix.
So the problem is that UIImage, in some case only, lose it's orientation when converted to CGImage. It affects portraits image, that are automatically put in landscape mode. So image that are landscape by default are not affected.
But where the bug is vicious is that it doesn't affect ALL portrait images !! Also imageorientation value won't help for some image.
Those problematic images are images that user has in it's library that he got from email, messages, or saved from the web, so not taken with a camera. These images possibly don't have orientation information, and thus in some case, an image in portrait.... REMAINS in portrait when converted to CGImage. I really got stuck on that until I realized that some of my image in my device library were saved from messages or emails.
So the only reliable way I found to guess which image will be reoriented, is to create both version of a given image: UIImage, and CGImage, and compare their height value. If they are equal, then the CGImage version will not be rotated and you could work with it as expected.
But if they height are different, you can be sure that the CGImage conversion from CGImageCreateWithImageInRect will landscapize the image.
In this case only, I swap the x/y coordinate of origin, that I pass as rectangle coordinate to crop and it treats those special images correctly.
That was a long post, but the main idea is to compare CGImage height to UIImage width, and if they are different, expect origin point to be inverted.
This is THE answer, credit to #awolf (Cropping an UIImage). Handles scale and orientation perfectly. Just call this method on the image you want to crop, and pass in the cropping CGRect without worrying about scale or orientation. Feel free to check whether cgImage is nil instead of force unwrapping it like I did here.
extension UIImage {
func croppedInRect(rect: CGRect) -> UIImage {
func rad(_ degree: Double) -> CGFloat {
return CGFloat(degree / 180.0 * .pi)
}
var rectTransform: CGAffineTransform
switch imageOrientation {
case .left:
rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
case .right:
rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
case .down:
rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
default:
rectTransform = .identity
}
rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)
let imageRef = self.cgImage!.cropping(to: rect.applying(rectTransform))
let result = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
return result
}
}
Another note: if you are working with imageView embedded in a scrollView, there is one additional step, you have to take the zoom factor into account. Assuming your imageView spans the entire content view of the scrollView, and you use the bounds of the scrollView as the cropping frame, the cropped image can be obtained as
let ratio = imageView.image!.size.height / scrollView.contentSize.height
let origin = CGPoint(x: scrollView.contentOffset.x * ratio, y: scrollView.contentOffset.y * ratio)
let size = CGSize(width: scrollView.bounds.size.width * ratio, let height: scrollView.bounds.size.height * ratio)
let cropFrame = CGRect(origin: origin, size: size)
let croppedImage = imageView.image!.croppedInRect(rect: cropFrame)
Change your UIImage creation call to:
finalImage = UIImage(CGImage:finalCroppedImage, scale:originalImage.scale, orientation:originalImage.orientation)
to maintain the original orientation (and scale) of the image.
SWIFT 5. I added the following as an extension to UIImage. Idea is to force the image inside your UIImage to match that of the UIImage orientation (which only plays a role in how it's displayed). Redrawing the actual image data inside the UIImage "container" will make the corresponding CGImage to have the same orientation
func forceSameOrientation() -> UIImage {
UIGraphicsBeginImageContext(self.size)
self.draw(in: CGRect(origin: CGPoint.zero, size: self.size))
guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
UIGraphicsEndImageContext()
return self
}
UIGraphicsEndImageContext()
return image
}
#JGuo has the only answer that has actually worked. I've updated only a little bit to return an optional UIImage? and for swift-er syntax. I prefer to never implicitly unwrap.
extension UIImage {
func crop(to rect: CGRect) -> UIImage? {
func rad(_ degree: Double) -> CGFloat {
return CGFloat(degree / 180.0 * .pi)
}
var rectTransform: CGAffineTransform
switch imageOrientation {
case .left:
rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
case .right:
rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
case .down:
rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
default:
rectTransform = .identity
}
rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)
guard let imageRef = self.cgImage?.cropping(to: rect.applying(rectTransform)) else { return nil }
let result = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
return result
}
}
Here's its implementation as a computed property in my ViewController.
var croppedImage: UIImage? {
guard let image = self.image else { return nil }
let ratio = image.size.height / self.contentSize.height
let origin = CGPoint(x: self.contentOffset.x * ratio, y: self.contentOffset.y * ratio)
let size = CGSize(width: self.bounds.size.width * ratio, height: self.bounds.size.height * ratio)
let cropFrame = CGRect(origin: origin, size: size)
let croppedImage = image.crop(to: cropFrame)
return croppedImage
}
Related
I'm using custom camera implementation in my app using Swift.
When the image is captured, is called func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?). I get the image data with photo.fileDataRepresentation() and after that I'm fixing the orientation of the photo using the following extension on UIImage.
func fixedOrientation() -> UIImage? {
guard imageOrientation != UIImage.Orientation.up else {
// This is default orientation, don't need to do anything
return self.copy() as? UIImage
}
guard let cgImage = self.cgImage else {
// CGImage is not available
return nil
}
guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
return nil // Not able to create CGContext
}
var transform = CGAffineTransform.identity
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat.pi)
case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat.pi / 2.0)
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat.pi / -2.0)
case .up, .upMirrored:
break
#unknown default:
break
}
// Flip image one more time if needed to, this is to prevent flipped image
switch imageOrientation {
case .upMirrored, .downMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
case .leftMirrored, .rightMirrored:
transform = transform.translatedBy(x: size.height, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
case .up, .down, .left, .right:
break
#unknown default:
break
}
ctx.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
default:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
}
guard let newCGImage = ctx.makeImage() else { return nil }
return UIImage(cgImage: newCGImage, scale: 1, orientation: .up)
}
Apparently this is working fine for images taken with back camera, but taking with frontal one I'm facing issues.
If the selfie photo is taken in portrait, the the method returns the photo mirrored.(which is not a big deal)
If the selfie photo is taken in landscape right/left, the outcode is photo also mirrored but wrongfully rotated. Here is where I want your help, to get the correct rotation of the photo.
Note: I'm also changing the videoOrientation from AVCaptureConnection when rotating the device.
It turned out that i was changing the videoOrientation when rotation did change, which was wrong. I had to move that logic before capturing the photo. Now it works fine. Also I've fixed the mirroring problem, by setting isVideoMirrored to true if using the front camera.
I hate to say that I'm the new guy to iOS development but the shoe fits...
My app needs to either load an image/capture one from camera (which it does) but then needs to crop in LANDSCAPE.
I now have an extension that takes the image and shrinks it down to a landscape image (looks good if the camera is taken from landscape position OR scrunches it to a landscape if taken in portrait). It places it in my "viewfinder" as a tiny landscape image (seen below) and I can swipe outward to make it the size of the "view finder" but that's not what I need.
(the "viewfinder" I had to draw because I didn't put borders on it but you can see the thumbnail in the top left)
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
setImageToCrop(image: info[UIImagePickerControllerOriginalImage] as!
UIImage)
picker.dismiss(animated: true, completion: nil)
}
// -------------
func setImageToCrop(image:UIImage){
imageView.image = image
imageViewWidth.constant = image.size.width
imageViewHeight.constant = image.size.height
let scaleHeight = scrollView.frame.size.width/image.size.width
let scaleWidth = scrollView.frame.size.height/image.size.height
scrollView.minimumZoomScale = max(scaleWidth, scaleHeight)
scrollView.zoomScale = max(scaleWidth, scaleHeight)
self.cropButton.isHidden = false
}
// ------------------
#IBAction func cropButtonPressed(_ sender: Any) {
let scale:CGFloat = 1/scrollView.zoomScale
let x:CGFloat = scrollView.contentOffset.x * scale
let y:CGFloat = scrollView.contentOffset.y * scale
let width:CGFloat = scrollView.frame.size.width * scale
let height:CGFloat = scrollView.frame.size.height * scale
let croppedCGImage = imageView.image?.cgImage?.cropping(to: CGRect(x: x, y: y, width: width, height: height))
let croppedImage = UIImage(cgImage: croppedCGImage!)
// "now you can use croppedImage as you like...."
setImageToCrop(image: croppedImage)
}
// ---------------
And then the extension that shrinks the image...
public extension UIImage {
/// Extension to fix orientation of an UIImage without EXIF
func fixOrientation() -> UIImage {
guard let cgImage = cgImage else { return self }
if imageOrientation == .up { return self }
var transform = CGAffineTransform.identity
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat(M_PI))
case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat(M_PI_2))
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat(-M_PI_2))
case .up, .upMirrored:
break
}
switch imageOrientation {
case .upMirrored, .downMirrored:
transform.translatedBy(x: size.width, y: 0)
transform.scaledBy(x: -1, y: 1)
case .leftMirrored, .rightMirrored:
transform.translatedBy(x: size.height, y: 0)
transform.scaledBy(x: -1, y: 1)
case .up, .down, .left, .right:
break
}
if let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: cgImage.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) {
ctx.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
default:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
}
if let finalImage = ctx.makeImage() {
return (UIImage(cgImage: finalImage))
}
}
// something failed -- return original
return self
}
}
I have been around and around with different answers in StackOverFlow but haven't found the solution I'm looking for. I first found an extension that was the "fix all holy grail" but it did nothing. (The app still crops the image but stil rotates when it's saved.)
I'm 100% sure it's something stupid/small that I'm missing but I'm now lost in my code and if it means anything, I've been chasing this issue for WEEKS, if not MONTHS, now. I didn't want to ask unless I absolutely had to. Is there a better way to get an image and crop it it landscape without having it rotate? or is there a fix to this code?
Part of my iOS app uses Firebase Storage to store and load images taken/uploaded by the user.
However, every time I upload a picture to Firebase in portrait orientation, when I retrieve it back from Firebase later on it comes in landscape orientation and thus displays incorrectly.
I've read that Firebase stores metadata to determine the correct orientation of an uploaded picture but it doesn't seem to be working for me.
Any one know how to get the orientation the image was uploaded in without manually having to rotate it within the app's code?
Thanks!
edit for code:
Uploading:
let imageMetaData = FIRStorageMetadata()
imageMetaData.contentType = "image/png"
var imageData = Data()
imageData = UIImagePNGRepresentation(chosenImage)!
let currentUserProfilePictureRef = currentUserStorageRef.child("ProfilePicture")
currentUserProfilePictureRef.put(imageData, metadata: imageMetaData) { (metaData, error) in
if error == nil {
let downloadUrl = metaData?.downloadURL()?.absoluteString
self.currentUserRef.updateChildValues(["profilePictureUrl": downloadUrl!])
if (chosenImage.size.width.isLess(than: (chosenImage.size.height))) {
self.currentUserRef.updateChildValues(["profilePictureOrientation": "portrait"])
}
else {
self.currentUserRef.updateChildValues(["profilePictureOrientation": "landscape"])
}
}
}
Retrieving:
self.currentUserStorageRef.child("ProfilePicture").data(withMaxSize: 20*1024*1024, completion: {(data, error) in
var profilePicture = UIImage(data:data!)
if(profilePicture?.size.width.isLess(than: (profilePicture?.size.height)!))! {
if (pictureOrientation == "landscape") {
profilePicture = profilePicture?.rotated(by: Measurement(value: -90.0, unit: .degrees))
}
} else {
if (pictureOrientation == "portrait") {
profilePicture = profilePicture?.rotated(by: Measurement(value: 90.0, unit: .degrees))
}
}
self.buttonProfilePicture.setImage(profilePicture, for: .normal)
self.buttonProfilePicture.imageView?.contentMode = .scaleAspectFill
})
From my own personal experience (and asking virtually the same question a year ago), I've found that when you save an image using UIImagePNGRepresentation, it doesn't actually store the orientation data for that specific image. You should use UIImageJPEGRepresentation when you're having orientation issues. JPEG uses the exif format that specifies the orientation for an image, whereas PNG apparently does not. Your issue should be resolved if you save your image using UIImageJPEGRepresentation
Swift 5 - Update
This worked for me, just apply this on your UIImage before uploading it.
extension UIImage {
func fixedOrientation() -> UIImage? {
guard imageOrientation != UIImage.Orientation.up else {
// This is default orientation, don't need to do anything
return self.copy() as? UIImage
}
guard let cgImage = self.cgImage else {
// CGImage is not available
return nil
}
guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
return nil // Not able to create CGContext
}
var transform: CGAffineTransform = CGAffineTransform.identity
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat.pi)
case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat.pi / 2.0)
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat.pi / -2.0)
case .up, .upMirrored:
break
#unknown default:
break
}
// Flip image one more time if needed to, this is to prevent flipped image
switch imageOrientation {
case .upMirrored, .downMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
case .leftMirrored, .rightMirrored:
transform = transform.translatedBy(x: size.height, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
case .up, .down, .left, .right:
break
#unknown default:
break
}
ctx.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
default:
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
break
}
guard let newCGImage = ctx.makeImage() else { return nil }
return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
}
}
Just an update, UIImageJPEGRepresentation has changed to jpegData in the recent versions of xcode.
jpegData(compressionQuality: 1.0) 1.0 being high quality and 0 being low quality.
I am working to add support for wide color photos in iOS 10. When the user takes a photo from the camera, I need to use the new API that supports the new color space to save the photo data - UIGraphicsImageRenderer's jpegData instead of UIImageJPEGRepresentation.
I'm running into some troubles with image orientations. Taking a photo on my iPad in portrait, the image isn't being drawn correctly. See the comments below:
Old API:
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
let imageData = UIImageJPEGRepresentation(image, 1)
New API:
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
let cgImage = image.cgImage!
let ciImage = CIImage(cgImage: cgImage)
let format = UIGraphicsImageRendererFormat()
format.scale = 1
format.prefersExtendedRange = true
let renderer = UIGraphicsImageRenderer(bounds: ciImage.extent, format: format)
let imageData = renderer.jpegData(withCompressionQuality: 1, actions: { context in
context.cgContext.draw(cgImage, in: ciImage.extent) //draws flipped horizontally
//image.draw(at: .zero) //draws rotated 90 degrees leaving black at bottom
//image.draw(in: ciImage.extent) //draws rotated 90 degrees stretching and compressing the image to fill the rect
})
What's the correct way to replace UIImageJPEGRepresentation with UIGraphicsImageRenderer's jpegData?
UIImage can have different orientation depending on camera rotation.
You can dynamically resolve the transformation needed to be applied to the image depending on that orientation, like this:
let renderer = UIGraphicsImageRenderer(size: image.size, format: format)
let imageData = renderer.jpegData(withCompressionQuality: 1, actions: { context in
var workSize = image.size;
workSize.width = floor(workSize.width / image.scale)
workSize.height = floor(workSize.height / image.scale)
// No-op if the orientation is already correct
// if image.imageOrientation == .up { draw image }
// We need to calculate the proper transformation to make the image upright.
// We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
var transform = CGAffineTransform.identity
switch image.imageOrientation
{
case .down, .downMirrored:
transform = transform.translatedBy(x: workSize.width, y: workSize.height)
transform = transform.rotated(by: CGFloat(Double.pi))
break
case .left, .leftMirrored:
transform = transform.translatedBy(x: workSize.width, y: 0.0)
transform = transform.rotated(by: CGFloat(Double.pi / 2.0))
break
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0.0, y: workSize.height)
transform = transform.rotated(by: CGFloat(-Double.pi / 2.0))
break
case .up, .upMirrored:
break
}
switch image.imageOrientation
{
case .upMirrored, .downMirrored:
transform = transform.translatedBy(x: workSize.width, y: 0.0)
transform = transform.scaledBy(x: -1.0, y: 1.0)
break
case .leftMirrored, .rightMirrored:
transform = transform.translatedBy(x: workSize.height, y: 0.0);
transform = transform.scaledBy(x: -1.0, y: 1.0);
break
case .up, .down, .left, .right:
break
}
// Now we draw the underlying CGImage into a new context, applying the transform
// calculated above.
let ctx = context.cgContext
ctx.concatenate(transform)
switch image.imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(image.cgImage!, in: CGRect(x: 0.0, y:0.0, width: workSize.height, height: workSize.width))
break;
default:
ctx.draw(image.cgImage!, in: CGRect(origin: .zero, size: workSize))
break;
}
})
Answer based on UIImage+fixOrientation
The image.draw() method should correct the orientation automatically for you:
This method draws the entire image in the current graphics context, respecting the image’s orientation setting. In the default coordinate system, images are situated down and to the right of the origin of the specified rectangle. This method respects any transforms applied to the current graphics context, however.
I'm not sure why you need to use ci.extent. The extent seems to be (I make no claims to be an expert in Core Image APIs) the dimensions of the "raw" image data, which is stored without any image orientation correction. If you raw data needs rotation (non-Up orientations like Left, Right), the extent will still be the original rect that the data is stored.
I use the following code for an image with imageOrientation.rawValue = 3 and my data turns out perfectly fine at the right size with "corrected" imageOrientation.rawValue = 0 when reloaded.
let imageSize = image.Size
let renderer = UIGraphicsImageRenderer(size: renderedSize, format: UIGraphicsImageRendererFormat())
return renderer.jpegData(withCompressionQuality: 0.95) { (rendererContext) in
let rect = CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)
image.draw(in: rect)
}
My input image "renders" its data correctly with my code, taking into account the image orientation. On the right, I use draw(in: extent). The image isn't rotated though. Just stretched.
I am analyzing the images captured by the camera, I am going through each pixel by pixel and storing the rgb value of each pixel in an dictionary, obviously it's taking a lot of time. Can I scale down an image without loosing the percentage of color ratio in the image...
Example: Original image has RGB color and count as
Color count
[0xF0F0F0] 100
[0xB0B0B0] 50
[0x909090] 20
Then after scaling the image by half the color count in scaled image is like this:
Color count
[0xF0F0F0] 50
[0xB0B0B0] 25
[0x909090] 10
So 2 questions:
1. Is this doable in Swift?
2. If yes, then how do I scale an image in swift
Scaling down an image will definitely lose some pixels.
To scale an image in Swift:
var scaledImage // your scaled-down image
var origImage // your original image
let itemSize = CGSizeMake(origImage.size.width/2, origImage.size.height/2)
UIGraphicsBeginImageContextWithOptions(itemSize, false, UIScreen.mainScreen().scale)
let imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height)
origImage.drawInRect(imageRect)
scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
This is my code for resizing a image taken by the camera, after this process I send it to our servers. I recommend it to resize it instead of eliminating colors.
Also if you don't want to resize it, you can't convert the image to JPEG and assign a quality.
Use UIImageJPEGRepresentation instead of UIImagePNGRepresentation
if let img = value as? UIImage {
var newWidth = 0
var newHeight = 0
var sizeLimit = 700 //in px
let originalWidth = Int(img.size.width)
let originalHeight = Int(img.size.height)
//Get new size with max w/h = 700
if originalWidth > originalHeight {
//Max width
newWidth = sizeLimit
newHeight = (originalHeight*sizeLimit)/originalWidth
}else{
newWidth = (originalWidth*sizeLimit)/originalHeight
newHeight = sizeLimit
}
let newSize = CGSizeMake(CGFloat(newWidth), CGFloat(newHeight))
UIGraphicsBeginImageContext(newSize)
img.drawInRect(CGRectMake(0, 0, CGFloat(newWidth), CGFloat(newHeight)))
let newImg = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
//This is a UIIMAGE
let imageData:UIImage = UIImagePNGRepresentation(newImg)
}
i would say this probably isnt possible, because resizing the image would change the value of the actual colours so it would give you different results.
but maybe you can look into speeding it up using the full sized image. Look into maybe using metal for GPU accelerated calculations
Is this doable in Swift?
Sure
If yes, then how do I scale an image in swift
If you don't mind low level functions, since this is something you want to have done fast and on the CPU, not GPU, you might want to look at the Accelerate framework and vImage_scale in particular. It provides direct access to the RGB data in memory as well.
When I did something similar in the past, I noticed that the scaling actually cuts off some colors and does not give accurate representation of the original. Unfortunately, I don't have the examples to show anymore, but I'd suggest that you try this technique with different scaling ratio to see if it gives satisfactory data in your case.
func scaleAndRotateImage(image: UIImage, MaxResolution iIntMaxResolution: Int) -> UIImage {
let kMaxResolution = iIntMaxResolution
let imgRef = image.cgImage!
let width: CGFloat = CGFloat(imgRef.width)
let height: CGFloat = CGFloat(imgRef.height)
var transform = CGAffineTransform.identity
var bounds = CGRect.init(x: 0, y: 0, width: width, height: height)
if Int(width) > kMaxResolution || Int(height) > kMaxResolution {
let ratio: CGFloat = width / height
if ratio > 1 {
bounds.size.width = CGFloat(kMaxResolution)
bounds.size.height = bounds.size.width / ratio
}
else {
bounds.size.height = CGFloat(kMaxResolution)
bounds.size.width = bounds.size.height * ratio
}
}
let scaleRatio: CGFloat = bounds.size.width / width
let imageSize = CGSize.init(width: CGFloat(imgRef.width), height: CGFloat(imgRef.height))
var boundHeight: CGFloat
let orient = image.imageOrientation
// The output below is limited by 1 KB.
// Please Sign Up (Free!) to remove this limitation.
switch orient {
case .up:
//EXIF = 1
transform = CGAffineTransform.identity
case .upMirrored:
//EXIF = 2
transform = CGAffineTransform.init(translationX: imageSize.width, y: 0.0)
transform = transform.scaledBy(x: -1.0, y: 1.0)
case .down:
//EXIF = 3
transform = CGAffineTransform.init(translationX: imageSize.width, y: imageSize.height)
transform = transform.rotated(by: CGFloat(Double.pi / 2))
case .downMirrored:
//EXIF = 4
transform = CGAffineTransform.init(translationX: 0.0, y: imageSize.height)
transform = transform.scaledBy(x: 1.0, y: -1.0)
case .leftMirrored:
//EXIF = 5
boundHeight = bounds.size.height
bounds.size.height = bounds.size.width
bounds.size.width = boundHeight
transform = CGAffineTransform.init(translationX: imageSize.height, y: imageSize.width)
transform = transform.scaledBy(x: -1.0, y: 1.0)
transform = transform.rotated(by: CGFloat(Double.pi / 2) / 2.0)
break
default: print("Error in processing image")
}
UIGraphicsBeginImageContext(bounds.size)
let context = UIGraphicsGetCurrentContext()
if orient == .right || orient == .left {
context?.scaleBy(x: -scaleRatio, y: scaleRatio)
context?.translateBy(x: -height, y: 0)
}
else {
context?.scaleBy(x: scaleRatio, y: -scaleRatio)
context?.translateBy(x: 0, y: -height)
}
context?.concatenate(transform)
context?.draw(imgRef, in: CGRect.init(x: 0, y: 0, width: width, height: height))
let imageCopy = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageCopy!
}