核心類別:System.Drawing 命名空間
Image / Bitmap:圖片載入、操作、儲存
Graphics:繪圖介面,DrawImage、DrawString
Font、Brush、Pen:文字樣式、填充、筆刷
GraphicsUnit.Pixel:座標單位
完整 ASP.NET WebForms 實作
.aspx 前端
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Watermark.aspx.cs" Inherits="Demo.Watermark" %>
圖片浮水印上傳
.aspx.cs 後端
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Web.UI;
namespace Demo
{
public partial class Watermark : Page
{
protected void Page_Load(object sender, EventArgs e) { }
protected void chkUseText_CheckedChanged(object sender, EventArgs e)
{
chkUseImage.Checked = !chkUseText.Checked;
fuWatermarkImage.Visible = chkUseImage.Checked;
}
protected void chkUseImage_CheckedChanged(object sender, EventArgs e)
{
chkUseText.Checked = !chkUseImage.Checked;
fuWatermarkImage.Visible = chkUseImage.Checked;
}
protected void btnUpload_Click(object sender, EventArgs e)
{
if (!fuImage.HasFile)
{
lblMessage.Text = "請選擇要上傳的圖片";
return;
}
try
{
// 1. 儲存原始上傳檔案
string ext = Path.GetExtension(fuImage.FileName).ToLower();
string[] allowed = { ".jpg", ".jpeg", ".png", ".bmp", ".gif" };
if (!allowed.Contains(ext)) throw new Exception("不支援的圖片格式");
string fileName = $"{DateTime.Now:yyyyMMddHHmmss}{ext}";
string uploadPath = Server.MapPath("~/Uploads/");
Directory.CreateDirectory(uploadPath);
string originalPath = Path.Combine(uploadPath, fileName);
fuImage.SaveAs(originalPath);
// 2. 載入圖片並建立 Graphics
using (Image image = Image.FromFile(originalPath))
using (Graphics g = Graphics.FromImage(image))
{
// 高品質繪圖設定
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
// 繪製原圖 (確保完整繪製到新 Bitmap)
g.DrawImage(image, 0, 0, image.Width, image.Height);
if (chkUseText.Checked)
{
// === 文字浮水印 ===
string text = txtWatermarkText.Text.Trim();
if (string.IsNullOrEmpty(text)) text = "Copyright";
// 字體大小依圖片寬度自動調整
float fontSize = Math.Max(12, image.Width / 20f);
using (Font font = new Font("Arial", fontSize, FontStyle.Bold))
using (Brush brush = new SolidBrush(Color.FromArgb(128, 255, 255, 255))) // 半透明白色
using (StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Far,
LineAlignment = StringAlignment.Far
})
{
// 右下角位置,留 10px 邊距
RectangleF rect = new RectangleF(10, 10, image.Width - 20, image.Height - 20);
g.DrawString(text, font, brush, rect, sf);
}
}
else if (chkUseImage.Checked && fuWatermarkImage.HasFile)
{
// === 圖片浮水印 ===
using (Image watermark = Image.FromStream(fuWatermarkImage.PostedFile.InputStream))
{
// 右下角位置
int x = image.Width - watermark.Width - 10;
int y = image.Height - watermark.Height - 10;
// 透明度調整 (可選)
ImageAttributes attr = new ImageAttributes();
ColorMatrix matrix = new ColorMatrix();
matrix.Matrix33 = 0.5f; // 50% 透明度
attr.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(watermark,
new Rectangle(x, y, watermark.Width, watermark.Height),
0, 0, watermark.Width, watermark.Height,
GraphicsUnit.Pixel, attr);
}
}
// 3. 儲存結果
string resultName = $"{Path.GetFileNameWithoutExtension(fileName)}_wm{ext}";
string resultPath = Path.Combine(uploadPath, resultName);
// 保持原格式儲存
ImageFormat format = ImageFormat.Jpeg;
if (ext == ".png") format = ImageFormat.Png;
else if (ext == ".bmp") format = ImageFormat.Bmp;
else if (ext == ".gif") format = ImageFormat.Gif;
image.Save(resultPath, format);
// 4. 顯示結果
imgResult.ImageUrl = $"~/Uploads/{resultName}";
imgResult.Visible = true;
lblMessage.Text = "浮水印加入成功!";
lblMessage.ForeColor = Color.Green;
}
// 5. 刪除原始檔 (可選)
File.Delete(originalPath);
}
catch (Exception ex)
{
lblMessage.Text = $"錯誤:{ex.Message}";
lblMessage.ForeColor = Color.Red;
}
}
}
}
關鍵技術細節
1. 文字浮水印進階:斜向重複平鋪
private void DrawTiledTextWatermark(Graphics g, Image image, string text)
{
using (Font font = new Font("Arial", 24, FontStyle.Bold))
using (Brush brush = new SolidBrush(Color.FromArgb(80, 200, 200, 200)))
{
// 計算文字大小
SizeF textSize = g.MeasureString(text, font);
// 旋轉 45 度
g.TranslateTransform(image.Width / 2f, image.Height / 2f);
g.RotateTransform(-45);
g.TranslateTransform(-image.Width / 2f, -image.Height / 2f);
// 平鋪繪製
float stepX = textSize.Width * 1.5f;
float stepY = textSize.Height * 2f;
for (float y = -image.Height; y < image.Height * 2; y += stepY)
{
for (float x = -image.Width; x < image.Width * 2; x += stepX)
{
g.DrawString(text, font, brush, x, y);
}
}
// 重置變換
g.ResetTransform();
}
}
2. 圖片浮水印:支援透明 PNG
private void DrawImageWatermark(Graphics g, Image image, Image watermark, float opacity = 0.5f)
{
// 建立帶透明度的 ImageAttributes
ImageAttributes attr = new ImageAttributes();
ColorMatrix cm = new ColorMatrix();
cm.Matrix33 = opacity; // Alpha 縮放
attr.SetColorMatrix(cm);
// 置中或指定位置
int x = (image.Width - watermark.Width) / 2;
int y = (image.Height - watermark.Height) / 2;
g.DrawImage(watermark,
new Rectangle(x, y, watermark.Width, watermark.Height),
0, 0, watermark.Width, watermark.Height,
GraphicsUnit.Pixel, attr);
}
3. 非同步處理大量圖片 (ASP.NET Core)
// ASP.NET Core Controller 範例
[HttpPost]
public async Task AddWatermark(IFormFile image, string text, IFormFile watermarkImage)
{
using (var ms = new MemoryStream())
{
await image.CopyToAsync(ms);
ms.Position = 0;
using (var img = Image.FromStream(ms))
using (var g = Graphics.FromImage(img))
{
// ... 繪製浮水印邏輯 ...
var resultMs = new MemoryStream();
img.Save(resultMs, ImageFormat.Png);
return File(resultMs.ToArray(), "image/png", "watermarked.png");
}
}
}
部署注意事項
| 議題 | 解決方案 |
| GDI+ 非執行緒安全 | 每請求建立獨立 Image/Graphics,勿共用靜態實例 |
| 記憶體洩漏 | 所有 Image、Graphics、Font、Brush 必須 using 或手動 Dispose() |
| 伺服器無桌面互動 | IIS 預設不允許 GDI+ 某些操作,需啟用「允許服務與桌面互動」或改用 ImageSharp/SkiaSharp |
| 高併發效能 | 考慮 ImageSharp (純 managed、跨平台、無 GDI+ 限制) |
現代替代方案:ImageSharp
// NuGet: SixLabors.ImageSharp
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.Fonts;
public byte[] AddWatermark(ImageSharp.Image image, string text)
{
var font = SystemFonts.CreateFont("Arial", 24, FontStyle.Bold);
var color = Color.White.WithAlpha(0.5f);
image.Mutate(x => x.DrawText(text, font, color, new PointF(10, 10)));
using (var ms = new MemoryStream())
{
image.SaveAsPng(ms);
return ms.ToArray();
}
}
本章小結
Graphics.DrawString 文字浮水印,Graphics.DrawImage 圖片浮水印
Color.FromArgb(alpha, r, g, b) 控制透明度
ImageAttributes.ColorMatrix 圖片層級透明度調整
- 所有 GDI+ 物件必須正確釋放,
using 區塊為最佳實務
- 生產環境高併發建議改用
ImageSharp 或 SkiaSharp 純 managed 函式庫
- 浮水印位置、大小、透明度、旋轉可依需求參數化配置