-
-
Save schickling/b5d86cb070130f80bb40 to your computer and use it in GitHub Desktop.
extension UIImage { | |
func fixedOrientation() -> UIImage { | |
if imageOrientation == UIImageOrientation.Up { | |
return self | |
} | |
var transform: CGAffineTransform = CGAffineTransformIdentity | |
switch imageOrientation { | |
case UIImageOrientation.Down, UIImageOrientation.DownMirrored: | |
transform = CGAffineTransformTranslate(transform, size.width, size.height) | |
transform = CGAffineTransformRotate(transform, CGFloat(M_PI)) | |
break | |
case UIImageOrientation.Left, UIImageOrientation.LeftMirrored: | |
transform = CGAffineTransformTranslate(transform, size.width, 0) | |
transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2)) | |
break | |
case UIImageOrientation.Right, UIImageOrientation.RightMirrored: | |
transform = CGAffineTransformTranslate(transform, 0, size.height) | |
transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2)) | |
break | |
case UIImageOrientation.Up, UIImageOrientation.UpMirrored: | |
break | |
} | |
switch imageOrientation { | |
case UIImageOrientation.UpMirrored, UIImageOrientation.DownMirrored: | |
CGAffineTransformTranslate(transform, size.width, 0) | |
CGAffineTransformScale(transform, -1, 1) | |
break | |
case UIImageOrientation.LeftMirrored, UIImageOrientation.RightMirrored: | |
CGAffineTransformTranslate(transform, size.height, 0) | |
CGAffineTransformScale(transform, -1, 1) | |
case UIImageOrientation.Up, UIImageOrientation.Down, UIImageOrientation.Left, UIImageOrientation.Right: | |
break | |
} | |
let ctx: CGContextRef = CGBitmapContextCreate(nil, Int(size.width), Int(size.height), CGImageGetBitsPerComponent(CGImage), 0, CGImageGetColorSpace(CGImage), CGImageAlphaInfo.PremultipliedLast.rawValue)! | |
CGContextConcatCTM(ctx, transform) | |
switch imageOrientation { | |
case UIImageOrientation.Left, UIImageOrientation.LeftMirrored, UIImageOrientation.Right, UIImageOrientation.RightMirrored: | |
CGContextDrawImage(ctx, CGRectMake(0, 0, size.height, size.width), CGImage) | |
break | |
default: | |
CGContextDrawImage(ctx, CGRectMake(0, 0, size.width, size.height), CGImage) | |
break | |
} | |
let cgImage: CGImageRef = CGBitmapContextCreateImage(ctx)! | |
return UIImage(CGImage: cgImage) | |
} | |
} |
Hey @akaashdev-sc thanks for the Objective-C version. I think you have a couple of small bugs in your version - you're forgetting to assign to the transform in the second switch since the ObjC version doesn't mutate the original.
Here's the corrected version for anybody that comes along later (and still gloriously uses Objective-C like me :p):
-(UIImage *) fixedOrientation:(UIImage *) image {
if (image.imageOrientation == UIImageOrientationUp) {
return image;
}
CGAffineTransform transform = CGAffineTransformIdentity;
switch (image.imageOrientation) {
case UIImageOrientationDown:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, image.size.width, image.size.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
transform = CGAffineTransformTranslate(transform, image.size.width, 0);
transform = CGAffineTransformRotate(transform, M_PI_2);
break;
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, 0, image.size.height);
transform = CGAffineTransformRotate(transform, -M_PI_2);
break;
default: break;
}
switch (image.imageOrientation) {
case UIImageOrientationUpMirrored:
case UIImageOrientationDownMirrored:
// CORRECTION: Need to assign to transform here
transform = CGAffineTransformTranslate(transform, image.size.width, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
case UIImageOrientationLeftMirrored:
case UIImageOrientationRightMirrored:
// CORRECTION: Need to assign to transform here
transform = CGAffineTransformTranslate(transform, image.size.height, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
default: break;
}
CGContextRef ctx = CGBitmapContextCreate(nil, image.size.width, image.size.height, CGImageGetBitsPerComponent(image.CGImage), 0, CGImageGetColorSpace(image.CGImage), kCGImageAlphaPremultipliedLast);
CGContextConcatCTM(ctx, transform);
switch (image.imageOrientation) {
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
CGContextDrawImage(ctx, CGRectMake(0, 0, image.size.height, image.size.width), image.CGImage);
break;
default:
CGContextDrawImage(ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
break;
}
CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
return [UIImage imageWithCGImage:cgImage];
}
Thank @haikieu . You save my life
For all the people that say @haikieu code or any other variant is not working, it is not flipped horizontally correctly. Take into account @vinamelody comment:
The second part of fixing the horizontal flip, the transform is not applied because the var is not reassigned to itself. The code should be like @vinamelody says:
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)
default:
break
}
Swift 4.2, with Mirroring case fix:
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)
break
case .left, .leftMirrored:
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.rotated(by: CGFloat.pi / 2.0)
break
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.height)
transform = transform.rotated(by: CGFloat.pi / -2.0)
break
case .up, .upMirrored:
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)
break
case .leftMirrored, .rightMirrored:
transform = transform.translatedBy(x: size.height, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
case .up, .down, .left, .right:
break
}
ctx.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
default:
ctx.draw(self.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)
}
}
@jeffmcfadden Thank you!
I really liked @jeffmcfadden code but I'm working in macOS. I converted the code to a CGImage extension, but I still had problems with rectangular (non-square) images. After several interesting changes and some personal learnings along the way I got something that works for my case. Here is my take on the problem as playground code with CGImage extension.
Observations:
- Created graphics context with newSize of the oriented image.
- .left, .leftMirrored uses x:size.height
- .right, .rightMirrored uses y: size.width
- .mirrored cases use x=size.width (since scale is always x: -1, y:1)
- ctx.draw uses original width: size.width, height: size:height.
This last of draw using the original size surprised me. So it seems draw uses the size it wants to draw into as though from the original image. It works in my tests in playground using rectangular images.
`// Test with square, wider then tall, taller than wide images.
enum CGImageOrientation {
case up
case down
case left
case right
case upMirrored
case downMirrored
case leftMirrored
case rightMirrored
}
extension CGImage {
func orientImage(_ imageOrientation: CGImageOrientation) -> CGImage? {
return orientImageWithTransform(imageOrientation).0
}
// Method to get image and transform in tuple.
func orientImageWithTransform(_ imageOrientation: CGImageOrientation) -> (CGImage?, CGAffineTransform) {
var transform = CGAffineTransform.identity
if imageOrientation == .up { return (self.copy(), transform)}
let size = NSSize(width: width, height: height)
let newSize = [.left,.leftMirrored, .right, .rightMirrored].contains(imageOrientation)
? NSSize(width: size.height, height: size.width) : size
// Guard that we have color space and core graphics context.
guard let colorSpace = self.colorSpace,
// New graphic context uses transformed width and height.
let ctx = CGContext(data: nil, width: Int(newSize.width), height: Int(newSize.height),
bitsPerComponent: self.bitsPerComponent, bytesPerRow: 0,
space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
else { return (nil, transform)}
// OK, now the actual work of constructing transform and creating new image.
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: size.width, y: size.height)
transform = transform.rotated(by: CGFloat.pi)
break
case .left,.leftMirrored:
transform = transform.translatedBy(x: size.height, y: 0)
transform = transform.rotated(by: CGFloat.pi/2)
break
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: size.width)
transform = transform.rotated(by: -CGFloat.pi/2)
break
case .up, .upMirrored:
break
}
if [.upMirrored, .downMirrored,.leftMirrored, .rightMirrored].contains(imageOrientation) {
transform = transform.translatedBy(x: size.width, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
}
ctx.concatenate(transform)
// Interestingly, drawing with the original width and height?!
// So width and height here are pre-transform.
ctx.draw(self, in: NSRect(x: 0, y: 0, width: size.width, height: size.height))
return (ctx.makeImage(), transform)
}
}
guard var image = NSImage(contentsOf: imageurl) else { exit(1) }
guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { exit(2)}
if let orientedImage = cgImage.orientImage(.upMirrored) {
image = NSImage(cgImage: orientedImage, size: NSZeroSize)
}
`
Swift 5.0
Includes previous fixes for mirroring issues.
extension UIImage {
/// Fix image orientaton to protrait up
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)
}
}
@Sam-Spencer Thanks a lot for extension. Could you please fix unneeded_break_in_switch and force_unwrapping. Thanks
@w-i-n-s Should be all good to go now!
@Sam-Spencer yep
To Flip UIImage, the simplest way is :
let flippedImage = originalImage.withHorizontallyFlippedOrientation()
tested and worked fine!
works well, but uses a lot of memory creating a spike.
Awesome! Thanks alot! This helps quite a bit :)
Seems to create massive memoryleak. I run it in a loop based on a CMSampleBuffer, and when I add the .fixedOrientation() mem usage keeps growing with 8MB per call. When removing that call, mem usage stays stable.
Update
Turns out my mem leak was caused by running this code on a background thread. It turns out the CGContext doesnt release it's buffer memory when not on Main thread. It can be fixed easily by adding return autoreleasepool {
at the start and }
at the end. Now the buffer gets released also when in a background thread.
I had a similar issue, but wanted to load an image from the file system (Data) and not have to go through an intermediate UIImage, as doing this translation to .up often causes a memory spike. I based this on Sam Spencer's code, and it works fine in Xcode 11.4 / Swift 5.2:
func image(data: Data, orientation: UIImage.Orientation = .up) -> UIImage? {
let context: CGContext
let width: CGFloat
let height: CGFloat
func defaultImage() -> UIImage? {
return UIImage(data: data)
}
do {
guard
let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as NSDictionary?,
let orientation = CGImagePropertyOrientation(rawValue: properties[kCGImagePropertyOrientation] as? UInt32 ?? 1),
let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
else { return defaultImage() }
guard orientation != .up else { return UIImage(cgImage: image) }
let imageOrientation = UIImage.Orientation(orientation)
let bytesPerRow: Int
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
width = CGFloat(image.height)
height = CGFloat(image.width)
bytesPerRow = ((Int(width)+15)/16) * 16 * (image.bitsPerPixel/8)
default:
width = CGFloat(image.width)
height = CGFloat(image.height)
bytesPerRow = image.bytesPerRow
}
guard let _context = CGContext(data: nil,
width: Int(width),
height: Int(height),
bitsPerComponent: image.bitsPerComponent,
bytesPerRow: bytesPerRow,
space: image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!,
bitmapInfo: image.bitmapInfo.rawValue)
else { return defaultImage() }
context = _context
let drawRect: CGRect
var transform: CGAffineTransform = CGAffineTransform.identity
switch imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: width, y: height)
transform = transform.rotated(by: CGFloat.pi)
case .left, .leftMirrored:
transform = transform.translatedBy(x: width, y: 0)
transform = transform.rotated(by: CGFloat.pi / 2.0)
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: 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: width, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
case .leftMirrored, .rightMirrored:
transform = transform.translatedBy(x: height, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
case .up, .down, .left, .right:
break
@unknown default:
break
}
context.concatenate(transform)
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
drawRect = CGRect(x: 0, y: 0, width: height, height: width)
default:
drawRect = CGRect(x: 0, y: 0, width: width, height: height)
}
context.draw(image, in: drawRect)
// image released
}
guard let newImage = context.makeImage() else { return defaultImage() }
let uiImage = UIImage(cgImage: newImage, scale: 1, orientation: .up)
return uiImage
}
Gosh, this works amazingly, but seems to cause a memory crash for me half the time. Just calling it once. Even after adding return autoreleasepool {}
around it as suggested by @peterdk.
@rubencodes
I also saw memory spikes. I first tried just keeping around a large enough memory blog to use with CGContext so it wouldn't have to keep creating one. That helped the smiles but the floor of memory usage rose (of course). Then I tripped on this CIImage filter (new in iOS11):
//image is a UIImage
let newImage = origImage.oriented(CGImagePropertyOrientation(image.imageOrientation))
guard let cgImage = context.createCGImage(newImage, from: newImage.extent) else { fatalError() }
let showImage = UIImage(cgImage: cgImage)
It creates much smaller memory spikes.
Swift 5
This version resolves memory spike as suggested by @peterdk.
extension UIImage {
static func fixedOrientation(for image: UIImage) -> UIImage? {
guard image.imageOrientation != .up else {
return image
}
let size = image.size
let imageOrientation = image.imageOrientation
var transform: CGAffineTransform = .identity
switch image.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
}
guard var cgImage = image.cgImage else {
return nil
}
autoreleasepool {
var context: CGContext?
guard let colorSpace = cgImage.colorSpace, let _context = CGContext(data: nil, width: Int(image.size.width), height: Int(image.size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
return
}
context = _context
context?.concatenate(transform)
var drawRect: CGRect = .zero
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
drawRect.size = CGSize(width: size.height, height: size.width)
default:
drawRect.size = CGSize(width: size.width, height: size.height)
}
context?.draw(cgImage, in: drawRect)
guard let newCGImage = context?.makeImage() else {
return
}
cgImage = newCGImage
}
let uiImage = UIImage(cgImage: cgImage, scale: 1, orientation: .up)
return uiImage
}
}
What about when the image is taken with frontal camera (selfie)? This does not work.
Great!
Nice!
Thanks all
Hi, here's my solution. It relies on iOS's UIImage
handing of the orientation.
So an "xyz" orientated image is rendered (using it's orientation info) onto an up
orientated canvas.
self.draw
of UIImage
, not CGImage
.
extension UIImage
{
/// `Re-orientate` the image to `up`.
///
func normalizedImage() -> UIImage?
{
if self.imageOrientation == .up
{
return self
}
else
{
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
defer
{
UIGraphicsEndImageContext()
}
self.draw(in: CGRect(origin: .zero, size: self.size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
}
Its saved my time! Thanks all !!!
Amazing. Great work buds.
Could someone explain me why we need transform.translatedBy
?
Wouldn't only .rotated(by...
and .scaledBy(...
(for flipped images) do it? 🤔
I believe those transform around the origin (0, 0) and would thus flip/rotate your image to be outside the box. if you try it with an angle of around 45° you’ll see how it would break.
I believe those transform around the origin (0, 0) and would thus flip/rotate your image to be outside the box. if you try it with an angle of around 45° you’ll see how it would break.
Thanks!
Hi, here's my solution. It relies on iOS's
UIImage
handing of the orientation. So an "xyz" orientated image is rendered (using it's orientation info) onto anup
orientated canvas.
⚠️ self.draw
ofUIImage
, notCGImage
.extension UIImage { /// `Re-orientate` the image to `up`. /// func normalizedImage() -> UIImage? { if self.imageOrientation == .up { return self } else { UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) defer { UIGraphicsEndImageContext() } self.draw(in: CGRect(origin: .zero, size: self.size)) return UIGraphicsGetImageFromCurrentImageContext() } } }
UIGraphicsBeginImageContextWithOptions
was deprecated, use UIGraphicsImageRenderer
instead.
extension UIImage {
func normalizedImage() -> UIImage {
guard imageOrientation != .up else {
return self
}
return UIGraphicsImageRenderer(size: size, format: .preferred())
.image {
draw(in: $0.format.bounds)
}
}
}
@estebanefi you should use a different scale in the end: