1. 程式人生 > >二維圖形的矩陣變換(三)——在WPF中的應用矩陣變換

二維圖形的矩陣變換(三)——在WPF中的應用矩陣變換

over 底層 hit 過程 相對 duration != closed com

原文:二維圖形的矩陣變換(三)——在WPF中的應用矩陣變換

UIElement和RenderTransform

首先,我們來看看什麽樣的對象可以進行變換。在WPF中,用於呈現給用戶的對象的基類為Visual類,但是Visual對象並不具有變換功能,具有變換功能的是它的子類UIElement。這個類也是非常底層的類了,幾乎我們所有的常用控件都是繼承自它,也就是說,基本上所有的UI對象都是可以應用變換的。

然後,我們在再來看看UIElement中變換種類。UIElement支持兩種變換:RenderTransform和LayoutTransform,其中LayoutTransform是會改變其布局,從而影響相鄰的空間大小和位置的,如下圖所示。

技術分享圖片 技術分享圖片

由於我們常用的是RenderTransfrom,並且兩種變換的使用方式非常類似,下面的文章中就主要以RenderTransfrom作為介紹對象。下面的例子就簡單的演示了其用法:

<StackPanel Orientation="Vertical">
<Button Content="A Button" Opacity="1" />
<Button Content="Rotated Button">
<Button.RenderTransform>

<RotateTransform Angle="45" />
</Button.RenderTransform>
</Button>
<Button Content="A Button" Opacity="1" />
</StackPanel>

矩陣變換MatrixTransform

前面的例子中演示了旋轉的變換RotateTransform的用法,其它幾種基本變換也有相對的變換類:ScaleTransform 、TranslateTransform 、SkewTransform。我們也可以將多個變換放到一個變換組中實現疊加的效果。

這些基本變換用法相對比較簡單,這裏就不多介紹了。下面介紹本文的重點:矩陣變換MatrixTransform。它的用法和RotateTransform實際上也差不多:

<Button Content="Rotated Button">
<Button.RenderTransform>
<MatrixTransform x:Name="myMatrixTransform">
<MatrixTransform.Matrix >
<Matrix OffsetX="10" OffsetY="100" />
</MatrixTransform.Matrix>
</MatrixTransform>
</Button.RenderTransform>
</Button>

從上面的代碼中可以看到,由於矩陣變換要設置六個值,並且這幾個值不容易讀,因此在XAML中使用顯得非常不直觀,大多數的時候我們是在代碼中進行設置的。

單單從這個例子來看,是無法看出矩陣變換的什麽優越性的。那是因為我們使用的變換比較簡單,在前文二維圖形的矩陣變換(一)——基本概念中介紹過,任何二維變換的序列均可存儲於單個的 Matrix 對象,因此它是可以非常容易實現變換疊加效果的,下面就以我之前的文章用WPF實現一個簡單的圖片查看器中介紹到的例子用矩陣變換來改寫一下。

這個例子的主要功能是實現一個支持鼠標拖動和滾輪縮放的圖片查看器,在原文中是靠平移變換和縮放變換疊加實現的,這裏用矩陣變換來實現一下。首先還是來看看XAML部分

<Grid>
<Image Source="source.jpg" MouseWheel="Image_MouseWheel" PreviewMouseLeftButtonDown="Image_MouseLeftButtonDown"
PreviewMouseMove="Image_MouseMove">
<Image.RenderTransform>
<MatrixTransform x:Name="transForm" />
</Image.RenderTransform>
</Image>
</Grid>

然後就是事件的實現了:

private void Image_MouseWheel(object sender, MouseWheelEventArgs e)
{
var center = getPosition(sender, e);
var scale = (e.Delta > 0 ? 1.2 : 1 / 1.2);

var matrix = transForm.Matrix;
matrix.ScaleAt(scale, scale, center.X, center.Y);

transForm.Matrix = matrix;
}

Point dragStart;
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dragStart = getPosition(sender, e);
}

private void Image_MouseMove(object sender, MouseEventArgs e)
{
if ((e.LeftButton != MouseButtonState.Pressed))
{
return;
}

var current = getPosition(sender, e);
var offset = current - dragStart;

var matrix = transForm.Matrix;
matrix.Translate(offset.X, offset.Y);

transForm.Matrix = matrix;

dragStart = current;
}

Point getPosition(object sender, MouseEventArgs e)
{
return e.GetPosition(sender as UIElement) * transForm.Matrix;
}

