* m% c/ G- ^6 |* d! g L- y8 b: ] j! j7 s
Home键位于上侧时,值为8。8 w; h$ @2 x E- c2 |2 n) k; E0 v
8 |6 u( H; {# t) L. L* g- K4 ~3 G
) Y& v$ ]# }" J4 \ Home键位于左侧时,值为3。
7 f2 w* B# u# j
v `3 C8 o! E& O" ^1 e
8 G$ @; F. n- r( a* N Home键位于下侧时,即正常手持手机的方向,值为6。
3 k1 H, E% |* Z
5 |' L0 R5 o+ ?$ }: w* K 对照前面的分析,完全一致。而且照片显示正常,说明在Mac上默认的预览程序会自动的处理EXIF中的Orientation信息。
9 ~# q" G( Z, Z4 [ 再次提醒:照片存储在手机中始终是以相机坐标系保存的,只是浏览工作在读取方向信息之后做了旋转。% J2 ^& ~; i; s H7 @ ?" p" O
Windows平台
- [4 v4 h! R" h& [; z/ _2 `- M 前面提到过,被写在图像文件头中的方向信息并没有被全部支持,Windows的照片查看器便是其中之一,这也是Windows用户最常使用的照片浏览工具。因为没有读取方向信息,照片被读入之后,完全按照其存储方式来显示,这样便出现了横向,或者颠倒的情况。下面四张图便分别是上一节中拍得的照片在Windows上的显示效果,注意看方向。6 f- Q0 R: U3 V' S3 O: x
) _# R4 C5 ?9 M$ \
' k% Z+ r7 c+ Q2 K
开发时如何避免9 B% ?, Q( p# ]' O# ?: G L0 w( W
既然不是所有的工具都支持方向属性,这其中甚至包含了具有最多用户群体的Windows,那么我们在开发照片相关的应用时,有没有什么应对之策?+ R6 {& Y. y$ l
当然有!因为可以非常容易的得到照片的方向信息,那么只需要在保存之前将照片旋转至正常观看的方向即可,然后直接将最终具有正确方向的照片保存下来,搞定。
4 q2 P. C. Z; J7 r 当我们得到一个UIImage对象时,它有一个属性叫:imageOrientation,这里面便保存了方向信息:Property
, n1 z! X& e0 p* yThe orientation of the receiver’s image. (read-only)6 p+ ?& Z2 f! Q& c( Q9 H2 K' {" @& Y
Discussion. ]* w5 F7 A# {% z* v0 Z2 X
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.
2 L# k2 c1 N2 ]% m 它刚好也可能为下面八种值,这些值可以和EXIF中Orientation的定义一一对应:
, b+ a8 O/ }6 Y) u' }
7 G& w& x, V1 E. G! G% a
那么我们便可以根据这一属性对图像进行相应的旋转,从而将图像的原始数据旋转至正确的方向,在浏览照片时无需方向信息便可正常浏览。
+ g6 g @+ X( x2 t3 u2 m2 P" E 关于如何旋转图像,StackOverflow上给出了很好的答案,比如这个。我们简单做一个介绍:
6 o1 q s: b/ r: Z 直观的解决方案
$ x1 c4 d; |/ K/ r0 ? 首先,为UIImage创建一个category,其中包含fixOrientation方法:
) n4 M8 ?7 u5 Z) T! l2 I9 h UIImage+fixOrientation.h @interface UIImage (fixOrientation)! h/ a& [0 L5 L! Q: g6 q) O
8 x/ P& U$ @; Q7 Q7 @ - (UIImage *)fixOrientation;4 D1 f: w- @$ o' W! L% I: H
8 X; f& Z1 p6 m* ^5 o4 B1 j* Y
@end' U: G2 G, h3 y0 l
UIImage+fixOrientation.m @implementation UIImage (fixOrientation)2 |! I+ [: h6 G! @) `$ d+ }6 U
$ W9 Y/ l8 o2 ?6 [; o1 S4 {0 ` - (UIImage *)fixOrientation {
" Y3 w. K7 p8 B# `( U1 | ]9 ^ B7 X
! r* g+ n- S" \, O // No-op if the orientation is already correct
! @+ N( A6 L$ _5 o: s- c$ W1 |! g if (self.imageOrientation == UIImageOrientationUp) return self;
$ O/ K, Q9 K D5 j! g8 S; h 7 X; j, ?2 R* Z: |
// We need to calculate the proper transformation to make the image upright.; E# N# i& t6 B; r4 W" m- L0 k8 L
// We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.0 g, X: a+ G+ k$ B
CGAffineTransform transform = CGAffineTransformIdentity;8 ?4 _) z7 ?/ G" d, a+ O
- q1 k; B& t w6 ]
switch (self.imageOrientation) {
5 a* k6 i" _, G6 J9 w: j case UIImageOrientationDown:2 @( p) f& p4 j% F! ^. E
case UIImageOrientationDownMirrored:
; D- w( i$ Y3 }+ U3 X1 k* J4 y transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);" I. I! h4 y% e/ q4 I
transform = CGAffineTransformRotate(transform, M_PI);3 a0 `7 ~' E* r0 h- b" s
break;
( M8 ^/ c% {/ q5 D, p" |$ Y1 Q
1 [% e' Z1 s. @0 h% {( i% o2 { case UIImageOrientationLeft:
$ c( T. Z, |8 J* F3 I/ d/ C! J# x case UIImageOrientationLeftMirrored:/ c8 I) u0 {; T9 e
transform = CGAffineTransformTranslate(transform, self.size.width, 0);% q( n, n$ ^/ _$ x: z1 A- E% K! y& J
transform = CGAffineTransformRotate(transform, M_PI_2);. b1 G* g$ l8 P1 L/ o+ G
break;& U5 w7 a j& |: z( t2 d
5 V% _: k- @! ?' h2 Q* u0 i
case UIImageOrientationRight:# c! F! z# l' o; s
case UIImageOrientationRightMirrored:4 s/ o& v% @9 X
transform = CGAffineTransformTranslate(transform, 0, self.size.height);- |5 @2 J9 d0 p0 @
transform = CGAffineTransformRotate(transform, -M_PI_2);
( c, B' ~& a2 g) k1 R break;
8 g# B1 D, n5 y$ f! N case UIImageOrientationUp:) `/ Z$ E1 Y( b6 D* {2 X
case UIImageOrientationUpMirrored:# d) I4 s; L4 V& b
break;
$ U; Y: I5 F/ y z }
* w7 ?) p D* q. T6 Z1 J+ O* [$ i
) f0 A3 ^/ G0 R2 ` switch (self.imageOrientation) {
4 ~1 m0 l/ W+ R9 F8 b case UIImageOrientationUpMirrored:* i* n' F: x3 D8 Z: b
case UIImageOrientationDownMirrored:
3 T' L5 _# q2 Z( d ~- @9 o& ^ transform = CGAffineTransformTranslate(transform, self.size.width, 0);, X4 Z6 ~* W. s9 S
transform = CGAffineTransformScale(transform, -1, 1);
' [' p9 o* x2 m$ Q6 C break;
& B7 }7 k5 j! y6 @7 m, c3 H6 z4 Z ' X- D! n3 V' N+ A
case UIImageOrientationLeftMirrored:
/ J5 U+ O+ [* n& k case UIImageOrientationRightMirrored:
# L+ H1 h% q7 n; N' Z transform = CGAffineTransformTranslate(transform, self.size.height, 0);( Y" L4 u* J& T: }, E
transform = CGAffineTransformScale(transform, -1, 1);1 _$ I2 j9 V* t" h
break;% ` L1 z" J/ C& v! s5 k) w) Q/ R
case UIImageOrientationUp:
: _- [. i6 ~% q1 B/ W case UIImageOrientationDown:
% v5 l% v0 _$ j2 Y6 C. w; G case UIImageOrientationLeft:$ |2 }6 [6 m9 u2 P- t+ N
case UIImageOrientationRight:0 x( O* A$ Q* ]2 V+ _+ K
break;
: s4 `. x& e4 Q% E/ n }; k" S* [% E$ p6 p
, m* r/ {) L8 x" X. x
// Now we draw the underlying CGImage into a new context, applying the transform
/ T) L0 C1 n7 @ n, P // calculated above.
0 T1 _+ K7 s: p/ W CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height,
8 Z" i* z ?$ d$ Q" j# A CGImageGetBitsPerComponent(self.CGImage), 0,# Z4 @6 j E7 c4 |) ]
CGImageGetColorSpace(self.CGImage),' P9 _1 \5 p7 s. s/ B3 O' s" u
CGImageGetBitmapInfo(self.CGImage));$ r* A0 p. \( r6 v" V6 P+ [1 K, c
CGContextConcatCTM(ctx, transform);) S# d, P; O( ?" `
switch (self.imageOrientation) {4 q) \* M X. b) S
case UIImageOrientationLeft:8 C# d% w9 p" B/ h+ K: ?2 U; q
case UIImageOrientationLeftMirrored:
( X! W, A- P; z. D0 \ case UIImageOrientationRight:' W" p* N. o& g' b J. I+ i
case UIImageOrientationRightMirrored:! ]' `7 U2 D6 B1 z5 |# X H) U8 D
// Grr...$ E F! X1 w6 W! V' l
CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage);
; r9 e* R& C1 V$ |0 U9 c break;; \& x _ U" H& U* g
- h; I0 D" l# o! q, A0 n- I4 D1 Y default:; D3 m: z1 W' [0 J7 h+ @! Z
CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage);
4 V! n+ i( H( H8 Z! Q break;5 j1 \- n( k; ^$ j5 A
}$ m4 R5 ?( J. h0 R+ h* G
7 J0 ?% u3 A# z: s$ G V // And now we just create a new UIImage from the drawing context
2 _/ a& @. `& s& `8 q( k* _ CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
8 p1 N9 X; B0 }0 x P3 v UIImage *img = [UIImage imageWithCGImage:cgimg];% H1 U. T1 p! ?6 D! F4 S$ s
CGContextRelease(ctx);' K! J* f# H( M9 n( l7 D$ c; l
CGImageRelease(cgimg);
9 t7 T! a5 u* R6 n" x7 U( W return img;7 [, z1 N0 k3 B* B
}$ ?: v2 \0 n7 F* d5 N1 V
" } i/ Z9 A0 V4 V# l @end
( g5 C* f9 G. _) f: Z 代码有些长,不过却非常直观。这里面涉及到图像矩阵变换的操作,理解起来可能稍稍有些困难,接下来,我会有另外一篇文章专门来介绍图像变换。现在,记住下面两点便能够很好的帮助理解:
5 ^% Y- B% @0 ? @
# T# W0 H% i$ U. e% x/ Y# F 图像的原点在左下角& s/ r' p J* N% }; T
- B) C# X2 I" D& F+ H5 w 矩阵变换时,后面的矩阵先作用,前面的矩阵后作用
$ B' G2 E3 |) X' [! U2 V6 ? 以UIImageOrientationDown方向为例,
,很明显它翻转了180度。那么对它的旋转需要两步,第一步是以左下方为原点旋转180度,(此时顺时针还是逆时针旋转效果一样)旋转后上图变为:
。用代码表示为:1 transform = CGAffineTransformRotate(transform, M_PI);. l6 g# v1 d& Q1 \; i F, i
因为是以左下方为原点旋转的,所以整幅图被移到了第三象限。第二步需要将其平移至第一象限,向右上方进行平移即可。x方向上移动距离为图像的宽度,y方向上移动距离为图像的高度,所以平移后图像变为:
。代码为:1 transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
, g. K8 {4 w1 N# m- n9 r 再加上我们前面所说的第二点,矩阵变换时,后面的矩阵先作用,前面的矩阵后作用,那么只需要将上面两步颠倒即可:1 transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);: C2 Z- g6 `6 M$ p: _9 @! ]: X
2 transform = CGAffineTransformRotate(transform, M_PI);
( J. a/ P$ ~/ m 其它的方向可以用完全一样的方法来分析,这里不再一一赘述。' U. e7 h5 n9 ?/ G& J$ S) Z
第二种简单的方法
" M6 w1 \( |+ y! N 第二种方法同样也是StackOverflow上的答案,没那么直观,但非常简单: - (UIImage *)normalizedImage {
5 e- m9 X. X3 A+ T \ if (self.imageOrientation == UIImageOrientationUp) return self;
- ]% [' _, Z$ b # X" }9 ~* ~! e/ P; R' q; g$ P
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
; c P- P: {- d% y+ { [self drawInRect CGRect){0, 0, self.size}];
; T# ~# c! T5 X+ r5 x$ v/ M UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
; D7 E% _- V( l2 h: m4 m- M UIGraphicsEndImageContext();& _1 _) H& A% x6 k# {9 Q
return normalizedImage;
9 {- j" \9 p7 }9 r. J/ T }3 q% s9 E @7 d, G
这里是利用了UIImage中的drawInRect方法,它会将图像绘制到画布上,并且已经考虑好了图像的方向,开发文档这样解释:-drawInRect:
: m9 Q0 J: C( [, k1 E" U% gDraws the entire image in the specified rectangle, scaling it as needed to fit.
1 b( x5 E3 C$ G. x/ V) b" W CDiscussion
0 J4 u5 k# J8 X- t& \5 TThis 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.! Z& z7 J4 z7 _
结尾: R: o0 c% [0 y! N2 d! M
关于照片方向的处理就介绍到这里,相信看完本文你已经知悉为何以及如何处理这个问题。& X) P8 q$ _9 x" P
关于EXIF,这里面包含了很多有趣的内容,比如iPhone拍摄后,可以记录当时的GPS位置,这样在查看照片的时候就可以很神奇的知道照片的拍摄地。如果感兴趣可以去一探究竟。$ U7 m% \) k$ Y$ S0 m
另外,除去专门的照片浏览工具,所有的现代浏览器也天生具备查看图片的功能。而且有很多浏览器也已经支持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.* ]- a$ A- H+ I: ~0 X3 q; D+ Q
但我一直没有用过Windows8,如果有使用过的,希望可以帮我验证一下是否微软已经修复这个问题。
: _9 Y) q( ?' p# _( Z$ F) H3 m$ ~5 o (全文完)8 {5 t/ m7 M) i: K9 X) e; A0 s; W
feihu% K4 V9 }6 v# t, B D7 }5 Q- P" E
2015.05.31 于 Shenzhen |