I am currently rotating images using CGContextDrawImage
, but there is a giant memory spike whenever it is called. Newer devices can handle it, but devices with lower ram such as the iPhone 4s can not. The image is a large file taken by the user at AVCaptureSessionPresetHigh
I'm currently rotating the image using this extension to UIImage
func rotate(orientation: UIImageOrientation) -> UIImage{
if(orientation == UIImageOrientation.Up){return self}
var transform: CGAffineTransform = CGAffineTransformIdentity
if(orientation == UIImageOrientation.Down || orientation == UIImageOrientation.DownMirrored){
transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height)
transform = CGAffineTransformRotate(transform, CGFloat(M_PI))
}
else if(orientation == UIImageOrientation.Left || orientation == UIImageOrientation.LeftMirrored){
transform = CGAffineTransformTranslate(transform, self.size.width, 0)
transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2))
}
else if(orientation == UIImageOrientation.Right || orientation == UIImageOrientation.RightMirrored){
transform = CGAffineTransformTranslate(transform, 0, self.size.height)
transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2))
}
if(orientation == UIImageOrientation.UpMirrored || orientation == UIImageOrientation.DownMirrored){
transform = CGAffineTransformTranslate(transform, self.size.width, 0)
transform = CGAffineTransformScale(transform, -1, 1)
}
else if(orientation == UIImageOrientation.LeftMirrored || orientation == UIImageOrientation.RightMirrored){
transform = CGAffineTransformTranslate(transform, self.size.height, 0)
transform = CGAffineTransformScale(transform, -1, 1);
}
let ref: CGContextRef = CGBitmapContextCreate(nil, Int(self.size.width), Int(self.size.height), CGImageGetBitsPerComponent(self.CGImage), 0, CGImageGetColorSpace(self.CGImage), CGImageGetBitmapInfo(self.CGImage))
CGContextConcatCTM(ref, transform)
if(orientation == UIImageOrientation.Left || orientation == UIImageOrientation.LeftMirrored || orientation == UIImageOrientation.Right || orientation == UIImageOrientation.RightMirrored){
CGContextDrawImage(ref, CGRectMake(0, 0, self.size.height, self.size.width), self.CGImage)
}
else{
CGContextDrawImage(ref, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage)
}
let cgImg: CGImageRef = CGBitmapContextCreateImage(ref)
let rotated: UIImage = UIImage(CGImage: cgImg)!
return rotated
}
This code works perfectly on devices such as the iPhone 5 and 6, but almost always causes the iPhone 4s to crash due to low memory.
I am aware that I could just upload the image with EXIF data specifying the orientation, but I feel like just rotating the image before uploading it would prevent further problems down the line.
How does iOS rotate the image for display using the EXIF data? For example, although using
UIImage(image.CGImage, scale: 1.0, orientation: UIImageOrientation.Right)
changes the EXIF data, the image is still displayed in the correct orientation on the screen, with a lot less of a memory impact. Is there some really low-level code that can be used to achieve this by using the GPU like the above code does?
I would also like to avoid scaling the image down before uploading it.
Is there any way that I can reduce the impact of rotating the image, such as rotating it in pieces, or will I have to find an alternative?
I was able to (mostly) solve this by calling UIGraphicsEndImageContext()
at the end of the above code, as well as not rotating the image until just before it is uploaded (users can rotate their photos before uploading them, so not rotating them until just before upload greatly decreases the memory usage).
So, whenever the user rotates a photo, I call this rotateExif(orientation)
function that I created
extension UIImage{
func rotateExif(orientation: UIImageOrientation) -> UIImage{
if(orientation == UIImageOrientation.Up){return self}
let current = self.imageOrientation
let currentDegrees: Int = (
current == UIImageOrientation.Down || current == UIImageOrientation.DownMirrored ? 180 : (
current == UIImageOrientation.Left || current == UIImageOrientation.LeftMirrored ? 270 : (
current == UIImageOrientation.Right || current == UIImageOrientation.RightMirrored ? 90 : 0
)
)
)
let changeDegrees: Int = (
orientation == UIImageOrientation.Down || orientation == UIImageOrientation.DownMirrored ? 180 : (
orientation == UIImageOrientation.Left || orientation == UIImageOrientation.LeftMirrored ? 270 : (
orientation == UIImageOrientation.Right || orientation == UIImageOrientation.RightMirrored ? 90 : 0
)
)
)
let mirrored: Bool = (
current == UIImageOrientation.DownMirrored || current == UIImageOrientation.UpMirrored ||
current == UIImageOrientation.LeftMirrored || current == UIImageOrientation.RightMirrored ||
orientation == UIImageOrientation.DownMirrored || orientation == UIImageOrientation.UpMirrored ||
orientation == UIImageOrientation.LeftMirrored || orientation == UIImageOrientation.RightMirrored
)
let degrees: Int = currentDegrees + changeDegrees
let newOrientation: UIImageOrientation = (
degrees == 270 || degrees == 630 ? (mirrored ? UIImageOrientation.LeftMirrored : UIImageOrientation.Left) : (
degrees == 180 || degrees == 540 ? (mirrored ? UIImageOrientation.DownMirrored : UIImageOrientation.Down) : (
degrees == 90 || degrees == 450 ? (mirrored ? UIImageOrientation.RightMirrored : UIImageOrientation.Right) : (
mirrored ? UIImageOrientation.UpMirrored : UIImageOrientation.Up
)
)
)
)
return UIImage(CGImage: self.CGImage!, scale: 1.0, orientation: newOrientation)
}
}
This rotates the image's EXIF by the amount that is inputted in orientation
. So, for example, if the original orientation was Right
, and it was rotated Right
, the new orientation would be Down
. If the original was RightMirrored
, and it was rotated Left
, the new orientation would be UpMirrored
.
Then, before uploading the photo, the actual pixels are rotated by calling the code in the question as an extension on UIImage
, image.rotate(image.imageOrientation)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With