Skip to content

Instantly share code, notes, and snippets.

@schickling
Last active September 9, 2024 13:54
Show Gist options
  • Save schickling/b5d86cb070130f80bb40 to your computer and use it in GitHub Desktop.
Save schickling/b5d86cb070130f80bb40 to your computer and use it in GitHub Desktop.
Extension to fix orientation of an UIImage (Sets orientation to portrait)
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)
}
}
@chika-kasymov
Copy link

@estebanefi you should use a different scale in the end:

return UIImage.init(cgImage: newCGImage, scale: scale, orientation: .up) // here use original images scale instead of 1

@iosdev-republicofapps
Copy link

iosdev-republicofapps commented Jun 23, 2018

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];
}

@totoro0103
Copy link

Thank @haikieu . You save my life

@LightMan
Copy link

LightMan commented Dec 11, 2018

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
        }

@jeffmcfadden
Copy link

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)
    }
}

@kirkbyo
Copy link

kirkbyo commented Feb 3, 2019

@jeffmcfadden Thank you!

@fractaldragonflies
Copy link

fractaldragonflies commented Apr 7, 2019

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)
}

`

@Sam-Spencer
Copy link

Sam-Spencer commented Apr 20, 2019

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)
    }
}

@w-i-n-s
Copy link

w-i-n-s commented Apr 28, 2019

@Sam-Spencer Thanks a lot for extension. Could you please fix unneeded_break_in_switch and force_unwrapping. Thanks

@Sam-Spencer
Copy link

@w-i-n-s Should be all good to go now!

@w-i-n-s
Copy link

w-i-n-s commented Apr 30, 2019

@TaoufikBouabid
Copy link

To Flip UIImage, the simplest way is :

let flippedImage = originalImage.withHorizontallyFlippedOrientation()

tested and worked fine!

@chrisvoronin
Copy link

works well, but uses a lot of memory creating a spike.

@jlorfeo
Copy link

jlorfeo commented Jan 28, 2020

Awesome! Thanks alot! This helps quite a bit :)

@peterdk
Copy link

peterdk commented Feb 13, 2020

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.

@dhoerl
Copy link

dhoerl commented Apr 18, 2020

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
}

@rubencodes
Copy link

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.

@dhoerl
Copy link

dhoerl commented Apr 26, 2020

@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.

@ChrisXu
Copy link

ChrisXu commented Oct 23, 2020

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
    }
}

@SlavcoPetkovski
Copy link

What about when the image is taken with frontal camera (selfie)? This does not work.

@githubdelegate
Copy link

Great!

@AD-Paladins
Copy link

Nice!
Thanks all

@raygun101
Copy link

raygun101 commented Aug 12, 2021

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()
        }
    }
}

@JanzTam
Copy link

JanzTam commented Aug 12, 2021

Its saved my time! Thanks all !!!

@logesh45
Copy link

Amazing. Great work buds.

@backslash-f
Copy link

Could someone explain me why we need transform.translatedBy?

Wouldn't only .rotated(by... and .scaledBy(... (for flipped images) do it? 🤔

@juliand665
Copy link

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.

@backslash-f
Copy link

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!

@0xfeedface1993
Copy link

0xfeedface1993 commented Sep 9, 2024

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()
        }
    }
}

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)
            }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment