iOS:将图像保存为PNG表示数据后,图像会旋转90度

47

我已经进行了足够的研究,但无法修复它。当我从相机中拍摄图片并将其存储为UIImage时,一切都很好,但是一旦我将此图像存储为PNG表示,它就会旋转90度。

以下是我的代码和我尝试的所有事情:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 
{
    NSString *mediaType = [info valueForKey:UIImagePickerControllerMediaType];

    if([mediaType isEqualToString:(NSString*)kUTTypeImage]) 
    {
        AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        delegate.originalPhoto  = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
        NSLog(@"Saving photo");
        [self saveImage];
        NSLog(@"Fixing orientation");
        delegate.fixOrientationPhoto  = [self fixOrientation:[UIImage imageWithContentsOfFile:[delegate filePath:imageName]]];      
        NSLog(@"Scaling photo");
        delegate.scaledAndRotatedPhoto  =  [self scaleAndRotateImage:[UIImage imageWithContentsOfFile:[delegate filePath:imageName]]];
    }
    [picker dismissModalViewControllerAnimated:YES];
    [picker release];
}


- (void)saveImage
{
    AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSData *imageData = UIImagePNGRepresentation(delegate.originalPhoto);
    [imageData writeToFile:[delegate filePath:imageName] atomically:YES];
}

这里的 fixOrientation 和 scaleAndRotateImage 函数分别取自 这里这里。它们可以很好地旋转 UIImage 中的图像,但如果我将其保存为 PNG 格式再应用这些函数,则无法工作。

请参考执行上述函数后的以下图片:

第一张照片是原始的,第二张是保存的,第三张和第四张是在保存的图像上应用 fixorientation 和 scaleandrotate 函数之后的效果


1
不好意思,伙计。你把所有关于这个问题的链接都粘贴上了。我已经浏览了它们! - Paresh Masani
1
匿名的,如果你给这个问题打负分,那说明你没理解这个问题!我看到你在这里有同样的问题 - https://dev59.com/Y3A65IYBdhLWcg3w7Taa :-) 你解决了吗? - Paresh Masani
可能是 iphone 相机拍摄的图像自动旋转 -90 度 的重复问题。 - Septronic
非常有用:
  1. 自然输出为横向
  2. .width / .height 受 .imageOrientation 影响
- Fattie
8个回答

75

iOS 4.0起,当相机拍照后,保存前不会自动旋转图片,而是在JPEG的EXIF数据中设置旋转标志。

如果将UIImage保存为JPEG,它会设置旋转标志。但PNG不支持旋转标志,因此如果将UIImage保存为PNG,则会错误地旋转并没有设置标志进行修正。所以,如果想要PNG图像,必须手动旋转它们,可以参考这个链接


我建议阅读这个答案,特别是由“an0”提供的答案似乎最为简洁。 - Mike Casa

30

Swift 4.2

将以下内容添加为 UIImage 扩展:

extension UIImage {
    func fixOrientation() -> UIImage? {
        if self.imageOrientation == UIImage.Orientation.up {
            return self
        }

        UIGraphicsBeginImageContext(self.size)
        self.draw(in: CGRect(origin: .zero, size: self.size))
        let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return normalizedImage
    }
}

用法示例:

let cameraImage = //image captured from camera
let orientationFixedImage = cameraImage.fixOrientation()

解释:

当您调用UIImage的draw(in:)函数时,神奇的事情发生了,它会重新绘制图像,并且尊重最初捕获的方向设置。[文档链接]

在调用draw之前,请确保使用图像的大小调用UIGraphicsBeginImageContext,以便让draw将UIImage重写到当前上下文的空间中。

UIGraphicsGetImageFromCurrentImageContext允许您捕获重新绘制后的图像结果,而UIGraphicsEndImageContext则结束并释放图形上下文。


非常有帮助。谢谢! - Baylor Mitchell
2
到目前为止,这可能是最好的答案,它不使用将函数拼凑在一起的黑客技巧。非常漂亮。 - Munib
错误 无法分配给值:函数调用返回不可变值 - King
点赞因为这个方法可行,但如果您能够添加一些解释为什么它可行,那就更好了,因为我无法理解。 - xleon
2
解释已添加。@bluewraith和xleon。 - Nagendra Rao
显示剩余2条评论

