当图像未与x-y轴对齐时,对ScaleTransform(WPF)进行泛化 [英] Generalizing the ScaleTransform (WPF) when image is not aligned to x-y axes

查看:112
本文介绍了当图像未与x-y轴对齐时,对ScaleTransform(WPF)进行泛化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的线性代数很弱. WPF是一个很棒的系统,可以在图像上渲染不同的变换.但是,标准ScaleTransform将仅沿x-y轴缩放图像.第一次旋转边缘时,应用ScaleTransform的结果将导致倾斜的变换(如下所示),因为边缘不再对齐.

My linear algebra is weak. WPF is a great system for Rendering different transformations upon an image. However, the standard ScaleTransform will only scale the image's along the x-y axes. When the edges have first been rotated, the result of applying the ScaleTransform will result in a skewed transformation (as shown below) since the edges are no longer aligned.

因此,如果我的图像经过了几次不同的转换,结果由WPF渲染系统显示,那么我该如何计算正确的矩阵转换以拍摄(最终旋转的图像)并缩放 沿着渲染图像的轴?

So, if I have an image that has undergone several different transforms with the result being shown by the WPF rendering system, how do I calculate the correct matrix transform to take the (final rotated image) and scale it along the axes of the rendered image?

任何帮助或建议将不胜感激.

Any help or suggestions will be most appreciated.

TIA

(有关完整代码,请参阅我之前的问题.)

(For the complete code, please see my previous question.)

编辑#1:要查看上述效果,

Edit #1: To see the above effect:

  1. 将图像拖放到Inkcavas上. -看不到歪斜.
  2. 逆时针旋转图像(大约45度)-看不到任何倾斜.
  3. 将图像放大(大约是其预缩放尺寸的两倍-看不到歪斜.
  4. 顺时针旋转图像(大约回到起始位置)-倾斜 在旋转期间和旋转后立即可见.
  1. Drop image onto Inkcavas. -- no skewing seen.
  2. Rotate image counterclockwise (to about 45deg) -- no skewing seen.
  3. Make the image larger (about twice its prescaled size -- no skewing seen.
  4. Rotate the image clockwise (about back to where it started) -- skewing is immediately seen during and after the rotation.

如果跳过了第3步,则简单的旋转-无论执行多少次-都不会引起偏斜效果.实际上,这是有道理的. ScaleTransform保留了从图像边缘到中心的距离.如果图像成一定角度,则从渲染边缘到渲染图像的宽度和长度的x-y距离不再恒定.因此,边缘得到了适当的缩放,但是角度发生了变化.

If step 3 is skipped, simple rotation -- no matter how many times done -- will not cause the skewing effect. Actually, this makes sense. The ScaleTransform preserves the distance from center from the edges of the image. If the image is at an angle, the x-y distance from the edges of the transform are no longer constant through the width and length of the rendered image. So the edges get appropriately scaled, but the angles are changed.

以下是最相关的代码:

private ImageResizing(Image image)
        {
            if (image == null)
                throw new ArgumentNullException("image");

           _image = image;
            TransformGroup tg = new TransformGroup();

            image.RenderTransformOrigin = new Point(0.5, 0.5);  // All transforms will be based on the center of the rendered element.
            tg.Children.Add(image.RenderTransform);             // Keeps whatever transforms have already been applied.
            image.RenderTransform = tg; 
            _adorner = new MyImageAdorner(image);               // Create the adorner.

            InstallAdorner();                                   // Get the Adorner Layer and add the Adorner.
        }

注意:image.RenderTransformOrigin = new Point(0.5,0.5)设置为中心 渲染的图像.所有变换都将基于变换时看起来图像的中心.

Note: The image.RenderTransformOrigin = new Point(0.5, 0.5) is set to the center of the rendered image. All transforms will be based on the center of the image at the time it is seem by the transform.

 public MyImageAdorner(UIElement adornedElement)
      : base(adornedElement)
    {
        visualChildren = new VisualCollection(this);


        // Initialize the Movement and Rotation thumbs.
        BuildAdornerRotate(ref moveHandle, Cursors.SizeAll);
        BuildAdornerRotate(ref rotateHandle, Cursors.Hand);

        // Add handlers for move and rotate.
        moveHandle.DragDelta += new DragDeltaEventHandler(moveHandle_DragDelta);
        moveHandle.DragCompleted += new DragCompletedEventHandler(moveHandle_DragCompleted);
        rotateHandle.DragDelta += new DragDeltaEventHandler(rotateHandle_DragDelta);
        rotateHandle.DragCompleted += new DragCompletedEventHandler(rotateHandle_DragCompleted);


        // Initialize the Resizing (i.e., corner) thumbs with specialized cursors.
        BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);

        // Add handlers for resizing.
        topLeft.DragDelta += new DragDeltaEventHandler(TopLeft_DragDelta);

        topLeft.DragCompleted += TopLeft_DragCompleted;

        // Put the outline border arround the image. The outline will be moved by the DragDelta's
        BorderTheImage();
    }

  #region [Rotate]
    /// <summary>
    /// Rotate the Adorner Outline about its center point. The Outline rotation will be applied to the image
    /// in the DragCompleted event.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void rotateHandle_DragDelta(object sender, DragDeltaEventArgs e)
    {
        // Get the position of the mouse relative to the Thumb.  (All cooridnates in Render Space)
        Point pos = Mouse.GetPosition(this);

        // Render origin is set at center of the adorned element. (all coordinates are in rendering space).
        double CenterX = AdornedElement.RenderSize.Width / 2;
        double CenterY = AdornedElement.RenderSize.Height / 2;

        double deltaX = pos.X - CenterX;
        double deltaY = pos.Y - CenterY;

        double angle;
        if (deltaY.Equals(0))
        {
            if (!deltaX.Equals(0))
                angle = 90;
            else
                return;

        }
        else
        {
            double tan = deltaX / deltaY;
            angle = Math.Atan(tan);  angle = angle * 180 / Math.PI;
        }

        // If the mouse crosses the vertical center, 
        // find the complementary angle.
        if (deltaY > 0)
            angle = 180 - Math.Abs(angle);

        // Rotate left if the mouse moves left and right
        // if the mouse moves right.
        if (deltaX < 0)
            angle = -Math.Abs(angle);
        else
            angle = Math.Abs(angle);

        if (double.IsNaN(angle))
            return;

        // Apply the rotation to the outline.  All Transforms are set to Render Center.
        rotation.Angle = angle;
        rotation.CenterX = CenterX;
        rotation.CenterY = CenterY;
        outline.RenderTransform = rotation;
    }

    /// Rotates image to the same angle as outline arround the render origin.
    void rotateHandle_DragCompleted(object sender, DragCompletedEventArgs e)
    {
        // Get Rotation Angle from outline. All element rendering is set to rendering center.
        RotateTransform _rt = outline.RenderTransform as RotateTransform;

        // Add RotateTransform to the adorned element.
        TransformGroup gT = AdornedElement.RenderTransform as TransformGroup;
        RotateTransform rT = new RotateTransform(_rt.Angle);
        gT.Children.Insert(0, rT);
        AdornedElement.RenderTransform = gT;

        outline.RenderTransform = Transform.Identity;  // clear transform from outline.
    }
    #endregion  //Rotate


 #region [TopLeft Corner
    // Top Left Corner is being dragged. Anchor is Bottom Right.
    void TopLeft_DragDelta(object sender, DragDeltaEventArgs e)
    {
        ScaleTransform sT = new ScaleTransform(1 - e.HorizontalChange / outline.ActualWidth, 1 - e.VerticalChange / outline.ActualHeight,
            outline.ActualWidth, outline.ActualHeight);

        outline.RenderTransform = sT;   // This will immediately show the new outline without changing the Image.
    }



    /// The resizing outline for the TopLeft is based on the bottom right-corner. The resizing transform for the
    /// element, however, is based on the render origin being in the center. Therefore, the Scale transform 
    /// received from the outling must be recalculated to have the same effect--only from the rendering center.
    /// 
    /// TopLeft_DragCompleted resize the element rendering.
    private void TopLeft_DragCompleted(object sender, DragCompletedEventArgs e)
    {
        // Get new scaling from the Outline.
        ScaleTransform _sT = outline.RenderTransform as ScaleTransform;
        scale.ScaleX *= _sT.ScaleX; scale.ScaleY *= _sT.ScaleY;

        Point Center = new Point(AdornedElement.RenderSize.Width/2, AdornedElement.RenderSize.Height/2);

        TransformGroup gT = AdornedElement.RenderTransform as TransformGroup;

        ScaleTransform sT = new ScaleTransform( _sT.ScaleX, _sT.ScaleY, Center.X, Center.Y);
        gT.Children.Insert(0, sT);

        AdornedElement.RenderTransform = gT;
        outline.RenderTransform = Transform.Identity;           // Clear outline transforms. (Same as null).
    }
    #endregion

注意:我正在将每个新转换添加到子级列表的第一个.这样可以简化图像的计算.

Note: I am adding each new transform to the first of the children list. This makes calculations on the image easier.

推荐答案

我无法在Google或文本中找到完全回答此问题所需的所有元素.因此,对于所有其他新手,例如我的自我,我将发布此答案(很长). (编辑和大师们请随时纠正).

I could not find with Google or in text all the elements needed to answer this question completely. So, for all other newbies like my self, I will post this (very long) answer. (Editors and Gurus please feel free to correct).

一个关于安装的词.我有一个可以将图像拖放到其上的墨盒,作为该墨盒的子代.在放置时,添加了一个装饰器,该装饰器在每个角上包含一个用于调整大小的Thumb,一个用于旋转的Top-Middle拇指和一个用于平移的中指,以最终定位图像.拇指和轮廓连同设计为路径元素的轮廓"一起完善了Adorner,并在装饰元素周围创建了一种线框.

A word toward setup. I have an inkcanvas onto which an image is dropped and added as a child of the inkcanvas. At the time of the drop, an adorner containing a Thumb on each corner for resizing, a Top-Middle thumb for rotating, and a middle thumb for translation is added for final positioning of the image. Along with a "outline" designed as a path element, the Thumbs and outline complete the Adorner and create a kind of wire frame around the adorned element.

有多个关键点:

  1. WPF首先使用布局遍历将元素放置在其父容器中,然后进行渲染遍历以布置元素.可以将变换应用于布局和渲染过程之一或两者.但是,需要注意的是,布局过程使用的x-y坐标系的原点位于父级的左上角,而渲染系统固有地引用了子元素的左上角.如果未特别定义放置元素的布局位置,则默认情况下会将其添加到父容器的原点".
  2. 默认情况下,RenderTransform是MatrixTransform,但可以用TransformGroup代替.使用这两种方法中的一种或两种都可以以任何顺序应用矩阵(在MatrixTransform中)或变换(在TransformGroup中).我更喜欢使用MatrixTransforms更好地查看缩放,旋转和平移之间的关系.
  3. 装饰器的渲染遵循其装饰的元素.也就是说,元素的渲染也将应用于Adorner.可以使用

  1. WPF first uses a layout pass to position elements within their parent container, followed by a rendering pass to arrange the element. Transforms can be applied to either or both the layout and rendering passes. However, it needs to be noted that the layout pass uses an x-y coordinate system with the origin on the top left of the parent, where as the rendering system inherently references the top left of the child element. If the layout position of the dropped element is not specifically defined, it will by default be added to the "origin" of the parent container.
  2. The RenderTransform is by default a MatrixTransform but can be replaced by a TransformGroup. Using either or both of these allows for Matrices (in the MatrixTransform) or Transforms (in the TransformGroup) to be applied in any order. My preference was to use the MatrixTransforms to better see the relationship between scaling, rotation, and translation.
  3. The rendering of the adorner follows the element it adorns. That is, the element's rendering will also be applied to the Adorner. This behavior can be overriden by use of

公共替代GeneralTransform GetDesiredTransform(GeneralTransform transform)

public override GeneralTransform GetDesiredTransform(GeneralTransform transform)

正如第一个问题所述,我避免使用SetTop()和SetLeft(),因为它们弄乱了我的其他矩阵.事后看来,我的矩阵失败的原因是因为SetTop()和SetLeft()显然在布局阶段可以工作-所以我所有的渲染坐标都关闭了. (我正在使用TransalateTransform在拖放时定位图像.)但是,使用SetTop()和SetLeft()显然在布局阶段起作用.使用此功能大大简化了渲染阶段所需的计算,因为所有坐标都可以参考图像,而无需考虑画布上的位置.

As noted in the initial question, I had avoided using SetTop() and SetLeft() as they messed up my other matrices. In hindsight, the reason my matrices failed was because SetTop() and SetLeft() apparently work during the layout phase--so all my coordinates for rendering were off. (I was using a TransalateTransform to position the image upon drag-drop.) However, using SetTop() and SetLeft() apparently act during the Layout Phase. Using this greatly simplified the calculations needed for the Rendering Phase since all coordinates could refer to the image without regard to the position on the canvas.

private void IC_Drop(object sender, DragEventArgs e)
    {
        InkCanvas ic = sender as InkCanvas;

        // Setting InkCanvasEditingMode.None is necessary to capture DrawingLayer_MouseDown.
        ic.EditingMode = InkCanvasEditingMode.None;

        ImageInfo image_Info = e.Data.GetData(typeof(ImageInfo)) as ImageInfo;
        if (image_Info != null)
        {
            // Display enlarged image on ImageLayer
            // This is the expected format for the Uri:
            //      ImageLayer.Source = new BitmapImage(new Uri("/Images/Female - Front.png", UriKind.Relative));
            // Source = new BitmapImage(image_Info.Uri);

            Image image = new Image();
            image.Width = image_Info.Width * 4;

            // Stretch.Uniform keeps the Aspect Ratio but totally screws up resizing the image.
            // Stretch.Fill allows for resizing the Image without keeping the Aspect Ratio.    
            image.Stretch = Stretch.Fill;
            image.Source = new BitmapImage(image_Info.Uri);

            // Position the drop. Note that SetLeft and SetTop are active during the Layout phase of the image drop and will
            // be applied before the Image hits its Rendering stage.
            Point position = e.GetPosition(ic);
            InkCanvas.SetLeft(image, position.X);
            InkCanvas.SetTop(image, position.Y);
            ic.Children.Add(image);
            ImageResizing imgResize = ImageResizing.Create(image);
        }
    }

由于我希望能够从任何方向调整图像大小,因此可以使用Stretch.Fill设置图像.使用Stretch.Uniform时,图像似乎先被调整大小,然后又跳回到其原始大小.

Since I want to be able to resize the image from any direction, the image is set with Stretch.Fill. When Stretch.Uniform was used, the image appeared to first be resized then jump back to its initial size.

  • 由于我使用的是MatrixTransform,因此矩阵的顺序很重要.因此,在应用矩阵时,供我使用

  • Since I am using MatrixTransform, the order of the Matrices is important. So when applying the Matrices, for my use

   // Make new render transform. The Matrix order of multiplication is extremely important.
        // Scaling should be done first, followed by (skewing), rotation and translation -- in 
        // that order.
        MatrixTransform gT = new MatrixTransform
        {
            Matrix = sM * rM * tM
        };

        ele.RenderTransform = gT;

缩放(sM),在旋转(rM)之前执行.最后应用翻译. (C#从左到右执行矩阵乘法).

Scaling (sM), is performed before Rotation (rM). Translation is applied last. (C# does matrix multiplication from left to right).

回顾矩阵,很明显,旋转矩阵还包含倾斜元素. (这很有意义,因为显然RotationTransform旨在使边缘的角度保持恒定).因此,旋转矩阵取决于图像的大小.

In review of the matrices, it is apparent that the Rotation Matrix also involves skewing elements. (Which makes sense since apparently the RotationTransform is intended to keep the angles at the edges constant). Thus, the Rotation Matrix depends on the size of the image.

在我的情况下,旋转后缩放导致倾斜的原因是缩放比例乘以图像点与x-y轴之间的距离.因此,如果图像的边缘与x-y轴的距离不是恒定的,则缩放会扭曲(即倾斜)图像.

In my case, the reason scaling after rotation was causing skewing is because the Scaling transform multiplies the distance between points of the image and the x-y axes. So if the edge of the image is not of constant distance to the x-y axes, scaling will distort (i.e., skew) the image.

将其放在一起,将产生以下方法来调整图像的大小:

Putting this together, results in the following method to resize the image:

Action<Matrix, Vector> DragCompleted = (growthMatrix, v) =>
        {
            var ele = AdornedElement;

            // Get the change vector.  Transform (i.e, Rotate) change vector into x-y axes.
            // The Horizontal and Vertical changes give the distance between the the current cursor position
            // and the Thumb.
            Matrix m = new Matrix();
            m.Rotate(-AngleDeg);
            Vector v1 = v * m;

            // Calculate Growth Vector.
            var gv = v1 * growthMatrix;

            // Apply new scaling along the x-y axes to obtain the rendered size. 
            // Use the current Image size as the reference to calculate the new scaling factors.
            var scaleX = sM.M11; var scaleY = sM.M22;
            var W = ele.RenderSize.Width * scaleX; var H = ele.RenderSize.Height * scaleY;
            var sx = 1 + gv.X/ W; var sy = 1 + gv.Y / H;

            // Change ScalingTransform by applying the new scaling factors to the existing scaling transform.
            // Do not add offsets to the scaling transform matrix as they will be included in future scalings.
            // With RenderTransformOrigin set to the image center (0.5, 0.5), scalling occurs from the center out.
            // Move the new center of the new resized image to its correct position such that the image's thumb stays
            // underneath the cursor.
            sM.Scale(sx, sy);


            tM.Translate(v.X / 2, v.Y / 2);


            // New render transform. The order of the transform's is extremely important.
            MatrixTransform gT = new MatrixTransform
            {
                Matrix = sM * rM * tM
            };
            ele.RenderTransform = gT;
            outline.RenderTransform = Transform.Identity;  // clear this transform from the outline.

        };

请明确说明,我的增长矩阵"的定义方式是,当光标从图像中心移开时,将导致正向"增长.例如,当向左和向上移动时,TopLeft角将增长"图像.因此

Just to be clear, my "Growth Matrix" is defined in such a manner as to result in "Positive" growth as the cursor is moved away from the center of the image. For Example, the TopLeft corner will "grow" the image when moved to the left and up. Hence

增长矩阵=左上角的新Matrix(-1,0,0,-1,0,0)

growth matrix = new Matrix(-1, 0, 0, -1, 0, 0) for top-left corner.

最后一个问题是正确计算旋转中心(即我要旋转而不是绕轨道运行).使用

The last problem is that of correctly calculating the rotation center (i.e., I want to spin, not orbit). This becomes greatly simplified by using

  // All transforms will be based on the center of the rendered element.
        AdornedElement.RenderTransformOrigin = new Point(0.5, 0.5);

最后,由于我是从角落缩放,因此需要平移图像的中心以将角落保持在光标下方.

Lastly, since I am scaling from a corner, the center of the image needs to be translated to keep the corner underneath the cursor.

很抱歉这个答案的长度,但是有很多内容要讲(并了解:)).希望这对某人有帮助.

Sorry for the length of this answer, but there is much to cover (and learn :) ). Hope this helps somebody.

这篇关于当图像未与x-y轴对齐时,对ScaleTransform(WPF)进行泛化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