由於這個例子本身並不復雜,並不能很好的體現矩陣變換的優越性,但還是可見一斑的。原文中是通過平移變換和縮放變換疊加實現的,因此這兩個變換是互相影響的,平移的時候需要考慮縮放率、縮放的時候要考慮偏移量,調整相應的參數進行校正。而矩陣變換相對簡單得多,只需要產生將變換矩陣和原始變換矩陣相乘即可獲得疊加效果。

另外,由於矩陣變換還可以應用於Point,因此非常方便實現一些附加功能的,例如,我們要獲取放大後的圖像在原始圖像的位置時,只需要取屏幕上四周的四個點,對檔期變換矩陣的逆矩陣相乘即可。其它的就不一一列舉了,要實現完整的圖片查看器,矩陣變換比平移變換和縮放變換疊加要方便太多。

矩陣變換的動畫

在WPF中,變換過程是可以非常容易的改成動畫的炫酷效果的,但不知道為什麽,系統並沒有提供動畫效果的矩陣變換的內置實現。不過這個並不難解決,Google了一下就發現在Stack overflow上已經有人實現了,原文地址如下:Smooth animation using MatrixTransform?,為了防止方校長哪天愛心泛濫把這個網站改成尋人啟事了,這裏還是轉錄一下。

技術分享圖片
    public class MatrixAnimation : MatrixAnimationBase
    {
        public Matrix? From
        {
            set { SetValue(FromProperty, value); }
            get { return (Matrix?)GetValue(FromProperty); }
        }

        public static DependencyProperty FromProperty =
            DependencyProperty.Register("From", typeof(Matrix?), typeof(MatrixAnimation),
                new PropertyMetadata(null));

        public Matrix? To
        {
            set { SetValue(ToProperty, value); }
            get { return (Matrix?)GetValue(ToProperty); }
        }

        public static DependencyProperty ToProperty =
            DependencyProperty.Register("To", typeof(Matrix?), typeof(MatrixAnimation),
                new PropertyMetadata(null));

        public IEasingFunction EasingFunction
        {
            get { return (IEasingFunction)GetValue(EasingFunctionProperty); }
            set { SetValue(EasingFunctionProperty, value); }
        }

        public static readonly DependencyProperty EasingFunctionProperty =
            DependencyProperty.Register("EasingFunction", typeof(IEasingFunction), typeof(MatrixAnimation),
                new UIPropertyMetadata(null));

        public MatrixAnimation()
        {
        }

        public MatrixAnimation(Matrix toValue, Duration duration)
        {
            To = toValue;
            Duration = duration;
        }

        public MatrixAnimation(Matrix toValue, Duration duration, FillBehavior fillBehavior)
        {
            To = toValue;
            Duration = duration;
            FillBehavior = fillBehavior;
        }

        public MatrixAnimation(Matrix fromValue, Matrix toValue, Duration duration)
        {
            From = fromValue;
            To = toValue;
            Duration = duration;
        }

        public MatrixAnimation(Matrix fromValue, Matrix toValue, Duration duration, FillBehavior fillBehavior)
        {
            From = fromValue;
            To = toValue;
            Duration = duration;
            FillBehavior = fillBehavior;
        }

        protected override Freezable CreateInstanceCore()
        {
            return new MatrixAnimation();
        }

        protected override Matrix GetCurrentValueCore(Matrix defaultOriginValue, Matrix defaultDestinationValue, AnimationClock animationClock)
        {
            if (animationClock.CurrentProgress == null)
            {
                return Matrix.Identity;
            }

            var normalizedTime = animationClock.CurrentProgress.Value;
            if (EasingFunction != null)
            {
                normalizedTime = EasingFunction.Ease(normalizedTime);
            }

            var from = From ?? defaultOriginValue;
            var to = To ?? defaultDestinationValue;

            var newMatrix = new Matrix(
                    ((to.M11 - from.M11) * normalizedTime) + from.M11,
                    ((to.M12 - from.M12) * normalizedTime) + from.M12,
                    ((to.M21 - from.M21) * normalizedTime) + from.M21,
                    ((to.M22 - from.M22) * normalizedTime) + from.M22,
                    ((to.OffsetX - from.OffsetX) * normalizedTime) + from.OffsetX,
                    ((to.OffsetY - from.OffsetY) * normalizedTime) + from.OffsetY);

            return newMatrix;
        }
    }
View Code

二維圖形的矩陣變換(三)——在WPF中的應用矩陣變換