29

以下是 Rao 发布的 UIImage 扩展的 Swift 3.1 版本:

extension UIImage {
    func fixOrientation() -> UIImage {
        if self.imageOrientation == UIImageOrientation.up {
            return self
        }
        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
        self.draw(in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height))
        if let normalizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() {
            UIGraphicsEndImageContext()
            return normalizedImage
        } else {
            return self
        }
    }
}

使用方法:

let cameraImage = //image captured from camera
let orientationFixedImage = cameraImage.fixOrientation()

Swift 4/5:

UIImageOrientation.up已更名为UIImage.Orientation.up


1
对于Swift 4-5:只需将UIImageOrientation.up替换为UIImage.Orientation.up - Ganpat
整洁的解决方案打包在一个扩展中,为我节省了大量的测试工作,感谢 @Andreas。 - Jorge Vicente Mendoza

6

我发现以下提示非常有用:

1. 自然输出是横向

2. .width / .height会受到.imageOrientation的影响

3. 使用短边而不是.width


(1) 对于静态图像,相机的“自然”输出是横向。

这很反直觉。UIImagePickerController等仅提供肖像模式。但是在使用“普通”肖像相机时,您将获得UIImageOrientationRight作为“正常”方向。

(我记住这个方法-视频的自然输出当然是横向;因此静态图像也是一样的-即使iPhone全部都是竖向。)


(2).width和.height确实会受到.imageOrientation的影响!!!!!!!

请确保在iPhone上双重尝试此方法。

NSLog(@"fromImage.imageOrientation is %d", fromImage.imageOrientation);
NSLog(@"fromImage.size.width  %f   fromImage.size.height  %f",
            fromImage.size.width, fromImage.size.height);

你会发现 .height 和 .width 交换了位置,即使实际像素是横向的。
(3) 只使用 "短边" 而非 .width,通常可以解决许多问题。
我觉得这个方法非常有帮助。比如说你只想要图片的顶部正方形:
CGRect topRectOfOriginal = CGRectMake(0,0, im.size.width,im.size.width);

实际上,这样做是行不通的,当相机以横向方式保持时,你将得到一个压缩的图像。

但是,如果你只是非常简单地做如下操作

float shortDimension = fminf(im.size.width, im.size.height);
CGRect topRectOfOriginal = CGRectMake(0,0, shortDimension,shortDimension);

然后“一切都已解决”,实际上您< strong>“不需要担心”方向标志。再次强调,点(3)并非万能药,但它往往可以解决所有问题。希望这可以帮助某人节省时间。

我想在最后一个例子中,你应该在某个地方使用 shortDimension(也许是第二行中的 im.size.width 的替代?) - furins

3
我在这里找到了一段代码,它成功地解决了我的问题。在我的应用中,我拍照并保存后,每次加载图片时都会出现烦人的旋转(我查了一下,这似乎与EXIF和iPhone拍摄和存储图像的方式有关)。这段代码为我解决了这个问题。我必须说,最初它是作为一个类/扩展/分类的补充存在的(你可以从链接中找到原始代码)。我像下面这样使用它作为一个简单的方法,因为我不想为此创建一个整个的类或分类。我只使用了竖屏,但我认为这段代码适用于任何方向。不过我不确定。
废话少说,以下是代码:
- (UIImage *)fixOrientationForImage:(UIImage*)neededImage {

    // No-op if the orientation is already correct
    if (neededImage.imageOrientation == UIImageOrientationUp) return neededImage;

    // 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 (neededImage.imageOrientation) {
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, neededImage.size.width, neededImage.size.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            transform = CGAffineTransformTranslate(transform, neededImage.size.width, 0);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;

        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, 0, neededImage.size.height);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationUpMirrored:
            break;
    }

    switch (neededImage.imageOrientation) {
        case UIImageOrientationUpMirrored:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, neededImage.size.width, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;

        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, neededImage.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, neededImage.size.width, neededImage.size.height,
                                             CGImageGetBitsPerComponent(neededImage.CGImage), 0,
                                             CGImageGetColorSpace(neededImage.CGImage),
                                             CGImageGetBitmapInfo(neededImage.CGImage));
    CGContextConcatCTM(ctx, transform);
    switch (neededImage.imageOrientation) {
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            // Grr...
            CGContextDrawImage(ctx, CGRectMake(0,0,neededImage.size.height,neededImage.size.width), neededImage.CGImage);
            break;

        default:
            CGContextDrawImage(ctx, CGRectMake(0,0,neededImage.size.width,neededImage.size.height), neededImage.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;
}

我不确定这对你有多大帮助,但我希望它能有所帮助 :)


