(转)如何处理iOS中照片的方向

如何处理iOS中照片的方向

如何处理iOS中照片的方向

使用过iPhone或者iPad的朋友在拍照时不知是否遇到过这样的问题,将设备中的照片导出到Windows上时,常常发现导出的照片方向会有问题,要么横着,要么颠倒着,须要旋转才适合观看。而若是直接在这些设备上浏览时,照片会始终显示正确的方向,在Mac上也能正确显示。最近在iOS的开发中也遇到了一样的问题,将拍摄的照片上传到服务器后,再由Windows端下载该照片,发现手机上彻底正常的照片到了这里显示的横七竖八。同一张照片为何在不一样的设备上表现的不一样?如何可以避免这种状况?本文将和你们一一解开这些问题。浏览器

目录


照片的存储演变

一切都得从相机的发展开始提及。服务器

胶片时代

通常相机拍摄出来的画面都是长方形,在拍摄的那一瞬间,它会将取景器中的场景对应的颜色值存到对应的像素位置。相机自己并无任何方向的概念,只是使用者想要拍摄的场景在他指望的照片中显示的方式与实际存在差别时,才有了方向一说。以下图,对一个场景F进行拍摄,相机的方向可能会有这样四个常见的角度:markdown

摄像头取景

相机是“自私”的,因为相机仅反应真实的场景,它不理解拍摄的内容,所以照片都以相机的坐标系保存,因而上面四种情形实际拍摄出来的照片会像这样:app

存储状况

最初的卡片机时代,照片都会经由底片洗出来。那时不存在照片的方向问题,由于无论咱们以何种角度拍摄,最终洗出来的照片,它自己很是容易旋转,因此咱们总能够经过简单的旋转来观看照片或者保存照片。好比这张照片墙中的照片,你可否说哪些照片是横着?哪些颠倒着?你甚至都没法判断每张照片相机是以何种角度拍摄的,由于每张都已经旋转至适合观看的角度。iphone

照片墙

数码时代

但是到了数码时代,再也不须要底片,照片须要被存成一个图像文件。对于上面的拍摄角度,存储方式并无变化,全部的场景仍然是以相机的坐标系来保存。因而这些照片仍像上面同样,原封不动的保存了下来:ide

存储状况

虽然存储方式不变,和卡机机时代的实体相片不一样的是,因为电脑屏幕可没洗出来的照片那么容易旋转,因此照片只可以以它存储于磁盘中的方向来展现。这即是为什么照片传到电脑上以后,会出现横了,或者颠倒的状况。正由于这样,咱们只有利用工具来旋转照片才可以正常观看。工具

方向传感器

为了克服这一状况,让照片能够真实的反应人们拍摄时看到的场景,如今不少相机中就加入了方向传感器,它可以记录下拍摄时相机的方向,并将这一信息保存在照片中。照片的存储方式仍是没有任何改变,它仍然是以相机的坐标系来保存,只是当相机来浏览这些照片时,相机能够根据照片中的方向信息,结合此时相机的方向,对照片进行旋转,从而转到适合人们观看的角度。post

可是很遗憾,这一标准并无被普遍的传播开来,或者说始终如一的贯彻,这也致使了本文所讨论的问题。ui

EXIF(Exchangeable Image File Format)

那么,方向信息究竟是记录在照片的什么位置?

了解图像格式的朋友可能会知道,图像通常都由两大部分组成,一部分是数据自己,它记录了每一个像素的颜色值,另一部分是文件头,这里面记录着形如图像的宽度,高度等信息。咱们所讨论的方向信息即是被存储于文件头中。更为具体一些:EXIF中,维基百科上对其的解释为:

可交换图像文件格式常被简称为Exif(Exchangeable image file format),是专门为数码相机的照片设定的,能够记录数码照片的属性信息和拍摄数据… Exif能够附加于JPEG、TIFF、RIFF等文件之中

注意:PNG格式的图像中不包含。

Orientation

EXIF涵盖的各类信息之中,其中有一个叫作Orientation (rotation)的标签,用于记录图像的方向,这即是相机写入方向信息的最终位置。它总共定义了八个值:

Orientation的八个值

注意:对于上面的八种方向中,加了*的并不常见,由于它们表明的是镜像方向,若是不作任何的处理,无论相机以任何角度拍摄,都没法出现镜像的状况。

这个表格表明什么意义?咱们来看第一行,值为1时,右边两列的值分别为:Row #0 isTop,Column #0 is Left side,其实很好理解,它表示照片的第一行位于顶端,而第一列位于左侧,那么这张照片天然就是以正常角度拍摄的。

对着前面的四种拍摄角度,因为相机都是以其自身的坐标系来保存照片,所以每张照片对应的第一行和第一列的位置始终以下:

第一行第一列

咱们来看第二张照片,这张照片须要逆时针旋转90度才可以正常观看。旋转以后,它的第一行位于左侧,而第一列位于下侧。如此一来,对比表格,它的Orientation值为8。因此说,这个Orientation值提供了想要正常观看图像时应该旋转的方式。

以一样的方法,咱们能够推断出上面四种方式拍摄时,对应EXIFOrientation的值以下所示:

图片的方向

因为相机加上了方向传感器的缘故,能够很是容易的检测出以上几种拍摄角度,并将角度对应的Orientation值保存至图像中。查看图像时,相机检测到其EXIF中的Orientation信息,并将图像旋转相应的角度显示给用户,这样便达到了智能显示的目的。

