修正UIImage的Orientation屬性

在 iOS 中展示圖片時有時會遇到圖片顛倒或者旋轉(zhuǎn)展示的情況搅方,通常來說這與圖片的 orientation 值有關(guān)系。

照片的“方向”

相機拍攝照片時是沒有方向概念的疯淫,只有使用者明確照片按照什么方向放置最合適宪卿,因此對于同一個方向的物體,例如 ^ 上箭頭望众,相機可能的拍攝角度就有上下左右四種匪补,能拍出 ^(上)、V(下)烂翰、<(左)夯缺、>(右) 這四種情況,而相機并不知道如何放置能正確表現(xiàn)其拍攝內(nèi)容的方向性甘耿。所以現(xiàn)代相機中加入了方向傳感器踊兜,能將拍攝時的相機方向記錄下來,需要顯示圖片的數(shù)字設(shè)備就可以根據(jù)相機的方向信息來正確擺放目標(biāo)圖片達到合理展示效果佳恬。

而這一方向信息就存放于圖像的 Exif 中捏境。EXIF 是一種可交換圖像文件格式于游,可以附加于 JPEG、TIFF垫言、RIFF 格式的圖像文件中贰剥,包含拍攝信息的內(nèi)容和索引圖或圖像處理軟件的版本等信息,也包括這里的方向信息 orientation筷频。

orientation 定義了八個值

Value 0th Row 0th Column
1 top left side
2 top right side
3 bottom right side
4 bottom left side
5 left side top
6 right side top
7 right side bottom
8 left side bottom

它們具體的區(qū)分如下圖所示

EXIFOrientation.png

從圖中可以看到蚌成,表格將圖片的 0th Row 定義為頂邊線,0th Column 定義為左邊線凛捏。例如担忧,對于 orientation 為 1,頂邊線就在頂部坯癣,左邊線就在左邊涵妥,所以這張照片就是以正常視角拍出來的。

正確展示照片

當(dāng)我們需要在 iOS 中展示一個圖片對象 UIImage 時坡锡,我們可以通過 UIImage 的 imageOrientation 屬性獲取到它的 orientation 信息,這是一個枚舉值窒所,它的定義如下

typedef NS_ENUM(NSInteger, UIImageOrientation) {
    UIImageOrientationUp,            // default orientation
    UIImageOrientationDown,          // 180 deg rotation
    UIImageOrientationLeft,          // 90 deg CCW
    UIImageOrientationRight,         // 90 deg CW
    UIImageOrientationUpMirrored,    // as above but image mirrored along other axis. horizontal flip
    UIImageOrientationDownMirrored,  // horizontal flip
    UIImageOrientationLeftMirrored,  // vertical flip
    UIImageOrientationRightMirrored, // vertical flip
};

對應(yīng)到圖片如下

UIImageOrientation.png

所以可以根據(jù) UIImage 的 orientation 屬性對 UIImage 做矩陣變換鹉勒,獲得正確方向下的圖片再展示,就不會出現(xiàn)展示的圖片旋轉(zhuǎn)顛倒的情況了吵取。

Github 有一個 UIImage 的 category 流傳很廣禽额,就是解決這一問題的,恰好在項目的歷史代碼中看到了皮官,下面是源碼

- (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;
    }

    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;
    }

    // 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;
}

這里用到了 CGAffineTransform 類來做矩陣變換脯倒,要注意一點是,合并兩個 Transform 有兩種方式捺氢,一種是 CGAffineTransformConcat(T1, T2)藻丢,它的執(zhí)行順序是 T1 -> T2,一種是 CGAffineTransformXXX(T1, T2)摄乒,它的執(zhí)行順序是 T2 -> T1悠反,所以在這里要注意所有變換操作都是后加入的先執(zhí)行。

fixOrientation 的基本思路是

  • 如果 orientation 為 UIImageOrientationUp 則不需要變換
  • 否則馍佑,首先對于鏡像類 orientation 先恢復(fù)原樣斋否,變?yōu)榉晴R像
  • 對于非鏡像類 orientation,旋轉(zhuǎn)回正常方向
  • 根據(jù)旋轉(zhuǎn)后的方向拭荤,利用 CoreGraphic 得到 UIImage 后返回

其中會有一些必要的移位操作茵臭,下面以 UIImageOrientationRightMirrored 為例進行具體操作說明

  • 這是 orientation 為 UIImageOrientationRightMirrored 的圖片
Step01.png
  • transform = CGAffineTransformScale(transform, -1, 1) 將圖片進行鏡像還原
Step02.png
  • transform = CGAffineTransformTranslate(transform, image.size.height, 0)將圖片移動到坐標(biāo)原點
Step03.png
  • transform = CGAffineTransformRotate(transform, -M_PI_2) 將圖片順時針旋轉(zhuǎn) 90 度
Step04.png
  • transform = CGAffineTransformTranslate(transform, 0, image.size.height) 將圖片移動到坐標(biāo)原點
Step05.png

這里還要注意一點,在最后進行 drawImage 操作時代碼如下

    switch (image.imageOrientation) {
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            CGContextDrawImage(ctx, CGRectMake(0,0,image.size.height,image.size.width), image.CGImage);
            break;
            
        default:
            CGContextDrawImage(ctx, CGRectMake(0,0,image.size.width,image.size.height), image.CGImage);
            break;
    }

對于左右放置的圖片舅世,由于進行了旋轉(zhuǎn)操作旦委,因此最終進行 drawImage 時奇徒,rect 需要進行長寬變換。

而實際上社证,UIImage 的 drawInRect 方法已經(jīng)實現(xiàn)了這一過程逼龟,并自動返回調(diào)整后的圖片,且圖片 orientation 為 UIImageOrientationUp追葡。

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.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腺律,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宜肉,更是在濱河造成了極大的恐慌匀钧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬返,死亡現(xiàn)場離奇詭異之斯,居然都是意外死亡,警方通過查閱死者的電腦和手機遣铝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門佑刷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酿炸,你說我怎么就攤上這事瘫絮。” “怎么了填硕?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵麦萤,是天一觀的道長。 經(jīng)常有香客問我扁眯,道長壮莹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任姻檀,我火速辦了婚禮命满,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绣版。我一直安慰自己周荐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布僵娃。 她就那樣靜靜地躺著概作,像睡著了一般。 火紅的嫁衣襯著肌膚如雪默怨。 梳的紋絲不亂的頭發(fā)上讯榕,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天,我揣著相機與錄音,去河邊找鬼愚屁。 笑死济竹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的霎槐。 我是一名探鬼主播送浊,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丘跌!你這毒婦竟也來了袭景?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤闭树,失蹤者是張志新(化名)和其女友劉穎耸棒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體报辱,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡与殃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碍现。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幅疼。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖昼接,靈堂內(nèi)的尸體忽然破棺而出衣屏,到底是詐尸還是另有隱情,我是刑警寧澤辩棒,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站膨疏,受9級特大地震影響一睁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜佃却,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一者吁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饲帅,春花似錦复凳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赦邻,卻和暖如春髓棋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抡笼。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓喊递,卻偏偏與公主長得像,于是被迫代替她去往敵國和親翘单。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容