Swift: Images rotated from portrait to landscape when loaded from Firebase - ios

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.

Related

Fix orientation of images taken with front camera ios

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.

How to rotate image in proper orientation

I'm Capturing image and storing it in NSData format. When i capture my image in portrait device orientation, the image is rotating left 90 degrees. when i'm capturing image in landscape device orientation, i'm getting image in proper manner. i trying below function. but i don't know what cgImage need to take as CGImage.
func rotateImageProperly(image:UIImage) -> UIImage {
let normalImage:UIImage!
if(image.imageOrientation == .up) {
normalImage = image
}else if (image.imageOrientation == .left){
normalImage = UIImage(cgImage: <#T##CGImage#>, scale: 1.0, orientation: UIImageOrientation.right)
}
return normalImage
}
use following code to properly rotate image :
func imageOrientation(_ src:UIImage)->UIImage {
if src.imageOrientation == UIImageOrientation.up {
return src
}
var transform: CGAffineTransform = CGAffineTransform.identity
switch src.imageOrientation {
case UIImageOrientation.down, UIImageOrientation.downMirrored:
transform = transform.translatedBy(x: src.size.width, y: src.size.height)
transform = transform.rotated(by: CGFloat(Double.pi))
break
case UIImageOrientation.left, UIImageOrientation.leftMirrored:
transform = transform.translatedBy(x: src.size.width, y: 0)
transform = transform.rotated(by: CGFloat(Double.pi))
break
case UIImageOrientation.right, UIImageOrientation.rightMirrored:
transform = transform.translatedBy(x: 0, y: src.size.height)
transform = transform.rotated(by: CGFloat(Double.pi))
break
case UIImageOrientation.up, UIImageOrientation.upMirrored:
break
}
switch src.imageOrientation {
case UIImageOrientation.upMirrored, UIImageOrientation.downMirrored:
transform.translatedBy(x: src.size.width, y: 0)
transform.scaledBy(x: -1, y: 1)
break
case UIImageOrientation.leftMirrored, UIImageOrientation.rightMirrored:
transform.translatedBy(x: src.size.height, y: 0)
transform.scaledBy(x: -1, y: 1)
case UIImageOrientation.up, UIImageOrientation.down, UIImageOrientation.left, UIImageOrientation.right:
break
}
let ctx:CGContext = CGContext(data: nil, width: Int(src.size.width), height: Int(src.size.height), bitsPerComponent: (src.cgImage)!.bitsPerComponent, bytesPerRow: 0, space: (src.cgImage)!.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
ctx.concatenate(transform)
switch src.imageOrientation {
case UIImageOrientation.left, UIImageOrientation.leftMirrored, UIImageOrientation.right, UIImageOrientation.rightMirrored:
ctx.draw(src.cgImage!, in: CGRect(x: 0, y: 0, width: src.size.height, height: src.size.width))
break
default:
ctx.draw(src.cgImage!, in: CGRect(x: 0, y: 0, width: src.size.width, height: src.size.height))
break
}
let cgimg:CGImage = ctx.makeImage()!
let img:UIImage = UIImage(cgImage: cgimg)
return img
}
Usage :
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]){
var image = info[UIImagePickerControllerOriginalImage] as! UIImage
image = self.imageOrientation(image)
}

How to get a landscape cropped image without rotation in Swift 3/4

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?

UIImage from camera is rotated 90 degrees - using extensions

When Image data is returned and applied to a UIImage, if the data comes from the camera then the image appears rotated 90 degrees.
I tried adding the
extension UIImage {
func correctlyOrientedImage() -> UIImage {
if self.imageOrientation == UIImageOrientation.up {
return self
}
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
self.draw(in: CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: self.size.width, height: self.size.height))
)
let normalizedImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext();
return normalizedImage;
}
}
in my code, I check to see if I have data saved for a specific user and if so I load the image data into profile_image, a UIImageView.
//profile_image
if let profile_imager = UserDefaults.standard.object(forKey: String(UserID)) as? Data {
let data = UserDefaults.standard.object(forKey: String(UserID)) as? Data
print("profile_imager: \(profile_imager)")
profile_image.image = UIImage(data: data!)
profile_image.backgroundColor = .white
}
How would I go about to use correctlyOrientedImage correctly
Thank you
Swift 4.2,
Created an UIImage extension to keep the image in the original position,
extension UIImage {
func fixedOrientation() -> UIImage {
if imageOrientation == .up {
return self
}
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)
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat.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 cgImage = self.cgImage, let colorSpace = cgImage.colorSpace,
let ctx: CGContext = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: 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 ctxImage: CGImage = ctx.makeImage() {
return UIImage(cgImage: ctxImage)
} else {
return self
}
} else {
return self
}
}
}
Then set profile_image from fixedOrientation() method,
//profile_image
if let profile_imager = UserDefaults.standard.object(forKey: String(UserID)) as? Data {
let data = UserDefaults.standard.object(forKey: String(UserID)) as? Data
print("profile_imager: \(profile_imager)")
let actualImage = UIImage(data: data!)?.fixedOrientation()
profile_image.image = actualImage
profile_image.backgroundColor = .white
}

Losing image orientation while converting an image to CGImage

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
}

Resources