使用過iPhone或者iPad的朋友在拍照時不知是否遇到過這樣的問題,將設備中的照片導出到Windows上時,常常發現導出的照片方向會有問題,要麼橫着,要麼顛倒着,須要旋轉才適合觀看。而若是直接在這些設備上瀏覽時,照片會始終顯示正確的方向,在Mac上也能正確顯示。最近在iOS的開發中也遇到了一樣的問題,將拍攝的照片上傳到服務器後,再由Windows端下載該照片,發現手機上徹底正常的照片到了這裏顯示的橫七豎八。同一張照片爲何在不一樣的設備上表現的不一樣?如何可以避免這種狀況?本文將和你們一一解開這些問題。瀏覽器
一切都得從相機的發展開始提及。服務器
通常相機拍攝出來的畫面都是長方形,在拍攝的那一瞬間,它會將取景器中的場景對應的顏色值存到對應的像素位置。相機自己並無任何方向的概念,只是使用者想要拍攝的場景在他指望的照片中顯示的方式與實際存在差別時,纔有了方向一說。以下圖,對一個場景F
進行拍攝,相機的方向可能會有這樣四個常見的角度:markdown
相機是「自私」的,因爲相機僅反應真實的場景,它不理解拍攝的內容,所以照片都以相機的座標系保存,因而上面四種情形實際拍攝出來的照片會像這樣:app
最初的卡片機時代,照片都會經由底片洗出來。那時不存在照片的方向問題,由於無論咱們以何種角度拍攝,最終洗出來的照片,它自己很是容易旋轉,因此咱們總能夠經過簡單的旋轉來觀看照片或者保存照片。好比這張照片牆中的照片,你可否說哪些照片是橫着?哪些顛倒着?你甚至都沒法判斷每張照片相機是以何種角度拍攝的,由於每張都已經旋轉至適合觀看的角度。iphone
但是到了數碼時代,再也不須要底片,照片須要被存成一個圖像文件。對於上面的拍攝角度,存儲方式並無變化,全部的場景仍然是以相機的座標系來保存。因而這些照片仍像上面同樣,原封不動的保存了下來:ide
雖然存儲方式不變,和卡機機時代的實體相片不一樣的是,因爲電腦屏幕可沒洗出來的照片那麼容易旋轉,因此照片只可以以它存儲於磁盤中的方向來展現。這即是爲什麼照片傳到電腦上以後,會出現橫了,或者顛倒的狀況。正由於這樣,咱們只有利用工具來旋轉照片纔可以正常觀看。工具
爲了克服這一狀況,讓照片能夠真實的反應人們拍攝時看到的場景,如今不少相機中就加入了方向傳感器,它可以記錄下拍攝時相機的方向,並將這一信息保存在照片中。照片的存儲方式仍是沒有任何改變,它仍然是以相機的座標系來保存,只是當相機來瀏覽這些照片時,相機能夠根據照片中的方向信息,結合此時相機的方向,對照片進行旋轉,從而轉到適合人們觀看的角度。post
可是很遺憾,這一標準並無被普遍的傳播開來,或者說始終如一的貫徹,這也致使了本文所討論的問題。ui
那麼,方向信息究竟是記錄在照片的什麼位置?
瞭解圖像格式的朋友可能會知道,圖像通常都由兩大部分組成,一部分是數據自己,它記錄了每一個像素的顏色值,另一部分是文件頭,這裏面記錄着形如圖像的寬度,高度等信息。咱們所討論的方向信息即是被存儲於文件頭中。更爲具體一些:EXIF
中,維基百科上對其的解釋爲:
可交換圖像文件格式常被簡稱爲Exif(Exchangeable image file format),是專門爲數碼相機的照片設定的,能夠記錄數碼照片的屬性信息和拍攝數據… Exif能夠附加於JPEG、TIFF、RIFF等文件之中
注意:PNG格式的圖像中不包含。
在EXIF
涵蓋的各類信息之中,其中有一個叫作Orientation (rotation)
的標籤,用於記錄圖像的方向,這即是相機寫入方向信息的最終位置。它總共定義了八個值:
注意:對於上面的八種方向中,加了*
的並不常見,由於它們表明的是鏡像方向,若是不作任何的處理,無論相機以任何角度拍攝,都沒法出現鏡像的狀況。
這個表格表明什麼意義?咱們來看第一行,值爲1時,右邊兩列的值分別爲:Row #0 isTop
,Column #0 is Left side
,其實很好理解,它表示照片的第一行位於頂端,而第一列位於左側,那麼這張照片天然就是以正常角度拍攝的。
對着前面的四種拍攝角度,因爲相機都是以其自身的座標系來保存照片,所以每張照片對應的第一行和第一列的位置始終以下:
咱們來看第二張照片,這張照片須要逆時針旋轉90度纔可以正常觀看。旋轉以後,它的第一行位於左側,而第一列位於下側。如此一來,對比表格,它的Orientation
值爲8。因此說,這個Orientation
值提供了想要正常觀看圖像時應該旋轉的方式。
以一樣的方法,咱們能夠推斷出上面四種方式拍攝時,對應EXIF
中Orientation
的值以下所示:
因爲相機加上了方向傳感器的緣故,能夠很是容易的檢測出以上幾種拍攝角度,並將角度對應的Orientation
值保存至圖像中。查看圖像時,相機檢測到其EXIF
中的Orientation
信息,並將圖像旋轉相應的角度顯示給用戶,這樣便達到了智能顯示的目的。
做爲智能手機的重要組成部分,形形色色的傳感器天然必不可少。在iOS的設備中也是包含了這樣的方向傳感器,它也採用了一樣的方式來保存照片的方向信息到EXIF
中。可是它默認的照片方向並非豎着拿手機時的狀況,而是橫向,即Home鍵在右側,以下:
如此一來,若是豎着拿手機拍攝時,就至關於對手機順時針旋轉了90度,也即上面相機圖片中的最後一幅,那麼它的Orientation
值爲6。
在通過上面的分析以後,咱們來看看實際狀況如何。咱們分別在Mac和Windows平臺上對前面的論述作一個驗證。
能夠將照片從iOS設備中導出到Mac系統上,(注意,不可以使用iPhoto或者Photos來導入,由於這樣照片在導入以前會被自動調整好方向)在這裏咱們像Windows中同樣,將iPhone當成移動硬盤,直接訪問其照片。在Mac上可使用iTools這一神器。
而後用Mac上的預覽
程序查看其EXIF
屬性,經過預覽-工具-顯示檢查器
打開對話框,便可查看到照片中關於方向的詳細信息。下面四張圖分別展現了上面四種方向下拍得照片的Orientation
值:
Home鍵位於右側時,即相機的默認方向,值爲1。
Home鍵位於上側時,值爲8。
Home鍵位於左側時,值爲3。
Home鍵位於下側時,即正常手持手機的方向,值爲6。
對照前面的分析,徹底一致。並且照片顯示正常,說明在Mac上默認的預覽
程序會自動的處理EXIF
中的Orientation
信息。
再次提醒:照片存儲在手機中始終是以相機座標系保存的,只是瀏覽工做在讀取方向信息以後作了旋轉。
前面提到過,被寫在圖像文件頭中的方向信息並無被所有支持,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.
它恰好也可能爲下面八種值,這些值能夠和EXIF
中Orientation
的定義一一對應:
那麼咱們即可以根據這一屬性對圖像進行相應的旋轉,從而將圖像的原始數據旋轉至正確的方向,在瀏覽照片時無需方向信息即可正常瀏覽。
關於如何旋轉圖像,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.