高质量缩略图需显式设置interpolationmode.highqualitybicubic、分步裁剪再缩放、精准选取jpeg编码器并设encoder.quality=95l、严格using释放gdi句柄。

为什么 Bitmap 直接缩放会糊得离谱
不是算法不行,是默认用了低质量插值模式。C# 的 Graphics.DrawImage 默认用 InterpolationMode.Low 或 InterpolationMode.Bilinear,拉伸时像素直接“平均摊开”,边缘发虚、文字锯齿严重。
- 必须显式设置
InterpolationMode.HighQualityBicubic,这是高质量缩放的底线 -
SmoothingMode.AntiAlias对线条和文字有帮助,但对照片主体影响不大,可选 - 务必关闭
CompositingQuality的默认值(它可能触发半透明混合,拖慢速度还添噪) - 别用
Bitmap.GetThumbnailImage()—— 它绕过所有自定义设置,内部硬编码低质量采样,且不支持裁剪
裁剪+缩放要分两步做,顺序不能反
先裁剪再缩放,不是为了省性能,而是避免缩放引入的亚像素偏移破坏裁剪精度。比如你要从原图中心抠 200×200 再缩到 100×100,如果先缩再裁,缩放过程已丢失原始坐标精度,裁出来的区域可能偏移 1–2 像素,边缘容易切掉关键内容。
- 计算裁剪区域用原始尺寸:确定
sourceX、sourceY、sourceWidth、sourceHeight - 用
new Bitmap(cropWidth, cropHeight)创建目标画布,再用Graphics.FromImage()绘制裁剪后图像 - 最后对这个裁剪后的
Bitmap执行缩放(即二次绘制到新尺寸画布) - 如果想一步到位,可用
RectangleF在最终画布上指定目标区域,但源矩形仍需基于原始图计算,否则比例错乱
ImageCodecInfo 和 EncoderParameters 怎么设才不丢质量
JPEG 压缩质量不是靠文件大小倒推的,而是由 EncoderParameter 中的 Encoder.Quality 决定,范围 0–100。但很多人设了 95 还是发灰、出水彩噪点,问题出在没选对编码器。
- 必须用
ImageCodecInfo.GetImageEncoders()找到MimeType == "image/jpeg"的那个,别硬写ImageFormat.Jpeg—— 后者可能触发系统默认编码器(Windows 旧版 GDI+ 编码器质量差) -
EncoderParameters必须包含且仅包含一个EncoderParameter(Encoder.Quality, 95L),多传或类型错(比如传 int 而非 long)会导致参数被忽略 - PNG 不需要设质量参数,但若强制用
ImageFormat.Png保存,确保原始图无 Alpha 通道,否则可能意外增大体积;有透明需求就保留,别转成 JPEG
内存爆炸和 GDI 句柄泄漏的真实原因
生成缩略图时 OutOfMemoryException 很常见,但它往往不是内存真不够,而是 GDI+ 句柄耗尽(Windows 单进程默认约 10000 句柄)。每次 new Bitmap 或 Graphics.FromImage 都占句柄,不及时释放就卡死。
- 所有
Bitmap和Graphics实例必须用using包裹,哪怕嵌套三层也要套全 - 别在循环里反复
new Bitmap(source)—— 先用Image.FromFile加载,再构造Bitmap,否则文件锁+句柄双倍占用 - 如果批量处理,考虑复用
Graphics对象(但注意线程不安全),或改用ImageSharp库 —— 它不依赖 GDI+,无句柄问题,压缩质量还更可控 - 调试时用任务管理器看“GDI 对象”列,超过 8000 就该查漏了
高质量缩略图真正的难点不在算法,而在控制每一步的中间状态:裁剪坐标的整数对齐、插值模式的显式锁定、编码器的精确选取、句柄生命周期的手动掐点。漏掉其中任何一个,结果就悄悄退化。