它可以正常工作,但会压缩图像质量。旋转后的图像质量不好。 - Ananta Prasad
谢谢你的回答。我在我的应用程序中使用了你的代码,但是我收到了崩溃报告,似乎从这个函数返回的图像已经损坏了。看起来这个函数会导致内存泄漏。你是否遇到了同样的问题,我该如何解决? - Karen Karapetyan
@Ananta Prasad 嗯,我会去查看并回复你。 - Septronic
@Karapetyan 我会调查导致问题的原因。你正在使用哪个版本的Xcode? - Septronic

3
尝试一下, 您可以使用

NSData *somenewImageData = UIImageJPEGRepresentation(newimg,1.0);

替代

NSData *somenewImageData = UIImagePNGRepresentation(newimg);

1
请尝试以下代码。
UIImage *sourceImage = ... // Our image
CGRect selectionRect = CGRectMake(100.0, 100.0, 300.0, 400.0);
CGImageRef resultImageRef = CGImageCreateWithImageInRect(sourceImage.CGImage,
selectionRect);
UIImage *resultImage = [[UIImage alloc] initWithCGImage:resultImageRef];

而且

CGRect TransformCGRectForUIImageOrientation(CGRect source, UIImageOrientation orientation, CGSize imageSize) {
switch (orientation) {
case UIImageOrientationLeft: { // EXIF #8
  CGAffineTransform txTranslate = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
  CGAffineTransform txCompound = CGAffineTransformRotate(txTranslate,M_PI_2);
  return CGRectApplyAffineTransform(source, txCompound);
}
case UIImageOrientationDown: { // EXIF #3
  CGAffineTransform txTranslate = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
  CGAffineTransform txCompound = CGAffineTransformRotate(txTranslate,M_PI);
  return CGRectApplyAffineTransform(source, txCompound);
}
case UIImageOrientationRight: { // EXIF #6
  CGAffineTransform txTranslate = CGAffineTransformMakeTranslation(0.0, imageSize.width);
  CGAffineTransform txCompound = CGAffineTransformRotate(txTranslate,M_PI + M_PI_2);
  return CGRectApplyAffineTransform(source, txCompound);
}
case UIImageOrientationUp: // EXIF #1 - do nothing
  default: // EXIF 2,4,5,7 - ignore
  return source;
}
}
  ...
UIImage *sourceImage = ... // Our image
 CGRect selectionRect = CGRectMake(100.0, 100.0, 300.0, 400.0);
CGRect transformedRect = TransformCGRectForUIImageOrientation(selectionRect,  sourceImage.imageOrientation, sourceImage.size);
CGImageRef resultImageRef = CGImageCreateWithImageInRect(sourceImage.CGImage, transformedRect);
UIImage *resultImage = [[UIImage alloc] initWithCGImage:resultImageRef];

我已经参考了以下链接,请查看更多详细信息

最好的问候 :-)


谢谢,但这个方法也没有起作用。我使用了CGRect selectionRect = CGRectMake(0, 0, sourceImage.size.width, sourceImage.size.height);来旋转整个图像,但它仍然显示为90度旋转。请注意,我是在将图像存储在文档文件夹后执行此操作的。谢谢。 - Paresh Masani
你从哪里传递“orientation”值给“CGRect TransformCGRectForUIImageOrientation(CGRect source, UIImageOrientation orientation, CGSize imageSize)”函数呢? - Developer

0

尝试这段代码:

NSData *imageData = UIImageJPEGRepresentation(delegate.originalPhoto,100);

这并没有提供对问题的答案。如果你想批评或请求作者澄清,请在他们的帖子下方留言 - 你总是可以评论自己的帖子,一旦你获得足够的声望,你就能够评论任何帖子。 - ericbn
1
当您将图像存储为PNG表示时,会出现旋转问题。如果您将图像存储为JPEG表示,则图像不会旋转! - Mubasher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接