WPF学习 - 用鼠标移动、缩放、旋转图片(2)- 使用MatrixTransform

在上一篇文章中,提到了以鼠标控制变换图片的方法。

这种方法在某种情况下可以,例如,直接在windows窗体上。但我发现,当把它封装到一个控件中的时候,它就不行了。

经过不断的尝试,我找到了一种更简单的方法,而且,封装到控件中也工作正常。

这里就要介绍WPF中另外一种变换:矩阵变换(MatrixTransform)。它就是通过对矩阵进行计算以改变图片的坐标系。

实际上,WPF中的所有变换,都是矩阵变换的特例。例如ScaleTransform、TranslateTransform等,都是通过MatrixTransform来实现的。而TransformGroup就是将ScaleTransform、TranslateTransform、

RotateTransform、SkewTransform等四种变换融合到MatrixTransform中进行计算。

因此,如果要控制图片的变形,可以直接使用MatrixTransform。而且,对于多种组合变换,它更简单。

这里还需要强调一个重点:

  // 这条代码在Mouse事件中

  // img为Iamge控件。而实际上可以是任何UIElement的实例

  Point start = e.GetPosition(img);

这条语句,获取到的点,其实是相对于img控件的原始坐标系的。不管img元素如何变换,获取到的点,只要鼠标的点不变,获取到的值永远不变。因此,如果想要以鼠标为中心点旋转、缩放,就需要将start点变换到控件现状的坐标系下(也就是鼠标在屏幕中的实际位置)。

直接上代码吧,其他的不解释了。

Xaml代码:

