15

I have a UIImage that I'm getting from a UIImagePickerController. When I receive the image from the - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info method I put it straight into a UIImageView for preview and give the user the option to either save or discard the image.

The preview screen

When the user selects the save option the application get the png data for the image and saves it to it's documents directory. But what happens under one of the 2 possible device orientations (landscape only) is that the image is saved upside down (more specifically, rotated 180 degrees from what it should be). So when I load the image in the gallery it appears upside down.

(See the bottom left image in the gallery)

See the bottom left image

I have worked this out to be that the raw image data in the UIImage from the UIImagePickerController is not rotated, rather, the orientation is stored as a property on the object and only applied when displaying. So I'm trying to rotate the CGImage associated with the UIImage using some code I found online. But it appears to have absolutely no affect on the image. The code I'm using follows:

- (void)rotateImage:(UIImage*)image byRadians:(CGFloat)rads
{   
    // calculate the size of the rotated view's containing box for our drawing space 
    UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,image.size.width, image.size.height)];
    CGAffineTransform t = CGAffineTransformMakeRotation(rads);
    rotatedViewBox.transform = t;
    CGSize rotatedSize = rotatedViewBox.frame.size;

    // Create the bitmap context
    UIGraphicsBeginImageContext(rotatedSize);
    CGContextRef bitmap = UIGraphicsGetCurrentContext();

    // Move the origin to the middle of the image you want to rotate and scale around the center. 
    CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2); 

    // Rotate the image context
    CGContextRotateCTM(bitmap, rads);

    // Now, draw the rotated/scaled image into the context
    CGContextScaleCTM(bitmap, 1.0, -1.0);
    CGContextDrawImage(bitmap, CGRectMake(image.size.width / 2, image.size.height / 2, image.size.width, image.size.height), [image CGImage]);

    image = UIGraphicsGetImageFromCurrentImageContext();
    image = [UIImage imageWithCGImage:image.CGImage scale:1.0 orientation:UIImageOrientationDown];
    UIGraphicsEndImageContext();
}

I would like to know why this code isn't working, since every piece of code I find online to do this image rotation appears to not work.

null0pointer
  • 1,533
  • 3
  • 15
  • 29

6 Answers6

26
-(UIImage*) rotate:(UIImage*) src andOrientation:(UIImageOrientation)orientation
{
    UIGraphicsBeginImageContext(src.size);

    CGContextRef context=(UIGraphicsGetCurrentContext());

    if (orientation == UIImageOrientationRight) {
        CGContextRotateCTM (context, 90/180*M_PI) ;
    } else if (orientation == UIImageOrientationLeft) {
        CGContextRotateCTM (context, -90/180*M_PI);
    } else if (orientation == UIImageOrientationDown) {
        // NOTHING
    } else if (orientation == UIImageOrientationUp) {
        CGContextRotateCTM (context, 90/180*M_PI);
    }

    [src drawAtPoint:CGPointMake(0, 0)];
    UIImage *img=UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;

}

use this one .. it works perfect ! Just make sure you use this function when you are picking images from UIImagePicker:

example:

UIImage *image;
image = [self rotate:image andOrientation:image.imageOrientation];

Make sure to use this function in place where you pick photos from documents directory.

Dream.In.Code
  • 399
  • 3
  • 5
  • nice answer... the only thing I would change is that "`return img;`" gets called before "`[img release];`" (which means the release call never actually hits). Also, this assumes the person is not using ARC. – Michael Dautermann May 11 '12 at 22:54
  • Does one know why rotation is necessary? – testing Dec 19 '18 at 16:41
  • @testing https://developer.apple.com/documentation/uikit/uiimage/orientation – leo Jul 10 '23 at 15:40
10

Rotate and Mirror UIImage CGImage Backing Data - Swift

I recently wanted to correct a UIImage backed by a CGImage to match the desired orientation rather than rely on APIs to respect the UIImageOrientation parameter.

