首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET 動態產生 JPEG 縮圖:支援品質參數調整的 HttpHandler 實作

     編輯:ASP.NET 基礎

在 ASP.NET 中動態產生圖片 (如縮圖、水印、驗證碼) 最標準的做法是實作 `IHttpHandler` (`.ashx`),直接輸出二進位串流至 `Response.OutputStream`,避免 `.aspx` 頁面生命週期的額外開銷。本文展示完整的 **BMP 轉 JPEG 動態壓縮** 範例,支援查詢字串參數控制壓縮品質 (0-100)。

完整 HttpHandler 實作

<%@ WebHandler Language="C#" Class="DynamicJpegHandler" %>
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web;

public class DynamicJpegHandler : IHttpHandler
{
    public bool IsReusable { get { return false; } }

    public void ProcessRequest(HttpContext context)
    {
        // 1. 取得壓縮品質參數 (0-100,預設 80)
        int quality = 80;
        if (!string.IsNullOrEmpty(context.Request.QueryString["comp"]))
            int.TryParse(context.Request.QueryString["comp"], out quality);
        quality = Math.Max(0, Math.Min(100, quality));

        // 2. 來源圖片路徑 (實務建議從資料庫/快取/Blob 讀取)
        string sourcePath = context.Server.MapPath("~/Images/source.bmp");
        if (!System.IO.File.Exists(sourcePath))
        {
            context.Response.StatusCode = 404;
            context.Response.Write("Source image not found");
            return;
        }

        // 3. 輸出 JPEG
        context.Response.Clear();
        context.Response.ContentType = "image/jpeg";
        context.Response.Cache.SetCacheability(HttpCacheability.Public);
        context.Response.Cache.SetMaxAge(TimeSpan.FromDays(30)); // 客戶端快取 30 天
        context.Response.Cache.SetETag("W/\"source-bmp-" + quality + "\"");

        using (Bitmap bmp = new Bitmap(sourcePath))
        {
            // 設定 JPEG 編碼參數
            ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
            EncoderParameters encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, (long)quality);

            // 直接輸出至 Response.OutputStream (無需暫存檔)
            bmp.Save(context.Response.OutputStream, jpegCodec, encoderParams);
        }
    }

    private ImageCodecInfo GetEncoderInfo(string mimeType)
    {
        foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
        {
            if (codec.MimeType == mimeType) return codec;
        }
        return null;
    }
}

Web.config 註冊 (IIS 7+ Integrated 模式)

<system.webServer>
    <handlers>
        <add name="DynamicJpeg" verb="GET" path="DynamicJpeg.ashx" 
             type="DynamicJpegHandler" preCondition="integratedMode" />
    </handlers>
</system.webServer>

前端使用方式

<!-- 基本用法 -->
<img src="DynamicJpeg.ashx" alt="動態縮圖" />

<!-- 指定壓縮品質 60 -->
<img src="DynamicJpeg.ashx?comp=60" alt="低品質縮圖" />

<!-- 搭配 ASP.NET Image 控制項 -->
<asp:Image ID="imgDynamic" runat="server" />

// 後置碼
imgDynamic.ImageUrl = "DynamicJpeg.ashx?comp=75";

進階擴充:支援寬高縮放、格式轉換、水印

public class AdvancedImageHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        // 參數解析
        int width = ParseInt(context.Request["w"], 0);
        int height = ParseInt(context.Request["h"], 0);
        int quality = ParseInt(context.Request["q"], 85);
        string format = context.Request["f"] ?? "jpeg"; // jpeg, png, gif
        string watermark = context.Request["wm"];       // 水印文字
        string source = context.Request["src"];         // 來源圖片虛擬路徑

        if (string.IsNullOrEmpty(source))
        {
            context.Response.StatusCode = 400;
            return;
        }

        string physicalPath = context.Server.MapPath(source);
        if (!System.IO.File.Exists(physicalPath))
        {
            context.Response.StatusCode = 404;
            return;
        }

        using (Image src = Image.FromFile(physicalPath))
        {
            // 計算目標尺寸 (等比例)
            int targetW = src.Width, targetH = src.Height;
            if (width > 0 || height > 0)
            {
                double ratio = 1.0;
                if (width > 0 && height > 0)
                    ratio = Math.Min((double)width / src.Width, (double)height / src.Height);
                else if (width > 0)
                    ratio = (double)width / src.Width;
                else
                    ratio = (double)height / src.Height;

                targetW = (int)(src.Width * ratio);
                targetH = (int)(src.Height * ratio);
            }

            using (Bitmap dest = new Bitmap(targetW, targetH))
            using (Graphics g = Graphics.FromImage(dest))
            {
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                g.DrawImage(src, 0, 0, targetW, targetH);

                // 水印
                if (!string.IsNullOrEmpty(watermark))
                {
                    using (Font font = new Font("Arial", 12, FontStyle.Bold))
                    using (Brush brush = new SolidBrush(Color.FromArgb(128, 255, 255, 255)))
                    {
                        g.DrawString(watermark, font, brush, targetW - 100, targetH - 20);
                    }
                }

                // 輸出
                context.Response.Clear();
                context.Response.ContentType = "image/" + format;
                ImageCodecInfo codec = GetEncoderInfo("image/" + format);
                EncoderParameters eps = new EncoderParameters(1);
                eps.Param[0] = new EncoderParameter(Encoder.Quality, (long)quality);
                dest.Save(context.Response.OutputStream, codec, eps);
            }
        }
    }

    private int ParseInt(string s, int def) => int.TryParse(s, out int v) ? v : def;
    private ImageCodecInfo GetEncoderInfo(string mime) => 
        Array.Find(ImageCodecInfo.GetImageEncoders(), c => c.MimeType == mime);
    public bool IsReusable => false;
}

效能與架構建議

層面建議
快取策略輸出 `ETag`/`Last-Modified`,搭配 `HttpCacheability.Public` + `MaxAge`;伺服器端可用 `MemoryCache`/`Redis` 快取產生後的位元組陣列
影子複製/檔案鎖定`Image.FromFile` 會鎖定檔案至物件 Dispose。高併發建議先 `File.ReadAllBytes` → `MemoryStream` → `Image.FromStream`,或使用 ImageSharp 避免鎖定
非同步處理ASP.NET Core 改用 `IAsyncHttpHandler` / Minimal API `app.MapGet`,支援 `await` 非同步 I/O
CDN 整合動態圖片產生後上傳至 Blob Storage,回傳 CDN URL,後續請求直接命中 CDN
跨平台.NET Core / 5+ 請改用 SixLabors.ImageSharp,`System.Drawing.Common` 僅支援 Windows 且效能較差

現代化替代:ASP.NET Core Minimal API

// Program.cs
app.MapGet("/api/image/{**path}", async (string path, int? w, int? h, int? q, HttpContext ctx) =>
{
    // 驗證路徑安全性 (防止路徑遍歷)
    string physical = Path.Combine(env.WebRootPath, "images", path);
    if (!File.Exists(physical)) return Results.NotFound();

    using var src = Image.Load(physical); // ImageSharp
    // ... 縮放、水印、編碼 ...
    var ms = new MemoryStream();
    src.SaveAsJpeg(ms, new JpegEncoder { Quality = q ?? 80 });
    ms.Position = 0;
    return Results.File(ms, "image/jpeg", enableRangeProcessing: true);
});
總結: `.ashx` HttpHandler 是傳統 ASP.NET 動態圖片輸出的標準做法,輕量、高效、無 ViewState 開銷。新專案請採用 ASP.NET Core + ImageSharp + Minimal API / Controller,獲得跨平台、非同步、現代化架構優勢。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved