Skip to content

Commit 1d6f70e

Browse files
committed
完成图片优化处理
1 parent e65a535 commit 1d6f70e

9 files changed

Lines changed: 242 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
6+
using SixLabors.ImageSharp;
7+
using SixLabors.ImageSharp.Formats.Gif;
8+
using SixLabors.ImageSharp.Formats.Png;
9+
using SixLabors.ImageSharp.PixelFormats;
10+
using SixLabors.ImageSharp.Processing;
11+
12+
namespace DotNetCampus.MediaConverters.Imaging.Optimizations;
13+
14+
public static class ImageFileOptimization
15+
{
16+
/// <summary>
17+
/// 对插入的图片进行一些预处理,以解决各种各样的图片处理问题(如 DPI 问题、内存问题、后缀名错误等)。
18+
/// 其中,最大宽高是图片为 96.0 DPI 下的逻辑宽高。
19+
/// 在处理完后,将返回一张新的图片路径。
20+
/// </summary>
21+
/// <param name="imageFile"></param>
22+
/// <param name="maxImageWidth">限制图片的最大宽度。为空则表示不限制</param>
23+
/// <param name="maxImageHeight">限制图片的最大高度。为空则表示不限制</param>
24+
/// <param name="copyNewFile">是否先行拷贝新的文件,再进行处理,避免图片占用。默认为 true。</param>
25+
/// <param name="workingFolder"></param>
26+
/// <returns></returns>
27+
public static async Task<ImageFileOptimizationResult> OptimizeImageFileAsync(FileInfo imageFile,
28+
DirectoryInfo workingFolder, int? maxImageWidth = null, int? maxImageHeight = null, bool copyNewFile = true)
29+
{
30+
if (!File.Exists(imageFile.FullName))
31+
{
32+
// 不能依靠 imageFile.Exists 属性,因为属性可能还没更新
33+
return new ImageFileOptimizationResult
34+
{
35+
OptimizedImageFile = null,
36+
FailureReason = ImageFileOptimizationFailureReason.FileNotFound
37+
};
38+
}
39+
40+
Directory.CreateDirectory(workingFolder.FullName);
41+
42+
var file = imageFile;
43+
if (copyNewFile)
44+
{
45+
var newFilePath = Path.Join(workingFolder.FullName, $"{Path.GetRandomFileName()}_{imageFile.Name}");
46+
file.CopyTo(newFilePath);
47+
file = new FileInfo(newFilePath);
48+
}
49+
50+
Image<Rgba32> image;
51+
try
52+
{
53+
await using var fileStream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
54+
55+
// 额外输出旋转图片的情况
56+
image = await Image.LoadAsync<Rgba32>(fileStream);
57+
}
58+
catch (ImageFormatException e)
59+
{
60+
// 这里是明确的图片处理的错误,可以转换为输出信息。如果是其他错误,继续抛出
61+
ImageFileOptimizationFailureReason failureReason = default;
62+
63+
if (e is UnknownImageFormatException)
64+
{
65+
failureReason = ImageFileOptimizationFailureReason.UnknownImageFormat;
66+
}
67+
else if (e is InvalidImageContentException)
68+
{
69+
failureReason = ImageFileOptimizationFailureReason.InvalidImageContent;
70+
}
71+
72+
return new ImageFileOptimizationResult()
73+
{
74+
OptimizedImageFile = null,
75+
Exception = e,
76+
FailureReason = failureReason
77+
};
78+
}
79+
80+
if (image.Metadata.DecodedImageFormat is GifFormat)
81+
{
82+
return new ImageFileOptimizationResult()
83+
{
84+
OptimizedImageFile = null,
85+
FailureReason = ImageFileOptimizationFailureReason.NotSupported
86+
};
87+
}
88+
89+
var fileExtension = image.Metadata.DecodedImageFormat?.FileExtensions.FirstOrDefault();
90+
91+
if (maxImageWidth is not null && maxImageHeight is not null)
92+
{
93+
LimitImageSize(image, maxImageWidth.Value * maxImageHeight.Value);
94+
}
95+
else
96+
{
97+
LimitImageSize(image, maxImageWidth, maxImageHeight);
98+
}
99+
100+
// 重新保存即可
101+
var outputImageFilePath = Path.Join(workingFolder.FullName, $"{Path.GetRandomFileName()}.png");
102+
await image.SaveAsPngAsync(outputImageFilePath, new PngEncoder()
103+
{
104+
ColorType = PngColorType.RgbWithAlpha,
105+
BitDepth = PngBitDepth.Bit8,
106+
});
107+
108+
return new ImageFileOptimizationResult()
109+
{
110+
OptimizedImageFile = new FileInfo(outputImageFilePath),
111+
FailureReason = ImageFileOptimizationFailureReason.Ok
112+
};
113+
}
114+
115+
/// <summary>
116+
/// 限制图片尺寸。按照面积方式限制,保持比例
117+
/// </summary>
118+
/// <param name="image"></param>
119+
/// <param name="maxPixelCount"></param>
120+
public static void LimitImageSize(Image<Rgba32> image, int maxPixelCount)
121+
{
122+
if (image.Width * image.Height <= maxPixelCount)
123+
{
124+
// 尺寸没有超过限制,直接返回
125+
return;
126+
}
127+
128+
var pixelWidth = (int) Math.Sqrt(maxPixelCount * image.Width / (double) image.Height);
129+
var pixelHeight = (int) Math.Sqrt(maxPixelCount * image.Height / (double) image.Width);
130+
image.Mutate(context => context.Resize(new Size(pixelWidth, pixelHeight), compand: true));
131+
}
132+
133+
/// <summary>
134+
/// 限制图片尺寸。保持比例
135+
/// </summary>
136+
/// <param name="image"></param>
137+
/// <param name="maxImageWidth"></param>
138+
/// <param name="maxImageHeight"></param>
139+
public static void LimitImageSize(Image<Rgba32> image, int? maxImageWidth, int? maxImageHeight)
140+
{
141+
if (maxImageWidth is null && maxImageHeight is null)
142+
{
143+
return;
144+
}
145+
146+
if (image.Width <= maxImageWidth && image.Height <= maxImageHeight)
147+
{
148+
return;
149+
}
150+
151+
var pixelWidth = image.Width;
152+
var pixelHeight = image.Height;
153+
154+
var scale = pixelWidth / (double) pixelHeight;
155+
156+
// 如果图片的宽度超过限制,则缩放到限制的宽度
157+
if (maxImageWidth is not null && pixelWidth > maxImageWidth)
158+
{
159+
pixelWidth = maxImageWidth.Value;
160+
pixelHeight = (int) Math.Round(pixelWidth / scale, MidpointRounding.AwayFromZero);
161+
}
162+
163+
if (maxImageHeight is not null && pixelHeight > maxImageHeight)
164+
{
165+
pixelHeight = maxImageHeight.Value;
166+
pixelWidth = (int) Math.Round(pixelHeight * scale, MidpointRounding.AwayFromZero);
167+
}
168+
169+
image.Mutate(context => context.Resize(new Size(pixelWidth, pixelHeight), compand: true));
170+
}
171+
172+
/// <summary>
173+
/// 图片压缩的最大宽度
174+
/// </summary>
175+
private const int MaxWidth = 3840;
176+
177+
/// <summary>
178+
/// 图片压缩的最大高度
179+
/// </summary>
180+
private const int MaxHeight = 2160;
181+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace DotNetCampus.MediaConverters.Imaging.Optimizations;
2+
3+
public enum ImageFileOptimizationFailureReason
4+
{
5+
/// <summary>
6+
/// 没有错误
7+
/// </summary>
8+
Ok,
9+
10+
/// <summary>
11+
/// 图片文件格式不支持
12+
/// </summary>
13+
UnknownImageFormat,
14+
15+
/// <summary>
16+
/// 图片内容无效
17+
/// </summary>
18+
InvalidImageContent,
19+
20+
/// <summary>
21+
/// 图片文件未找到
22+
/// </summary>
23+
FileNotFound,
24+
25+
/// <summary>
26+
/// 不支持的图片格式
27+
/// </summary>
28+
NotSupported,
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.IO;
4+
5+
namespace DotNetCampus.MediaConverters.Imaging.Optimizations;
6+
7+
/// <summary>
8+
/// 图片文件优化结果
9+
/// </summary>
10+
public readonly record struct ImageFileOptimizationResult()
11+
{
12+
/// <summary>
13+
/// 优化后的图片文件
14+
/// </summary>
15+
public required FileInfo? OptimizedImageFile { get; init; }
16+
17+
public Exception? Exception { get; init; }
18+
public ImageFileOptimizationFailureReason FailureReason { get; init; } = ImageFileOptimizationFailureReason.Ok;
19+
20+
[MemberNotNullWhen(true, nameof(OptimizedImageFile))]
21+
public bool IsSuccess => OptimizedImageFile is not null;
22+
}
499 KB
Loading
99.7 KB
Loading

src/MediaConverters/MediaConverters.Tests/Assets/TestFiles/file_example_SVG_20kB.svg

Lines changed: 10 additions & 0 deletions
Loading
Binary file not shown.
49.2 KB
Loading
Binary file not shown.

0 commit comments

Comments
 (0)