private const double ScaleInternal = 0.2;
protected override void OnMouseWheel(MouseWheelEventArgs e)
base.OnMouseWheel(e);
ScaleImg(e.Delta > 0);
private void ScaleImg(bool isEnlarge)
//获取放大/缩小系数
var tempScale = isEnlarge ? ImageScale + ScaleInternal : ImageScale - ScaleInternal;
//最小允许缩小到缩放比间隔,最大允许放大到50倍
if (Abs(tempScale) < ScaleInternal || Abs(tempScale - 50) < 0.001)
return;
ImageScale = tempScale;
但这个缩放存在一个问题,缩放中心在
(0,0)
,这就意味着图像左上角不动,往右、下边拉伸,这通常与用户意愿不符,合理的缩放应该是用户将鼠标移到关心的位置,缩放时焦点应该一直保持在这个位置,以便用户观察细节
要实现缩放时焦点保持在鼠标位置,那就需要移动图像来配合,在做这个功能之前,先实现一个最简单的效果——居中缩放,图像在未铺满容器前能看到所有内容,这时候不允许移动,因此在可移动前先居中缩放至铺满容器
改造一下
ScaleImg
函数:
private void ScaleImg(bool isEnlarge)
//获取放大/缩小系数
var tempScale = isEnlarge ? ImageScale + ScaleInternal : ImageScale - ScaleInternal;
//最小允许缩小到缩放比间隔,最大允许放大到50倍
if (Abs(tempScale) < ScaleInternal || Abs(tempScale - 50) < 0.001)
return;
ImageScale = tempScale;
var baseMarginX = .5 * _scaleInternalWidth;
var baseMarginY = .5 * _scaleInternalHeight;
//默认偏移都只取缩放间隔的一半,以便居中
var marginX = baseMarginX;
var marginY = baseMarginY;
ImageMargin = isEnlarge ? new Thickness(ImageMargin.Left - marginX, ImageMargin.Top - marginY, 0, 0) :
new Thickness(ImageMargin.Left + marginX, ImageMargin.Top + marginY, 0, 0); ;
这里在缩放后,ImageMargin
的Top、Left分别 +/- 上缩放间隔宽高的一半就实现了居中,宽高的缩放间隔在得到图像后就不再变化了,因此放在初始化的时候计算
/// <summary>
/// 缩放高度间隔
/// </summary>
private double _scaleInternalHeight;
/// <summary>
/// 缩放宽度间隔
/// </summary>
private double _scaleInternalWidth;
private void Init()
if (ImageSource == null || !_isLoaded) return;
ImageScale = 1;
//记录宽高
ImageWidth = ImageSource.PixelWidth;
ImageHeight = ImageSource.PixelHeight;
//原始宽高
ImageOriWidth = ImageSource.PixelWidth;
ImageOriHeight = ImageSource.PixelHeight;
//记录当前缩放间隔下的缩放宽高
_scaleInternalWidth = ImageOriWidth * ScaleInternal;
_scaleInternalHeight = ImageOriHeight * ScaleInternal;
//记录图像宽高比
_imgWidHeiScale = _imgWidHeiScale = ImageWidth / ImageHeight;
ImgSizeAdaption();
AutoCentering();
改动后效果如下:
下一步,图片铺满容器后缩放中心移到鼠标指针的位置
再改造一下ScaleImg
函数,当图像铺满容器后让ImageMargin
以鼠标位置在图像长宽上的比例移动,这时候缩放中心就到了鼠标指针位置了
private void ScaleImg(bool isEnlarge)
var beforeScaleImgWidth = ImageWidth;
var beforeScaleImgHeight = ImageHeight;
//获取放大/缩小系数
var tempScale = isEnlarge ? ImageScale + ScaleInternal : ImageScale - ScaleInternal;
//最小允许缩小到缩放比间隔,最大允许放大到50倍
if (Abs(tempScale) < ScaleInternal || Abs(tempScale - 50) < 0.001)
return;
ImageScale = tempScale;
//获取鼠标在画布上的位置
var posCanvas = Mouse.GetPosition(_imgPanel);
//对应在图像上的坐标
var posImg = new Point(posCanvas.X - ImageMargin.Left, posCanvas.Y - ImageMargin.Top);
var baseMarginX = .5 * _scaleInternalWidth;
var baseMarginY = .5 * _scaleInternalHeight;
//默认偏移都只取缩放间隔的一半,以便居中
var marginX = baseMarginX;
var marginY = baseMarginY;
//图片尺寸未大于容器尺寸前以图片中点作为缩放中心,图像尺寸大于容器尺寸时以鼠标位置作为缩放中心
if (beforeScaleImgWidth >= ActualWidth && beforeScaleImgHeight >= ActualHeight)
//按鼠标位置与图片长度的比例调整xy移动
marginX = posImg.X / beforeScaleImgWidth * _scaleInternalWidth;
marginY = posImg.Y / beforeScaleImgHeight * _scaleInternalHeight;
ImageMargin = isEnlarge ? new Thickness(ImageMargin.Left - marginX, ImageMargin.Top - marginY, 0, 0) :
new Thickness(ImageMargin.Left + marginX, ImageMargin.Top + marginY, 0, 0); ;
这时候又引入了一个新问题,在不同点缩放后图像不再居中了,更严重的时候图像甚至会移动到容器之外
再做一点改动来解决这个问题,加上两个策略
//获取放大/缩小系数
var tempScale = isEnlarge ? ImageScale + ScaleInternal : ImageScale - ScaleInternal;
//最小允许缩小到缩放比间隔,最大允许放大到50倍
if (Abs(tempScale) < ScaleInternal || Abs(tempScale - 50) < 0.001)
return;
ImageScale = tempScale;
//获取鼠标在画布上的位置
var posCanvas = Mouse.GetPosition(_imgPanel);
//对应在图像上的坐标
var posImg = new Point(posCanvas.X - ImageMargin.Left, posCanvas.Y - ImageMargin.Top);
var baseMarginX = .5 * _scaleInternalWidth;
var baseMarginY = .5 * _scaleInternalHeight;
//默认偏移都只取缩放间隔的一半,以便居中
var marginX = baseMarginX;
var marginY = baseMarginY;
//图片尺寸未大于容器尺寸前以图片中点作为缩放中心,图像尺寸大于容器尺寸时以鼠标位置作为缩放中心
if (beforeScaleImgWidth >= ActualWidth && beforeScaleImgHeight >= ActualHeight)
//按鼠标位置与图片长度的比例调整xy移动
marginX = posImg.X / beforeScaleImgWidth * _scaleInternalWidth;
marginY = posImg.Y / beforeScaleImgHeight * _scaleInternalHeight;
Thickness thickness;
//缩放时移动 向上/向左 增大/缩小 的值
if (isEnlarge)
thickness = new Thickness(ImageMargin.Left - marginX, ImageMargin.Top - marginY, 0, 0);
var marginActualX = ImageMargin.Left + marginX;
var marginActualY = ImageMargin.Top + marginY;
//修正因图片尺寸大于容器尺寸时缩放中心移动而产生的偏移,当某一边缘与容器相接时该边缘不再移动
var right = Abs(beforeScaleImgWidth - ActualWidth + ImageMargin.Left);
//靠近左右边缘时
if (Abs(ImageMargin.Left) < baseMarginX || right < baseMarginX)
marginActualX = ImageMargin.Left + ImageMargin.Left /
(ActualWidth - beforeScaleImgWidth) * _scaleInternalWidth;
var bottom = Abs(beforeScaleImgHeight - ActualHeight + ImageMargin.Top);
//靠近上下边缘时
if (Abs(ImageMargin.Top) < baseMarginY || bottom < baseMarginY)
marginActualY = ImageMargin.Top + ImageMargin.Top /
(ActualHeight - beforeScaleImgWidth) * _scaleInternalWidth;
//图片尺寸与容器尺寸相近时恢复居中
var subX = ImageWidth - ActualWidth;
var subY = ImageHeight - ActualHeight;
if (subX < 0.001) marginActualX = (ActualWidth - ImageWidth) / 2;
if (subY < 0.001) marginActualY = (ActualHeight - ImageHeight) / 2;
thickness = new Thickness(marginActualX, marginActualY, 0, 0);
ImageMargin = thickness;
好了,大功告成!
缩放效果如下: