WPF图片预览之移动、旋转、缩放

时间:2024-10-21 23:37:20

原文:WPF图片预览之移动、旋转、缩放

RT,这个功能比较常见,但凡涉及到图片预览的都跑不了,在说自己的实现方式前,介绍一个好用的控件:Extended.Toolkit中的Zoombox,感兴趣的同学可以去搜一下这个控件,它封装了常用的预览功能。那么为什么我没用这个控件呢?因为产品不同意- -!

开始撸代码,整理一下思路先:

首先打算用一个Window来展示图片,但是QQ、微信预览图片的时候,任务栏并没有出现图标,难道用了什么黑科技?不管它,将Window的ShowInTaskbar设置为False就可以不显示在任务栏了。

继续,提到移动、旋转、缩放之类的,WPF提供了与之对应的Transform:TranslateTransform、RotateTransform、ScaleTransform,所以我们必然要使用他们仨了。怎么触发呢?那就用到事件了,移动么MouseMove,缩放么MouseWheel,旋转的话就是固定的按钮的点击事件了。另外,缩放的话可以限制一下最大和最小的缩放系数,我这边只设了缩小时如果小于系数0.3就不再缩小了,放大系数没有设置。

说一下其中的坑:

MouseWheel事件参数里会取到鼠标的Delta,这个参数不好直接用来设置缩放系数,那多少合适呢,看到别人的经验之谈,用这个Delta/3000即可。

double delta = (double)e.Delta / 3000;
ScaleTransform scale = group.Children[0] as ScaleTransform;
if (scale.ScaleX < 0.3 && scale.ScaleY < 0.3 && e.Delta < 0)
{
return;
}
scale.CenterX = this.imageControl.ActualWidth / 2;
scale.CenterY = this.imageControl.ActualHeight / 2;
scale.ScaleX += delta;
scale.ScaleY += delta;