<Style TargetType="{x:Type rayCtl:ImgViewer}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type rayCtl:ImgViewer}">
                    
                    <Grid ClipToBounds="{TemplateBinding ClipToBounds}">
                        
                        <Image Name="Part_Image" RenderOptions.BitmapScalingMode="NearestNeighbor" Source="{TemplateBinding Source}"/>

              <!-- 以下是控件按钮,以执行某些命令。如果不需要,可以删除 --> <StackPanel Name="toolBar" Orientation="Horizontal" Opacity="0" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,15,0" > <StackPanel.Background> <LinearGradientBrush> <GradientStop Color="#EAE5C9" Offset="0"/> <GradientStop Color="#6CC6CB" Offset="1"/> </LinearGradientBrush> </StackPanel.Background> <StackPanel.Resources> <ControlTemplate TargetType="Separator" x:Key="simpleSeparator"> <Border SnapsToDevicePixels="True" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" BorderThickness="{TemplateBinding Border.BorderThickness}" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" > </Border> </ControlTemplate> <Style TargetType="Button"> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Padding" Value="2"/> <Setter Property="Margin" Value="3"/> </Style> <Style TargetType="Path"> <Setter Property="Fill" Value="White"/> <Setter Property="Width" Value="16"/> <Setter Property="Stretch" Value="Uniform"/> </Style> </StackPanel.Resources> <Button Command="rayCtl:TransformCommands.MoveLeft"> <Path Data="M896 544H250.4l242.4 242.4L448 832 173.6 557.6 128 512l45.6-45.6L448 192l45.6 45.6L250.4 480H896v64z" /> </Button> <Button Command="rayCtl:TransformCommands.MoveRight"> <Path Data="M128 480h645.6L530.4 237.6 576 192l274.4 274.4L896 512l-45.6 45.6L576 832l-45.6-45.6L773.6 544H128v-64z"/> </Button> <Button Command="rayCtl:TransformCommands.MoveUp"> <Path Data="M480 896V250.4L237.6 493.6 192 448l274.4-274.4L512 128l45.6 45.6L832 448l-45.6 45.6L544 250.4V896h-64z"/> </Button> <Button Command="rayCtl:TransformCommands.MoveDown"> <Path Data="M544 128v645.6l242.4-242.4L832 576l-274.4 274.4L512 896l-45.6-45.6L192 576l45.6-45.6L480 773.6V128h64z" /> </Button> <Separator Width="1" Template="{StaticResource simpleSeparator}"/> <Button Command="rayCtl:TransformCommands.RotateLeft"> <Path Data="M64 536.6c0.6-19.4 2.5-38.8 5.5-58l143 17.6c-1 13.5-1.2 27-0.5 40.4H64zM81.7 423.2c10.3-36.3 25.1-71.3 43.7-103.8l117.3 62.1c-11.4 24.2-19.7 49.6-24.9 75.5L81.7 423.2zM156.1 272.6c22.3-29.9 48.1-56.9 76.6-80.4l80.4 91c-19.6 19-36.7 40.2-51 63l-106-73.6zM277.4 159.7c31-19.8 64.3-35.8 98.9-47.6L415.2 215a336.57 336.57 0 0 0-71.1 41.5l-66.7-96.8zM429.1 97.8c35.6-7.4 71.9-10.4 107.8-9.1v98.6c-28.1 1.3-55.9 5.8-82.5 13.6L429.1 97.8zM590.5 93.8c35.5 5.5 70.2 15.4 103 29.2l-31 81.6c-27.1-8.9-55.2-14.5-83.4-16.6l11.4-94.2zM741.3 146.6c30.8 17.6 59.3 38.9 84.7 63.1l-50.3 56.8c-22.6-18.3-47.2-33.8-73.2-46l38.8-73.9zM861.8 248.2c22.3 27 41.2 56.5 56.2 87.8l-57 30c-14.9-25.5-32.8-49-53.1-70l53.9-47.8zM937.7 384c11.1 32.7 18.2 66.7 21 100.8l-52.8 6.4c-4.9-29.6-13.3-58.4-25-85.6l56.8-21.6zM960 535.9c-1.2 34-6.5 67.8-15.7 100.3l-40.6-9.9c6-29.8 8.2-60.2 6.9-90.3l49.4-0.1zM927.6 684c-13 31-29.8 60.3-49.7 87l-25.1-17.2c16.4-26.1 29.6-54.1 39.2-83.1l35.6 13.3zM845.7 809c-22.9 24-48.5 45.2-76.2 62.9l-10.9-15.6c25-18.9 47.5-40.8 67.1-64.9l20 17.6zM726.6 895.9c-29.5 14.2-60.5 24.7-92.3 31.3l-1.9-7.4c30.5-8.9 59.8-21.5 87-37.4l7.2 13.5zM586.1 934.2c-16.5 1.4-33 1.7-49.5 1.1 16.4-0.6 32.8-2.3 49-4.9l0.5 3.8z"/> </Button> <TextBlock /> <Button Command="rayCtl:TransformCommands.RotateRight"> <Path Data="M158.6,104.8c0.1-2.6,0.1-5.3-0.1-7.9l27.9-3.4c0.6,3.8,1,7.5,1.1,11.3H158.6z M157.5,89.3c-1-5.1-2.6-10-4.9-14.7 l22.9-12.1c3.6,6.3,6.5,13.2,8.5,20.3L157.5,89.3z M148.8,67.6c-2.8-4.5-6.1-8.6-10-12.3l15.7-17.8c5.6,4.6,10.6,9.9,15,15.7 L148.8,67.6z M132.8,50.1c-4.3-3.3-8.9-6-13.9-8.1l7.6-20.1c6.8,2.3,13.3,5.4,19.3,9.3C145.8,31.2,132.8,50.1,132.8,50.1z M111.3,39.2c-5.2-1.5-10.6-2.4-16.1-2.7V17.3c7-0.3,14.1,0.3,21.1,1.8L111.3,39.2z M86.9,36.7c-5.5,0.4-11,1.5-16.3,3.2L64.6,24 c6.4-2.7,13.2-4.6,20.1-5.7L86.9,36.7z M62.8,43.1c-5.1,2.4-9.9,5.4-14.3,9L38.7,41c5-4.7,10.5-8.9,16.5-12.3L62.8,43.1z M31.7,48.5 l10.5,9.3c-4,4.1-7.5,8.7-10.4,13.7l-11.1-5.9C23.6,59.5,27.3,53.8,31.7,48.5L31.7,48.5z M27.9,79.2c-2.3,5.3-3.9,10.9-4.9,16.7 l-10.3-1.3c0.5-6.7,1.9-13.3,4.1-19.7C16.9,75,27.9,79.2,27.9,79.2z M12.5,104.7l9.6,0c-0.3,5.9,0.2,11.8,1.3,17.6l-7.9,1.9 C13.8,117.9,12.7,111.3,12.5,104.7L12.5,104.7z M25.8,131c1.9,5.7,4.5,11.1,7.7,16.2l-4.9,3.4c-3.9-5.2-7.2-10.9-9.7-17L25.8,131z M34.8,158l3.9-3.4c3.8,4.7,8.2,9,13.1,12.7l-2.1,3C44.3,166.8,39.3,162.7,34.8,158L34.8,158z M58.1,175l1.4-2.6 c5.3,3.1,11,5.6,17,7.3l-0.4,1.4C69.9,179.8,63.8,177.8,58.1,175L58.1,175z M85.5,182.5l0.1-0.7c3.2,0.5,6.4,0.8,9.6,1 C92,182.8,88.8,182.7,85.5,182.5L85.5,182.5z"/> </Button> <Separator Width="1" Template="{StaticResource simpleSeparator}"/> <Button Command="rayCtl:TransformCommands.Enlarge"> <Path> <Path.Data> <GeometryGroup> <PathGeometry Figures="M763.221333 702.848L981.333333 921.002667 921.002667 981.333333l-218.154667-218.112A403.626667 403.626667 0 0 1 448 853.333333a405.333333 405.333333 0 1 1 405.333333-405.333333 403.626667 403.626667 0 0 1-90.112 254.848zM448 768a320 320 0 1 0 0-640 320 320 0 0 0 0 640z"/> <PathGeometry Figures="M490.666667 405.333333h128v85.333334h-128v128h-85.333334v-128h-128v-85.333334h128v-128h85.333334z"/> </GeometryGroup> </Path.Data> </Path> </Button> <TextBlock /> <Button Command="rayCtl:TransformCommands.Reduce"> <Path> <Path.Data> <GeometryGroup> <PathGeometry Figures="M763.221333 702.848L981.333333 921.002667 921.002667 981.333333l-218.154667-218.112A403.626667 403.626667 0 0 1 448 853.333333a405.333333 405.333333 0 1 1 405.333333-405.333333 403.626667 403.626667 0 0 1-90.112 254.848zM448 768a320 320 0 1 0 0-640 320 320 0 0 0 0 640z"/> <PathGeometry Figures="M277.333333 512v-85.333333h341.333334v85.333333z"/> </GeometryGroup> </Path.Data> </Path> </Button> <Separator Width="1" Template="{StaticResource simpleSeparator}"/> <Button Command="rayCtl:TransformCommands.FlipX"> <Path Data="M507.164444 913.92h398.222223l-397.653334-796.444444v796.444444M450.275556 913.92h-398.222223l398.222223-796.444444v796.444444m-42.382223-56.888889v-568.888889l-284.444444 568.888889h284.444444"/> </Button> <Button Command="rayCtl:TransformCommands.FlipY"> <Path Data="M724.48 202.808889l-224.142222 206.791111L275.911111 202.808889h448.568889m145.635556-56.888889h-739.555556l369.777778 341.333333 369.777778-341.333333zM500.337778 544.142222l-369.777778 341.333334h739.555556l-369.777778-341.333334z" /> </Button> <Separator Width="1" Template="{StaticResource simpleSeparator}"/> <Button Command="rayCtl:TransformCommands.Reset"> <Path Data="M750.72 170.666667h-111.061333V85.333333h256.170666v256.256h-85.290666V231.424l-196.010667 195.157333-60.16-60.458666 196.352-195.498667z m-0.64 640l-195.712-195.84 60.330667-60.288L810.666667 750.677333v-110.848H896V896h-256.341333v-85.333333h110.421333zM170.666667 750.677333l195.413333-196.096 60.416 60.245334L231.168 810.666667h110.506667V896H85.333333v-256.170667h85.290667v110.805334zM170.666667 230.613333v111.061334H85.333333V85.333333h254.506667v85.333334H231.424L426.666667 366.421333 366.250667 426.666667z"/> </Button> </StackPanel> </Grid>
            <!-- 触发器只是为了让按钮显示,如果没有按钮,可以删除 --> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="toolBar" Property="Opacity" Value="1" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>

后台代码:

namespace RaymondLib.Controls
{
    [TemplatePart(Name = elementName, Type =typeof(Image))]
    public class ImgViewer : Control
    {
        const string elementName = "Part_Image";
        Image? img;
        MatrixTransform tranformer;                 // 变形器

        Point geometryCenter;                       // img的几何中心
        Point moveStart;
        Point rotateStart;
        bool mouseDown;
        bool executable;                            // 命令是否能执

        #region 依赖属性

        internal static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
            "Source", typeof(BitmapSource), typeof(ImgViewer), new PropertyMetadata(null));

        /// <summary>
        /// 图片的源
        /// </summary>
        public BitmapSource Source
        {
            get => (BitmapSource)GetValue(SourceProperty);
            set => SetValue(SourceProperty, value);
        }
#endregion

        static ImgViewer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ImgViewer), new FrameworkPropertyMetadata(typeof(ImgViewer)));
        }


        public ImgViewer()
        {
            // 注册命令
            CommandBindings.Add(new CommandBinding(TransformCommands.MoveUp, OnMoveUp));
            CommandBindings.Add(new CommandBinding(TransformCommands.MoveDown, OnMoveDown));
            CommandBindings.Add(new CommandBinding(TransformCommands.MoveLeft, OnMoveLeft));
            CommandBindings.Add(new CommandBinding(TransformCommands.MoveRight, OnMoveRight));
            CommandBindings.Add(new CommandBinding(TransformCommands.RotateLeft, OnRotateLeft));
            CommandBindings.Add(new CommandBinding(TransformCommands.RotateRight, OnRotateRight));
            CommandBindings.Add(new CommandBinding(TransformCommands.Enlarge, OnEnlarge));
            CommandBindings.Add(new CommandBinding(TransformCommands.Reduce, OnReduce));
            CommandBindings.Add(new CommandBinding(TransformCommands.FlipX, OnFlipX));
            CommandBindings.Add(new CommandBinding(TransformCommands.FlipY, OnFlipY));
            CommandBindings.Add(new CommandBinding(TransformCommands.Reset, OnReset));
        }
        #region 命令:对基础方法的调用

        // 向上移动20
        private void OnMoveUp(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoMove(0, -20);
        }

        // 向下移动20
        private void OnMoveDown(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoMove(0, 20);
        }

        // 向左移动20
        private void OnMoveLeft(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoMove(-20, 0);
        }

        // 向右移动20
        private void OnMoveRight(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoMove(20, 0);
        }

        // 向左旋转90°
        private void OnRotateLeft(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoRotate(-90);
        }

        // 向右旋转90°
        private void OnRotateRight(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoRotate(90);
        }

        // 放大0.2倍
        private void OnEnlarge(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoScale(true);
        }


        // 缩小0.2倍
        private void OnReduce(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoScale(false);
        }


        // 垂直翻转命令
        private void OnFlipY(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoVerFlip();
        }


        // 水平翻转命令
        private void OnFlipX(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoHorFlip();
        }


        // 重置
        private void OnReset(object sender, ExecutedRoutedEventArgs e)
        {
            if (executable) DoReset();
        }

        #endregion


        #region 基础方法:平移、缩放、旋转

        /// <summary>
        /// 移动图片
        /// </summary>
        /// <param name="deltaX">水平方向增量</param>
        /// <param name="deltaY">垂直方向增量</param>
        private void DoMove(double deltaX,double deltaY)
        {
            Vector v = new Vector(deltaX, deltaY);
            DoMove(v);
        }

        /// <summary>
        /// 移动图片
        /// </summary>
        /// <param name="vector">移动的向量</param>
        private void DoMove(Vector vector)
        {
            Matrix temp = tranformer.Matrix;
            Vector moveReal = temp.Transform(vector);           // 将相对于图片原始坐标的向量,变换为经过变换的向量,其实就是考虑到了旋转、翻转的情况
            temp.Translate(moveReal.X, moveReal.Y);
            tranformer.Matrix = temp;
        }

        /// <summary>
        /// 缩放元素:以几何中心点为缩放中心点。
        /// 最小缩放0.2倍,最大30倍
        /// </summary>
        /// <param name="delta">缩放倍率的增量</param>
        private void DoScale(bool enlarge)
        {
            DoScale(enlarge, geometryCenter);
        }

        /// <summary>
        /// 缩放元素
        /// </summary>
        /// <param name="delta">缩放倍率</param>
        /// <param name="center">缩放的中心点。这个中心点就是e.GetPosition(img)获取到的点</param>
        private void DoScale(bool enlarge,Point center)
        {
            Matrix temp = tranformer.Matrix;
            Point centerReal = temp.Transform(center);    // 将相对于img原始坐标的点,变换成现状坐标系的点(鼠标在屏幕中的位置)

            // 缩放倍率分为三段:
            // 小于0.2时,只允许放大
            // 0.2-50之间,可以放大、缩小
            // 大于50,只允许缩小

            if(Math.Abs(temp.M11) < 0.2)
            {
                if (enlarge) 
                {
            // 这里需要注意:ScaleAt()方法内部将创建一个缩放倍数的矩阵与现有矩阵相乘。因此但给定一个小于1的缩放倍数,则实际将缩小图片 temp.ScaleAt(
1.1, 1.1, centerReal.X, centerReal.Y); } } else if (Math.Abs(temp.M11) > 50) { if(!enlarge) { temp.ScaleAt(0.9, 0.9, centerReal.X, centerReal.Y); }0 } else { if (enlarge) { temp.ScaleAt(1.1, 1.1, centerReal.X, centerReal.Y); } else { temp.ScaleAt(0.9, 0.9, centerReal.X, centerReal.Y); } } tranformer.Matrix = temp; } /// <summary> /// 旋转元素:以几何中心点为旋转中心点 /// </summary> /// <param name="angle">旋转的角度增量</param> private void DoRotate(double angle) { DoRotate(angle, geometryCenter); } /// <summary> /// 旋转元素 /// </summary> /// <param name="angle">旋转的角度增量</param> /// <param name="center">旋转的中心点</param> private void DoRotate(double angle,Point center) { Matrix temp = tranformer.Matrix; Point centerReal = temp.Transform(center); temp.RotateAt(angle, centerReal.X, centerReal.Y); tranformer.Matrix = temp; } /// <summary> /// 水平翻转:以元素的几何中心为中心点水平翻转 /// </summary> private void DoHorFlip() { Matrix temp = tranformer.Matrix; temp.M11 *= -1; temp.M21 *= -1; temp.OffsetX = img.ActualWidth - temp.OffsetX;  // 以几何中心为原点,沿Y轴翻转 tranformer.Matrix = temp; } /// <summary> /// 垂直翻转:以元素的几何中心为中心点垂直翻转 /// </summary> private void DoVerFlip() { Matrix temp = tranformer.Matrix; temp.M12 *= -1; temp.M22 *= -1; temp.OffsetY = img.ActualHeight - temp.OffsetY;    // 以几何中心为原点,沿X轴翻转 tranformer.Matrix = temp; } /// <summary> /// 重置 /// </summary> private void DoReset() { tranformer.Matrix = new Matrix(); } #endregion // 设置模板 public override void OnApplyTemplate() { executable = false; if (img != null) { tranformer = null; } img = GetTemplateChild(elementName) as Image; if (img != null) { tranformer = new MatrixTransform(); img.RenderTransform = tranformer; executable = true; // 命令是否能执行 // 鼠标事件 img.MouseLeftButtonDown += Element_MouseLeftButtonDown; img.MouseRightButtonDown += Element_MouseRightButtonDown; img.MouseMove += Element_MouseMove; img.MouseUp += Element_MouseUp; img.MouseWheel += Element_MouseWheel; img.SizeChanged += Element_SizeChanged; } } // 尺寸发生改变时:中心发生变化 private void Element_SizeChanged(object sender, SizeChangedEventArgs e) {
        // 这里应该使用img的ActualXX属性,因为img有可能控件在布局的时候,有可能缩放。
