(轉)如何處理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

相關文章
相關標籤/搜索