之前我写了一篇 .NetCore实现图片缩放与裁剪 - 基于ImageSharp,里面有生成尺寸随机图片的算法,同时也是StarBlog博客中原有的实现方式,不过偶尔刷新页面的时候我注意到有些图片加载不出来,调试了一下发现原来是报错了,原本这个算法有bug。于是利用周末时间重新实现了一遍,这下可以说是完美了~
生成随机尺寸图片的功能目前用在文章卡片上,原本使用的是LoremPicsum提供的服务,但它的服务器在国外,上线之后发现加载太慢了,经常加载不出来,于是决定自己实现一版。功能基础是上文提到的文章中的ImageSharp。

其他的还有诸如指定随机seed、将seed与图片进行静态映射等扩展功能的实现。
关键功能在于「根据指定的尺寸缩放或裁剪图片」
难点在于裁剪和缩放图片要保证:
第一版我是将横屏和竖屏的图片分开处理,(在输入尺寸不超过原图尺寸的情况下)先把比例接近的边调整成一样的大小,再裁剪中间部分,不过问题也很明显,如果调整大小之后另一条边的长度小于输入长度,那就会拉伸图片,导致比例改变。
在参考几个类似的MATLAB和Python项目之后,我换了别的思路:
比如原图是 1080 x 2340 的尺寸,输入的图片是 400 x 200 尺寸
那第一步判断尺寸不超过原图,不需要缩放
然后是「按输入的尺寸比例裁剪」,把 400 x 200 化简成 2 : 1 的比例
在原图中截取 2 : 1 的大小,即 1080 x 540
然后再把截取的图片调整到 400 x 200,搞定!
看下效果
| 原图 | 输出(400x200) | 输出(200x300) |
|---|---|---|
![]() | ![]() | ![]() |
虽然比一开始的方案更费一丢丢内存,但却实实在在提升了出图成功率,nice~
直接上代码好了,根据上面提到的思路,分两步走,代码也比一开始的方案更整洁
async Task<(Image, IImageFormat)> GenerateSizedImageAsync(string imagePath, int width, int height) { await using var fileStream = new FileStream(imagePath, FileMode.Open); var (image, format) = await Image.LoadWithFormatAsync(fileStream); // 输出尺寸超出原图片尺寸,放大 if (width > image.Width && height > image.Height) { image.Mutate(a => a.Resize(width, height)); } else if (width > image.Width || height > image.Height) { // 改变比例大的边 if (width / image.Width < height / image.Height) image.Mutate(a => a.Resize(0, height)); else image.Mutate(a => a.Resize(width, 0)); } // 将输入的尺寸作为裁剪比例 var (scaleWidth, scaleHeight) = GetPhotoScale(width, height); var cropWidth = image.Width; var cropHeight = (int) (image.Width / scaleWidth * scaleHeight); if (cropHeight > image.Height) { cropHeight = image.Height; cropWidth = (int) (image.Height / scaleHeight * scaleWidth); } var cropRect = new Rectangle((image.Width - cropWidth) / 2, (image.Height - cropHeight) / 2, cropWidth, cropHeight); image.Mutate(a => a.Crop(cropRect)); image.Mutate(a => a.Resize(width, height)); return (image, format); }里面还用到了计算图片比例,很简单,先算出图片宽度和高度的最大公约数,然后宽高分别除以这个最大公约数,就是比例了(也就是化简分数)
计算最大公约数代码
private static int GetGreatestCommonDivisor(int m, int n) { if (m < n) (n, m) = (m, n); while (n != 0) { var r = m % n; m = n; n = r; } return m;}计算图片比例代码
private static (double, double) GetPhotoScale(int width, int height) { if (width == height) return (1, 1); var gcd = GetGreatestCommonDivisor(width, height); return ((double)width / gcd, (double)height / gcd);}