為圖片添加水印是保護版權、品牌推廣的常見需求。本文基於 System.Drawing,實作圖片水印、文字水印 (支援半透明/抗鋸齒)、等比縮圖等核心功能,並封裝為可重用的 ImageHelper 靜態類。
public static Size CalculateResizeSize(Size original, int maxWidth, int maxHeight)
{
if (original.Width <= maxWidth && original.Height <= maxHeight) return original;
double ratio = Math.Min((double)maxWidth / original.Width, (double)maxHeight / original.Height);
return new Size((int)(original.Width * ratio), (int)(original.Height * ratio));
}
public static Image GenerateThumbnail(Image source, int maxWidth, int maxHeight)
{
Size newSize = CalculateResizeSize(source.Size, maxWidth, maxHeight);
var thumb = new Bitmap(newSize.Width, newSize.Height);
using (var g = Graphics.FromImage(thumb))
{
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawImage(source, new Rectangle(Point.Empty, newSize));
}
return thumb;
}
public static void AddImageWatermark(string imagePath, string watermarkPath,
WatermarkPosition position = WatermarkPosition.BottomRight, int margin = 10, float opacity = 1.0f)
{
using (var src = Image.FromFile(imagePath))
using (var wm = Image.FromFile(watermarkPath))
using (var g = Graphics.FromImage(src))
{
// 計算位置
Point loc = CalculatePosition(src.Size, wm.Size, position, margin);
// 透明度處理
var attrs = new ImageAttributes();
var cm = new ColorMatrix { Matrix33 = opacity }; // Alpha 縮放
attrs.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(wm, new Rectangle(loc, wm.Size), 0, 0, wm.Width, wm.Height,
GraphicsUnit.Pixel, attrs);
}
// 覆蓋儲存 (建議先備份)
File.Copy(imagePath, imagePath + ".bak", true);
using (var final = Image.FromFile(imagePath)) // 重新載入避免 GDI+ 鎖定
{
// 這裡需將修改後的 src 存回,實務上建議存到新檔再替換
}
}
public static void AddTextWatermark(string imagePath, string text,
Font font, Color color, WatermarkPosition position = WatermarkPosition.BottomRight,
int margin = 10, float opacity = 0.6f)
{
using (var src = Image.FromFile(imagePath))
using (var g = Graphics.FromImage(src))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
// 量測文字大小
SizeF textSize = g.MeasureString(text, font);
PointF loc = CalculatePositionF(src.Size, textSize, position, margin);
// 半透明筆刷
int alpha = (int)(255 * opacity);
using (var brush = new SolidBrush(Color.FromArgb(alpha, color)))
{
// 可選:加陰影增強可讀性
// g.DrawString(text, font, Brushes.Black, loc.X + 1, loc.Y + 1);
g.DrawString(text, font, brush, loc);
}
}
// 儲存邏輯同上
}
public enum WatermarkPosition { TopLeft, TopRight, BottomLeft, BottomRight, Center }
private static Point CalculatePosition(Size container, Size watermark, WatermarkPosition pos, int margin)
{
int x = pos switch
{
WatermarkPosition.TopLeft or WatermarkPosition.BottomLeft => margin,
WatermarkPosition.Center => (container.Width - watermark.Width) / 2,
_ => container.Width - watermark.Width - margin
};
int y = pos switch
{
WatermarkPosition.TopLeft or WatermarkPosition.TopRight => margin,
WatermarkPosition.Center => (container.Height - watermark.Height) / 2,
_ => container.Height - watermark.Height - margin
};
return new Point(Math.Max(0, x), Math.Max(0, y));
}
private static PointF CalculatePositionF(Size container, SizeF watermark, WatermarkPosition pos, int margin)
=> new PointF(CalculatePosition(container, watermark.ToSize(), pos, margin));
public static class ImageHelper
{
// 水印 Logo 相對路徑 (Web 專案常用)
public static string LogoPath => "/images/watermark.png";
// 公開 API
public static void AddWatermark(string imagePath, string watermarkPath, ...);
public static void AddTextWatermark(string imagePath, string text, ...);
public static Image Resize(Image source, int maxW, int maxH);
public static void Save(Image img, string path, ImageFormat fmt = null);
// Stream 版本 (Web 上傳直接處理)
public static void AddWatermark(Stream input, Stream watermark, Stream output, ...);
}
// 上傳即時加水印儲存
public void ProcessUpload(HttpPostedFileBase file)
{
string savePath = Server.MapPath($"~/uploads/{Guid.NewGuid()}.jpg");
using (var ms = new MemoryStream())
{
file.InputStream.CopyTo(ms);
ms.Position = 0;
using (var src = Image.FromStream(ms))
using (var wm = Image.FromFile(Server.MapPath(ImageHelper.LogoPath)))
{
ImageHelper.AddImageWatermark(src, wm, WatermarkPosition.BottomRight);
src.Save(savePath, ImageFormat.Jpeg);
}
}
}
| 主題 | 說明 |
|---|---|
| GDI+ 檔案鎖定 | Image.FromFile 會鎖定檔案直到 Dispose。修改後存回同路徑會報錯。解決:存到暫存檔 → 刪原檔 → 改名,或先 File.Copy 到記憶體處理。 |
| 透明度實現 | 使用 ColorMatrix.Matrix33 縮放 Alpha 通道,配合 ImageAttributes 套用至 DrawImage。 |
| 文字清晰度 | 設定 SmoothingMode.AntiAlias + TextRenderingHint.ClearTypeGridFit。 |
| 大圖記憶體 | 處理超大圖 (如 4K+ 照片) 易 OOM。建議分塊處理或使用 ImageSharp 等現代庫。 |
| 跨平台 | System.Drawing.Common 僅支援 Windows/Linux (需 libgdiplus)。Linux 容器建議改用 SixLabors.ImageSharp (純 managed)。 |
| EXIF 方向 | 手機照片含 Orientation 標籤,直接畫會旋轉錯誤。需先讀取 EXIF 旋轉修正。 |
掌握 Graphics.DrawImage (配合 ImageAttributes) 與 DrawString 即可實作專業級水印功能。封裝為 ImageHelper 統一管理參數與流程,專案中即可一鍵呼叫。現代雲原生環境建議遷移至 ImageSharp 獲得更好的跨平台穩定性與記憶體安全。