-
-
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) | |
} | |
} |
@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)
}
}
}
Swift 5.0
Includes previous fixes for mirroring issues.