普通处理
以下是一个使用 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);
注意事项
-
环境要求:
- .NET Framework 4.6+ 可直接使用
System.Drawing。 - .NET Core / .NET 5+ 需通过 NuGet 安装
System.Drawing.Common。 - 该方案在 Windows 上完全支持,跨平台(Linux/macOS)需安装
libgdiplus。
- .NET Framework 4.6+ 可直接使用
-
输出格式 :
即使输入文件扩展名不是
.jpg,只要文件本身是 JPEG 格式,函数依然可以处理并输出为 JPEG。如需严格校验输入格式,可在Image.FromFile后检查originalImage.RawFormat。 -
内存管理 :
使用了多个
using块,确保非托管资源(GDI+ 句柄)及时释放。 -
性能优化 :
InterpolationMode.HighQualityBicubic提供较好的缩放质量,但速度略慢。若对速度有更高要求,可改为InterpolationMode.Bilinear或InterpolationMode.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);
}
}
✨ 核心改动说明
-
自动检测覆盖场景
使用
Path.GetFullPath规范化路径并忽略大小写比较,准确判断输入、输出是否为同一文件。 -
内存加载解除文件锁
当需要覆盖原文件时,通过
File.ReadAllBytes将图片数据载入内存,再用Image.FromStream加载,此时原文件已完全释放,后续可安全写入。 -
提取共用缩放保存逻辑
将缩放和保存代码抽离为
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 会自动处理覆盖
}
}
根据项目环境选择合适方案即可。