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+ }
0 commit comments