【问题标题】:Zoom in on a fixed point using matrices使用矩阵放大固定点
【发布时间】:2015-03-08 10:22:14
【问题描述】:

我正在尝试使用单个全局矩阵实现关于固定点的缩放。运行时,如果单击控件,它会缩放,但每次单击时测试矩形会进一步向下和向右移动。 据我所知,每个转换(到原点、比例和返回到原始位置)单独工作正常,但是当我将所有 3 个转换在一起时,我没有得到正确的行为。

缩放代码

当点击控件时,代码(应该)转换为原点,放大一个因子,然后转换回原始位置。

protected override void OnMouseDown(MouseEventArgs e)
      {
         base.OnMouseDown(e);
         if (e.Button == System.Windows.Forms.MouseButtons.Left)
         {
            float xPos = e.Location.X - viewMatrix.OffsetX;
            float yPos = e.Location.Y - viewMatrix.OffsetY;

            Matrix translateOrigin = new Matrix(1, 0, 0, 1, -xPos, -yPos);
            Matrix translateBack = new Matrix(1, 0, 0, 1, xPos, yPos);
            Matrix scaleMatrix = new Matrix(1.5f, 0, 0, 1.5f, 0, 0);

            viewMatrix.Multiply(translateOrigin);
            viewMatrix.Multiply(scaleMatrix);
            viewMatrix.Multiply(translateBack);
         }
         else
         {
            viewMatrix = new Matrix();
         }
         Refresh();
      }

绘图代码

这是我用来绘制的代码。这两个矩形仅供参考,new Pen(2) 上的第二个值是为了确保我的线条保持 1 像素宽。

protected override void OnPaint(PaintEventArgs e)
      {
         base.OnPaint(e);

         GraphicsState gState = e.Graphics.Save();

         e.Graphics.MultiplyTransform(viewMatrix);
         e.Graphics.DrawRectangle(new Pen(Color.Pink, 1.0f / viewMatrix.Elements[3]), -5, -5, 10, 10);
         e.Graphics.DrawRectangle(new Pen(Color.Pink, 1.0f / viewMatrix.Elements[3]), 20, 20, 10, 10);

         e.Graphics.Restore(gState);
      }

编辑

在休息了一天(或两天)之后再次查看代码,我意识到我脑子里有一个错误的想法(这就是我在一天结束时试图弄清楚这一点的结果)。我正在寻找的行为是视图将随着单击点保持在同一位置而缩放。例如,如果我单击其中一个矩形的右下角,则视图将对其进行缩放,同时将右下角保持在鼠标下方。

编辑 2

在@TaW 的大量帮助下,我得到了以下代码,该代码将缩放并保持鼠标下的点固定。

protected override void OnMouseDown(MouseEventArgs e)
{
 base.OnMouseDown(e);

 if (e.Button == System.Windows.Forms.MouseButtons.Left)
 {

    //Get the inverse of the view matrix so that we can transform the mouse point into the view
    Matrix viewMatrixRev = viewMatrix.Clone();
    viewMatrixRev.Invert();

    //Translate the mouse point
    PointF mousePoint = e.Location;
    viewMatrixRev.TransformPoints(new PointF[] { mousePoint });

    //Transform the view
    viewMatrix.Translate(-mousePoint.X, -mousePoint.Y, MatrixOrder.Append);
    viewMatrix.Scale(zoom, zoom, MatrixOrder.Append);
    viewMatrix.Translate(mousePoint.X, mousePoint.Y, MatrixOrder.Append);
 }
 else
 {
    viewMatrix = new Matrix();
 }
 Refresh();
}

【问题讨论】:

  • 您是否真的尝试测试过这个viewMatrixRev.TransformPoints(new PointF[] { mousePoint }); 部分??这是对我的代码的简化,但我无法让它工作;结构是值类型,在转换数组中放置一个似乎只是创建一个副本,然后被丢弃..???花了我一个小时才知道..
  • 嗯....我现在更困惑了。它确实对我有用,但你是对的,如果我单步执行代码 mousePoint 在调用转换后不会改变。所以我尝试了一些东西并完全删除了翻译mousePoint的代码,它也能正常工作
  • 嘿嘿,也许再睡个好觉?
  • 我意识到这就是 Ripple 想要表达的意思。不知何故,我一定是搞砸了他/她的解决方案,并认为它不起作用
  • @amura.cxg 很高兴您完成了目标。绘制的对象和鼠标光标在同一个坐标系中,所以不需要变换鼠标坐标。这些事情对我来说总是很难解释。对不起,如果我的英语很差而且没有解释让你感到困惑。

标签: c# winforms matrix


【解决方案1】:

您的代码运行良好 imo。不过当然要清楚自己想要什么!

这是您用来放大的点:

float xPos = e.Location.X - viewMatrix.OffsetX;
float yPos = e.Location.Y - viewMatrix.OffsetY;

这就是发生的事情。

如果您希望第一个矩形保持其位置(以原点为中心),您只需将其更改为

float xPos = - viewMatrix.OffsetX;
float yPos = - viewMatrix.OffsetY;    

从而忽略鼠标点击的位置。

当你放大一个可以实际上停留在相同的位置!

更新:如果您希望该点成为鼠标点击位置,您所需要的只是使其成为新起点的翻译:

float xPos = -e.Location.X;
float yPos = -e.Location.Y;

现在,当您单击一个正方形的中间时,该矩形将保持固定状态,并会在鼠标光标周围增长..

注意:减号用于弥补您编写代码的方式。从概念上讲,您首先移动 Origin(正),然后将其移回(负)。

更新 2

上述代码更改仅在您不更改放大点时才有效。为了让它在涉及更多的地方进行一系列点击。

问题是,第一次缩放后有两个不同的视图:

  • 您单击的控件报告鼠标位置的未缩放和未平移点..
  • ..但是用户看到并点击了缩放和翻译的图形。

下一次翻译需要的是我们点击原始版本的点。 为了获得这些点,我们可以保留另一个矩阵,该矩阵具有将鼠标位置从感知回到原始坐标所需的反向转换:

// make the zoom factor accessible
float zoom = 1.5f;
// the graphics transformation
Matrix viewMatrix = new Matrix();
// the reverse transformation for the mouse point
Matrix viewMatrixRev = new Matrix();

private void panel1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        // first we reverse translate the point
        // we need an array!
        PointF[] tv = new PointF[] { e.Location };
        viewMatrixRev.TransformPoints(tv);
        // after the reversal we can use the coordinates
        float xPos = tv[0].X;
        float yPos = tv[0].Y;

        // revers translation for the point
        Matrix scaleMatrixRev = new Matrix(1f / zoom, 0, 0, 1f / zoom, 0, 0);
        // the other transformations
        Matrix scaleMatrix = new Matrix(zoom, 0, 0, zoom, 0, 0);
        Matrix translateOrigin = new Matrix(1, 0, 0, 1, xPos, yPos);
        Matrix translateBack = new Matrix(1, 0, 0, 1, -xPos, -yPos);

        // we need two different orders, not sure yet why(?)
        MatrixOrder moP = MatrixOrder.Prepend;
        MatrixOrder moA = MatrixOrder.Append;

        // graphics transfomation
        viewMatrix.Multiply(translateOrigin, moP );
        viewMatrix.Multiply(scaleMatrix, moP ); 
        viewMatrix.Multiply(translateBack, moP ); 

        // store the next point reversal:
        viewMatrixRev.Multiply(translateBack, moA); 
        viewMatrixRev.Multiply(scaleMatrixRev, moA); 
        viewMatrixRev.Multiply(translateOrigin, moA); 

    }
    else
    {
        // reset
        viewMatrix = new Matrix();
        viewMatrixRev = new Matrix();
    }
    panel1.Invalidate();
}

现在我可以点击任何地方,它会放大鼠标。

但是,只要我们不改变点,为什么缩放之前在任何点上都有效?因为我们点击的点没有被移动,所以它一直保持不变和“有效”..

顺便说一句,我认为您不需要在 Paint 事件中保存 Graphics 状态。无论如何,它在每次调用中都会重置。 - 我的代码正在处理一个简单的Panel;你可以适应你的OnMouseDown..

【讨论】:

  • 感谢@TaW,在清醒地回到这个问题之后,你是对的,它正在按应有的方式工作。但是我想要不同的行为。我已经更新了我的问题以(希望)解释我正在寻找的行为。
  • 谢谢!这更接近我正在寻找的行为,但是如果您第一次单击说第二个矩形的中心,然后再次单击同一矩形的左下角,它不会在左下角附近放大(它会稍微放大远离那个点)。这只是我尝试实现它的方式的一个缺陷吗?
  • 你是对的。我花了一段时间才弄明白(我仍然不太清楚 MatrixOrder!),但现在它对我有用。 - 看看更新的答案..!
  • 我什至没有考虑使用视图矩阵的反转,我尝试使用它自己的视图矩阵,但显然这不起作用。谢谢你的帮助,我真的很感激!另外,我在那里有图形状态的东西,因为这只是一个测试应用程序,希望代码可以在其他地方使用,并且重置状态可能很重要,所以我把它留在了。
  • 我发生了什么事,您可以使用 viewMatrix 的倒数将这些点转换回来,而不是保留单独的 viewMatrixRev。我使用viewMatrix 的反函数尝试了代码,它的行为与您的代码相同。无论如何,非常感谢您的帮助,因为我会一直带我去弄清楚我自己:p
【解决方案2】:

Matrix.Multiply 带一个参数

将此矩阵乘以在矩阵参数中指定的矩阵,通过前置指定的矩阵。

因此,您的矩阵序列正在以相反的顺序应用。

试试这个:

viewMatrix.Multiply(translateOrigin, MatrixOrder.Append);
viewMatrix.Multiply(scaleMatrix, MatrixOrder.Append);
viewMatrix.Multiply(translateBack, MatrixOrder.Append);

编辑:
这个想法很简单。

您需要做的就是以正确的顺序平移到原点、缩放并平移回枢轴(鼠标点)。 您的 viewMatrix 保留了之前的结果,因此应该在 之后应用新的转换矩阵,这将由 MatrixOrder.Append 完成。

现在的解决方案是:

float xPos = e.Location.X;
float yPos = e.Location.Y;

Matrix translateOrigin = new Matrix(1, 0, 0, 1, -xPos, -yPos);
Matrix translateBack = new Matrix(1, 0, 0, 1, xPos, yPos);
Matrix scaleMatrix = new Matrix(1.5f, 0, 0, 1.5f, 0, 0);

viewMatrix.Multiply(translateOrigin, MatrixOrder.Append);
viewMatrix.Multiply(scaleMatrix, MatrixOrder.Append);
viewMatrix.Multiply(translateBack, MatrixOrder.Append);

此外,这可以更简单地完成。

float xPos = e.Location.X;
float yPos = e.Location.Y;

viewMatrix.Translate(-xPos, -yPos, MatrixOrder.Append);
viewMatrix.Scale(1.5f, 1.5f, MatrixOrder.Append);
viewMatrix.Translate(xPos, yPos, MatrixOrder.Append);

【讨论】:

  • 感谢您的回答,您的回答对于问题的原始版本是正确的。然而,TaW 让我意识到我正在寻找的行为实际上并不是围绕固定点缩放,而是缩放并保持当前点固定。
  • 我喜欢你最后建议的简化版本。我将在我的最终解决方案中实现这一点,因为它减少了很多额外的工作。再次感谢!
猜你喜欢
  • 2013-02-13
  • 2021-04-28
  • 2021-12-23
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 2019-01-11
  • 2016-10-02
相关资源
最近更新 更多