func createMatchingBackingDataWithImage(imageRef: CGImage?, orienation: UIImageOrientation) -> CGImage?
{
    var orientedImage: CGImage?
    
    if let imageRef = imageRef {
        let originalWidth = imageRef.width
        let originalHeight = imageRef.height
        let bitsPerComponent = imageRef.bitsPerComponent
        
        let bitmapInfo = imageRef.bitmapInfo
        
        guard let colorSpace = imageRef.colorSpace else {
            return nil
        }
        
        var degreesToRotate: Double
        var swapWidthHeight: Bool
        var mirrored: Bool
        switch orienation {
        case .up:
            degreesToRotate = 0.0
            swapWidthHeight = false
            mirrored = false
            break
        case .upMirrored:
            degreesToRotate = 0.0
            swapWidthHeight = false
            mirrored = true
            break
        case .right:
            degreesToRotate = 90.0
            swapWidthHeight = true
            mirrored = false
            break
        case .rightMirrored:
            degreesToRotate = 90.0
            swapWidthHeight = true
            mirrored = true
            break
        case .down:
            degreesToRotate = 180.0
            swapWidthHeight = false
            mirrored = false
            break
        case .downMirrored:
            degreesToRotate = 180.0
            swapWidthHeight = false
            mirrored = true
            break
        case .left:
            degreesToRotate = -90.0
            swapWidthHeight = true
            mirrored = false
            break
        case .leftMirrored:
            degreesToRotate = -90.0
            swapWidthHeight = true
            mirrored = true
            break
        }
        let radians = degreesToRotate * Double.pi / 180.0
        
        var width: Int
        var height: Int
        if swapWidthHeight {
            width = originalHeight
            height = originalWidth
        } else {
            width = originalWidth
            height = originalHeight
        }
        
        let contextRef = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
        contextRef?.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0)
        if mirrored {
            contextRef?.scaleBy(x: -1.0, y: 1.0)
        }
        contextRef?.rotate(by: CGFloat(radians))
        if swapWidthHeight {
            contextRef?.translateBy(x: -CGFloat(height) / 2.0, y: -CGFloat(width) / 2.0)
        } else {
            contextRef?.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0)
        }
        contextRef?.draw(imageRef, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(originalWidth), height: CGFloat(originalHeight)))
        orientedImage = contextRef?.makeImage()
    }
    
    return orientedImage
}
Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126
  • " you're rotating the context (and its coordinate system) then drawing the image into it." For that reason, they could have called a context an easel, right? Wonder why they didn't. It's easier to understand what an "easel" is (after all, most people have used one) than to understand what a "context" is. –  May 29 '15 at 01:07
  • What is the difference between your solution and the one from [Dream.In.Code](https://stackoverflow.com/a/10548409/426227)? You are taking more cases into account (e.g. mirrored images). But what else? Some angles seem to be different and also the used methods (`CGContextRotateCTM ` vs. `rotate`). Also you are doing a `translateBy`, which the other solution doesn't take into account ... – testing Dec 19 '18 at 14:34
  • Also what is the difference to this [Gist](https://gist.github.com/schickling/b5d86cb070130f80bb40)? – testing Dec 19 '18 at 17:23
  • The difference is... relying on UIKit to be there. This is exclusively using CoreGraphics which is available on macOS and iOS. – Cameron Lowell Palmer Jan 01 '23 at 22:20
5

The Swift 3 version for the Cameron Lowell Palmer answer:

func createMatchingBackingDataWithImage(imageRef: CGImage?, orienation: UIImageOrientation) -> CGImage? {
    var orientedImage: CGImage?

    if let imageRef = imageRef {
        let originalWidth = imageRef.width
        let originalHeight = imageRef.height
        let bitsPerComponent = imageRef.bitsPerComponent
        let bytesPerRow = imageRef.bytesPerRow

        let colorSpace = imageRef.colorSpace
        let bitmapInfo = imageRef.bitmapInfo

        var degreesToRotate: Double
        var swapWidthHeight: Bool
        var mirrored: Bool
        switch orienation {
        case .up:
            degreesToRotate = 0.0
            swapWidthHeight = false
            mirrored = false
            break
        case .upMirrored:
            degreesToRotate = 0.0
            swapWidthHeight = false
            mirrored = true
            break
        case .right:
            degreesToRotate = 90.0
            swapWidthHeight = true
            mirrored = false
            break
        case .rightMirrored:
            degreesToRotate = 90.0
            swapWidthHeight = true
            mirrored = true
            break
        case .down:
            degreesToRotate = 180.0
            swapWidthHeight = false
            mirrored = false
            break
        case .downMirrored:
            degreesToRotate = 180.0
            swapWidthHeight = false
            mirrored = true
            break
        case .left:
            degreesToRotate = -90.0
            swapWidthHeight = true
            mirrored = false
            break
        case .leftMirrored:
            degreesToRotate = -90.0
            swapWidthHeight = true
            mirrored = true
            break
        }
        let radians = degreesToRotate * Double.pi / 180

        var width: Int
        var height: Int
        if swapWidthHeight {
            width = originalHeight
            height = originalWidth
        } else {
            width = originalWidth
            height = originalHeight
        }

        if let contextRef = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace!, bitmapInfo: bitmapInfo.rawValue) {

            contextRef.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0)
            if mirrored {
                contextRef.scaleBy(x: -1.0, y: 1.0)
            }
            contextRef.rotate(by: CGFloat(radians))
            if swapWidthHeight {
                contextRef.translateBy(x: -CGFloat(height) / 2.0, y: -CGFloat(width) / 2.0)
            } else {
                contextRef.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0)
            }
            contextRef.draw(imageRef, in: CGRect(x: 0, y: 0, width: originalWidth, height: originalHeight))

            orientedImage = contextRef.makeImage()
        }
    }

    return orientedImage
}

