c# - 使用 Image.Save 和 EncoderParameters 无损旋转 JPG 图像失败
问题描述
我必须在 .net (90°|180°|270°) 中无损旋转 JPG 图像。以下文章展示了如何做到这一点:
- 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 文件,来自互联网的相机等),所以我想将旋转后的图像也作为字节数组返回。这里是(简化的)代码:
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 更改EncoderParameter
为Encoder.Quality
,代码可以正常工作:
encParameters.Param[0] = new EncoderParameter(Encoder.Quality, 50L);
我在互联网上发现了一些关于这个问题的有趣帖子,但没有真正的解决方案。其中一个特别包含 Hans Passant 的声明,这似乎真的是一个错误,来自 MS 员工的回应,我不明白,或者这也可能很奇怪:
然而,这篇文章已有 10 年的历史,我不敢相信这不是固定的,特别是因为转换在 MSDN 文档中有一个明确的例子。
有没有人有提示,我做错了什么,或者,如果这真的是一个错误,我该如何规避它?
请注意,我必须使转换无损(只要像素大小允许)。因此,Image.RotateFlip
不是一个选择。
Windows 版本是 10.0.17763,.Net 是 4.7.2
解决方案
using (var ms = new MemoryStream(originalImageData)) {
image = System.Drawing.Image.FromStream(ms);
}
这是万恶之源,使第一次尝试失败。它违反了文档备注部分中规定的规则,您必须在 Image 的生命周期内保持流打开。违反规则不会导致一致的麻烦,请注意 Save() 调用如何失败但 Bitmap(image) 构造函数成功。GDI+ 有点懒惰,你有很好的证据表明 JPEG 编解码器确实试图避免重新压缩图像。但这是行不通的,因为流被处理,流中的原始数据不再可访问。这个例外很糟糕,因为本机 GDI+ 代码不知道有关 MemoryStream 的 bean。修复很简单,只需在 Save() 调用后移动右括号。
从那里开始,它又以另一种方式出错,主要由新bmp
对象触发。image
和bmp
对象都没有被处理。这会很快消耗地址空间,因为位图的数据存储在非托管内存中,GC 无法经常运行以使您摆脱麻烦。现在,当 MemoryStream 无法再分配内存时,Save() 调用将失败。
您必须在这些对象上使用该using
语句,这样就不会发生这种情况。
应该解决问题,请摆脱位图解决方法,因为这会强制重新压缩 JPEG。从技术上讲,当图像很大时,您仍然会遇到麻烦,因为 32 位进程中的地址空间碎片会受到影响。密切关注进程的“私有字节”内存计数器,理想情况下它保持在千兆字节以下。如果没有,请使用 Project > Properties > Build 选项卡,取消勾选“Prefer 32-bit”。
推荐阅读
- powershell - 阻止移动/删除文件夹,但不阻止子文件夹或文件
- android - 即使应用程序在棉花糖中被杀死,如何避免杀死后台服务?
- mysql - 删除 FROM Kunden_ansprechpartner as a left join Ansprechpartner as b on a.AnsPaID=b.AnsPaID where b.AnsPaID=400 and Kdnr = 10088
- excel - VBA 打印 Outlook 项目
- php - 数组多维搜索返回所有多个键
- python - Pynput 侦听器不允许带有字母数字字符的 if 语句
- powershell - 如何创建 2 个运行时间相差一小时但从不重叠的计划任务?
- excel - 宏不运行公式?
- angular - 订阅多个异步 http 调用
- angular - 这个函数有什么问题?当我运行 ng serve (Angular) 时,它经常显示语法错误