this.geometryCenter = new Point(img.ActualWidth / 2, img.ActualHeight / 2); } // 按下鼠标左键 private void Element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { img.CaptureMouse(); mouseDown = true; moveStart = e.GetPosition(img); } private void Element_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { img.CaptureMouse(); mouseDown = true; rotateStart = e.GetPosition(img); } // 鼠标移动 private void Element_MouseMove(object sender, MouseEventArgs e) { if (!mouseDown) return; Point mouseEnd = e.GetPosition(img); if (e.LeftButton == MouseButtonState.Pressed) { var delta = mouseEnd - moveStart; DoMove(delta.X, delta.Y); //moveStart = mouseEnd; } else if(e.RightButton == MouseButtonState.Pressed) { // 按下鼠标右键,旋转图片 double angle = (mouseEnd.Y - rotateStart.Y) * 0.2; DoRotate(angle,rotateStart); resetRotater = true; } } // 释放鼠标 private void Element_MouseUp(object sender, MouseButtonEventArgs e) { img.ReleaseMouseCapture(); mouseDown = false; } // 滚动鼠标滚轮 private void Element_MouseWheel(object sender, MouseWheelEventArgs e) { Point scaleCenter = e.GetPosition(img); bool enlarge = e.Delta > 0; DoScale(enlarge, scaleCenter); } }
   // 以下为该控件支持的命令
