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

ASP.NET 產生縮圖完整類別:多重載支援、比例縮放、品質控制、儲存至檔案/串流

     編輯:ASP.NET 基礎

前言

ASP.NET 專案常需產生圖片縮圖 (列表預覽、上傳後自動生成)。本文提供功能完整的 `ImageHelper` 類別,支援:固定寬高、等比例縮放、百分比縮放、輸出至檔案/串流/Byte[]、JPEG 品質控制、高品質插值模式、自動處理 EXIF 旋轉。

完整實作代碼

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

public static class ImageHelper
{
    /// 預設 JPEG 品質 (1-100)
    public const int DefaultJpegQuality = 85;

    /// 縮圖模式
    public enum ThumbnailMode
    {
        /// 固定寬高,可能變形
        FixedSize,
        /// 等比例縮放,寬高不超過指定值 (預設)
        KeepAspectRatio,
        /// 等比例縮放並裁切至指定寬高 (居中裁切)
        CropToSize
    }

    /// 
    /// 產生縮圖並儲存至檔案
    /// 
    public static bool CreateThumbnail(
        string sourcePath, 
        string destPath, 
        int maxWidth, 
        int maxHeight, 
        ThumbnailMode mode = ThumbnailMode.KeepAspectRatio,
        int jpegQuality = DefaultJpegQuality)
    {
        try
        {
            using (var srcImage = Image.FromFile(sourcePath))
            using (var thumb = GenerateThumbnail(srcImage, maxWidth, maxHeight, mode))
            {
                SaveImage(thumb, destPath, jpegQuality);
                return true;
            }
        }
        catch (Exception ex)
        {
            // 記錄日誌: ex.Message
            return false;
        }
    }

    /// 
    /// 產生縮圖並回傳 Byte[] (適合存資料庫或回傳 Web API)
    /// 
    public static byte[] CreateThumbnailBytes(
        byte[] sourceBytes, 
        int maxWidth, 
        int maxHeight, 
        ThumbnailMode mode = ThumbnailMode.KeepAspectRatio,
        int jpegQuality = DefaultJpegQuality)
    {
        using (var ms = new MemoryStream(sourceBytes))
        using (var srcImage = Image.FromStream(ms))
        using (var thumb = GenerateThumbnail(srcImage, maxWidth, maxHeight, mode))
        {
            return ImageToBytes(thumb, jpegQuality);
        }
    }

    /// 
    /// 產生縮圖並寫入輸出串流 (適合 HTTP 回應)
    /// 
    public static void CreateThumbnailToStream(
        Stream sourceStream, 
        Stream outputStream, 
        int maxWidth, 
        int maxHeight, 
        ThumbnailMode mode = ThumbnailMode.KeepAspectRatio,
        int jpegQuality = DefaultJpegQuality)
    {
        using (var srcImage = Image.FromStream(sourceStream))
        using (var thumb = GenerateThumbnail(srcImage, maxWidth, maxHeight, mode))
        {
            SaveImageToStream(thumb, outputStream, jpegQuality);
        }
    }

    /// 
    /// 核心縮圖邏輯:支援三種模式、高品質插值、EXIF 旋轉修正
    /// 
    private static Image GenerateThumbnail(Image source, int maxWidth, int maxHeight, ThumbnailMode mode)
    {
        // 1. 修正 EXIF 方向 (避免手機拍照旋轉 90 度)
        source = AutoRotateImage(source);

        int srcW = source.Width;
        int srcH = source.Height;

        // 2. 計算目標尺寸
        int destW, destH;
        switch (mode)
        {
            case ThumbnailMode.FixedSize:
                destW = maxWidth; destH = maxHeight;
                break;
            case ThumbnailMode.KeepAspectRatio:
                double ratio = Math.Min((double)maxWidth / srcW, (double)maxHeight / srcH);
                destW = (int)Math.Round(srcW * ratio);
                destH = (int)Math.Round(srcH * ratio);
                break;
            case ThumbnailMode.CropToSize:
                double ratioW = (double)maxWidth / srcW;
                double ratioH = (double)maxHeight / srcH;
                double cropRatio = Math.Max(ratioW, ratioH); // 取較大比例,確保覆蓋目標區域
                int cropW = (int)Math.Round(srcW * cropRatio);
                int cropH = (int)Math.Round(srcH * cropRatio);
                // 先縮放至覆蓋尺寸,再裁切
                var temp = new Bitmap(cropW, cropH);
                using (var g = Graphics.FromImage(temp))
                    g.DrawImage(source, 0, 0, cropW, cropH);
                // 裁切居中區域
                int x = (cropW - maxWidth) / 2;
                int y = (cropH - maxHeight) / 2;
                var cropped = new Bitmap(maxWidth, maxHeight);
                using (var g = Graphics.FromImage(cropped))
                    g.DrawImage(temp, -x, -y);
                temp.Dispose();
                return cropped;
            default:
                throw new ArgumentOutOfRangeException(nameof(mode));
        }

        // 3. 建立高品質縮圖 (使用 Graphics 而非 GetThumbnailImage,品質更好、可控插值模式)
        var result = new Bitmap(destW, destH);
        result.SetResolution(source.HorizontalResolution, source.VerticalResolution);

        using (var g = Graphics.FromImage(result))
        {
            // 高品質設定
            g.InterpolationMode = InterpolationMode.HighQualityBicubic; // 雙三次插值
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.CompositingQuality = CompositingQuality.HighQuality;
            
            // 白色背景 (避免透明 PNG 轉 JPEG 產生黑底)
            g.Clear(Color.White);
            
            g.DrawImage(source, 0, 0, destW, destH);
        }
        return result;
    }

    /// 根據 EXIF Orientation 標籤自動旋轉圖片
    private static Image AutoRotateImage(Image img)
    {
        const int OrientationId = 0x0112; // EXIF Orientation Tag
        if (!img.PropertyIdList.Contains(OrientationId)) return img;

        var prop = img.GetPropertyItem(OrientationId);
        int orientation = BitConverter.ToUInt16(prop.Value, 0);
        
        RotateFlipType rotateType = orientation switch
        {
            2 => RotateFlipType.RotateNoneFlipX,
            3 => RotateFlipType.Rotate180FlipNone,
            4 => RotateFlipType.Rotate180FlipX,
            5 => RotateFlipType.Rotate90FlipX,
            6 => RotateFlipType.Rotate90FlipNone,
            7 => RotateFlipType.Rotate270FlipX,
            8 => RotateFlipType.Rotate270FlipNone,
            _ => RotateFlipType.RotateNoneFlipNone
        };

        if (rotateType != RotateFlipType.RotateNoneFlipNone)
        {
            var rotated = (Image)img.Clone();
            rotated.RotateFlip(rotateType);
            rotated.RemovePropertyItem(OrientationId); // 移除標籤避免重複旋轉
            return rotated;
        }
        return img;
    }

    /// 儲存圖片至檔案 (支援 JPEG 品質)
    private static void SaveImage(Image image, string path, int jpegQuality)
    {
        string ext = Path.GetExtension(path).ToLowerInvariant();
        if (ext == ".jpg" || ext == ".jpeg")
        {
            var encoder = GetEncoder(ImageFormat.Jpeg);
            var params_ = new EncoderParameters(1);
            params_.Param[0] = new EncoderParameter(Encoder.Quality, (long)jpegQuality);
            image.Save(path, encoder, params_);
        }
        else if (ext == ".png")
        {
            image.Save(path, ImageFormat.Png);
        }
        else
        {
            image.Save(path); // 預設格式
        }
    }

    /// 儲存圖片至串流
    private static void SaveImageToStream(Image image, Stream stream, int jpegQuality)
    {
        var encoder = GetEncoder(ImageFormat.Jpeg);
        var params_ = new EncoderParameters(1);
        params_.Param[0] = new EncoderParameter(Encoder.Quality, (long)jpegQuality);
        image.Save(stream, encoder, params_);
    }

    /// 轉換為 Byte[] (JPEG)
    private static byte[] ImageToBytes(Image image, int jpegQuality)
    {
        using (var ms = new MemoryStream())
        {
            SaveImageToStream(image, ms, jpegQuality);
            return ms.ToArray();
        }
    }

    /// 取得影像編碼器
    private static ImageCodecInfo GetEncoder(ImageFormat format)
    {
        return ImageCodecInfo.GetImageDecoders()
            .FirstOrDefault(c => c.FormatID == format.Guid);
    }
}

使用範例

1. 上傳後自動產生多種尺寸縮圖

