員工管理系統需儲存大頭照。原始照片 1024×768、2MB,直接存入資料庫造成:
解決方案:**上傳時即時產生縮圖 + JPEG 壓縮**,僅儲存 5~10KB 縮圖。
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
public static class ImageHelper
{
///
/// 產生縮圖並壓縮為 JPEG 位元組陣列
///
/// 來源圖片路徑
/// 最大寬度
/// 最大高度
/// JPEG 品質 1~100 (建議 75~85)
/// JPEG 位元組陣列,可直接寫入資料庫 VARBINARY/IMAGE/BLOB 欄位
public static byte[] CreateThumbnailToBytes(string sourcePath, int maxWidth = 120, int maxHeight = 160, long quality = 80L)
{
using (var original = Image.FromFile(sourcePath))
using (var thumbnail = CreateThumbnail(original, maxWidth, maxHeight))
using (var ms = new MemoryStream())
{
var encoder = GetEncoder(ImageFormat.Jpeg);
var params = new EncoderParameters(1);
params.Param[0] = new EncoderParameter(Encoder.Quality, quality);
thumbnail.Save(ms, encoder, params);
return ms.ToArray();
}
}
///
/// 等比例縮放,維持長寬比
///
public static Image CreateThumbnail(Image source, int maxWidth, int maxHeight)
{
double ratioX = (double)maxWidth / source.Width;
double ratioY = (double)maxHeight / source.Height;
double ratio = Math.Min(ratioX, ratioY);
int newWidth = (int)(source.Width * ratio);
int newHeight = (int)(source.Height * ratio);
var dest = new Bitmap(newWidth, newHeight);
using (var g = Graphics.FromImage(dest))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawImage(source, 0, 0, newWidth, newHeight);
}
return dest;
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
return ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.FormatID == format.Guid);
}
}
// 存入
byte[] photoBytes = ImageHelper.CreateThumbnailToBytes(@"C:\Photos\emp001.jpg", 120, 160, 80);
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand("UPDATE Employee SET Photo = @photo WHERE Id = @id", conn))
{
cmd.Parameters.Add("@photo", SqlDbType.VarBinary, -1).Value = photoBytes;
cmd.Parameters.AddWithValue("@id", empId);
conn.Open();
cmd.ExecuteNonQuery();
}
// 讀出並繫結 PictureBox (WinForms)
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand("SELECT Photo FROM Employee WHERE Id = @id", conn))
{
cmd.Parameters.AddWithValue("@id", empId);
conn.Open();
byte[] bytes = (byte[])cmd.ExecuteScalar();
if (bytes != null && bytes.Length > 0)
{
using (var ms = new MemoryStream(bytes))
{
pictureBox1.Image = Image.FromStream(ms);
}
}
}
// Controller 上傳端點
[HttpPost]
public async Task UploadPhoto(IFormFile file, int empId)
{
if (file == null || file.Length == 0) return BadRequest();
using (var ms = new MemoryStream())
{
await file.CopyToAsync(ms);
ms.Position = 0;
using (var img = Image.Load(ms)) // 建議用 ImageSharp 取代 System.Drawing (跨平台)
{
img.Mutate(x => x.Resize(new ResizeOptions
{
Mode = ResizeMode.Max,
Size = new Size(120, 160)
}));
using (var outMs = new MemoryStream())
{
img.SaveAsJpeg(outMs, new JpegEncoder { Quality = 80 });
byte[] bytes = outMs.ToArray();
// 存入資料庫...
}
}
}
return Ok();
}
// 讀取端點 (回傳檔案流)
[HttpGet("{empId}")]
public async Task GetPhoto(int empId)
{
byte[] bytes = await _repo.GetPhotoAsync(empId);
if (bytes == null) return NotFound();
return File(bytes, "image/jpeg"); // 瀏覽器直接顯示、快取
}
| 面向 | 建議 |
|---|---|
| 函式庫選擇 | **ImageSharp (SixLabors.ImageSharp)**:純 managed、跨平台 (Linux/Docker/ARM)、無 GDI+ 相依、記憶體安全、支援 .NET Core/.NET 5+。 **System.Drawing.Common** 僅支援 Windows、Linux 需安裝 libgdiplus、易發生 GDI+ 通用錯誤。 |
| 儲存策略 | 資料庫僅存縮圖 (≤50KB);原圖存物件儲存 (Azure Blob/S3/MinIO/NAS),資料庫存 URL/路徑。 |
| 格式選擇 | JPEG (照片)、WebP (現代瀏覽器、體積更小)、PNG (需透明背景)。 |
| 安全性 | 驗證 MIME 型別、檔案簽名 (Magic Number)、限制上傳大小、掃描惡意軟體。 |
| 效能 | 非同步 I/O、串流處理避免大記憶體佔用、CDN 快取縮圖。 |