在 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ū)分如下圖所示
從圖中可以看到蚌成,表格將圖片的 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)到圖片如下
所以可以根據(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 的圖片
-
transform = CGAffineTransformScale(transform, -1, 1)
將圖片進行鏡像還原
-
transform = CGAffineTransformTranslate(transform, image.size.height, 0)
將圖片移動到坐標(biāo)原點
-
transform = CGAffineTransformRotate(transform, -M_PI_2)
將圖片順時針旋轉(zhuǎn) 90 度
-
transform = CGAffineTransformTranslate(transform, 0, image.size.height)
將圖片移動到坐標(biāo)原點
這里還要注意一點,在最后進行 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.