在c#中,缩放jpg文件的尺寸

普通处理

以下是一个使用 System.Drawing 调整 JPG 图像宽度为 365 像素、高度按比例自动缩放的 C# 函数。该方法适用于 .NET Framework 4.6+ 或 .NET Core/.NET 5+(需安装 System.Drawing.Common NuGet 包)。

csharp 复制代码
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

public static class ImageHelper
{
    /// <summary>
    /// 将 JPG 图像宽度调整为指定像素(默认 365),高度按原比例自动计算,并保存为新文件。
    /// </summary>
    /// <param name="inputPath">输入 JPG 文件路径</param>
    /// <param name="outputPath">输出 JPG 文件路径</param>
    /// <param name="targetWidth">目标宽度,默认 365</param>
    /// <param name="quality">JPEG 质量(1-100),默认 90</param>
    public static void ResizeJpgToWidth(string inputPath, string outputPath, int targetWidth = 365, int quality = 90)
    {
        if (string.IsNullOrEmpty(inputPath))
            throw new ArgumentNullException(nameof(inputPath));
        if (string.IsNullOrEmpty(outputPath))
            throw new ArgumentNullException(nameof(outputPath));
        if (targetWidth <= 0)
            throw new ArgumentOutOfRangeException(nameof(targetWidth), "目标宽度必须大于0");
        if (quality < 1 || quality > 100)
            throw new ArgumentOutOfRangeException(nameof(quality), "质量值必须在1到100之间");

        using (var originalImage = Image.FromFile(inputPath))
        {
            // 计算等比例高度
            float ratio = (float)targetWidth / originalImage.Width;
            int newHeight = (int)(originalImage.Height * ratio);

            // 创建目标位图
            using (var resizedImage = new Bitmap(targetWidth, newHeight))
            {
                using (var graphics = Graphics.FromImage(resizedImage))
                {
                    // 设置高质量缩放参数
                    graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    graphics.SmoothingMode = SmoothingMode.HighQuality;
                    graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    graphics.CompositingQuality = CompositingQuality.HighQuality;

                    // 绘制缩放后的图像
                    graphics.DrawImage(originalImage, 0, 0, targetWidth, newHeight);
                }

                // 保存为 JPEG,可设置压缩质量
                var encoderParams = new EncoderParameters(1);
                encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);

                var jpegCodec = GetEncoder(ImageFormat.Jpeg);
                if (jpegCodec != null)
                {
                    resizedImage.Save(outputPath, jpegCodec, encoderParams);
                }
                else
                {
                    // 若找不到JPEG编码器则使用默认保存(可能质量较低)
                    resizedImage.Save(outputPath, ImageFormat.Jpeg);
                }
            }
        }
    }

    private static ImageCodecInfo GetEncoder(ImageFormat format)
    {
        var codecs = ImageCodecInfo.GetImageEncoders();
        foreach (var codec in codecs)
        {
            if (codec.FormatID == format.Guid)
                return codec;
        }
        return null;
    }
}

使用示例

csharp 复制代码
// 将 "photo.jpg" 宽度改为 365 像素,高度自动等比例,输出为 "resized.jpg"
ImageHelper.ResizeJpgToWidth(@"C:\temp\photo.jpg", @"C:\temp\resized.jpg");

// 自定义质量(例如 80%)
ImageHelper.ResizeJpgToWidth(@"C:\temp\photo.jpg", @"C:\temp\resized.jpg", quality: 80);

注意事项

  1. 环境要求

    • .NET Framework 4.6+ 可直接使用 System.Drawing
    • .NET Core / .NET 5+ 需通过 NuGet 安装 System.Drawing.Common
    • 该方案在 Windows 上完全支持,跨平台(Linux/macOS)需安装 libgdiplus
  2. 输出格式

    即使输入文件扩展名不是 .jpg,只要文件本身是 JPEG 格式,函数依然可以处理并输出为 JPEG。如需严格校验输入格式,可在 Image.FromFile 后检查 originalImage.RawFormat

  3. 内存管理

    使用了多个 using 块,确保非托管资源(GDI+ 句柄)及时释放。

  4. 性能优化
    InterpolationMode.HighQualityBicubic 提供较好的缩放质量,但速度略慢。若对速度有更高要求,可改为 InterpolationMode.BilinearInterpolationMode.Default

