最可靠缩略图生成法是手动用graphics.drawimage:先等比计算尺寸并居中,再创建bitmap画布,设置高质量插值后绘制;加载时用file.readallbytes+memorystream避免文件锁;保存时显式指定jpeg编码器及质量参数;所有image/graphics对象必须using及时释放且不可跨线程共享。

用 Graphics.DrawImage 手动缩放最可控
直接调用 Image.GetThumbnailImage 看似简单,但实际返回的缩略图质量差、尺寸不准、还可能黑边或拉伸变形——它内部用了系统缩略图缓存逻辑,根本不尊重你传的宽高参数。
真正靠谱的做法是自己算比例、创建新画布、用 Graphics.DrawImage 绘制。核心就三步:
- 计算等比缩放后的真实输出尺寸(保持宽高比,不裁剪)
- 新建
Bitmap作为目标画布,指定你要的最终尺寸(比如 200×150) - 用
Graphics.FromImage获取绘图上下文,设置高质量插值和渲染模式
示例关键片段:
var targetWidth = 200; var targetHeight = 150; var ratio = Math.Min((double)targetWidth / src.Width, (double)targetHeight / src.Height); var drawWidth = (int)(src.Width * ratio); var drawHeight = (int)(src.Height * ratio); var x = (targetWidth - drawWidth) / 2; var y = (targetHeight - drawHeight) / 2; <p>using var thumb = new Bitmap(targetWidth, targetHeight); using var g = Graphics.FromImage(thumb); g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.DrawImage(src, x, y, drawWidth, drawHeight);</p>
批量处理时别用 Image.FromFile 直接加载
在循环里反复调用 Image.FromFile 会锁住原文件,导致后续读取失败或抛出 System.IO.IOException: The process cannot access the file。尤其 Windows 下特别明显。
正确做法是先用 File.ReadAllBytes 把文件读进内存,再用 new Bitmap(new MemoryStream(bytes)) 构造图像对象:
- 避免文件句柄长期占用
- 支持并发生成(只要不写同一个输出路径)
- 能统一捕获图像解码异常(比如损坏的 JPEG)
注意:MemoryStream 必须保持打开状态直到 Bitmap 使用完毕,否则会报 Parameter is not valid。
Save 时指定 JpegEncoder 而不是依赖默认格式
直接调用 thumb.Save("out.jpg") 看似省事,但 .NET 默认 JPEG 压缩质量只有 75%,且不支持控制色度抽样、是否保存 EXIF 等。批量生成时容易出现体积偏大或细节糊掉。
显式使用 ImageCodecInfo 和 EncoderParameters 才能稳定输出:
- 压缩质量设为 85–92 是视觉与体积的合理平衡点
- 务必设置
Encoder.Quality参数,否则无效 - 不要用
ImageFormat.Jpeg,它不带编码器参数,只是格式标识
简短示意:
var jpegCodec = ImageCodecInfo.GetImageEncoders().First(x => x.MimeType == "image/jpeg");
var ep = new EncoderParameters(1);
ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
thumb.Save("thumb.jpg", jpegCodec, ep);
注意 Dispose 顺序和跨线程图像对象
所有从 Image 派生的对象(Bitmap、Graphics)都必须显式释放,尤其在批量循环中。漏掉 Dispose 会导致 GDI 句柄泄漏,跑几百张后直接抛 OutOfMemoryException。
更隐蔽的问题是:图像对象不能跨线程访问。如果你用 Parallel.ForEach 处理图片列表,千万别把原始 Image 实例传进去共享——它不是线程安全的。每个线程必须独立加载、处理、保存、释放。
一句话提醒:哪怕只生成一张缩略图,也要用 using 包裹所有 Bitmap 和 Graphics;批量时,每个任务块里完成全部生命周期。











