必须用encoderparameters显式设置quality参数(0–100l)并配合imagecodecinfo获取jpeg编码器,才能控制image.save的jpeg压缩质量;直接调用save(..., imageformat.jpeg)使用默认不可控质量(.net 6+为80),无法修改。

直接改 Image.Save 的参数不能“无损降低质量”——JPEG 本身是有损格式,所谓“无损”只存在于 PNG/TIFF 等格式中;你真正能做的,是控制有损压缩的强度(Quality),并配合像素尺寸缩放,在视觉可接受前提下最小化体积。
用 Image.Save 显式传 EncoderParameters 控制 JPEG 质量
默认调 image.Save("x.jpg", ImageFormat.Jpeg) 会走系统内置策略(.NET 6+ 是 80,旧版可能更低),但这个值不可控、不透明,且无法修改。必须手动找 JPEG 编码器、构造参数对象、严格匹配类型。
-
Encoder.Quality的值必须是long类型,写75L,别写75或75.0,否则某些 .NET 版本会抛ArgumentException -
EncoderParameters构造时必须指定容量为1,且Param[0]是唯一元素,多一个或少一个都会失败 - 编码器必须从
ImageCodecInfo.GetImageEncoders()中筛选出MimeType == "image/jpeg"的那个,不能只靠后缀或ImageFormat.Jpeg的Guid直接匹配(部分环境不一致) - 常见错误现象:
Parameter is not valid,基本就是编码器没找到,或EncoderParameter构造时键/值类型不对
缩放尺寸必须用 Graphics.DrawImage + HighQualityBicubic
Bitmap.SetResolution(72, 72) 只改 DPI 元数据,像素总数不变,文件体积几乎无变化。真要降体积,得减少像素数量。
- 别用
Bitmap.Clone(Rectangle, ...)截图式缩放——它不重采样,只是硬裁剪,目标尺寸不对时直接丢内容 - 务必在
Graphics绘图前设graphics.InterpolationMode = InterpolationMode.HighQualityBicubic,否则默认最近邻插值,模糊+块状+压缩后体积反而更大 - 缩放比例低于 0.5 时(比如 4000px → 1200px),建议分两步:先缩到中间尺寸(如 2200px),再缩到目标,抗锯齿和色带更少
- 输出宽高尽量设为偶数(尤其 JPEG),避免解码器对齐补零,徒增几个字节
处理 PNG 透明图时,别直接 Save 为 JPG
PNG 含 Alpha 通道,直接 Save(..., ImageFormat.Jpeg) 会静默丢透明信息,背景变黑或灰,甚至在某些 GDI+ 版本崩溃。
- 必须先填充背景色:用
Graphics.Clear(Color.White)或其它指定色,再绘原图 - 如果原始 PNG 是图标类小图(ImageSharp 的 PNG 有损压缩(
PngEncoder.CompressionLevel = 9) - 不要依赖
Bitmap.PixelFormat判断是否含透明——它常返回Format32bppArgb却实际全不透明,得查像素数据或用image.Flags & ImageFlags.HasAlpha
为什么推荐 ImageSharp 而不是 System.Drawing
System.Drawing 在非 Windows 平台需装 libgdiplus,容器里易出 DllNotFoundException;GDI+ 句柄泄漏风险高,批量处理时服务器句柄数几分钟见顶。
-
ImageSharp纯托管实现,跨平台开箱即用,JpegEncoder.Quality直接设int,无需封装EncoderParameter - 它的
Quality = 80实际压缩强度 ≈System.Drawing的72L,别按数字直觉换算,实测调低 3–5 点更稳 - 内存中未
Dispose()的Image实例仍占大量托管内存(它不带Finalizer),必须显式释放 - 不支持“按目标文件大小自动调节质量”,若需 ≤200KB,得手写循环尝试不同
Quality值并检查MemoryStream.Length
最易被忽略的一点:所有 Image、Bitmap、Graphics(GDI+)必须用 using 包裹,哪怕只读操作;ImageSharp.Image 同理,不 Dispose() 就等着 OutOfMemoryException。











