轉(zhuǎn)載鏈接http://www.cocoachina.com/ios/20150605/12021.html
作者blog地址http://feihu.me/

使用過(guò)iPhone或者iPad的朋友在拍照時(shí)不知是否遇到過(guò)這樣的問(wèn)題唧领,將設(shè)備中的照片導(dǎo)出到Windows上時(shí)躯畴,經(jīng)常發(fā)現(xiàn)導(dǎo)出的照片方向會(huì)有問(wèn)題,要么橫著,要么顛倒著憎乙,需要旋轉(zhuǎn)才適合觀看氮昧。而如果直接在這些設(shè)備上瀏覽時(shí),照片會(huì)始終顯示正確的方向佩研,在Mac上也能正確顯示铜犬。最近在iOS的開(kāi)發(fā)中也遇到了同樣的問(wèn)題,將拍攝的照片上傳到服務(wù)器后轻庆,再由Windows端下載該照片癣猾,發(fā)現(xiàn)手機(jī)上完全正常的照片到了這里顯示的橫七豎八。同一張照片為什么在不同的設(shè)備上表現(xiàn)的不同余爆?如何能夠避免這種情況纷宇?本文將和大家一一解開(kāi)這些問(wèn)題。
<span style="color: rgb(0, 176, 80);">目錄</span>
照片的存儲(chǔ)演變
膠片時(shí)代
數(shù)碼時(shí)代
方向傳感器
EXIF(Exchangeable Image File Format)
Orientation
iPhone上的情況
驗(yàn)證EXIF
Mac平臺(tái)
Windows平臺(tái)
開(kāi)發(fā)時(shí)如何避免
直觀的解決方案
第二種簡(jiǎn)單的方法
結(jié)尾
<span style="color: rgb(0, 176, 80);">照片的存儲(chǔ)演變</span>
一切都得從相機(jī)的發(fā)展開(kāi)始說(shuō)起蛾方。
膠片時(shí)代
一般相機(jī)拍攝出來(lái)的畫面都是長(zhǎng)方形像捶,在拍攝的那一瞬間上陕,它會(huì)將取景器中的場(chǎng)景對(duì)應(yīng)的顏色值存到對(duì)應(yīng)的像素位置。相機(jī)本身并沒(méi)有任何方向的概念拓春,只是使用者想要拍攝的場(chǎng)景在他期望的照片中顯示的方式與實(shí)際存在差異時(shí)释簿,才有了方向一說(shuō)。如下圖硼莽,對(duì)一個(gè)場(chǎng)景F進(jìn)行拍攝庶溶,相機(jī)的方向可能會(huì)有這樣四個(gè)常見(jiàn)的角度:

相機(jī)是“自私”的,由于相機(jī)僅反應(yīng)真實(shí)的場(chǎng)景懂鸵,它不理解拍攝的內(nèi)容偏螺,因此照片都以相機(jī)的坐標(biāo)系保存,于是上面四種情形實(shí)際拍攝出來(lái)的照片會(huì)像這樣:

最初的卡片機(jī)時(shí)代匆光,照片都會(huì)經(jīng)由底片洗出來(lái)套像。那時(shí)不存在照片的方向問(wèn)題,因?yàn)椴还芪覀円院畏N角度拍攝终息,最終洗出來(lái)的照片夺巩,它本身非常容易旋轉(zhuǎn),所以我們總可以通過(guò)簡(jiǎn)單的旋轉(zhuǎn)來(lái)觀看照片或者保存照片采幌。比如這張照片墻中的照片劲够,你能否說(shuō)哪些照片是橫著?哪些顛倒著休傍?你甚至都無(wú)法判斷每張照片相機(jī)是以何種角度拍攝的征绎,因?yàn)槊繌埗家呀?jīng)旋轉(zhuǎn)至適合觀看的角度。

數(shù)碼時(shí)代
可是到了數(shù)碼時(shí)代磨取,不再需要底片人柿,照片需要被存成一個(gè)圖像文件。對(duì)于上面的拍攝角度忙厌,存儲(chǔ)方式并沒(méi)有變化凫岖,所有的場(chǎng)景仍然是以相機(jī)的坐標(biāo)系來(lái)保存。于是這些照片仍像上面一樣逢净,原封不動(dòng)的保存了下來(lái):

雖然存儲(chǔ)方式不變哥放,和卡機(jī)機(jī)時(shí)代的實(shí)體相片不同的是,由于電腦屏幕可沒(méi)洗出來(lái)的照片那么容易旋轉(zhuǎn)爹土,所以照片只能夠以它存儲(chǔ)于磁盤中的方向來(lái)展示甥雕。這便是為何照片傳到電腦上之后,會(huì)出現(xiàn)橫了胀茵,或者顛倒的情況社露。正因?yàn)檫@樣,我們只有利用工具來(lái)旋轉(zhuǎn)照片才能夠正常觀看琼娘。
方向傳感器
為了克服這一情況峭弟,讓照片可以真實(shí)的反應(yīng)人們拍攝時(shí)看到的場(chǎng)景附鸽,現(xiàn)在很多相機(jī)中就加入了方向傳感器,它能夠記錄下拍攝時(shí)相機(jī)的方向瞒瘸,并將這一信息保存在照片中坷备。照片的存儲(chǔ)方式還是沒(méi)有任何改變,它仍然是以相機(jī)的坐標(biāo)系來(lái)保存挨务,只是當(dāng)相機(jī)來(lái)瀏覽這些照片時(shí)击你,相機(jī)可以根據(jù)照片中的方向信息,結(jié)合此時(shí)相機(jī)的方向谎柄,對(duì)照片進(jìn)行旋轉(zhuǎn)丁侄,從而轉(zhuǎn)到適合人們觀看的角度。
但是很遺憾朝巫,這一標(biāo)準(zhǔn)并沒(méi)有被廣泛的傳播開(kāi)來(lái)鸿摇,或者說(shuō)始終如一的貫徹,這也導(dǎo)致了本文所討論的問(wèn)題劈猿。
<span style="color: rgb(0, 176, 80);">EXIF(Exchangeable Image File Format)</span>
那么拙吉,方向信息到底是記錄在照片的什么位置?
了解圖像格式的朋友可能會(huì)知道揪荣,圖像一般都由兩大部分組成筷黔,一部分是數(shù)據(jù)本身,它記錄了每個(gè)像素的顏色值仗颈,另外一部分是文件頭佛舱,這里面記錄著形如圖像的寬度,高度等信息挨决。我們所討論的方向信息便是被存儲(chǔ)于文件頭中请祖。更為具體一些:EXIF中,維基百科上對(duì)其的解釋為:
可交換圖像文件格式常被簡(jiǎn)稱為Exif(Exchangeable image file format)脖祈,是專門為數(shù)碼相機(jī)的照片設(shè)定的肆捕,可以記錄數(shù)碼照片的屬性信息和拍攝數(shù)據(jù)… Exif可以附加于JPEG、TIFF盖高、RIFF等文件之中
注意:PNG格式的圖像中不包含慎陵。
Orientation
在EXIF涵蓋的各種信息之中,其中有一個(gè)叫做Orientation (rotation)的標(biāo)簽喻奥,用于記錄圖像的方向席纽,這便是相機(jī)寫入方向信息的最終位置。它總共定義了八個(gè)值:

注意:對(duì)于上面的八種方向中映凳,加了*的并不常見(jiàn)胆筒,因?yàn)樗鼈兇淼氖晴R像方向邮破,如果不做任何的處理诈豌,不管相機(jī)以任何角度拍攝仆救,都無(wú)法出現(xiàn)鏡像的情況。
這個(gè)表格代表什么意義矫渔?我們來(lái)看第一行彤蔽,值為1時(shí),右邊兩列的值分別為:Row #0 is Top庙洼,Column #0 is Left side顿痪,其實(shí)很好理解,它表示照片的第一行位于頂端油够,而第一列位于左側(cè)蚁袭,那么這張照片自然就是以正常角度拍攝的。
對(duì)著前面的四種拍攝角度石咬,由于相機(jī)都是以其自身的坐標(biāo)系來(lái)保存照片揩悄,因此每張照片對(duì)應(yīng)的第一行和第一列的位置始終如下:

我們來(lái)看第二張照片,這張照片需要逆時(shí)針旋轉(zhuǎn)90度才能夠正常觀看鬼悠。旋轉(zhuǎn)之后删性,它的第一行位于左側(cè),而第一列位于下側(cè)焕窝。如此一來(lái)蹬挺,對(duì)比表格,它的Orientation值為8它掂。所以說(shuō)巴帮,這個(gè)Orientation值提供了想要正常觀看圖像時(shí)應(yīng)該旋轉(zhuǎn)的方式。
以同樣的方法群发,我們可以推斷出上面四種方式拍攝時(shí)晰韵,對(duì)應(yīng)EXIF中Orientation的值如下所示:

由于相機(jī)加上了方向傳感器的緣故,可以非常容易的檢測(cè)出以上幾種拍攝角度熟妓,并將角度對(duì)應(yīng)的Orientation值保存至圖像中雪猪。查看圖像時(shí),相機(jī)檢測(cè)到其EXIF中的Orientation信息起愈,并將圖像旋轉(zhuǎn)相應(yīng)的角度顯示給用戶只恨,這樣便達(dá)到了智能顯示的目的。
iPhone上的情況
作為智能手機(jī)的重要組成部分抬虽,形形色色的傳感器自然必不可少官觅。在iOS的設(shè)備中也是包含了這樣的方向傳感器,它也采用了同樣的方式來(lái)保存照片的方向信息到EXIF中阐污。但是它默認(rèn)的照片方向并不是豎著拿手機(jī)時(shí)的情況休涤,而是橫向,即Home鍵在右側(cè),如下:

如此一來(lái)功氨,如果豎著拿手機(jī)拍攝時(shí)序苏,就相當(dāng)于對(duì)手機(jī)順時(shí)針旋轉(zhuǎn)了90度(圖像的原點(diǎn)在左下角)
,也即上面相機(jī)圖片中的最后一幅捷凄,那么它的Orientation值為6忱详。

