本文只是告诉大家如何计算缩放之后的宽度和高度,不包含实际的图片缩放方法
如下图,我要将图片的大小进行等比缩放,此时我要求图片的宽度和高度大于最小尺寸,但是要求宽度和高度都不大于最大尺寸,如果这两个规则冲突,优先满足不大于最大尺寸
按照规则可以看到,如下图,图片的宽度等于最大宽度了,此时虽然图片的高度小于最小高度,但是也不应该对图片进行缩放
为什么需要有最大限制?原因是等比缩放对于长图计算不友好,如果我有一张图片的宽度和高度比例是 1:1000 那么此时如果没有限制最大高度,那么将宽度缩放到最小宽度需要缩放10倍,此时的高度就太大了
下面就是计算方法
先定义大小这个类
public class Size
{
public Size(double width, double height)
{
Width = width;
Height = height;
}
public double Width { set; get; }
public double Height { set; get; }
/// <inheritdoc />
public override string ToString()
{
return $"Width={Width:0.00} Height={Height:0.00}";
}
}
计算的方法需要传入当前图片的宽度和高度,最小的宽度和高度和最大的宽度和高度,返回的值是计算的宽度和高度
static Size OptimizationSize(Size currentSize, Size minSize, Size maxSize)
计算的方法就是先获取宽度和高度的缩放到最小大小的缩放值
var height = currentSize.Height;
var width = currentSize.Width;
var widthScale = minSize.Width / width;
var heightScale = minSize.Height / height;
如果此时的图片本来就大于最小宽度和高度,就不能让图片的缩放比原来小,除非大于最大宽度或高度
var minScale = Math.Max(widthScale, heightScale);
minScale = Math.Max(minScale, 1.0);
计算图片和最大宽度和高度的缩放,同时拿到最大缩放里面的最小的一个,这样缩放完成之后就不会大于最大的宽度和高度
var maxWidthScale = maxSize.Width / width;
var maxHeightScale = maxSize.Height / height;
var maxScale = Math.Min(maxWidthScale, maxHeightScale);
现在获取了 minScale 和 maxScale 需要根据这两个缩放计算最小的缩放
var scale = Math.Min(minScale, maxScale);
为了让图片显示更锐利,这里让缩放是整数,使用整数可能略大于最大的宽度和高度
scale = Math.Ceiling(scale);
所有代码
/// <summary>
/// 宽度和高度不小于最小大小,但是不大于最大大小,缩放使用等比缩放
/// <para/>
/// 规则:
/// <para/>
/// - 如果有一边小于最小大小,那么缩放到这一边大于等于最小大小
/// <para/>
/// - 如果一边缩放之后大于最大的大小,那么限制不能超过最大的大小
/// <para/>
/// - 尽可能让大小接近最小大小,但是保证宽度和高度都不大于最大大小
/// </summary>
/// <param name="currentSize"></param>
/// <param name="minSize"></param>
/// <param name="maxSize"></param>
/// <returns></returns>
static Size OptimizationSize(Size currentSize, Size minSize, Size maxSize)
{
if (currentSize.Width > minSize.Width && currentSize.Height > minSize.Height)
{
if (currentSize.Width <= maxSize.Width && currentSize.Height <= maxSize.Height)
{
return currentSize;
}
}
var height = currentSize.Height;
var width = currentSize.Width;
var widthScale = minSize.Width / width;
var heightScale = minSize.Height / height;
// 如果超过最大的大小
var maxWidthScale = maxSize.Width / width;
var maxHeightScale = maxSize.Height / height;
var maxScale = Math.Min(maxWidthScale, maxHeightScale);
var minScale = Math.Max(widthScale, heightScale);
minScale = Math.Max(minScale, 1.0);
var scale = Math.Min(minScale, maxScale);
// 为了尽量使用整数,这里使用了 Math.Ceiling 计算
return new Size(Math.Ceiling(width * scale), Math.Ceiling(height * scale));
}
在 WPF 中可以通过设置 Image 控件的宽度和高度,此时因为尺寸是使用相同的值缩放,所以刚好图片使用 Fill 就能贴上去
但是无论用什么的算法,只要图片和原始尺寸不相同,那么一定会糊。无论是图片进行放大或缩小,都会模糊,这是基础的知识,想了解原理请自行百度
另一个算法是按照填充像素数量计算的,限制填充像素数量限制大小
public static Size GetImageOptimizationSize(Size originSize, int maxPixel, int requestedPixel)
传入图片的大小,以及限制的最大像素和期望的像素大小。如果图片大小大于期望的像素,小于最大的像素大小,那么返回图片原始大小
public static Size GetImageOptimizationSize(Size originSize, int maxPixel, int requestedPixel)
{
var imagePixel = originSize.Width * originSize.Height;
if (imagePixel <= maxPixel)
{
// 是不是太小了,需要缩放
if (imagePixel < requestedPixel)
{
return GetOptimizationSize(originSize, requestedPixel);
}
}
else
{
return GetOptimizationSize(originSize, maxPixel);
}
return originSize;
static Size GetOptimizationSize(Size originSize, double requestedPixel)
{
// 定义 Ow 是 originSize.Width
// 定义 Oh 是 originSize.Height
// 返回值为 requestedSize 根据 requestedPixel 计算
// 定义 Rw 是 requestedSize.Width
// 定义 Rh 是 requestedSize.Height
// 假定比例不变,于是有
// Ow / Oh = Rw / Rh
// 也就是前后的宽度高度比例不变
// 上面表达式交换可以等于
// Ow * Rh = Rw * Oh
// 而 requestedPixel = requestedSize.Width * requestedSize.Height
// 定义 P 是 requestedPixel
// 于是有 Rw = P / Rh
// 因此 Ow * Rh = Rw * Oh = P / Rh * Oh
// Ow * Rh = P / Rh * Oh
// Rh * Rh = P * Oh / Ow
// 因此 Rh = Math.Sqrt(P * Oh / Ow)
// 相同方式可以计算 Rw 的值
var requestedWidth = (int)Math.Sqrt(requestedPixel * originSize.Width / originSize.Height);
var requestedHeight = (int)Math.Sqrt(requestedPixel * originSize.Height / originSize.Width);
return new Size(requestedWidth, requestedHeight);
}
}
上面的方法更简单,但是我添加了很多注释用来告诉大家这是如何计算的
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-C-%E5%9B%BE%E7%89%87%E7%AD%89%E6%AF%94%E9%99%90%E5%88%B6%E6%9C%80%E5%A4%A7%E5%92%8C%E6%9C%80%E5%B0%8F%E5%A4%A7%E5%B0%8F%E7%BC%A9%E6%94%BE%E7%AE%97%E6%B3%95.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利