public static class TransformCommands { /// <summary> /// 向上移动 /// </summary> public static RoutedCommand MoveUp { get; } = new(nameof(MoveUp), typeof(TransformCommands)); /// <summary> /// 向下移动 /// </summary> public static RoutedCommand MoveDown { get; } = new(nameof(MoveDown), typeof(TransformCommands)); /// <summary> /// 向左移动 /// </summary> public static RoutedCommand MoveLeft { get; } = new(nameof(MoveLeft), typeof(TransformCommands)); /// <summary> /// 向右移动 /// </summary> public static RoutedCommand MoveRight { get; } = new(nameof(MoveRight), typeof(TransformCommands)); /// <summary> /// 向左旋转 /// </summary> public static RoutedCommand RotateLeft { get; } = new(nameof(RotateLeft), typeof(TransformCommands)); /// <summary> /// 向右旋转 /// </summary> public static RoutedCommand RotateRight { get; } = new(nameof(RotateRight), typeof(TransformCommands)); /// <summary> /// 放大 /// </summary> public static RoutedCommand Enlarge { get; } = new(nameof(Enlarge), typeof(TransformCommands)); /// <summary> /// 缩小 /// </summary> public static RoutedCommand Reduce { get; } = new(nameof(Reduce), typeof(TransformCommands)); /// <summary> /// 水平翻转 /// </summary> public static RoutedCommand FlipX { get; } = new(nameof(FlipX), typeof(TransformCommands)); /// <summary> /// 垂直翻转 /// </summary> public static RoutedCommand FlipY { get; } = new(nameof(FlipY), typeof(TransformCommands)); /// <summary> /// 重置 /// </summary> public static RoutedCommand Reset { get; } = new(nameof(Reset), typeof(TransformCommands)); } }

以上为个人自己摸索所得。如有错误,还请高手指点。

posted @ 2022-09-25 11:26  卓尔不设凡  阅读(1442)  评论(0)    收藏  举报