WPF中的图片组件,本身是支持不同的拉伸效果。具体如下:
None, //不做拉伸
Fill, //完全填充(会变形)
Uniform, //等比缩放,不会变形
UniformToFill //等比缩放,并完全填充。不会变形,但是长的部分会被裁剪
但是,如果我们要实现像QQ或者微信这样子的聊天气泡功能,直接使用图片组件就无法满足要求了。
我们可以观察下微信的聊天气泡,他的宽度和高度可以根据我们输入的内容自动调整,并且背景图片也不会存在变形的问题。
今天我们就用WPF来实现这个功能!
要实现不变形的拉伸功能,我们可以针对1个像素来进行拉伸,这样拉伸出来的图片,除了拉伸区域的像素都是一样的,其它区域还是保留了原来的图片的外观。
这里主要需要用到 CroppedBitmap
类,该类主要用于裁剪,可以对 BitmapImage
进行裁剪。
微信聊天气泡这种场景,它需要支持水平和垂直的方向的拉伸效果,我们可以利用 CroppedBitmap
,将原始图片裁剪成9张图,渲染的时候,我们分别将9张图渲染到对应的位置。拉伸的区域就是9张图中的上面中间位置,下面中间位置,左边中间位置,右边中间位置以及最中间的位置。这几张图片,都按1个像素进行裁剪,这样就不会出现拉伸的图片了。
关键代码:
//根据裁剪区域,获取裁剪后的图片。ImageSource指的是原始图片
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 GetHorizontalOffset()
{
return (int)(HorizontalPrecent * ImageSource.Width / 100);
}
/// <summary>
/// 获取垂直位置的偏移像素值
/// </summary>
/// <returns></returns>
private int GetVerticalOffset()
{
return (int)(VerticalPrecent * ImageSource.Height / 100);
}
/// <summary>
/// 获取水平偏移像素值
/// </summary>
/// <returns></returns>
private int GetStretchHeight()
{
return (int)(RenderSize.Height - ImageSource.Height);
}
/// <summary>
/// 获取垂直位置的偏移像素值
/// </summary>
/// <returns></returns>
private int GetStretchWidth()
{
return (int)(RenderSize.Width - ImageSource.Width);
}
/// <summary>
/// 水平拉伸是否可用
/// </summary>
private bool IsHorizontalStretchEnabled
{
get
{
if (HorizontalPrecent > 0 && HorizontalPrecent < 100)
{
return true;
}
return false;
}
}
/// <summary>
/// 按水平方向进行裁剪的偏移量
/// </summary>
public double HorizontalPrecent { get; set; }
/// <summary>
/// 按垂直方向进行裁剪的偏移量
/// </summary>
public double VerticalPrecent { get; set; }
//绘制水平+垂直拉伸的方法
protected override void OnRender(DrawingContext drawingContext)
{
//这个需要9张图
//左上,左中,左下,右上,右中,右下,水平中,垂直中
var horizontalOffset = GetHorizontalOffset();
var verticalOffset = GetVerticalOffset();
var leftTop = GetCroppedBitmap(0, 0, horizontalOffset, verticalOffset);
var leftBottom = GetCroppedBitmap(0, verticalOffset + 1, horizontalOffset, ImageSource.Height - verticalOffset - 1);
var rightTop = GetCroppedBitmap(horizontalOffset + 1, 0, ImageSource.Width - horizontalOffset - 1, ImageSource.Height - verticalOffset - 1);
var rightBottom = GetCroppedBitmap(horizontalOffset + 1, verticalOffset + 1, ImageSource.Width - horizontalOffset - 1, ImageSource.Height - verticalOffset - 1);
//最中间的
var center = GetCroppedBitmap(horizontalOffset, verticalOffset, 1, 1);
var leftCenter = GetCroppedBitmap(0, verticalOffset + 1, horizontalOffset, 1);
var rightCenter = GetCroppedBitmap(horizontalOffset + 1, verticalOffset + 1, ImageSource.Width - horizontalOffset - 1, 1);
var topCenter = GetCroppedBitmap(horizontalOffset + 1, 0, 1, verticalOffset);
var bottomCenter = GetCroppedBitmap(horizontalOffset + 1, verticalOffset + 1, 1, ImageSource.Height - verticalOffset - 1);
//------------------------------- 上面的逻辑是切图,下面的逻辑是绘制 -----------------------------------
var stretchHeight = GetStretchHeight();
if (stretchHeight < 0) stretchHeight = 0;
var stretchWidth = GetStretchWidth();
if (stretchWidth < 0) stretchWidth = 0;
drawingContext.DrawImage(leftTop, new Rect(0, 0, horizontalOffset, verticalOffset));
drawingContext.DrawImage(rightTop, new Rect(horizontalOffset + stretchWidth, 0, ImageSource.Width - horizontalOffset - 1, ImageSource.Height - verticalOffset));
//绘制水平方向的拉伸像素
if (stretchHeight > 0)
{
drawingContext.DrawImage(leftCenter, new Rect(0, verticalOffset, horizontalOffset, stretchHeight));
drawingContext.DrawImage(rightCenter, new Rect(horizontalOffset + stretchWidth, verticalOffset, ImageSource.Width - 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.Height - 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.Height - verticalOffset - 1));
drawingContext.DrawImage(rightBottom, new Rect(horizontalOffset + stretchWidth, verticalOffset + stretchHeight, ImageSource.Width - horizontalOffset - 1, ImageSource.Height - verticalOffset - 1));
}
//仅支持水平方向拉伸
protected override void OnRender(DrawingContext drawingContext)
{
var horizontalOffset = GetHorizontalOffset();
var left = GetCroppedBitmap(0, 0, horizontalOffset, ImageSource.Height);
var center = GetCroppedBitmap(horizontalOffset, 0, 1, ImageSource.Height);
var right = GetCroppedBitmap(horizontalOffset + 1, 0, ImageSource.Width - horizontalOffset - 1, ImageSource.Height);
drawingContext.DrawImage(left, new Rect(0, 0, horizontalOffset, ImageSource.Height));
var stretchWidth = GetStretchWidth();
if (stretchWidth > 0)
{
drawingContext.DrawImage(center, new Rect(horizontalOffset, 0, stretchWidth, ImageSource.Height));
}
else
{
stretchWidth = 0;
}
drawingContext.DrawImage(right, new Rect(horizontalOffset + stretchWidth, 0, ImageSource.Width - horizontalOffset - 1, ImageSource.Height));
}
//仅支持垂直方向拉伸
protected override void OnRender(DrawingContext drawingContext)
{
var verticalOffset = GetVerticalOffset();
var top = GetCroppedBitmap(0, 0, ImageSource.Width, verticalOffset);
var center = GetCroppedBitmap(0, verticalOffset, ImageSource.Width, 1);
var bottom = GetCroppedBitmap(0, verticalOffset + 1, ImageSource.Width, ImageSource.Height - verticalOffset - 1);
drawingContext.DrawImage(top, new Rect(0, 0, ImageSource.Width, verticalOffset));
var stretchHeight = GetStretchHeight();
if (stretchHeight > 0)
{
drawingContext.DrawImage(center, new Rect(0, verticalOffset, ImageSource.Width, stretchHeight));
}
else
{
stretchHeight = 0;
}
drawingContext.DrawImage(bottom, new Rect(0, verticalOffset + stretchHeight, ImageSource.Width, ImageSource.Height - 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.Height);
}
else if (IsVerticalStretchEnabled)
{
naturalSize = new Size(imgSource.Width, size.Height);
}
else
{
return size;
}
return naturalSize;
}
以上代码就可以实习水平拉伸,垂直拉伸或者水平+垂直拉伸的功能了。目前的测试代码还不支持不同分辨率的图片,demo中的计算是使用了ImageSource的宽高。如果需要支持任意分辨率,可以按渲染的宽高和图片的实际宽高做个比例缩放运算即可。