必须用ImageCodecInfo+EncoderParameters显式设置Quality参数才能控制JPG质量,Save(..., ImageFormat.Jpeg)因使用无参编码器而无效;Quality值为0–100的long类型,推荐文字图90–95、摄影图80–85;务必Dispose()释放资源防文件锁。

用 Image.Save() 保存为 JPG,但默认质量差
直接调用 image.Save("a.jpg") 会走系统默认编码器,对 JPG 来说通常等效于质量 80 左右,肉眼可见模糊,尤其文字或线条图。这不是 bug,是 .NET 的保守默认——它优先保兼容性,不保画质。
真正控制质量,必须显式传入 EncoderParameters,指定 Encoder.Quality。关键点:JPG 编码器不认文件扩展名,只认你给的 ImageCodecInfo 和参数。
- 先用
ImageCodecInfo.GetImageEncoders()找到 JPEG 对应的编码器(MimeType == "image/jpeg") - 构造
EncoderParameters,含一个EncoderParameter(Encoder.Quality, 95L)(值范围 0–100,必须是 long 类型) - 调用
image.Save(path, encoderInfo, encoderParams),三参数缺一不可
为什么 Save(..., ImageFormat.Jpeg) 不生效
这个重载看似简洁,实际绕过了质量控制:它内部用的是无参编码器,等价于没传 EncoderParameters。哪怕你写 image.Save("x.jpg", ImageFormat.Jpeg),质量仍是默认值。
根本原因:.NET 的 ImageFormat.Jpeg 只是格式标识,不携带编码参数。它背后绑定的编码器实例是“裸”的,没有 Quality 设置。
- 错误写法:
image.Save("out.jpg", ImageFormat.Jpeg)→ 质量固定,不可控 - 正确路径:必须通过
ImageCodecInfo+EncoderParameters组合调用 - 注意
Encoder.Quality的 GUID 是{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB},别手写错
压缩质量设太高反而可能变大
JPG 是有损压缩,但“高质量”不等于“小文件”。设 95–100 时,高频细节保留多,量化表压制弱,某些平滑渐变图反而比 85 时体积更大。
实测常见阈值:文字/图标类图选 90–95;摄影类图 80–85 更平衡。别盲目追 100 —— 它不是“无损”,只是“少损”,且解码兼容性略降(极老设备可能报错)。
- 质量 100 生成的 JPG,部分 Android 4.x 解码器会失败,报
System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+ - 质量低于 30 后,色块和振铃效应明显,人眼易察觉,但体积下降趋缓
- 若需最小体积,建议用 75–80 + 后续用
jpegtran或cjpeg二次优化
记得释放资源,否则图片文件被锁住
用完 Image 实例后不调 Dispose(),会导致文件句柄未释放,下次保存同名文件时抛 IOException: The process cannot access the file。
尤其在循环批量处理时,漏掉 Dispose 是最常踩的坑。别依赖 GC —— GDI+ 句柄是稀缺资源,GC 不保证及时回收。
- 务必用
using (var img = Image.FromFile("in.png")) { ... img.Save(...) } - 如果从内存流创建,也要确保流未被其他地方持有或已关闭
- 保存后立即读取该 JPG 文件?先确认
img.Dispose()已执行,再开新FileStream
质量参数是 long、编码器要手动找、Save 两参数重载无效、文件锁容易忘 —— 这四点串起来,才是 C# 写 JPG 真正卡人的地方。









