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

原文: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图片预览之移动、旋转、缩放

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

上一篇:Session&Cookie(Introduction、Application)


下一篇:迁移桌面程序到MS Store(7)——APPX + Service