使用 `.ashx` 而非 `.aspx` 可避免頁面生命週期開銷,直接輸出二進位圖片流。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web;
public class Watermark : IHttpHandler
{
public bool IsReusable => true;
public void ProcessRequest(HttpContext context)
{
// 1. 參數驗證
string text = context.Request.QueryString["text"] ?? "Sample";
if (text.Length > 20) text = text.Substring(0, 20); // 限制長度防止溢出
string imgPath = context.Server.MapPath("~/Images/base-photo.jpg");
if (!System.IO.File.Exists(imgPath))
{
context.Response.StatusCode = 404;
return;
}
// 2. 載入原圖
using (Image baseImg = Image.FromFile(imgPath))
using (Graphics g = Graphics.FromImage(baseImg))
{
// 3. 繪圖品質設定
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
// 4. 字體與筆刷
using (Font font = new Font("微軟正黑體", 24, FontStyle.Bold, GraphicsUnit.Pixel))
using (SolidBrush brush = new SolidBrush(Color.FromArgb(180, 255, 255, 255))) // 半透明白
using (SolidBrush shadowBrush = new SolidBrush(Color.FromArgb(180, 0, 0, 0))) // 陰影
{
// 5. 計算位置 (右下角偏移 10px)
SizeF textSize = g.MeasureString(text, font);
float x = baseImg.Width - textSize.Width - 10;
float y = baseImg.Height - textSize.Height - 10;
// 6. 繪製陰影 (偏移 1px) 增強可讀性
g.DrawString(text, font, shadowBrush, x + 1, y + 1);
// 7. 繪製主文字
g.DrawString(text, font, brush, x, y);
}
// 8. 輸出 JPEG (品質 85%)
var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 85L);
ImageCodecInfo jpgEncoder = GetEncoderInfo("image/jpeg");
context.Response.ContentType = "image/jpeg";
baseImg.Save(context.Response.OutputStream, jpgEncoder, encoderParams);
}
}
private ImageCodecInfo GetEncoderInfo(string mimeType)
{
foreach (var codec in ImageCodecInfo.GetImageEncoders())
if (codec.MimeType == mimeType) return codec;
return null;
}
}
<system.webServer>
<handlers>
<add name="Watermark" verb="GET" path="Watermark.ashx" type="YourNamespace.Watermark, YourAssembly" preCondition="integratedMode" />
</handlers>
</system.webServer>
<!-- 直接用 img 標籤指向 Handler -->
<img src="Watermark.ashx?text=Hello%20World" alt="動態水印圖片" />
<!-- 或在 Razor/WebForms 後端產生 URL -->
<img src="<%= ResolveUrl("~/Watermark.ashx?text=" + Server.UrlEncode(user.Name)) %>" />
| 功能 | 實作要點 |
|---|---|
| 多行文字自動換行 | `StringFormat` 設定 `FormatFlags = StringFormatFlags.LineLimit`,配合 `MeasureString` 計算高度逐行繪製 |
| 圖片水印 (Logo) | `g.DrawImage(logoImg, x, y, width, height)`,支援 PNG 透明通道 |
| 斜向平鋪水印 | 雙重迴圈 `for (int y=-h; y<2*h; y+=step) for (int x=-w; x<2*w; x+=step) g.DrawString(...)` 並 `g.RotateTransform(-30)` |
| 字體自適應縮放 | 依圖片寬度計算 `fontSize = Math.Max(12, img.Width / 30)` |
| 快取輸出 | `context.Response.Cache.SetMaxAge(TimeSpan.FromDays(30))` + `ETag` 依參數雜湊 |
| 防盜連結 | 驗證 `Request.UrlReferrer` 或簽名 Token (HMAC) 過期時間 |
ASP.NET 搭配 `IHttpHandler` + `System.Drawing` 實作動態文字水印,架構輕量、效能足夠中小流量場景。高併發、跨平台 (Linux Docker) 生產環境,**強烈建議遷移至 ImageSharp + Minimal API**,徹底解決 GDI+ 原生相依與記憶體洩漏風險。