【IT168 技术】用鼠标将浏览器中的图片进行移动和缩放是经常会遇到的应用。一般情况下,我可以通过MFC或WinForm来实现这一功能,其原理是利用事件处理函数获知鼠标位置和滚轮滚动的偏移量,参照这一更改对图片相应的位置和高宽比进行调整。
但是页面显示情况复杂时,通过这种方式处理起来就很麻烦了。先来看看通常用WinForm来实现图片缩放的代码:
用鼠标拖放来实现:
private Point m_PreviousMousePosition;
private void DoImageMove(object sender, MouseEventArgs e)
{
// 将sender转化成触发鼠标事件的控件,在Winform程序里面,
// 一般都是PictureBox控件。
PictureBox picture = sender as PictureBox;
Debug.Assert(picture != null);
// 或者鼠标在PictureBox控件的相对坐标,下面的GetMouseRelativePositionTo
// 函数是一个虚构的函数,具体的实现可以Google一下他人的实现方式。
Point mousePosition = GetMouseRelativePositionTo(picture);
// 移动图片,由于MouseMove事件会在我们移动鼠标的时候触发多次,
// 因此我们可以通过获取两次鼠标移动之间,鼠标指针位置的偏移量
// 来知道图片应该移动的偏移量。
picture.X += mousePosition.X - m_PreviousMousePosition.X;
picture.Y += mousePosition.Y - m_PreviousMousePosition.Y;
// 将这次鼠标的位置保存下来。
m_PreviousMousePosition = mousePosition;
}
通过滚动条滚动来实现:
{
PictureBox picture = sender as PictureBox;
Debug.Assert(picture != null);
// 强迫PictureBox控件在更改大小的时候,自动缩放图片
// 以便填充整个PictureBox控件
picture.SizeMode = PictureBoxSizeMode.StretchImage;
// 0.001是我随便取的一个值,因为滚轮的Delta值太大了
// 根据鼠标滚轮的偏移量来更改PictureBox宽度和高度。
picture.Width += e.Delta * 0.001;
picture.Height += e.Delta * 0.001;
}
以上俩段代码虽然简洁,但是也有问题存在。其一,改变其中一个图片,其他图片也会随之而改变,虽然可以在MasterImage_MouseMove 和 MasterImage_MouseWheel函数里面显示修改所有图片的位置做修改,却很麻烦;其二,以上代码实际上是以图片的左上角为基准点的,如果想以图片中心为基准点又是一件麻烦的事。
WPF为我们提供了方便,WPF中包含了很多处理图片的函数,例如,用于移动、缩放和旋转图片的Transform类,修改图片外观的Effect类。这些类都可以在XAML代码中直接设置,因此可以将Resource和XAML相结合使用,以此来压缩代码量并且实现更丰富的应用。
步骤如下:
1. 定义一个TranslateTransform实例来修改图片显示的起始位置。
2. 定义一个ScaleTransform实例来缩放图片的大小,你可以通过设置CenterX和CenterY的值来指定图片缩放的原点。
3. 将两个Transform放到一个TransformGroup里面,这样Image控件就可以在显示的时候综合使用两个Transform的效果了。你可以注意一下,TransformGroup的基类也是Transform,想一想这采用的是哪一个设计模式?不知道,呃……看样子我还需要写另外一篇文章解释一下这个设计模式……不过我最近有点忙,如果你等不及看到我下一篇文章的话,还是自己Google一下吧。
4. 将TransformGroup放到当前窗体的Resource里面,这样窗体里面所有的Image控件都可以引用到这个实例。
5. 在鼠标移动事件里面修改TranslateTransform对应的值。
6. 在鼠标滚轮事件里面修改ScaleTransform的ScaleX和ScaleY的值来缩放图片。
Window1.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MouseMove"
Title="Window1" Height="600" Width="800">
<Grid x:Name="MainPanel">
<Grid.Resources>
<TransformGroup x:Key="ImageTransformResource">
<ScaleTransform />
<TranslateTransform />
</TransformGroup>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" x:Name="MasterImage"
MouseLeftButtonDown="MasterImage_MouseLeftButtonDown"
MouseLeftButtonUp="MasterImage_MouseLeftButtonUp"
MouseMove="MasterImage_MouseMove"
MouseWheel="MasterImage_MouseWheel">
<Rectangle.Fill>
<VisualBrush Transform="{StaticResource ImageTransformResource}" Stretch="Uniform">
<VisualBrush.Visual>
<Image Source="C:"Windows"Web"Wallpaper"Architecture"Img15.jpg" />
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Column="1"
MouseLeftButtonDown="MasterImage_MouseLeftButtonDown"
MouseLeftButtonUp="MasterImage_MouseLeftButtonUp"
MouseMove="MasterImage_MouseMove"
MouseWheel="MasterImage_MouseWheel">
<Rectangle.Fill>
<VisualBrush Transform="{StaticResource ImageTransformResource}" Stretch="Uniform">
<VisualBrush.Visual>
<Image Source="C:"Windows"Web"Wallpaper"Architecture"Img14.jpg" />
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
Window1.xaml.cs
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
namespace MouseMove
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
private bool m_IsMouseLeftButtonDown;
public Window1()
{
InitializeComponent();
}
private void MasterImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Rectangle rectangle = sender as Rectangle;
if (rectangle == null)
return;
rectangle.ReleaseMouseCapture();
m_IsMouseLeftButtonDown = false;
}
private Point m_PreviousMousePoint;
private void MasterImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Rectangle rectangle = sender as Rectangle;
if (rectangle == null)
return;
rectangle.CaptureMouse();
m_IsMouseLeftButtonDown = true;
m_PreviousMousePoint = e.GetPosition(rectangle);
}
private void MasterImage_MouseMove(object sender, MouseEventArgs e)
{
Rectangle rectangle = sender as Rectangle;
if (rectangle == null)
return;
if (m_IsMouseLeftButtonDown)
DoImageMove(rectangle, e);
}
private void DoImageMove(Rectangle rectangle, MouseEventArgs e)
{
//Debug.Assert(e.LeftButton == MouseButtonState.Pressed);
if (e.LeftButton != MouseButtonState.Pressed)
return;
TransformGroup group = MainPanel.FindResource("ImageTransformResource") as TransformGroup;
Debug.Assert(group != null);
TranslateTransform transform = group.Children[1] as TranslateTransform;
Point position = e.GetPosition(rectangle);
transform.X += position.X - m_PreviousMousePoint.X;
transform.Y += position.Y - m_PreviousMousePoint.Y;
m_PreviousMousePoint = position;
}
private void MasterImage_MouseWheel(object sender, MouseWheelEventArgs e)
{
TransformGroup group = MainPanel.FindResource("ImageTransformResource") as TransformGroup;
Debug.Assert(group != null);
ScaleTransform transform = group.Children[0] as ScaleTransform;
transform.ScaleX += e.Delta * 0.001;
transform.ScaleY += e.Delta * 0.001;
}
}
}
实现了以图片中心为基点的缩放拖拽之后,又想实现以图片焦点为基点的缩放拖拽。下面让一起通过实例来进行学习吧。
下图里面右下方方块是一个WPF程序里面的一个图片,大小是40 x 40,里面的黑点是预备缩放的原点,假设黑点的坐标是(10, 10),在运行程序的时候,用户首先将方块移动到左边的位置,当然原点(黑点)也移动了,假如这个时候图片移动了50个像素。
图1 移动图片
接着用户在移动后的位置上,将图片缩放,比如说放大了2倍,这个操作也会移动原点(黑点)在最终图片的位置。因为放大图片,实际上就是将原始图片的各个像素移动到新的位置(红点),这个时候,新的原点(红点)的坐标应该是(20, 20),相邻两个像素的空间使用插值的方法填充。这个时候,
ScaleTransform.ScaleX = 2;
ScaleTransform.ScaleY = 2;
图2 移动后缩放
这个时候,用户打算放大图片当中的另外一个区域,再放大一倍(即放大到原图的3倍),下图里是蓝点,假设坐标是(50, 50),因为无论图片缩放与否,用户只会以他在实际图片看到的内容来判断新的缩放焦点:
图3 再次放大
如果我们直接盲目地将ScaleTransform的各个属性设置为类似下面的值的话:
ScaleTransform.ScaleX = 3;
ScaleTransform.ScaleY = 3;
ScaleTransform.CenterX = 50;
ScaleTransform.CenterY = 50;
就发生问题了, 因为ScaleX = 3表示新图是原图的3倍,然而我们的原点却是在2倍图片上设置的—原图的大小只有40 x 40。解决方案当然是将蓝点的位置转换回在原始图片的位置,注意原始图片应该是下图右下方的图片,而不是左边的—用户最初已经移动了图片。
图4 注意原始图片位置
看起来转换起来有点麻烦,不过WPF提供了一个 函数TransformGroup.Inverse,可以把转换后图片上的坐标转换会在原始图片的坐标。当然啦,如果你熟悉图形学和线性代数的话,实际上,图片的缩放和移动就是将原始图片乘上一个矩阵,而TransformGroup.Inverse函数就是执行矩阵求逆操作。
下面就是关键代码:
XAML代码:
<TransformGroup x:Key="ImageCompareResources">
<ScaleTransform />
<TranslateTransform/>
</TransformGroup>
</Grid.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Disabled" Grid.Row="0" Grid.Column="0" x:Name="MasterScrollViewer" Margin="5" Background="WhiteSmoke">
<ContentControl x:Name="TestContentControl1"
MouseLeftButtonDown="MasterImage_MouseLeftButtonDown"
MouseLeftButtonUp="MasterImage_MouseLeftButtonUp"
MouseMove="MasterImage_MouseMove"
MouseWheel="MasterImage_MouseWheel">
<Image RenderOptions.BitmapScalingMode="NearestNeighbor"
x:Name="MasterImage" Source="{Binding Path=MasterImagePath}" Stretch="Uniform"
RenderTransform="{StaticResource ImageCompareResources}"/>
</ContentControl>
</ScrollViewer>
C#代码:
{
ContentControl image = sender as ContentControl;
if (image == null)
{
return;
}
TransformGroup group = ImageComparePanel.FindResource("ImageCompareResources") as TransformGroup;
Debug.Assert(group != null, "Can't find transform group from image compare panel resource");
Point point = e.GetPosition(image);
double scale = e.Delta * 0.001;
ZoomImage(group, point, scale);
}
private static void ZoomImage(TransformGroup group, Point point, double scale)
{
Debug.Assert(group != null, "Oops, ImageCompareResources is removed from current control's resouce");
Point pointToContent = group.Inverse.Transform(point);
ScaleTransform transform = group.Children[0] as ScaleTransform;
if (transform.ScaleX + scale < 1)
{
return;
}
transform.ScaleX += scale;
transform.ScaleY += scale;
TranslateTransform transform1 = group.Children[1] as TranslateTransform;
transform1.X = -1 * ((pointToContent.X * transform.ScaleX) - point.X);
transform1.Y = -1 * ((pointToContent.Y * transform.ScaleY) - point.Y);
}
private void MasterImage_MouseMove(object sender, MouseEventArgs e)
{
ContentControl image = sender as ContentControl;
if (image == null)
{
return;
}
if (this.isMouseLeftButtonDown && e.LeftButton == MouseButtonState.Pressed)
{
this.DoImageMove(image, e.GetPosition(image));
}
}
private void DoImageMove(ContentControl image, Point position)
{
TransformGroup group = ImageComparePanel.FindResource("ImageCompareResources") as TransformGroup;
Debug.Assert(group != null, "Can't find transform group from image compare panel resource");
TranslateTransform transform = group.Children[1] as TranslateTransform;
transform.X += position.X - this.previousMousePoint.X;
transform.Y += position.Y - this.previousMousePoint.Y;
this.previousMousePoint = position;
}