iPhone上的状况

做为智能手机的重要组成部分,形形色色的传感器天然必不可少。在iOS的设备中也是包含了这样的方向传感器,它也采用了一样的方式来保存照片的方向信息到EXIF中。可是它默认的照片方向并非竖着拿手机时的状况,而是横向,即Home键在右侧,以下:

iPhone正常方向

如此一来,若是竖着拿手机拍摄时,就至关于对手机顺时针旋转了90度,也即上面相机图片中的最后一幅,那么它的Orientation值为6。

iPhone竖向

验证EXIF

在通过上面的分析以后,咱们来看看实际状况如何。咱们分别在Mac和Windows平台上对前面的论述作一个验证。

Mac平台

能够将照片从iOS设备中导出到Mac系统上,(注意,不可以使用iPhoto或者Photos来导入,由于这样照片在导入以前会被自动调整好方向)在这里咱们像Windows中同样,将iPhone当成移动硬盘,直接访问其照片。在Mac上可使用iTools这一神器。

而后用Mac上的预览程序查看其EXIF属性,经过预览-工具-显示检查器打开对话框,便可查看到照片中关于方向的详细信息。下面四张图分别展现了上面四种方向下拍得照片的Orientation值:

  • Home键位于右侧时,即相机的默认方向,值为1。Home键在右侧

  • Home键位于上侧时,值为8。Home键在上侧

  • Home键位于左侧时,值为3。Home键在左侧

  • Home键位于下侧时,即正常手持手机的方向,值为6。Home键在下侧

对照前面的分析,彻底一致。并且照片显示正常,说明在Mac上默认的预览程序会自动的处理EXIF中的Orientation信息。

再次提醒:照片存储在手机中始终是以相机坐标系保存的,只是浏览工做在读取方向信息以后作了旋转。

Windows平台

前面提到过,被写在图像文件头中的方向信息并无被所有支持,Windows的照片查看器即是其中之一,这也是Windows用户最常使用的照片浏览工具。由于没有读取方向信息,照片被读入以后,彻底按照其存储方式来显示,这样便出现了横向,或者颠倒的状况。下面四张图便分别是上一节中拍得的照片在Windows上的显示效果,注意看方向。

Windows上的状况

开发时如何避免

既然不是全部的工具都支持方向属性,这其中甚至包含了具备最多用户群体的Windows,那么咱们在开发照片相关的应用时,有没有什么应对之策?

固然有!由于能够很是容易的获得照片的方向信息,那么只须要在保存以前将照片旋转至正常观看的方向便可,而后直接将最终具备正确方向的照片保存下来,搞定。

当咱们获得一个UIImage对象时,它有一个属性叫:imageOrientation,这里面便保存了方向信息:

Property
The orientation of the receiver’s image. (read-only)
Discussion
Image orientation affects the way the image data is displayed when drawn. By default, images are displayed in the “up” orientation. If the image has associated metadata (such as EXIF information), however, this property contains the orientation indicated by that metadata. For a list of possible values for this property, see UIImageOrientation.

它恰好也可能为下面八种值,这些值能够和EXIFOrientation的定义一一对应:

  • Up UIImageOrientationUp
  • Down UIImageOrientationDown
  • Left UIImageOrientationLeft
  • Right UIImageOrientationRight
  • UpMirror UIImageOrientationUpMirrored
  • DownMirror UIImageOrientationDownMirrored
  • LeftMirror UIImageOrientationLeftMirrored
  • RightMirror UIImageOrientationRightMirrored

那么咱们即可以根据这一属性对图像进行相应的旋转,从而将图像的原始数据旋转至正确的方向,在浏览照片时无需方向信息即可正常浏览。

关于如何旋转图像,StackOverflow上给出了很好的答案,好比这个。咱们简单作一个介绍:

直观的解决方案

首先,为UIImage建立一个category,其中包含fixOrientation方法:

UIImage+fixOrientation.h

@interface UIImage (fixOrientation) - (UIImage *)fixOrientation; @end

UIImage+fixOrientation.m

@implementation UIImage (fixOrientation) - (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; } @end

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

  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.

结尾

关于照片方向的处理就介绍到这里,相信看完本文你已经知悉为什么以及如何处理这个问题。

关于EXIF,这里面包含了不少有趣的内容,好比iPhone拍摄后,能够记录当时的GPS位置,这样在查看照片的时候就能够很神奇的知道照片的拍摄地。若是感兴趣能够去一探究竟。

另外,除去专门的照片浏览工具,全部的现代浏览器也天生具有查看图片的功能。并且有不少浏览器也已经支持EXIF中的Orientation,好比Firefox, Chrome, Safari。但一样很惋惜,IE并不支持(一直到IE9.0尚不支持)。也许和Win7设计时并无这些具备方向传感器的手机有关,我从网上了解到,在当初2012年收集building Windows8意见时,就有人提到过这一问题,但愿可以考虑图片的方向信息,微软也给出了回应

(In Windows8)Explorer now respects EXIF orientation information for JPEG images. If your camera sets this value accurately, you will rarely need to correct orientation.

但我一直没有用过Windows8,若是有使用过的,但愿能够帮我验证一下是否微软已经修复这个问题。

(全文完)

feihu2015.05.31 于 Shenzhen

相关文章
相关标签/搜索