MouseMove移动图片的时候,是可以将整个图片移到Window外面去的,这就要我们限制一下图片的移动范围了:

   private void TopMove(TranslateTransform translate, Point position, Point imagePos)
{
if (scaleValue > 1.0)
{
if (mouseXY.Y - position.Y > 0.0)//up
{
double wm = winHeight - position.Y;
double im = (this.imageControl.ActualHeight - imagePos.Y) * scaleValue;
if (wm - im <= -5.0)
{
translate.Y -= mouseXY.Y - position.Y;
}
}
else
{
if (position.Y / scaleValue - imagePos.Y <= -5.0)
{
translate.Y -= mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
double iw = (this.imageControl.ActualWidth - imagePos.X) * scaleValue;
double ww = winWidth - position.X;
if (ww - iw <= -5.0)
{
translate.X -= mouseXY.X - position.X;
}
}
else
{
if (position.X / scaleValue - imagePos.X <= -5.0)
{
translate.X -= mouseXY.X - position.X;
}
}
}
else
{
if (mouseXY.Y - position.Y > 0.0)//up
{
if (position.Y / scaleValue >= imagePos.Y)
{
translate.Y -= mouseXY.Y - position.Y;
}
}
else
{
double im = (this.imageControl.ActualHeight - imagePos.Y) * scaleValue;
double wm = winHeight - position.Y;
if (im <= wm)
{
translate.Y -= mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
if (position.X / scaleValue >= imagePos.X)
{
translate.X -= mouseXY.X - position.X;
}
}
else
{
double iw = (this.imageControl.ActualWidth - imagePos.X) * scaleValue;
double ww = winWidth - position.X;
if (iw <= ww)
{
translate.X -= mouseXY.X - position.X;
}
}
}
}

稍微解释一下上面的代码,position是鼠标相对于Window的位置,imagePos是鼠标相对于图片的位置,scaleValue是图片当前的缩放比例,当图片是缩小状态(scaleValue<1)时,允许图片在整个Window里拖动,上移时,如果图片上边框触碰到了Window上边框,则不能再上移(另外3个方向原理相同),反之(scaleValue>1),只有当图片超出Window范围时才允许拖动,假设,图片上下超出了Window,则只能上下移动不能左右移动,并且,上移时,如果图片下边框触碰到了Window下边框,则不能再上移(另外3个方向原理相同)。

眼尖的同学可能注意到了上面这个方法名:TopMove,没错,这个算法只适用于图片正着放的时候的位移计算,我们不是有旋转吗,旋转了只有就不能这么算了,原因在于,imagePos始终是相对于图片来的,也就是说,如果图片旋转了90°,那么imagePos的坐标系也要旋转90°,但是我们最终要计算的是相对于Window的坐标系,所以要做相应的转换。

好了,上完整的代码:

<Window x:Class="VcreditChat.Windows.MaxPicture"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
WindowStyle="None"
AllowsTransparency="True"
Background="#82000000"
KeyDown="PicWin_KeyDown"
Loaded="PicWin_Loaded"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
WindowStartupLocation="CenterScreen"
Icon="../Resources/Images/ico_48.ico"
ShowInTaskbar="False"
Closed="PicWin_Closed"
x:Name="picWin">
<Grid x:Name="outGrid">
<Grid.Resources>
<TransformGroup x:Key="transGroup">
<ScaleTransform/>
<TranslateTransform/>
<RotateTransform/>
</TransformGroup>
</Grid.Resources> <ContentControl x:Name="picControl"
MouseLeftButtonDown="PicControl_MouseLeftButtonDown"
MouseLeftButtonUp="PicControl_MouseLeftButtonUp"
MouseMove="PicControl_MouseMove"
MouseWheel="PicControl_MouseWheel">
<Image x:Name="imageControl"
RenderTransform="{StaticResource transGroup}"
Stretch="None"
RenderOptions.BitmapScalingMode="Fant"
MouseEnter="ImageControl_MouseEnter"
MouseLeave="ImageControl_MouseLeave">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Foreground="#333333" Header="复制" Click="MaxImageCopy_Click"></MenuItem>
<MenuItem Foreground="#333333" Header="另存为" Click="MaxImageSave_Click"></MenuItem>
</ContextMenu>
</Image.ContextMenu>
</Image>
</ContentControl> <StackPanel x:Name="failedImg" VerticalAlignment="Center" Visibility="Collapsed">
<Image Source="../Resources/Images/img_failed.png" Stretch="None"></Image>
<TextBlock x:Name="failedTxt" HorizontalAlignment="Center" Foreground="White" FontSize="14" Margin="0 6 0 0"></TextBlock>
</StackPanel> <Border Background="#393A3C" Width="30" Height="30" VerticalAlignment="Top" HorizontalAlignment="Right" MouseLeftButtonDown="Image_MouseLeftButtonDown" MouseEnter="Border_MouseEnter" MouseLeave="Border_MouseLeave">
<Grid>
<Path Data="M 8,8 L 22,22" Stroke="#FFFFFF" StrokeThickness="2"/>
<Path Data="M 22,8 L 8,22" Stroke="#FFFFFF" StrokeThickness="2"/>
</Grid>
</Border> <Border x:Name="scalBorder" Background="#80393A3C" Width="210" Height="40" VerticalAlignment="Bottom">
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" HorizontalAlignment="Center">
<Image MouseLeftButtonDown="Small_Image_MouseLeftButtonDown" Stretch="None" Source="../Resources/Images/zoom_small.png" Cursor="Hand"></Image>
<TextBlock Margin="12 0 0 0" x:Name="scalTxt" FontSize="20" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center">100%</TextBlock>
<Image Margin="12 0 0 0" MouseLeftButtonDown="Big_Image_MouseLeftButtonDown" Stretch="None" Source="../Resources/Images/zoom_big.png" Cursor="Hand"></Image>
<Path Margin="10 0 0 0" Data="M 0,2 L 0,28" Stroke="#D8D8D8" StrokeThickness="1"></Path>
<Image Margin="10 0 0 0" MouseLeftButtonDown="Rotate_Image_MouseLeftButtonDown" Stretch="None" Source="../Resources/Images/rotate.png" Cursor="Hand"></Image>
</StackPanel>
</Border>
</Grid>
</Window>
using System;
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.Shapes;
using VcreditChat.Common; namespace VcreditChat.Windows
{
public partial class MaxPicture : Window
{
private double picWidth = 0.0;
private double picHeight = 0.0; private double winWidth = 0.0;
private double winHeight = 0.0; private bool mouseDown;
private Point mouseXY; private double scaleValue = 1.0; private double rotateAngle = 0.0; private string picUrl = string.Empty; public MaxPicture(string picUrl)
{
InitializeComponent();
this.picUrl = picUrl;
System.IO.FileInfo fileInfo = new System.IO.FileInfo(picUrl);
if (fileInfo.Length<=0)
{
this.failedImg.Visibility = Visibility.Visible;
this.scalBorder.Visibility = Visibility.Collapsed;
this.failedTxt.Text = "图片过大,下载未完成。大图建议以文件方式发送!";
return;
}
BitmapSource bitmap = BitmapHelper.GetImgSource(picUrl);
if (bitmap==null)
{
this.failedImg.Visibility = Visibility.Visible;
this.scalBorder.Visibility = Visibility.Collapsed;
this.failedTxt.Text = "图片加载出错!";
return;
}
picHeight = bitmap.Height;
picWidth = bitmap.Width;
this.imageControl.Source = bitmap;
} private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
try
{
if (!mouseDown)
{
this.DragMove();
}
}
catch (Exception)
{
}
} private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.Close();
} private void Border_MouseEnter(object sender, MouseEventArgs e)
{
Border border = sender as Border;
border.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#DC143C"));
} private void Border_MouseLeave(object sender, MouseEventArgs e)
{
Border border = sender as Border;
border.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#393A3C"));
} private void PicWin_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
this.Close();
}
} private void PicControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var img = sender as ContentControl;
if (img == null)
{
return;
}
img.CaptureMouse();
this.mouseDown = true;
this.mouseXY = e.GetPosition(img);
} private void PicControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var img = sender as ContentControl;
if (img==null)
{
return;
}
img.ReleaseMouseCapture();
this.mouseDown = false;
} private void PicControl_MouseMove(object sender, MouseEventArgs e)
{
var img = sender as ContentControl;
if (img == null)
{
return;
}
if (this.mouseDown)
{
if (e.LeftButton != MouseButtonState.Pressed)
{
return;
}
this.Cursor = Cursors.SizeAll;
var group = this.outGrid.FindResource("transGroup") as TransformGroup;
TranslateTransform translate = group.Children[1] as TranslateTransform;
Point imagePos = e.GetPosition(this.imageControl);
Point position = e.GetPosition(img);
int i = Convert.ToInt16(rotateAngle % 360);
switch (i)
{
case 0:
TopMove(translate, position, imagePos);
break;
case 90:
RightMove(translate, position, imagePos);
break;
case 270:
LeftMove(translate, position, imagePos);
break;
case 180:
BottomMove(translate, position, imagePos);
break;
default:
break;
}
mouseXY = position;
}
} private void RightMove(TranslateTransform translate, Point position, Point imagePos)
{
if (scaleValue > 1.0)
{
if (mouseXY.Y - position.Y > 0.0)//up
{
if (winHeight - position.Y - (this.imageControl.ActualWidth-imagePos.X) * scaleValue <= -5.0)
{
translate.X -= mouseXY.Y - position.Y;
}
}
else
{
if (position.Y / scaleValue - imagePos.X <= -5.0)
{
translate.X -= mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
if (winWidth - position.X - imagePos.Y * scaleValue <= -5.0)
{
translate.Y += mouseXY.X - position.X;
}
}
else
{
if (position.X / scaleValue -this.imageControl.ActualHeight+ imagePos.Y <= -5.0)
{
translate.Y += mouseXY.X - position.X;
}
}
}
else
{
if (mouseXY.Y - position.Y > 0.0)//up
{
if (position.Y / scaleValue >= imagePos.X)
{
translate.X -= mouseXY.Y - position.Y;
}
}
else
{
if ((this.imageControl.ActualWidth - imagePos.X) * scaleValue <= winHeight - position.Y)
{
translate.X -= mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
if (position.X / scaleValue >=this.imageControl.ActualHeight- imagePos.Y)
{
translate.Y += mouseXY.X - position.X;
}
}
else
{
if (imagePos.Y * scaleValue <= winWidth - position.X)
{
translate.Y += mouseXY.X - position.X;
}
}
}
} private void LeftMove(TranslateTransform translate, Point position, Point imagePos)
{
if (scaleValue>1.0)
{
if (mouseXY.Y - position.Y > 0.0)//up
{
if (winHeight - position.Y - imagePos.X * scaleValue <= -5.0)
{
translate.X += mouseXY.Y - position.Y;
}
}
else
{
if (position.Y / scaleValue - this.imageControl.ActualWidth + imagePos.X <= -5.0)
{
translate.X += mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
double iw = (this.imageControl.ActualHeight - imagePos.Y) * scaleValue;
double ww = winWidth - position.X;
if (ww - iw <= -5.0)
{
translate.Y -= mouseXY.X - position.X;
}
}
else
{
if (position.X-imagePos.Y * scaleValue <=-0.5 )
{
translate.Y -= mouseXY.X - position.X;
}
}
}
else
{
if (mouseXY.Y - position.Y > 0.0)//up
{
if (position.Y / scaleValue >= this.imageControl.ActualWidth - imagePos.X)
{
translate.X += mouseXY.Y - position.Y;
}
}
else
{
if (imagePos.X * scaleValue <= winHeight - position.Y)
{
translate.X += mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
if (position.X / scaleValue >= imagePos.Y)
{
translate.Y -= mouseXY.X - position.X;
}
}
else
{
if ((this.imageControl.ActualHeight - imagePos.Y) * scaleValue <= winWidth - position.X)
{
translate.Y -= mouseXY.X - position.X;
}
}
}
} private void BottomMove(TranslateTransform translate, Point position, Point imagePos)
{
if (scaleValue > 1.0)
{
if (mouseXY.Y - position.Y > 0.0)//up
{
if (winHeight - position.Y - imagePos.Y * scaleValue <= -5.0)
{
translate.Y += mouseXY.Y - position.Y;
}
}
else
{
if (position.Y / scaleValue -this.imageControl.ActualHeight+ imagePos.Y <= -5.0)
{
translate.Y += mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
if (winWidth - position.X - imagePos.X * scaleValue <= -5.0)
{
translate.X += mouseXY.X - position.X;
}
}
else
{
if (position.X / scaleValue -this.imageControl.ActualWidth + imagePos.X <= -5.0)
{
translate.X += mouseXY.X - position.X;
}
}
}
else
{
if (mouseXY.Y - position.Y > 0.0)//up
{
if (position.Y / scaleValue >= this.imageControl.ActualHeight - imagePos.Y)
{
translate.Y += mouseXY.Y - position.Y;
}
}
else
{
if (imagePos.Y * scaleValue <= winHeight - position.Y)
{
translate.Y += mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
if (position.X / scaleValue >=this.imageControl.ActualWidth- imagePos.X)
{
translate.X += mouseXY.X - position.X;
}
}
else
{
if (imagePos.X * scaleValue <= winWidth - position.X)
{
translate.X += mouseXY.X - position.X;
}
}
}
} private void TopMove(TranslateTransform translate, Point position, Point imagePos)
{
if (scaleValue > 1.0)
{
if (mouseXY.Y - position.Y > 0.0)//up
{
double wm = winHeight - position.Y;
double im = (this.imageControl.ActualHeight - imagePos.Y) * scaleValue;
if (wm - im <= -5.0)
{
translate.Y -= mouseXY.Y - position.Y;
}
}
else
{
if (position.Y / scaleValue - imagePos.Y <= -5.0)
{
translate.Y -= mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
double iw = (this.imageControl.ActualWidth - imagePos.X) * scaleValue;
double ww = winWidth - position.X;
if (ww - iw <= -5.0)
{
translate.X -= mouseXY.X - position.X;
}
}
else
{
if (position.X / scaleValue - imagePos.X <= -5.0)
{
translate.X -= mouseXY.X - position.X;
}
}
}
else
{
if (mouseXY.Y - position.Y > 0.0)//up
{
if (position.Y / scaleValue >= imagePos.Y)
{
translate.Y -= mouseXY.Y - position.Y;
}
}
else
{
double im = (this.imageControl.ActualHeight - imagePos.Y) * scaleValue;
double wm = winHeight - position.Y;
if (im <= wm)
{
translate.Y -= mouseXY.Y - position.Y;
}
}
if (mouseXY.X - position.X > 0.0)//left
{
if (position.X / scaleValue >= imagePos.X)
{
translate.X -= mouseXY.X - position.X;
}
}
else
{
double iw = (this.imageControl.ActualWidth - imagePos.X) * scaleValue;
double ww = winWidth - position.X;
if (iw <= ww)
{
translate.X -= mouseXY.X - position.X;
}
}
}
} private void PicControl_MouseWheel(object sender, MouseWheelEventArgs e)
{
ContentControl img = sender as ContentControl;
if (img == null)
{
return;
}
Point point = e.GetPosition(img);
TransformGroup group = this.outGrid.FindResource("transGroup") as TransformGroup;
double delta = (double)e.Delta / 3000;
ScaleTransform scale = group.Children[0] as ScaleTransform;
if (scale.ScaleX < 0.3 && scale.ScaleY < 0.3 && e.Delta < 0)
{
return;
}
scale.CenterX = this.imageControl.ActualWidth / 2;
scale.CenterY = this.imageControl.ActualHeight / 2;
scale.ScaleX += delta;
scale.ScaleY += delta;
scaleValue = scale.ScaleX;
this.scalTxt.Text = Math.Ceiling(scaleValue * 100) + "%";
} private void PicWin_Loaded(object sender, RoutedEventArgs e)
{
winWidth = this.picWin.Width;
winHeight = this.picWin.Height; if (picWidth > winWidth || picHeight > winHeight)
{
this.imageControl.Stretch = Stretch.Uniform;
}
} private void ImageControl_MouseEnter(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.SizeAll;
} private void ImageControl_MouseLeave(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.Arrow;
} private void MaxImageCopy_Click(object sender, RoutedEventArgs e)
{
BitmapHelper.ImageCopy(this.picUrl);
} private void MaxImageSave_Click(object sender, RoutedEventArgs e)
{
BitmapHelper.ImageSave(this.picUrl);
} private void PicWin_Closed(object sender, EventArgs e)
{
System.GC.Collect();
} private void Rotate_Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
TransformGroup group = this.outGrid.FindResource("transGroup") as TransformGroup;
TranslateTransform translate = group.Children[1] as TranslateTransform;
RotateTransform rotate = group.Children[2] as RotateTransform;
translate.X = 0;
translate.Y = 0; rotate.CenterX = this.imageControl.ActualWidth / 2;
rotate.CenterY = this.imageControl.ActualHeight / 2;
rotateAngle += 90.0;
rotate.Angle = rotateAngle;
} private void Small_Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ZoomImage(-0.05,-1);
} private void Big_Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ZoomImage(0.05,1);
} private void ZoomImage(double delta,int type)
{
TransformGroup group = this.outGrid.FindResource("transGroup") as TransformGroup;
ScaleTransform scale = group.Children[0] as ScaleTransform;
if (type==-1)
{
if (scale.ScaleX < 0.3 && scale.ScaleY < 0.3)
{
return;
}
}
scale.CenterX = this.imageControl.ActualWidth / 2;
scale.CenterY = this.imageControl.ActualHeight / 2;
scale.ScaleX += delta;
scale.ScaleY += delta;
scaleValue = scale.ScaleX;
this.scalTxt.Text = Math.Ceiling(scaleValue * 100) + "%";
} }
}

最后上个图吧:

WPF图片预览之移动、旋转、缩放