我已经在这个问题上花费了几个小时,现在我清楚发生了什么以及如何解决它。
重复一遍,我遇到的问题是:
当我使用 imagePickerController 在相机上以纵向模式拍摄图像并将该图像传递给 MFMailComposeViewController 并通过电子邮件发送时,它会到达目标电子邮件地址并在横向模式下错误地显示在那里。
但是,如果我以横向模式拍摄照片然后发送它,它会以横向模式正确显示在电子邮件的目的地
那么,我怎样才能使以纵向模式拍摄的照片以纵向模式到达电子邮件目的地?这是我最初的问题。
这是我在原始问题中展示的代码,除了我现在将图像作为 JPEG 而不是 PNG 发送,但这没有任何区别。
这就是我使用 imagePickerController 捕获图像并将其放入名为 gImag 的全局的方式:
gImag = (UIImage *)[info valueForKey: UIImagePickerControllerOriginalImage];
[[self imageView] setImage: gImag]; // send image to screen
[self imagePickerControllerRelease: picker ]; // free the picker object
这就是我使用 MFMailComposeViewController 发送电子邮件的方式:
if ( [MFMailComposeViewController canSendMail] )
{
MFMailComposeViewController * mailVC = [MFMailComposeViewController new];
NSArray * aAddr = [NSArray arrayWithObjects: gAddr, nil];
NSData * imageAsNSData = UIImageJPEGRepresentation( gImag );
[mailVC setMailComposeDelegate: self];
[mailVC setToRecipients: aAddr];
[mailVC setSubject: gSubj];
[mailVC addAttachmentData: imageAsNSData
mimeType: @"image/jpg"
fileName: @"myPhoto.jpg"];
[mailVC setMessageBody: @"Blah blah"
isHTML: NO];
[self presentViewController: mailVC
animated: YES
completion: nil];
}
else NSLog( @"Device is unable to send email in its current state." );
当我描述如何解决这个问题时,我将专注于使用 iPhone 5 并使用它的主摄像头拍摄 3264x2448 以保持简单。但是,此问题确实会影响其他设备和分辨率。
解开这个问题的关键是意识到当你拍摄一张图像时,无论是横向的纵向,iPhone 总是以相同的方式存储 UIImage:宽 3264,高 2448。
一个 UIImage 有一个属性,它描述了它在图像被捕获时的方向,你可以像这样得到它:
UIImageOrientation orient = image.imageOrientation;
请注意,orientation 属性不描述了 UIImage 中的数据是如何物理配置的(如 3264w x 2448h);它仅描述捕获图像时的方向。
该属性的作用是告诉即将显示图像的软件如何旋转它以使其正确显示。
如果您以纵向模式捕获图像,image.imageOrientation 将返回 UIImageOrientationRight。这告诉显示软件它需要将图像旋转 90 度才能正确显示。请注意,这种“旋转”不会影响 UIImage 的底层存储,它保持为 3264w x 2448h。
如果您在横向模式下捕获图像,image.imageOrientation 将返回 UIImageOrientationUp。 UIImageOrientationUp 告诉显示软件图像可以按原样显示;无需旋转。同样,UIIMage 的底层存储是 3264w x 2448h。
一旦您清楚数据的物理存储方式与orientation 属性如何用于描述其在捕获时的方向之间的区别,事情就会变得更加清晰。
我创建了几行调试代码来“查看”所有这些。
这里又是 imagePickerController 代码,添加了调试代码:
gImag = (PIMG)[info valueForKey: UIImagePickerControllerOriginalImage];
UIImageOrientation orient = gImag.imageOrientation;
CGFloat width = CGImageGetWidth(gImag.CGImage);
CGFloat height = CGImageGetHeight(gImag.CGImage);
[[self imageView] setImage: gImag]; // send image to screen
[self imagePickerControllerRelease: picker ]; // free the picker object
如果我们拍摄人像,gImage 会以 UIImageOrientationRight 和 width = 3264 和 height = 2448 到达。
如果我们拍摄风景,gImage 会以 UIImageOrientationUp 和 width = 3264 和 height = 2448 到达。
如果我们继续到 E-Mailng MFMailComposeViewController 代码,我也在其中添加了调试代码:
if ( [MFMailComposeViewController canSendMail] )
{
MFMailComposeViewController * mailVC = [MFMailComposeViewController new];
NSArray * aAddr = [NSArray arrayWithObjects: gAddr, nil];
UIImageOrientation orient = gImag.imageOrientation;
CGFloat width = CGImageGetWidth(gImag.CGImage);
CGFloat height = CGImageGetHeight(gImag.CGImage);
NSData * imageAsNSData = UIImageJPEGRepresentation( gImag );
[mailVC setMailComposeDelegate: self];
[mailVC setToRecipients: aAddr];
[mailVC setSubject: gSubj];
[mailVC addAttachmentData: imageAsNSData
mimeType: @"image/jpg"
fileName: @"myPhoto.jpg"];
[mailVC setMessageBody: @"Blah blah"
isHTML: NO];
[self presentViewController: mailVC
animated: YES
completion: nil];
}
else NSLog( @"Device is unable to send email in its current state." );
这里没什么了不起的。我们得到的值与我们在 imagePickerController 代码中所做的完全相同。
让我们看看问题的具体表现:
首先,相机拍摄一张人像照片,并在人像模式下按线条正确显示:
[[self imageView] setImage: gImag]; // send image to screen
它可以正确显示,因为这行代码看到了方向属性并适当地旋转了图像(同时没有触及 3264x2448 的底层存储)。
控制流现在转到电子邮件程序代码,并且方向属性仍然存在于 gImag 中,因此当 MFMailComposeViewController 代码在外发电子邮件中显示图像时,它的方向正确。物理图像仍存储为 3264x2448。
电子邮件已发送,但在接收端,方向属性的知识已丢失,因此接收软件将图像显示为物理布局为 3264x2448,即横向。
在调试时,我遇到了额外的困惑。也就是说,如果您不正确地复制它,则可以从 UIImage 中删除方向属性。
这段代码说明了问题:
if ( [MFMailComposeViewController canSendMail] )
{
MFMailComposeViewController * mailVC = [MFMailComposeViewController new];
NSArray * aAddr = [NSArray arrayWithObjects: gAddr, nil];
UIImageOrientation orient = gImag.imageOrientation;
CGFloat width = CGImageGetWidth(gImag.CGImage);
CGFloat height = CGImageGetHeight(gImag.CGImage);
UIImage * tmp = [[UIImage alloc] initWithCGImage: gImag.CGImage];
orient = tmp.imageOrientation;
width = CGImageGetWidth(tmp.CGImage);
height = CGImageGetHeight(tmp.CGImage);
NSData * imageAsNSData = UIImageJPEGRepresentation( tmp );
[mailVC setMailComposeDelegate: self];
[mailVC setToRecipients: aAddr];
[mailVC setSubject: gSubj];
[mailVC addAttachmentData: imageAsNSData
mimeType: @"image/jpg"
fileName: @"myPhoto.jpg"];
[mailVC setMessageBody: @"Blah blah"
isHTML: NO];
[self presentViewController: mailVC
animated: YES
completion: nil];
}
else NSLog( @"Device is unable to send email in its current state." );
当我们查看新 UIImage tmp 的调试数据时,我们得到 UIImageOrientationUp 和 width = 3264 和 height = 2448。
方向属性已被剥离,默认方向为向上。如果你不知道剥离是怎么回事,那真的很混乱。
如果我运行这段代码,我现在得到以下结果:
imagePickerController 代码中的东西没有改变;图像像以前一样被捕获。
控制流继续到 E-Mailer 代码,但现在方向属性已从 tmp 图像中剥离,因此当 MFMailComposeViewController 代码在外发电子邮件中显示 tmp 图像时,它以横向模式显示(因为默认方向是 UIImageOrientationUp 所以没有旋转 3264x2448 图像)。
电子邮件已发送,而在接收端,方向属性的知识也丢失了,因此接收软件将图像显示为物理布局为 3264x2448,即横向。
在制作 UIImage 的副本时,可以避免这种方向属性的“剥离”,如果有人知道它正在发生,通过使用以下代码制作 UIImage 副本:
UIImage * tmp = [UIImage imageWithCGImage: gImag.CGImage
scale: gImag.scale
orientation: gImag.imageOrientation];
这可以让您避免在途中丢失方向属性,但当您通过电子邮件发送图像时,它仍然不会处理远端的丢失。
有一个比这一切搞砸和担心方向属性更好的方法。
我发现了一些代码here,已集成到我的程序中。此代码将根据其方向属性物理旋转底层存储的图像。
对于方向为 UIImageOrientationRight 的 UIImage,它将物理旋转 UIImage,使其最终为 2448x3264,并且它会剥离方向属性,因此此后将其视为默认的 UIImageOrientationUp。
对于具有 UIImageOrientationUp 方向的 UIImage,它什么也不做。它让睡觉的风景狗躺着。
如果你这样做,那么我认为(基于我目前所见),UIImage 的方向属性此后就多余了。只要它仍然丢失/剥离或设置为 UIImageOrientationUp,您的图像应该在沿途的每一步正确显示,并且在显示嵌入在您的电子邮件中的图像时在远端正确显示。
我在答案中讨论的所有内容,我都亲眼目睹了它的发生。
所以,这是我的最终代码:
gImag = (PIMG)[info valueForKey: UIImagePickerControllerOriginalImage];
[[self imageView] setImage: gImag]; // send image to screen
[self imagePickerControllerRelease: picker ]; // free the picker object
和
if ( [MFMailComposeViewController canSendMail] )
{
MFMailComposeViewController * mailVC = [MFMailComposeViewController new];
NSArray * aAddr = [NSArray arrayWithObjects: gAddr, nil];
//...lets not touch the original UIImage
UIImage * tmpImag = [UIImage imageWithCGImage: gImag.CGImage
scale: gImag.scale
orientation: gImag.imageOrientation];
//...do physical rotation, if needed
PIMG ImgOut = [gU scaleAndRotateImage: tmpImag];
//...note orientation is UIImageOrientationUp now
NSData * imageAsNSData = UIImageJPEGRepresentation( ImgOut, 0.9f );
[mailVC setMailComposeDelegate: self];
[mailVC setToRecipients: aAddr];
[mailVC setSubject: gSubj];
[mailVC addAttachmentData: imageAsNSData
mimeType: @"image/jpg"
fileName: @"myPhoto.jpg"];
[mailVC setMessageBody: @"Blah blah"
isHTML: NO];
[self presentViewController: mailVC
animated: YES
completion: nil];
}
else NSLog( @"Device is unable to send email in its current state." );
最后,这是我从here 获取的代码,如有必要,可以进行物理旋转:
- (UIImage *) scaleAndRotateImage: (UIImage *) imageIn
//...thx: http://blog.logichigh.com/2008/06/05/uiimage-fix/
{
int kMaxResolution = 3264; // Or whatever
CGImageRef imgRef = imageIn.CGImage;
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
CGAffineTransform transform = CGAffineTransformIdentity;
CGRect bounds = CGRectMake( 0, 0, width, height );
if ( width > kMaxResolution || height > kMaxResolution )
{
CGFloat ratio = width/height;
if (ratio > 1)
{
bounds.size.width = kMaxResolution;
bounds.size.height = bounds.size.width / ratio;
}
else
{
bounds.size.height = kMaxResolution;
bounds.size.width = bounds.size.height * ratio;
}
}
CGFloat scaleRatio = bounds.size.width / width;
CGSize imageSize = CGSizeMake( CGImageGetWidth(imgRef), CGImageGetHeight(imgRef) );
UIImageOrientation orient = imageIn.imageOrientation;
CGFloat boundHeight;
switch(orient)
{
case UIImageOrientationUp: //EXIF = 1
transform = CGAffineTransformIdentity;
break;
case UIImageOrientationUpMirrored: //EXIF = 2
transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
break;
case UIImageOrientationDown: //EXIF = 3
transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;
case UIImageOrientationDownMirrored: //EXIF = 4
transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
break;
case UIImageOrientationLeftMirrored: //EXIF = 5
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;
case UIImageOrientationLeft: //EXIF = 6
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;
case UIImageOrientationRightMirrored: //EXIF = 7
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeScale(-1.0, 1.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;
case UIImageOrientationRight: //EXIF = 8
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;
default:
[NSException raise: NSInternalInconsistencyException
format: @"Invalid image orientation"];
}
UIGraphicsBeginImageContext( bounds.size );
CGContextRef context = UIGraphicsGetCurrentContext();
if ( orient == UIImageOrientationRight || orient == UIImageOrientationLeft )
{
CGContextScaleCTM(context, -scaleRatio, scaleRatio);
CGContextTranslateCTM(context, -height, 0);
}
else
{
CGContextScaleCTM(context, scaleRatio, -scaleRatio);
CGContextTranslateCTM(context, 0, -height);
}
CGContextConcatCTM( context, transform );
CGContextDrawImage( UIGraphicsGetCurrentContext(), CGRectMake( 0, 0, width, height ), imgRef );
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return( imageCopy );
}
干杯,来自新西兰。