protected void btnUpload_Click(object sender, EventArgs e)
{
    if (!fuImage.HasFile) return;

    string uploadFolder = Server.MapPath("~/Uploads/");
    string thumbFolder = Server.MapPath("~/Thumbs/");
    Directory.CreateDirectory(uploadFolder);
    Directory.CreateDirectory(thumbFolder);

    string fileName = Guid.NewGuid() + Path.GetExtension(fuImage.FileName);
    string srcPath = Path.Combine(uploadFolder, fileName);
    fuImage.SaveAs(srcPath);

    // 產生三種尺寸
    ImageHelper.CreateThumbnail(srcPath, Path.Combine(thumbFolder, "large_" + fileName), 800, 600);
    ImageHelper.CreateThumbnail(srcPath, Path.Combine(thumbFolder, "medium_" + fileName), 400, 300);
    ImageHelper.CreateThumbnail(srcPath, Path.Combine(thumbFolder, "small_" + fileName), 150, 150, ImageHelper.ThumbnailMode.CropToSize); // 正方形裁切
}

2. 通用處理程式 即時輸出縮圖

// Thumbnail.ashx
public class ThumbnailHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        string src = context.Request["src"]; // 相對路徑
        int w = int.TryParse(context.Request["w"], out var _w) ? _w : 200;
        int h = int.TryParse(context.Request["h"], out var _h) ? _h : 200;
        string modeStr = context.Request["mode"] ?? "keep";
        ImageHelper.ThumbnailMode mode = Enum.TryParse(modeStr, true, out ImageHelper.ThumbnailMode m) ? m : ImageHelper.ThumbnailMode.KeepAspectRatio;

        string physPath = context.Server.MapPath(src);
        if (!File.Exists(physPath))
        {
            context.Response.StatusCode = 404;
            return;
        }

        context.Response.ContentType = "image/jpeg";
        context.Response.Cache.SetCacheability(HttpCacheability.Public);
        context.Response.Cache.SetMaxAge(TimeSpan.FromDays(30));

        ImageHelper.CreateThumbnailToStream(
            new FileStream(physPath, FileMode.Open, FileAccess.Read, FileShare.Read),
            context.Response.OutputStream,
            w, h, mode);
    }
    public bool IsReusable => true;
}

3. Web API 回傳 Base64 / Byte[]

[HttpPost]
public IHttpActionResult GenerateThumb()
{
    var file = HttpContext.Current.Request.Files[0];
    if (file == null) return BadRequest();

    using (var ms = new MemoryStream())
    {
        file.InputStream.CopyTo(ms);
        byte[] thumbBytes = ImageHelper.CreateThumbnailBytes(ms.ToArray(), 300, 200);
        return Ok(Convert.ToBase64String(thumbBytes));
    }
}

關鍵技術細節解析

技術點說明
Graphics.DrawImage vs GetThumbnailImage`GetThumbnailImage` 速度快但品質差、不支援插值模式控制。`Graphics.DrawImage` 配合 `InterpolationMode.HighQualityBicubic` 品質最佳。
EXIF 旋轉修正手機拍照含 Orientation 標籤 (1-8),直接顯示會旋轉錯誤。`AutoRotateImage` 讀取標籤並實際旋轉像素,移除標籤。
白色背景填充透明 PNG 轉 JPEG 會產生黑底。`g.Clear(Color.White)` 解決。
JPEG 品質參數1-100,85 為平衡點。`Encoder.Quality` 需搭配 `ImageCodecInfo` 使用。
記憶體管理所有 `Image`、`Graphics`、`Stream` 均用 `using` 確保釋放非托管資源,防止 GDI+ 記憶體洩漏。
解析度保留`result.SetResolution` 保持原圖 DPI,列印品質不受影響。

效能優化建議

  • 快取:相同參數請求直接回傳快取檔案/Byte[] (檔案名含參數 Hash)。
  • 非同步:大量圖片處理使用 `Task.Run` 避免阻塞 IIS 執行緒。
  • ImageSharp (跨平台替代):.NET Core / Linux 環境建議改用 SixLabors.ImageSharp (純 managed、無 GDI+ 相依)。

總結

本文提供的 `ImageHelper` 類別涵蓋企業級縮圖需求的核心功能:多種縮放模式、高品質插值、EXIF 修正、JPEG 品質控制、多種輸出目標。直接複製至專案共用庫即可使用,並可依專案需求擴充水印、圓角、濾鏡等功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved