Loss of image orientation when converting an image to CGImage

I am facing the problem of image orientation when cropping a square part of an image from a rectangular source image. When the image is in the landscape, it is beautiful. But when it is in the portrait, it seems that the orientation of the image is not preserved, which leads to an image with the wrong orientation and poor cropping:

func cropImage(cropRectangleCoordinates: CGRect) { let croppedImage = originalImage let finalCroppedImage : CGImageRef = CGImageCreateWithImageInRect(croppedImage.CGImage, cropRectangleCoordinates) finalImage = UIImage(CGImage: finalCroppedImage)! } 

I think the problem is croppedImage.CGImage . Here the image is converted to CGImage , but does not seem to retain orientation. It’s easy to keep the orientation using only UIImage , but to UIImage , the image must be temporarily CGImage , and this is the problem. Even if I reorient the image when converting back to UIImage , it may be in the correct orientation, but the damage is already done when CGImage .

This is a quick question, so please answer quickly, as the solution may differ in Objective-C.

+7
source share
6 answers

I found a solution ... time will tell if it is enough enough, but it seems to work in all situations. It was a vicious mistake to fix.

So the problem is that UIImage, in some cases, only loses orientation when converting to CGImage. This affects the image of portraits that are automatically placed in landscape mode. Thus, the image, which is the default landscape, does not change. But where the error is perverse, it is that it does not affect ALL portrait images !! Also, the image value for the image will not help. These problematic images are images that the user has in his library, which he received from e-mail, messages or saved from the Internet, so they are not removed from the camera. These images may not have orientation information, and therefore, in some cases, the image in the portrait ... remains in the portrait image when it is converted to CGImage. I was really stuck on this until I realized that some of my images in my device library were saved from messages or letters.

Thus, the only reliable way that I decided to guess which image will be reoriented is to create both versions of this image: UIImage and CGImage and compare their height value. If they are equal, the version of CGImage will not be rotated, and you can work with it as expected. But if they differ from each other, you can be sure that converting a CGImage from a CGImageCreateWithImageInRect will landscape the image. In this case, I replace the x / y coordinate of the beginning, which I pass as the coordinate of the cropping rectangle, and correctly processes these special images.

It was a long post, but the main idea is to compare the height of the CGImage with the width of the UIImage, and if they are different, expect the origin to be inverted.

+6
source

SWIFT 3:

convert rotated cgImage to UIImage using this method

 UIImage(cgImage:croppedCGImage, scale:originalImage.scale, orientation:originalImage.imageOrientation) 

Source @ David Berry is responsible

+11
source

Here's the UIImage extension, which I wrote after looking at some old code snippets written by others. It is written in Swift 3 and uses the iOS orientation property plus CGAffineTransform to re-draw the image in the correct 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 } } 
+7
source

Change the call to create UIImage to:

 finalImage = UIImage(CGImage:finalCroppedImage, scale:originalImage.scale, orientation:originalImage.orientation) 

to maintain the original orientation (and scale) of the image.

+3
source

This is the answer, credit @awolf ( Circumcision UIImage ). Handles perfectly scale and orient. Just call this method on the image you want to crop and go to CGRect without worrying about scale or orientation. Feel free to check if cgImage zero instead of the force deploying it like I am 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 built into scrollView , there is one more step, you need to consider the scaling factor. Assuming your imageView spans the entire scrollView content view, and you use the scrollView border as a 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) 
+2
source

@JGuo has the only answer that really worked. I updated only a little to return an additional UIImage? and for the syntax swift-er. I prefer never to divulge.

 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 is 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 } 
0
source

Source: https://habr.com/ru/post/982385/


All Articles