<span style="color: rgb(0, 176, 80);">驗(yàn)證EXIF</span>
在經(jīng)過(guò)上面的分析之后,我們來(lái)看看實(shí)際情況如何跺涤。我們分別在Mac和Windows平臺(tái)上對(duì)前面的論述做一個(gè)驗(yàn)證匈睁。
Mac平臺(tái)
可以將照片從iOS設(shè)備中導(dǎo)出到Mac系統(tǒng)上,(注意桶错,不能夠使用iPhoto或者Photos來(lái)導(dǎo)入航唆,因?yàn)檫@樣照片在導(dǎo)入之前會(huì)被自動(dòng)調(diào)整好方向)在這里我們像Windows中一樣,將iPhone當(dāng)成移動(dòng)硬盤院刁,直接訪問(wèn)其照片佛点。在Mac上可以使用iTools這一神器。
然后用Mac上的預(yù)覽程序查看其EXIF屬性黎比,通過(guò)預(yù)覽-工具-顯示檢查器
打開(kāi)對(duì)話框超营,即可查看到照片中關(guān)于方向的詳細(xì)信息。下面四張圖分別展示了上面四種方向下拍得照片的Orientation值:(這里的方向逆時(shí)針阅虫,參考上面的F圖來(lái))
- Home鍵位于右側(cè)時(shí)演闭,即相機(jī)的默認(rèn)方向,值為1颓帝。

- Home鍵位于上側(cè)時(shí)米碰,值為8。

- Home鍵位于左側(cè)時(shí)购城,值為3吕座。

- Home鍵位于下側(cè)時(shí),即正常手持手機(jī)的方向瘪板,值為6吴趴。

對(duì)照前面的分析,完全一致侮攀。而且照片顯示正常锣枝,說(shuō)明在Mac上默認(rèn)的預(yù)覽程序會(huì)自動(dòng)的處理EXIF中的Orientation信息。
再次提醒:照片存儲(chǔ)在手機(jī)中始終是以相機(jī)坐標(biāo)系保存的兰英,只是瀏覽工作在讀取方向信息之后做了旋轉(zhuǎn)撇叁。
Windows平臺(tái)
前面提到過(guò),被寫在圖像文件頭中的方向信息并沒(méi)有被全部支持畦贸,Windows的照片查看器便是其中之一陨闹,這也是Windows用戶最常使用的照片瀏覽工具。因?yàn)闆](méi)有讀取方向信息,照片被讀入之后趋厉,完全按照其存儲(chǔ)方式來(lái)顯示泡一,這樣便出現(xiàn)了橫向,或者顛倒的情況觅廓。下面四張圖便分別是上一節(jié)中拍得的照片在Windows上的顯示效果,注意看方向涵但。


<span style="color: rgb(0, 176, 80);">開(kāi)發(fā)時(shí)如何避免</span>
既然不是所有的工具都支持方向?qū)傩澡境瘢@其中甚至包含了具有最多用戶群體的Windows,那么我們?cè)陂_(kāi)發(fā)照片相關(guān)的應(yīng)用時(shí)矮瘟,有沒(méi)有什么應(yīng)對(duì)之策瞳脓?
當(dāng)然有!因?yàn)榭梢苑浅H菀椎牡玫秸掌姆较蛐畔ⅲ?lt;span style="color: rgb(0, 176, 80);">那么只需要在保存之前將照片旋轉(zhuǎn)至正常觀看的方向即可澈侠,然后直接將最終具有正確方向的照片保存下來(lái)劫侧,搞定。</span>
當(dāng)我們得到一個(gè)UIImage對(duì)象時(shí)哨啃,它有一個(gè)屬性叫: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.
它剛好也可能為下面八種值,這些值可以和EXIF中Orientation的定義一一對(duì)應(yīng):

那么我們便可以根據(jù)這一屬性對(duì)圖像進(jìn)行相應(yīng)的旋轉(zhuǎn)拳球,從而將圖像的原始數(shù)據(jù)旋轉(zhuǎn)至正確的方向审姓,在瀏覽照片時(shí)無(wú)需方向信息便可正常瀏覽。
關(guān)于如何旋轉(zhuǎn)圖像祝峻,StackOverflow上給出了很好的答案魔吐,比如這個(gè)。我們簡(jiǎn)單做一個(gè)介紹:
直觀的解決方案
首先莱找,為UIImage創(chuàng)建一個(gè)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
代碼有些長(zhǎng),不過(guò)卻非常直觀奥溺。這里面涉及到圖像矩陣變換的操作辞色,理解起來(lái)可能稍稍有些困難,接下來(lái)浮定,我會(huì)有另外一篇文章專門來(lái)介紹圖像變換∫В現(xiàn)在,記住下面兩點(diǎn)便能夠很好的幫助理解:
<span style="color: rgb(0, 176, 80);">
1.圖像的原點(diǎn)在左下角
2.矩陣變換時(shí)壶唤,后面的矩陣先作用雳灵,前面的矩陣后作用
</span>
