WPF学习 - 用鼠标移动、缩放、旋转图片(2)- 使用MatrixTransform
阅读原文时间:2023年09月05日阅读:1

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

这种方法在某种情况下可以,例如,直接在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代码:

            

后台代码:

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
{
///

/// 向上移动 ///
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));  
}  

}

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

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章