Usage:

let newCgImage = createMatchingBackingDataWithImage(imageRef: cgImageRef, orienation: .left)
Community
  • 1
  • 1
Enrique
  • 1,586
  • 17
  • 14
4

The problem with all these answers is that they have UIKit dependencies. I'm working on a SwiftUI Universal app targeting both iOS and macOS. The Cameron Lowell Palmer answer works with a few modifications :

  1. The bytesPerRow calculation needs to be done after the swap of height and width, or the CGImageContext whines about incorrect bytesPerRow

  2. change the UIImageOrientation to CGImagePropertyOrientation, and swap the signs for left & right rotations

Here is my version, suitable for both iOS and macOS:

extension CGImage {
func rotating(to orientation: CGImagePropertyOrientation) -> CGImage? {
    var orientedImage: CGImage?

    let originalWidth = self.width
    let originalHeight = self.height
    let bitsPerComponent = self.bitsPerComponent
    let bitmapInfo = self.bitmapInfo

    guard let colorSpace = self.colorSpace else {
        return nil
    }

    var degreesToRotate: Double
    var swapWidthHeight: Bool
    var mirrored: Bool

    switch orientation {
    case .up:
        degreesToRotate = 0.0
        swapWidthHeight = false
        mirrored = false
        break
    case .upMirrored:
        degreesToRotate = 0.0
        swapWidthHeight = false
        mirrored = true
        break
    case .right:
        degreesToRotate = -90.0
        swapWidthHeight = true
        mirrored = false
        break
    case .rightMirrored:
        degreesToRotate = -90.0
        swapWidthHeight = true
        mirrored = true
        break
    case .down:
        degreesToRotate = 180.0
        swapWidthHeight = false
        mirrored = false
        break
    case .downMirrored:
        degreesToRotate = 180.0
        swapWidthHeight = false
        mirrored = true
        break
    case .left:
        degreesToRotate = 90.0
        swapWidthHeight = true
        mirrored = false
        break
    case .leftMirrored:
        degreesToRotate = 90.0
        swapWidthHeight = true
        mirrored = true
        break
    }

    let radians = degreesToRotate * Double.pi / 180.0

    var width: Int
    var height: Int

    if swapWidthHeight {
        width = originalHeight
        height = originalWidth
    } else {
        width = originalWidth
        height = originalHeight
    }

    let bytesPerRow = (width * bitsPerPixel) / 8

    let contextRef = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)

    contextRef?.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0)

    if mirrored {
        contextRef?.scaleBy(x: -1.0, y: 1.0)
    }

    contextRef?.rotate(by: CGFloat(radians))

    if swapWidthHeight {
        contextRef?.translateBy(x: -CGFloat(height) / 2.0, y: -CGFloat(width) / 2.0)
    } else {
        contextRef?.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0)
    }

    contextRef?.draw(self, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(originalWidth), height: CGFloat(originalHeight)))

    orientedImage = contextRef?.makeImage()

    return orientedImage
}

}

dang
  • 587
  • 1
  • 5
  • 10
3

I got your problem.You have to translate back the context using CGContextTranslateCTM(context, -rotatedSize.width/2, -rotatedSize.height/2); as well as set origin of rect in drawing to rotatedViewBox.frame.origin.x,rotatedViewBox.frame.origin.y. use this code.

UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,image.size.width, image.size.height)];
CGAffineTransform t = CGAffineTransformMakeRotation(0.3);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;



UIGraphicsBeginImageContext(rotatedSize);

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, rotatedSize.width/2, rotatedSize.height/2);
CGContextRotateCTM (context, 0.3);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, -rotatedSize.width/2, -rotatedSize.height/2);




CGContextDrawImage(context, CGRectMake(-rotatedViewBox.frame.origin.x, -rotatedViewBox.frame.origin.y, image.size.width, image.size.height), [image CGImage]);


UIImage *nn = UIGraphicsGetImageFromCurrentImageContext();

NSLog(@"size is %f, %f",nn.size.width,nn.size.height);
UIGraphicsEndImageContext();

UIImageWriteToSavedPhotosAlbum(nn, nil,nil,nil);
j0k
  • 22,600
  • 28
  • 79
  • 90
Tushar Jain
  • 141
  • 1
  • 10
2

This is just refactoring of previous answer.

func correctImageOrientation(cgImage: CGImage?, orienation: UIImage.Orientation) -> CGImage? {
    guard let cgImage = cgImage else { return nil }
    var orientedImage: CGImage?

    let originalWidth = cgImage.width
    let originalHeight = cgImage.height
    let bitsPerComponent = cgImage.bitsPerComponent
    let bytesPerRow = cgImage.bytesPerRow
    let bitmapInfo = cgImage.bitmapInfo

    guard let colorSpace = cgImage.colorSpace else { return nil }

    let degreesToRotate = orienation.getDegree()
    let mirrored = orienation.isMirror()

    var width = originalWidth
    var height = originalHeight

    let radians = degreesToRotate * Double.pi / 180.0
    let swapWidthHeight = Int(degreesToRotate / 90) % 2 != 0

    if swapWidthHeight {
        swap(&width, &height)
    }

    let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)

    context?.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0)
    if mirrored {
        context?.scaleBy(x: -1.0, y: 1.0)
    }
    context?.rotate(by: CGFloat(radians))
    if swapWidthHeight {
        swap(&width, &height)
    }
    context?.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0)

    context?.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(originalWidth), height: CGFloat(originalHeight)))
    orientedImage = context?.makeImage()

    return orientedImage
}

extension UIImage.Orientation {
    func getDegree() -> Double {
        switch self {
        case .up, .upMirrored:
            return 0.0
        case .right, .rightMirrored:
            return 90.0
        case .down, .downMirrored:
            return 180.0
        case .left, .leftMirrored:
            return -90.0
        default:
            return 0
        }
    }

    func isMirror() -> Bool {
        switch self {
        case .up, .right, .down, .left:
            return false
        case .leftMirrored, .upMirrored, .downMirrored, .rightMirrored:
            return true
        default:
            return false
        }
    }
}
Learner
  • 621
  • 6
  • 9