轉(zhuǎn)載自:這里
使用過(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)題。
目錄
EXIF(Exchangeable Image File Format)
照片的存儲(chǔ)演變
一切都得從相機(jī)的發(fā)展開(kāi)始說(shuō)起诗茎。
膠片時(shí)代
一般相機(jī)拍攝出來(lái)的畫(huà)面都是長(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ǔ)于磁盤(pán)中的方向來(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)題浓恳。
EXIF(Exchangeable Image File Format)
那么,方向信息到底是記錄在照片的什么位置碗暗?
了解圖像格式的朋友可能會(huì)知道颈将,圖像一般都由兩大部分組成,一部分是數(shù)據(jù)本身言疗,它記錄了每個(gè)像素的顏色值晴圾,另外一部分是文件頭,這里面記錄著形如圖像的寬度噪奄,高度等信息死姚。我們所討論的方向信息便是被存儲(chǔ)于文件頭中。更為具體一些:EXIF中勤篮,維基百科上對(duì)其的解釋為:
可交換圖像文件格式常被簡(jiǎn)稱(chēng)為Exif(Exchangeable image file format)都毒,是專(zhuān)門(mén)為數(shù)碼相機(jī)的照片設(shè)定的,可以記錄數(shù)碼照片的屬性信息和拍攝數(shù)據(jù)… Exif可以附加于JPEG碰缔、TIFF账劲、RIFF等文件之中
注意:PNG格式的圖像中不包含。
Orientation
在EXIF涵蓋的各種信息之中金抡,其中有一個(gè)叫做Orientation (rotation)的標(biāo)簽瀑焦,用于記錄圖像的方向,這便是相機(jī)寫(xiě)入方向信息的最終位置梗肝。它總共定義了八個(gè)值:
注意:對(duì)于上面的八種方向中榛瓮,加了*的并不常見(jiàn),因?yàn)樗鼈兇淼氖晴R像方向巫击,如果不做任何的處理禀晓,不管相機(jī)以任何角度拍攝精续,都無(wú)法出現(xiàn)鏡像的情況。
這個(gè)表格代表什么意義匆绣?我們來(lái)看第一行驻右,值為1時(shí),右邊兩列的值分別為:Row #0 isTop崎淳,Column #0 isLeft 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度,也即上面相機(jī)圖片中的最后一幅萄凤,那么它的Orientation值為6室抽。
驗(yàn)證EXIF
在經(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)硬盤(pán)漾月,直接訪問(wèn)其照片病梢。在Mac上可以使用iTools這一神器。
然后用Mac上的預(yù)覽程序查看其EXIF屬性梁肿,通過(guò)預(yù)覽-工具-顯示檢查器打開(kāi)對(duì)話框蜓陌,即可查看到照片中關(guān)于方向的詳細(xì)信息。下面四張圖分別展示了上面四種方向下拍得照片的Orientation值:
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ò),被寫(xiě)在圖像文件頭中的方向信息并沒(méi)有被全部支持芥喇,Windows的照片查看器便是其中之一西采,這也是Windows用戶最常使用的照片瀏覽工具。因?yàn)闆](méi)有讀取方向信息继控,照片被讀入之后械馆,完全按照其存儲(chǔ)方式來(lái)顯示胖眷,這樣便出現(xiàn)了橫向,或者顛倒的情況霹崎。下面四張圖便分別是上一節(jié)中拍得的照片在Windows上的顯示效果珊搀,注意看方向。
開(kāi)發(fā)時(shí)如何避免
既然不是所有的工具都支持方向?qū)傩晕补剑@其中甚至包含了具有最多用戶群體的Windows境析,那么我們?cè)陂_(kāi)發(fā)照片相關(guān)的應(yīng)用時(shí),有沒(méi)有什么應(yīng)對(duì)之策错沽?
當(dāng)然有簿晓!因?yàn)榭梢苑浅H菀椎牡玫秸掌姆较蛐畔ⅲ敲粗恍枰诒4嬷皩⒄掌D(zhuǎn)至正常觀看的方向即可千埃,然后直接將最終具有正確方向的照片保存下來(lái)憔儿,搞定。
當(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):
UIImageOrientationUp
UIImageOrientationDown
UIImageOrientationLeft
UIImageOrientationRight
UIImageOrientationUpMirrored
UIImageOrientationDownMirrored
UIImageOrientationLeftMirrored
UIImageOrientationRightMirrored
那么我們便可以根據(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
1@interfaceUIImage(fixOrientation)23-(UIImage*)fixOrientation;45@end
UIImage+fixOrientation.m
1@implementationUIImage(fixOrientation)23-(UIImage*)fixOrientation{45// No-op if the orientation is already correct6if(self.imageOrientation==UIImageOrientationUp)returnself;78// We need to calculate the proper transformation to make the image upright.9// We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.10CGAffineTransformtransform=CGAffineTransformIdentity;1112switch(self.imageOrientation){13caseUIImageOrientationDown:14caseUIImageOrientationDownMirrored:15transform=CGAffineTransformTranslate(transform,self.size.width,self.size.height);16transform=CGAffineTransformRotate(transform,M_PI);17break;1819caseUIImageOrientationLeft:20caseUIImageOrientationLeftMirrored:21transform=CGAffineTransformTranslate(transform,self.size.width,0);22transform=CGAffineTransformRotate(transform,M_PI_2);23break;2425caseUIImageOrientationRight:26caseUIImageOrientationRightMirrored:27transform=CGAffineTransformTranslate(transform,0,self.size.height);28transform=CGAffineTransformRotate(transform,-M_PI_2);29break;30caseUIImageOrientationUp:31caseUIImageOrientationUpMirrored:32break;33}3435switch(self.imageOrientation){36caseUIImageOrientationUpMirrored:37caseUIImageOrientationDownMirrored:38transform=CGAffineTransformTranslate(transform,self.size.width,0);39transform=CGAffineTransformScale(transform,-1,1);40break;4142caseUIImageOrientationLeftMirrored:43caseUIImageOrientationRightMirrored:44transform=CGAffineTransformTranslate(transform,self.size.height,0);45transform=CGAffineTransformScale(transform,-1,1);46break;47caseUIImageOrientationUp:48caseUIImageOrientationDown:49caseUIImageOrientationLeft:50caseUIImageOrientationRight:51break;52}5354// Now we draw the underlying CGImage into a new context, applying the transform55// calculated above.56CGContextRefctx=CGBitmapContextCreate(NULL,self.size.width,self.size.height,57CGImageGetBitsPerComponent(self.CGImage),0,58CGImageGetColorSpace(self.CGImage),59CGImageGetBitmapInfo(self.CGImage));60CGContextConcatCTM(ctx,transform);61switch(self.imageOrientation){62caseUIImageOrientationLeft:63caseUIImageOrientationLeftMirrored:64caseUIImageOrientationRight:65caseUIImageOrientationRightMirrored:66// Grr...67CGContextDrawImage(ctx,CGRectMake(0,0,self.size.height,self.size.width),self.CGImage);68break;6970default:71CGContextDrawImage(ctx,CGRectMake(0,0,self.size.width,self.size.height),self.CGImage);72break;73}7475// And now we just create a new UIImage from the drawing context76CGImageRefcgimg=CGBitmapContextCreateImage(ctx);77UIImage*img=[UIImageimageWithCGImage:cgimg];78CGContextRelease(ctx);79CGImageRelease(cgimg);80returnimg;81}8283@end
代碼有些長(zhǎng),不過(guò)卻非常直觀翰守。這里面涉及到圖像矩陣變換的操作孵奶,理解起來(lái)可能稍稍有些困難,接下來(lái)蜡峰,我會(huì)有另外一篇文章專(zhuān)門(mén)來(lái)介紹圖像變換×嗽現(xiàn)在,記住下面兩點(diǎn)便能夠很好的幫助理解:
圖像的原點(diǎn)在左下角
矩陣變換時(shí)湿颅,后面的矩陣先作用载绿,前面的矩陣后作用
以UIImageOrientationDown方向?yàn)槔?/p>
,很明顯它翻轉(zhuǎn)了180度油航。那么對(duì)它的旋轉(zhuǎn)需要兩步崭庸,第一步是以左下方為原點(diǎn)旋轉(zhuǎn)180度,(此時(shí)順時(shí)針還是逆時(shí)針旋轉(zhuǎn)效果一樣)旋轉(zhuǎn)后上圖變?yōu)椋?/p>
。用代碼表示為:
1transform=CGAffineTransformRotate(transform,M_PI);
因?yàn)槭且宰笙路綖樵c(diǎn)旋轉(zhuǎn)的冀自,所以整幅圖被移到了第三象限揉稚。第二步需要將其平移至第一象限,向右上方進(jìn)行平移即可熬粗。x方向上移動(dòng)距離為圖像的寬度搀玖,y方向上移動(dòng)距離為圖像的高度,所以平移后圖像變?yōu)椋?/p>
驻呐。代碼為:
1transform=CGAffineTransformTranslate(transform,self.size.width,self.size.height);
再加上我們前面所說(shuō)的第二點(diǎn)灌诅,矩陣變換時(shí),后面的矩陣先作用含末,前面的矩陣后作用猜拾,那么只需要將上面兩步顛倒即可:
1transform=CGAffineTransformTranslate(transform,self.size.width,self.size.height);2transform=CGAffineTransformRotate(transform,M_PI);
其它的方向可以用完全一樣的方法來(lái)分析,這里不再一一贅述佣盒。
第二種簡(jiǎn)單的方法
第二種方法同樣也是StackOverflow上的答案挎袜,沒(méi)那么直觀,但非常簡(jiǎn)單:
1-(UIImage*)normalizedImage{2if(self.imageOrientation==UIImageOrientationUp)returnself;34UIGraphicsBeginImageContextWithOptions(self.size,NO,self.scale);5[selfdrawInRect:(CGRect){0,0,self.size}];6UIImage*normalizedImage=UIGraphicsGetImageFromCurrentImageContext();7UIGraphicsEndImageContext();8returnnormalizedImage;9}
這里是利用了UIImage中的drawInRect方法肥惭,它會(huì)將圖像繪制到畫(huà)布上盯仪,并且已經(jīng)考慮好了圖像的方向,開(kāi)發(fā)文檔這樣解釋?zhuān)?/p>
-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.
結(jié)尾
關(guān)于照片方向的處理就介紹到這里蜜葱,相信看完本文你已經(jīng)知悉為何以及如何處理這個(gè)問(wèn)題全景。
關(guān)于EXIF,這里面包含了很多有趣的內(nèi)容牵囤,比如iPhone拍攝后爸黄,可以記錄當(dāng)時(shí)的GPS位置,這樣在查看照片的時(shí)候就可以很神奇的知道照片的拍攝地揭鳞。如果感興趣可以去一探究竟炕贵。
另外,除去專(zhuān)門(mén)的照片瀏覽工具汹桦,所有的現(xiàn)代瀏覽器也天生具備查看圖片的功能鲁驶。而且有很多瀏覽器也已經(jīng)支持EXIF中的Orientation鉴裹,比如Firefox, Chrome, Safari舞骆。但同樣很可惜,IE并不支持(一直到IE9.0尚不支持)径荔。也許和Win7設(shè)計(jì)時(shí)并沒(méi)有這些具有方向傳感器的手機(jī)有關(guān)督禽,我從網(wǎng)上了解到,在當(dāng)初2012年收集building Windows8意見(jiàn)時(shí)总处,就有人提到過(guò)這一問(wèn)題狈惫,希望能夠考慮圖片的方向信息,微軟也給出了回應(yīng):
(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.
但我一直沒(méi)有用過(guò)Windows8,如果有使用過(guò)的胧谈,希望可以幫我驗(yàn)證一下是否微軟已經(jīng)修復(fù)這個(gè)問(wèn)題忆肾。
(全文完)