Skip to content

Instantly share code, notes, and snippets.

@schickling
Last active February 4, 2024 15:00
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)
}
}
@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!

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