WPF中的图片组件,本身是支持不同的拉伸效果。具体如下:
None, //不做拉伸
Fill, //完全填充(会变形)
Uniform, //等比缩放,不会变形
UniformToFill //等比缩放,并完全填充。不会变形,但是长的部分会被裁剪
但是,如果我们要实现像QQ或者微信这样子的聊天气泡功能,直接使用图片组件就无法满足要求了。
我们可以观察下微信的聊天气泡,他的宽度和高度可以根据我们输入的内容自动调整,并且背景图片也不会存在变形的问题。
今天我们就用WPF来实现这个功能!
要实现不变形的拉伸功能,我们可以针对1个像素来进行拉伸,这样拉伸出来的图片,除了拉伸区域的像素都是一样的,其它区域还是保留了原来的图片的外观。
这里主要需要用到CroppedBitmap
类,该类主要用于裁剪,可以对BitmapImage
进行裁剪。
微信聊天气泡这种场景,它需要支持水平和垂直的方向的拉伸效果,我们可以利用CroppedBitmap
,将原始图片裁剪成9张图,渲染的时候,我们分别将9张图渲染到对应的位置。
拉伸的区域就是9张图中的上面中间位置,下面中间位置,左边中间位置,右边中间位置以及最中间的位置。这几张图片,都按1个像素进行裁剪,这样就不会出现拉伸的图片了。
关键代码:
private ImageSource GetCroppedBitmap(double x, double y, double width, double height)
{
return new CroppedBitmap(ImageSource, new Int32Rect((int)x, (int)y, (int)width, (int)height));
}
/// <summary>
/// 获取水平偏移像素值
/// </summary>
/// <returns></returns>
private int GetStretchHeight()
{
return (int)(RenderSize.Height - ImageSource.PixelHeight);
}
/// <summary>
/// 获取垂直位置的偏移像素值
/// </summary>
/// <returns></returns>
private int GetStretchWidth()
{
return (int)(RenderSize.Width - ImageSource.PixelWidth);
}
/// <summary>
/// 水平拉伸是否可用
/// </summary>
private bool IsHorizontalStretchEnabled
{
get
{
if (HorizontalOffset > 0 && HorizontalOffset < ImageSource.PixelWidth)
{
return true;
}
return false;
}
}
/// <summary>
/// 垂直拉伸是否可用
/// </summary>
private bool IsVerticalStretchEnabled
{
get
{
if (VerticalOffset > 0 && VerticalOffset < ImageSource.PixelHeight)
{
return true;
}
return false;
}
}
//绘制水平+垂直拉伸的方法
protected override void OnRender(DrawingContext drawingContext)
{
var stretchHeight = GetStretchHeight();
if (stretchHeight < 0) stretchHeight = 0;
var stretchWidth = GetStretchWidth();
if (stretchWidth < 0) stretchWidth = 0;
if(stretchHeight==0 && stretchWidth == 0)
{
drawingContext.DrawImage(ImageSource, new Rect(0, 0, ImageSource.PixelWidth, ImageSource.PixelHeight));
return;
}
//这个需要9张图
//左上,左中,左下,右上,右中,右下,水平中,垂直中
var leftTop = GetCroppedBitmap(0, 0, HorizontalOffset, VerticalOffset);
var leftBottom = GetCroppedBitmap(0, VerticalOffset + 1, HorizontalOffset, ImageSource.PixelHeight - VerticalOffset - 1);
var rightTop = GetCroppedBitmap(HorizontalOffset + 1, 0, ImageSource.PixelWidth - HorizontalOffset - 1, VerticalOffset);
var rightBottom = GetCroppedBitmap(HorizontalOffset + 1, VerticalOffset + 1, ImageSource.PixelWidth - HorizontalOffset - 1, ImageSource.PixelHeight - VerticalOffset - 1);
//最中间的
var center = GetCroppedBitmap(HorizontalOffset, VerticalOffset, 1, 1);
var leftCenter = GetCroppedBitmap(0, VerticalOffset, HorizontalOffset, 1);
var rightCenter = GetCroppedBitmap(HorizontalOffset + 1, VerticalOffset, ImageSource.PixelWidth - HorizontalOffset - 1, 1);
var topCenter = GetCroppedBitmap(HorizontalOffset, 0, 1, VerticalOffset);
var bottomCenter = GetCroppedBitmap(HorizontalOffset, VerticalOffset + 1, 1, ImageSource.PixelHeight - VerticalOffset - 1);
//------------------------------- 上面的逻辑是切图,下面的逻辑是绘制 -----------------------------------
drawingContext.DrawImage(leftTop, new Rect(0, 0, HorizontalOffset, VerticalOffset));
drawingContext.DrawImage(rightTop, new Rect(HorizontalOffset + stretchWidth, 0, ImageSource.PixelWidth - HorizontalOffset - 1, VerticalOffset));
if (stretchHeight > 0)
{
drawingContext.DrawImage(leftCenter, new Rect(0, VerticalOffset, HorizontalOffset, stretchHeight));
drawingContext.DrawImage(rightCenter, new Rect(HorizontalOffset + stretchWidth, VerticalOffset, ImageSource.PixelWidth - HorizontalOffset - 1, stretchHeight));
}
if (stretchWidth > 0)
{
drawingContext.DrawImage(topCenter, new Rect(HorizontalOffset, 0, stretchWidth, VerticalOffset));
drawingContext.DrawImage(bottomCenter, new Rect(HorizontalOffset, VerticalOffset + stretchHeight, stretchWidth, ImageSource.PixelHeight - VerticalOffset - 1));
}
if (stretchHeight > 0 && stretchWidth > 0)
{
drawingContext.DrawImage(center, new Rect(HorizontalOffset, VerticalOffset, stretchWidth, stretchHeight));
}
drawingContext.DrawImage(leftBottom, new Rect(0, VerticalOffset + stretchHeight, HorizontalOffset, ImageSource.PixelHeight - VerticalOffset - 1));
drawingContext.DrawImage(rightBottom, new Rect(HorizontalOffset + stretchWidth, VerticalOffset + stretchHeight, ImageSource.PixelWidth - HorizontalOffset - 1, ImageSource.PixelHeight - VerticalOffset - 1));
}
//仅支持水平方向拉伸
protected override void OnRender(DrawingContext drawingContext)
{
var stretchWidth = GetStretchWidth();
if (stretchWidth <= 0)
{
drawingContext.DrawImage(ImageSource, new Rect(0, 0, ImageSource.PixelWidth, ImageSource.PixelHeight));
return;
}
var left = GetCroppedBitmap(0, 0, HorizontalOffset, ImageSource.PixelHeight);
var center = GetCroppedBitmap(HorizontalOffset, 0, 1, ImageSource.PixelHeight);
var right = GetCroppedBitmap(HorizontalOffset + 1, 0, ImageSource.PixelWidth - HorizontalOffset - 1, ImageSource.PixelHeight);
drawingContext.DrawImage(left, new Rect(0, 0, HorizontalOffset, ImageSource.PixelHeight));
if (stretchWidth > 0)
{
drawingContext.DrawImage(center, new Rect(HorizontalOffset, 0, stretchWidth, ImageSource.PixelHeight));
}
else
{
stretchWidth = 0;
}
drawingContext.DrawImage(right, new Rect(HorizontalOffset + stretchWidth, 0, ImageSource.PixelWidth - HorizontalOffset - 1, ImageSource.PixelHeight));
}
//仅支持垂直方向拉伸
protected override void OnRender(DrawingContext drawingContext)
{
var stretchHeight = GetStretchHeight();
if (stretchHeight <= 0)
{
drawingContext.DrawImage(ImageSource, new Rect(0, 0, ImageSource.PixelWidth, ImageSource.PixelHeight));
return;
}
//裁剪还是按原始图片尺寸来裁剪,这里的原始图片尺寸是按dpi 96计算的
var top = GetCroppedBitmap(0, 0, ImageSource.PixelWidth, VerticalOffset);
var center = GetCroppedBitmap(0, VerticalOffset, ImageSource.PixelWidth, 1);
var bottom = GetCroppedBitmap(0, VerticalOffset + 1, ImageSource.PixelWidth, ImageSource.PixelHeight - VerticalOffset - 1);
//这里用 PixelWidth,而不是 Width,主要是因为显示的时候,要按图片的解码尺寸来显示,否则图片就会变大或者变小,这个是由DPI决定的
drawingContext.DrawImage(top, new Rect(0, 0, ImageSource.PixelWidth, VerticalOffset));
//超长才需要显示拉伸部分的图片
if (stretchHeight > 0)
{
drawingContext.DrawImage(center, new Rect(0, VerticalOffset, ImageSource.PixelWidth, stretchHeight));
}
else
{
stretchHeight = 0;
}
drawingContext.DrawImage(bottom, new Rect(0, VerticalOffset + stretchHeight, ImageSource.PixelWidth, ImageSource.PixelHeight - VerticalOffset - 1));
}
//测量布局大小,这里要记得重写下。这个版本不支持不同尺寸的分辨率,可以通过计算缩放比来实现
private Size MeasureCore(Size size, ImageSource imgSource)
{
if (imgSource == null) return size;
Size naturalSize;
if (IsHorizontalStretchEnabled && IsVerticalStretchEnabled)
{
naturalSize = new Size(size.Width, size.Height);
}
else if (IsHorizontalStretchEnabled)
{
naturalSize = new Size(size.Width, imgSource.PixelHeight);
}
else if (IsVerticalStretchEnabled)
{
naturalSize = new Size(imgSource.PixelWidth, size.Height);
}
else
{
return size;
}
return naturalSize;
}
以上代码就可以实习水平拉伸,垂直拉伸或者水平+垂直拉伸的功能了。目前的测试代码还不支持不同分辨率的图片,demo中的计算是使用了ImageSource的宽高。如果需要支持任意分辨率,可以按渲染的宽高和图片的实际宽高做个比例缩放运算即可。
微信公众号:
欢迎转载分享,如若转载,请标注署名。
本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/WPF-%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%9B%BE%E7%89%87%E6%8C%89%E5%83%8F%E7%B4%A0%E6%8B%89%E4%BC%B8.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。