替代方案(跨平台推荐)

如果需要在非 Windows 环境(如 Linux 容器)中运行,推荐使用 SixLabors.ImageSharp,无需依赖系统库:

csharp 复制代码
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;

public static void ResizeJpgToWidth(string inputPath, string outputPath, int targetWidth = 365)
{
    using (var image = Image.Load(inputPath))
    {
        image.Mutate(x => x.Resize(targetWidth, 0)); // 0 表示保持纵横比
        image.Save(outputPath);
    }
}

使用 ImageSharp 更简洁,且完全托管,无需额外配置。建议在 .NET 6+ 项目中优先考虑。

以上函数可直接复制使用,按需调整参数即可。

需要覆盖原文件的方法

要让函数直接覆盖原文件,只需在调用时将输出路径设为与输入路径相同,但需处理文件锁问题:Image.FromFile 会一直锁定文件,导致无法直接写回原路径。
解决方案:当输入和输出路径相同时,先将原始图像完整读入内存(释放文件锁),再执行缩放并保存。

以下是改造后的函数,自动判断路径是否相同,并采用安全的内存加载方式:

csharp 复制代码
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

public static class ImageHelper
{
    /// <summary>
    /// 将 JPG 图像宽度调整为指定像素(默认 365),高度按原比例自动计算。
    /// 若输出路径与输入路径相同,则自动覆盖原文件(不会因文件锁定而报错)。
    /// </summary>
    /// <param name="inputPath">输入 JPG 文件路径</param>
    /// <param name="outputPath">输出 JPG 文件路径(可与输入相同)</param>
    /// <param name="targetWidth">目标宽度,默认 365</param>
    /// <param name="quality">JPEG 质量(1-100),默认 90</param>
    public static void ResizeJpgToWidth(string inputPath, string outputPath, int targetWidth = 365, int quality = 90)
    {
        if (string.IsNullOrEmpty(inputPath))
            throw new ArgumentNullException(nameof(inputPath));
        if (string.IsNullOrEmpty(outputPath))
            throw new ArgumentNullException(nameof(outputPath));
        if (targetWidth <= 0)
            throw new ArgumentOutOfRangeException(nameof(targetWidth), "目标宽度必须大于0");
        if (quality < 1 || quality > 100)
            throw new ArgumentOutOfRangeException(nameof(quality), "质量值必须在1到100之间");

        // 判断是否为同一物理文件(忽略大小写,并规范化路径)
        bool isOverwrite = string.Equals(
            Path.GetFullPath(inputPath),
            Path.GetFullPath(outputPath),
            StringComparison.OrdinalIgnoreCase);

        if (isOverwrite)
        {
            // 覆盖原文件:先将整个文件读入内存,释放文件锁
            byte[] imageBytes = File.ReadAllBytes(inputPath);
            using (var ms = new MemoryStream(imageBytes))
            using (var originalImage = Image.FromStream(ms))
            {
                ResizeAndSave(originalImage, outputPath, targetWidth, quality);
            }
        }
        else
        {
            // 不同文件:直接从文件加载(保留原有逻辑)
            using (var originalImage = Image.FromFile(inputPath))
            {
                ResizeAndSave(originalImage, outputPath, targetWidth, quality);
            }
        }
    }

