使用 Image.Save 和 EncoderParameters 的 JPG 图像无损旋转失败
Lossless rotation of a JPG image with Image.Save and EncoderParameters fails
我必须在 .net 中无损旋转 JPG 图片 (90°|180°|270°)。以下文章展示了如何操作:
- https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoder.transformation?view=netframework-4.7.2
- https://www.codeproject.com/tips/64116/Using-GDIplus-code-in-a-WPF-application-for-lossle.aspx
这些例子看起来很简单;但是,我没有运气让这个工作。我的源数据以数组的形式出现(各种 JPG 文件,来自互联网的相机等),所以我想 return 旋转的图像也作为字节数组。这是(简化的)代码:
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();
}
我总是从 GDI+ 得到一个 ArgumentException
而没有任何有用的信息:
The operation failed with the final exception [ArgumentException].
Source: System.Drawing
我尝试了很多东西,但从来没有成功过。
主要代码似乎是正确的,因为如果我将 EncoderParameter
更改为 Encoder.Quality
,代码工作正常:
encParameters.Param[0] = new EncoderParameter(Encoder.Quality, 50L);
我在互联网上发现了一些关于这个问题的有趣 post,但是没有真正的解决方案。一个特别包含 Hans Passant 的声明,这似乎真的是一个错误,来自 MS 员工的回复,我不明白或者可能也很奇怪:
然而,这个 post 已有 10 年历史了,我无法相信,这还没有修复,尤其是因为转换在 MSDN 文档中有一个明确的示例。
有没有人提示我做错了什么,或者,如果这真的是一个错误,我该如何规避它?
请注意,我必须使转换无损(只要像素大小允许)。因此,Image.RotateFlip
不是一个选项。
Windows版本为10.0.17763,.Net为4.7.2
using (var ms = new MemoryStream(originalImageData)) {
image = System.Drawing.Image.FromStream(ms);
}
这是万恶之源,让第一次尝试失败了。它违反了 the documentation 的备注部分中规定的规则,您必须在图像的生命周期内保持流打开 。违反规则不会导致一致的麻烦,请注意 Save() 调用是如何失败但 Bitmap(image) 构造函数成功的。 GDI+ 有点懒惰,你有很好的证据表明 JPEG 编解码器确实试图避免重新压缩图像。但这是行不通的,流中的原始数据在流被处理后无法再访问。这个异常很糟糕,因为本机 GDI+ 代码不知道有关 MemoryStream 的 bean。修复很简单,只需在调用 Save() 之后移动右括号即可。
从那里它以另一种方式出错,主要由新的 bmp
对象触发。 image
和 bmp
对象都没有被释放。这会很快消耗地址 space,GC 不能 运行 足够频繁地让你远离麻烦,因为位图的数据存储在非托管内存中。现在,当 MemoryStream 无法再分配内存时,Save() 调用将失败。
您必须对这些对象使用using
语句,这样就不会发生这种情况。
应该解决这些问题,请摆脱位图解决方法,因为这会强制重新压缩 JPEG。从技术上讲,当图像很大时,您仍然会遇到麻烦,在 32 位进程中遭受地址 space 碎片的困扰。密切关注进程的 "Private bytes" 内存计数器,理想情况下它保持在 1 GB 以下。如果不是,则使用 Project > Properties > Build 选项卡,取消勾选 "Prefer 32-bit".
我必须在 .net 中无损旋转 JPG 图片 (90°|180°|270°)。以下文章展示了如何操作:
- https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoder.transformation?view=netframework-4.7.2
- https://www.codeproject.com/tips/64116/Using-GDIplus-code-in-a-WPF-application-for-lossle.aspx
这些例子看起来很简单;但是,我没有运气让这个工作。我的源数据以数组的形式出现(各种 JPG 文件,来自互联网的相机等),所以我想 return 旋转的图像也作为字节数组。这是(简化的)代码:
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();
}
我总是从 GDI+ 得到一个 ArgumentException
而没有任何有用的信息:
The operation failed with the final exception [ArgumentException].
Source: System.Drawing
我尝试了很多东西,但从来没有成功过。
主要代码似乎是正确的,因为如果我将 EncoderParameter
更改为 Encoder.Quality
,代码工作正常:
encParameters.Param[0] = new EncoderParameter(Encoder.Quality, 50L);
我在互联网上发现了一些关于这个问题的有趣 post,但是没有真正的解决方案。一个特别包含 Hans Passant 的声明,这似乎真的是一个错误,来自 MS 员工的回复,我不明白或者可能也很奇怪:
然而,这个 post 已有 10 年历史了,我无法相信,这还没有修复,尤其是因为转换在 MSDN 文档中有一个明确的示例。
有没有人提示我做错了什么,或者,如果这真的是一个错误,我该如何规避它?
请注意,我必须使转换无损(只要像素大小允许)。因此,Image.RotateFlip
不是一个选项。
Windows版本为10.0.17763,.Net为4.7.2
using (var ms = new MemoryStream(originalImageData)) {
image = System.Drawing.Image.FromStream(ms);
}
这是万恶之源,让第一次尝试失败了。它违反了 the documentation 的备注部分中规定的规则,您必须在图像的生命周期内保持流打开 。违反规则不会导致一致的麻烦,请注意 Save() 调用是如何失败但 Bitmap(image) 构造函数成功的。 GDI+ 有点懒惰,你有很好的证据表明 JPEG 编解码器确实试图避免重新压缩图像。但这是行不通的,流中的原始数据在流被处理后无法再访问。这个异常很糟糕,因为本机 GDI+ 代码不知道有关 MemoryStream 的 bean。修复很简单,只需在调用 Save() 之后移动右括号即可。
从那里它以另一种方式出错,主要由新的 bmp
对象触发。 image
和 bmp
对象都没有被释放。这会很快消耗地址 space,GC 不能 运行 足够频繁地让你远离麻烦,因为位图的数据存储在非托管内存中。现在,当 MemoryStream 无法再分配内存时,Save() 调用将失败。
您必须对这些对象使用using
语句,这样就不会发生这种情况。
应该解决这些问题,请摆脱位图解决方法,因为这会强制重新压缩 JPEG。从技术上讲,当图像很大时,您仍然会遇到麻烦,在 32 位进程中遭受地址 space 碎片的困扰。密切关注进程的 "Private bytes" 内存计数器,理想情况下它保持在 1 GB 以下。如果不是,则使用 Project > Properties > Build 选项卡,取消勾选 "Prefer 32-bit".