首页 > 解决方案 > 使用 Image.Save 和 EncoderParameters 无损旋转 JPG 图像失败

问题描述

我必须在 .net (90°|180°|270°) 中无损旋转 JPG 图像。以下文章展示了如何做到这一点:

这些例子看起来很简单。但是,我没有运气让它发挥作用。我的源数据以数组的形式出现(各种 JPG 文件,来自互联网的相机等),所以我想将旋转后的图像也作为字节数组返回。这里是(简化的)代码:

Image image;
using (var ms = new MemoryStream(originalImageData)) {
    image = System.Drawing.Image.FromStream(ms);
}

// If I don't copy the image into a new bitmap, every try to save the image fails with a general GDI+ exception. This seems to be another bug of GDI+.
var bmp = new Bitmap(image);    

// Creating the parameters for saving
var encParameters = new EncoderParameters(1);            
encParameters.Param[0] = new EncoderParameter(Encoder.Transformation, (long)EncoderValue.TransformRotate90);              
using (var ms = new MemoryStream()) {                
    // Now saving the image, what fails always with an ArgumentException from GDI+
    // There is no difference, if I try to save to a file or to a stream.
    bmp.Save(ms, GetJpgEncoderInfo(), encParameters);
    return ms.ToArray();
}

我总是ArgumentException从 GDI+ 得到一个没有任何有用信息的:

操作失败,出现最后一个异常 [ArgumentException]。
来源:System.Drawing

我尝试了很多东西,但从未成功过。主要代码似乎是正确的,因为如果我将 to 更改EncoderParameterEncoder.Quality,代码可以正常工作:

encParameters.Param[0] = new EncoderParameter(Encoder.Quality, 50L);

我在互联网上发现了一些关于这个问题的有趣帖子,但没有真正的解决方案。其中一个特别包含 Hans Passant 的声明,这似乎真的是一个错误,来自 MS 员工的回应,我不明白,或者这也可能很奇怪:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/de74ec2e-643d-41c7-9d04-254642a9775c/imagesave-quotparameter-is-not-validquot-in-windows-7?forum=netfxbcl

然而,这篇文章已有 10 年的历史,我不敢相信这不是固定的,特别是因为转换在 MSDN 文档中有一个明确的例子。

有没有人有提示,我做错了什么,或者,如果这真的是一个错误,我该如何规避它?

请注意,我必须使转换无损(只要像素大小允许)。因此,Image.RotateFlip不是一个选择。

Windows 版本是 10.0.17763,.Net 是 4.7.2

标签: c#.netjpeggdi+system.drawing

解决方案


using (var ms = new MemoryStream(originalImageData)) {
    image = System.Drawing.Image.FromStream(ms);
}

这是万恶之源,使第一次尝试失败。它违反了文档备注部分中规定的规则,您必须在 Image 的生命周期内保持流打开。违反规则不会导致一致的麻烦,请注意 Save() 调用如何失败但 Bitmap(image) 构造函数成功。GDI+ 有点懒惰,你有很好的证据表明 JPEG 编解码器确实试图避免重新压缩图像。但这是行不通的,因为流被处理,流中的原始数据不再可访问。这个例外很糟糕,因为本机 GDI+ 代码不知道有关 MemoryStream 的 bean。修复很简单,只需在 Save() 调用后移动右括号。

从那里开始,它又以另一种方式出错,主要由新bmp对象触发。imagebmp对象都没有被处理。这会很快消耗地址空间,因为位图的数据存储在非托管内存中,GC 无法经常运行以使您摆脱麻烦。现在,当 MemoryStream 无法再分配内存时,Save() 调用将失败。

必须在这些对象上使用该using语句,这样就不会发生这种情况。

应该解决问题,请摆脱位图解决方法,因为这会强制重新压缩 JPEG。从技术上讲,当图像很大时,您仍然会遇到麻烦,因为 32 位进程中的地址空间碎片会受到影响。密切关注进程的“私有字节”内存计数器,理想情况下它保持在千兆字节以下。如果没有,请使用 Project > Properties > Build 选项卡,取消勾选“Prefer 32-bit”。


推荐阅读