縮圖生成的本質是幾何映射與像素重採樣。Graphics.DrawImage 透過「來源矩形 → 目標矩形」的仿射變換,配合插值演算法決定輸出品質。本文拆解關鍵計算步驟,並提供支援多種輸出格式的擴充實作。
設原圖寬高 (ow, oh),目標 (tw, th):
srcRect = (0,0,ow,oh) → destRect = (0,0,tw,th),直接拉伸。th = oh * tw / ow,高度等比縮放。tw = ow * th / oh,寬度等比縮放。
double targetRatio = (double)tw / th;
double sourceRatio = (double)ow / oh;
if (sourceRatio > targetRatio) // 原圖較寬 → 左右裁切
{
srcH = oh;
srcW = (int)(oh * targetRatio);
x = (ow - srcW) / 2;
}
else // 原圖較高 → 上下裁切
{
srcW = ow;
srcH = (int)(ow / targetRatio);
y = (oh - srcH) / 2;
}
g.InterpolationMode = InterpolationMode.HighQualityBicubic; // 雙三次插值,縮放最佳
g.SmoothingMode = SmoothingMode.HighQuality; // 邊緣平滑
g.PixelOffsetMode = PixelOffsetMode.HighQuality; // 像素偏移修正
g.CompositingQuality = CompositingQuality.HighQuality; // 合成品質
public static class ThumbnailExtensions
{
public enum Mode { HW, W, H, Cut }
public static void Generate(
string sourcePath, string destPath,
int width, int height, Mode mode,
ImageFormat format = null)
{
using (Image src = Image.FromFile(sourcePath))
{
int ow = src.Width, oh = src.Height;
int tw = width, th = height;
int sx = 0, sy = 0, sw = ow, sh = oh;
switch (mode)
{
case Mode.W: th = oh * tw / ow; break;
case Mode.H: tw = ow * th / oh; break;
case Mode.Cut:
double tr = (double)tw / th, sr = (double)ow / oh;
if (sr > tr) { sh = oh; sw = (int)(oh * tr); sx = (ow - sw) / 2; }
else { sw = ow; sh = (int)(ow / tr); sy = (oh - sh) / 2; }
break;
}
using (Bitmap bmp = new Bitmap(tw, th))
using (Graphics g = Graphics.FromImage(bmp))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
g.Clear(Color.Transparent);
g.DrawImage(src, new Rectangle(0, 0, tw, th), new Rectangle(sx, sy, sw, sh), GraphicsUnit.Pixel);
// 預設 JPEG,支援自訂格式
bmp.Save(destPath, format ?? ImageFormat.Jpeg);
}
}
}
}
// 呼叫範例
ThumbnailExtensions.Generate("src.jpg", "thumb.png", 200, 200, ThumbnailExtensions.Mode.Cut, ImageFormat.Png);
| 問題 | 原因 | 解決 |
|---|---|---|
| 縮圖背景變黑 | PNG 透明背景未處理 | g.Clear(Color.Transparent) + 確保目標 Bitmap 為 32bppArgb |
| GIF 動畫只取第一幀 | Image.FromFile 僅載入第一幀 | 使用 ImageSharp 或 System.Drawing.Common 逐幀處理 |
| 大圖 OOM | 一次載入完整像素陣列 | 分塊處理或改用 ImageSharp 串流解碼 |
| Linux 部署失敗 | libgdiplus 缺失 | 安裝 libgdiplus 或改用跨平台庫 |
Image.FromFile 讀取檔案頭。Task.Run 卸載 CPU 密集運算,避免阻塞 UI/請求執行緒。Bitmap/Graphics 減少 GC 壓力。