    /// <summary>
    /// 缩放图像并保存为 JPEG(内部复用逻辑)
    /// </summary>
    private static void ResizeAndSave(Image originalImage, string outputPath, int targetWidth, int quality)
    {
        // 计算等比例高度
        float ratio = (float)targetWidth / originalImage.Width;
        int newHeight = (int)(originalImage.Height * ratio);

        using (var resizedImage = new Bitmap(targetWidth, newHeight))
        using (var graphics = Graphics.FromImage(resizedImage))
        {
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
            graphics.CompositingQuality = CompositingQuality.HighQuality;

            graphics.DrawImage(originalImage, 0, 0, targetWidth, newHeight);

            // 设置 JPEG 质量
            var encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);

            var jpegCodec = GetEncoder(ImageFormat.Jpeg);
            if (jpegCodec != null)
            {
                resizedImage.Save(outputPath, jpegCodec, encoderParams);
            }
            else
            {
                resizedImage.Save(outputPath, ImageFormat.Jpeg);
            }
        }
    }

    private static ImageCodecInfo GetEncoder(ImageFormat format)
    {
        var codecs = ImageCodecInfo.GetImageEncoders();
        foreach (var codec in codecs)
        {
            if (codec.FormatID == format.Guid)
                return codec;
        }
        return null;
    }

    // --- 便捷方法:直接覆盖原文件 ---
    public static void ResizeJpgToWidthAndOverwrite(string filePath, int targetWidth = 365, int quality = 90)
    {
        ResizeJpgToWidth(filePath, filePath, targetWidth, quality);
    }
}

✨ 核心改动说明

  1. 自动检测覆盖场景

    使用 Path.GetFullPath 规范化路径并忽略大小写比较,准确判断输入、输出是否为同一文件。

  2. 内存加载解除文件锁

    当需要覆盖原文件时,通过 File.ReadAllBytes 将图片数据载入内存,再用 Image.FromStream 加载,此时原文件已完全释放,后续可安全写入。

  3. 提取共用缩放保存逻辑

    将缩放和保存代码抽离为 ResizeAndSave 方法,避免重复代码。


📌 使用示例

csharp 复制代码
// 直接覆盖原文件(两种写法等价)
ImageHelper.ResizeJpgToWidth(@"C:\photo.jpg", @"C:\photo.jpg");
// 或使用专用方法
ImageHelper.ResizeJpgToWidthAndOverwrite(@"C:\photo.jpg");

⚠️ 注意事项

  • 格式校验 :该函数不强制检查输入文件是否为 JPEG,若输入其他格式,缩放仍可执行,但保存时会强行转为 JPEG。如需严格校验,可在 ResizeAndSave 前添加 if (originalImage.RawFormat.Guid != ImageFormat.Jpeg.Guid) throw ...
  • 性能:覆盖原文件时多一次内存拷贝,但对普通图片影响极小。
  • 跨平台 :若使用 System.Drawing.Common 在非 Windows 环境运行,仍需安装 libgdiplus。跨平台场景推荐改用 SixLabors.ImageSharp(完全托管,无文件锁问题)。

如果需要更简洁的跨平台方案(无需处理文件锁),可直接采用 ImageSharp:

csharp 复制代码
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;

public static void ResizeAndOverwrite(string filePath, int targetWidth = 365)
{
    using (var image = Image.Load(filePath))
    {
        image.Mutate(x => x.Resize(targetWidth, 0));
        image.Save(filePath);  // ImageSharp 会自动处理覆盖
    }
}

根据项目环境选择合适方案即可。

相关推荐
那起舞的日子1 小时前
卡拉兹函数
java·算法
颜酱2 小时前
滑动窗口算法通关指南:从模板到实战,搞定LeetCode高频题
javascript·后端·算法
Stringzhua2 小时前
队列-双端队列【Queue2】
java·数据结构·算法·队列
Never_Satisfied2 小时前
在c#中,控件的事件执行耗时操作导致窗体无法及时处理绘制、鼠标点击
开发语言·c#
侧岭灵风2 小时前
yolov5颈部网络图解
深度学习·算法·yolo
夕除2 小时前
js--21
java·python·算法
冬夜戏雪2 小时前
单词拆分/分割等和子集
算法·leetcode·职场和发展
追随者永远是胜利者2 小时前
(LeetCode-Hot100)21. 合并两个有序链表
java·算法·leetcode·链表·go
重生之后端学习2 小时前
994. 腐烂的橘子
java·开发语言·数据结构·后端·算法·深度优先