iOS拍照图片旋转的问题

  很久之前,遇到了这种情况,iOS某端拍照上传到服务器,其他iOS端从服务器下载该照片展示,发现图片逆时针旋转了90度。当时百度了一下,找到一段代码修正image方向,问题解决了,但没有深入理解底层原理。最近又遇到这个问题,还是同样的解决方案。但是codereview的时候同事问为什么这么写,就深入研究了一下。

  首先我们要知道image的imageOrientation属性。它是记录拍照时手机方向的,iOS默认横屏Home键在右侧为标准拍照姿势,imageOrientation为UIImageOrientationUp。知道了拍照时相机方向,展示的时候就能对照片就行仿射变换,让它能正确显示。

  看到这里,就可以直接去大神的深度分析文章了:如何处理iOS中照片的方向

直观的解决方案

- (UIImage *)fixOrientation {

    // No-op if the orientation is already correct
    if (self.imageOrientation == UIImageOrientationUp) return self;

    // We need to calculate the proper transformation to make the image upright.
    // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
    CGAffineTransform transform = CGAffineTransformIdentity;

    switch (self.imageOrientation) {
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, 0);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;

        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, 0, self.size.height);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationUpMirrored:
            break;
    }

    switch (self.imageOrientation) {
        case UIImageOrientationUpMirrored:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;

        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.height, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationDown:
        case UIImageOrientationLeft:
        case UIImageOrientationRight:
            break;
    }

    // Now we draw the underlying CGImage into a new context, applying the transform
    // calculated above.
    CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height,
                                             CGImageGetBitsPerComponent(self.CGImage), 0,
                                             CGImageGetColorSpace(self.CGImage),
                                             CGImageGetBitmapInfo(self.CGImage));
    CGContextConcatCTM(ctx, transform);
    switch (self.imageOrientation) {
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            // Grr...
            CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage);
            break;

        default:
            CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage);
            break;
    }

    // And now we just create a new UIImage from the drawing context
    CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
    UIImage *img = [UIImage imageWithCGImage:cgimg];
    CGContextRelease(ctx);
    CGImageRelease(cgimg);
    return img;
}

代码有些长,不过却非常直观。这里面涉及到图像矩阵变换的操作,理解起来可能稍稍有些困难,接下来,我会有另外一篇文章专门来介绍图像变换。现在,记住下面两点便能够很好的帮助理解:

  1. 图像的原点在左下角
  2. 矩阵变换时,后面的矩阵先作用,前面的矩阵后作用

UIImageOrientationDown方向为例,UIImageOrientationDown,很明显它翻转了180度。那么对它的旋转需要两步,第一步是以左下方为原点旋转180度,(此时顺时针还是逆时针旋转效果一样)旋转后上图变为:旋转180度后 。用代码表示为:

transform = CGAffineTransformRotate(transform, M_PI);

因为是以左下方为原点旋转的,所以整幅图被移到了第三象限。第二步需要将其平移至第一象限,向右上方进行平移即可。x方向上移动距离为图像的宽度,y方向上移动距离为图像的高度,所以平移后图像变为:平移后。代码为:

transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);

再加上我们前面所说的第二点,矩阵变换时,后面的矩阵先作用,前面的矩阵后作用,那么只需要将上面两步颠倒即可:

transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
transform = CGAffineTransformRotate(transform, M_PI);

其它的方向可以用完全一样的方法来分析,这里不再一一赘述。

第二种简单的方法

第二种方法同样也是StackOverflow上的答案,没那么直观,但非常简单:

- (UIImage *)normalizedImage {
    if (self.imageOrientation == UIImageOrientationUp) return self; 

    UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
    [self drawInRect:(CGRect){0, 0, self.size}];
    UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return normalizedImage;
}

 

这里是利用了UIImage中的drawInRect方法,它会将图像绘制到画布上,并且已经考虑好了图像的方向,开发文档这样解释:

 

-drawInRect:
Draws the entire image in the specified rectangle, scaling it as needed to fit.

Discussion
This method draws the entire image in the current graphics context, respecting the image’s orientation setting. In the default coordinate system, images are situated down and to the right of the origin of the specified rectangle. This method respects any transforms applied to the current graphics context, however.

 

第二个方法简单易于理解,我就采用了第二种解决方案。希望能帮到大家。

 
posted @ 2019-03-15 14:13 6度XZ 阅读(...) 评论(...) 编辑 收藏