使用矩阵放大固定点
Zoom in on a fixed point using matrices
我正在尝试使用单个全局矩阵实现关于固定点的缩放。当 运行 时,如果单击控件,它会缩放,但每次单击时测试矩形会进一步向下和向右移动。
据我所知,每个转换(到原点、缩放和返回到原始位置)单独工作都很好,但是当我将所有 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();
}
我觉得你的代码运行良好。但是当然要清楚自己想要什么!
这是您用来放大的点:
float xPos = e.Location.X - viewMatrix.OffsetX;
float yPos = e.Location.Y - viewMatrix.OffsetY;
事情就是这样。
如果您希望第一个矩形保持其位置(以原点为中心),您只需将其更改为
float xPos = - viewMatrix.OffsetX;
float yPos = - viewMatrix.OffsetY;
从而忽略鼠标点击的位置。
当你只放大一个点时可以实际上停留在相同的位置!
Update : 如果你想让那个点成为鼠标点击的位置,你所需要的只是一个使其成为新原点的翻译:
float xPos = -e.Location.X;
float yPos = -e.Location.Y;
现在,当您在正方形中间单击时,该矩形将保持固定并会围绕鼠标光标增长。
注意:减号是为了弥补你编写代码的方式。从概念上讲,您首先移动 Origin
(正),然后将其移回(负)。
更新 2:
以上代码更改仅在您不更改放大点时才有效。为了让它在任何地方都能进行一系列点击,它会涉及更多。
问题是,第一次缩放后有两个不同的视图:
- 您单击的 控件 报告鼠标位置未缩放且未翻译的点..
- ..但是 用户 看到并点击了缩放和平移的图形。
下一个翻译需要的是我们点击原始版本的地方。
为了获得这些点,我们可以保留另一个具有 reverse 转换的矩阵,将鼠标位置从 perceived 返回到 原坐标:
// 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();
}
现在我可以点击任何地方,它会在鼠标上放大。
但是为什么之前只要我们不改变点,缩放就可以在任何点上工作?因为我们点击的点没有移动所以它保持不变并且一直'valid'..
顺便说一句,我认为您不需要在 Paint
事件中保存 Graphics
状态。无论如何,它都会在每次通话中重置。 - 我的代码正在处理一个简单的 Panel
;你可以让它适应你的 OnMouseDown
..
Matrix.Multiply
有一个参数
Multiplies this Matrix by the matrix specified in the matrix parameter, by prepending the specified Matrix.
因此,您的矩阵序列正在以相反的顺序应用。
试试这个:
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);
我正在尝试使用单个全局矩阵实现关于固定点的缩放。当 运行 时,如果单击控件,它会缩放,但每次单击时测试矩形会进一步向下和向右移动。 据我所知,每个转换(到原点、缩放和返回到原始位置)单独工作都很好,但是当我将所有 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();
}
我觉得你的代码运行良好。但是当然要清楚自己想要什么!
这是您用来放大的点:
float xPos = e.Location.X - viewMatrix.OffsetX;
float yPos = e.Location.Y - viewMatrix.OffsetY;
事情就是这样。
如果您希望第一个矩形保持其位置(以原点为中心),您只需将其更改为
float xPos = - viewMatrix.OffsetX;
float yPos = - viewMatrix.OffsetY;
从而忽略鼠标点击的位置。
当你只放大一个点时可以实际上停留在相同的位置!
Update : 如果你想让那个点成为鼠标点击的位置,你所需要的只是一个使其成为新原点的翻译:
float xPos = -e.Location.X;
float yPos = -e.Location.Y;
现在,当您在正方形中间单击时,该矩形将保持固定并会围绕鼠标光标增长。
注意:减号是为了弥补你编写代码的方式。从概念上讲,您首先移动 Origin
(正),然后将其移回(负)。
更新 2:
以上代码更改仅在您不更改放大点时才有效。为了让它在任何地方都能进行一系列点击,它会涉及更多。
问题是,第一次缩放后有两个不同的视图:
- 您单击的 控件 报告鼠标位置未缩放且未翻译的点..
- ..但是 用户 看到并点击了缩放和平移的图形。
下一个翻译需要的是我们点击原始版本的地方。 为了获得这些点,我们可以保留另一个具有 reverse 转换的矩阵,将鼠标位置从 perceived 返回到 原坐标:
// 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();
}
现在我可以点击任何地方,它会在鼠标上放大。
但是为什么之前只要我们不改变点,缩放就可以在任何点上工作?因为我们点击的点没有移动所以它保持不变并且一直'valid'..
顺便说一句,我认为您不需要在 Paint
事件中保存 Graphics
状态。无论如何,它都会在每次通话中重置。 - 我的代码正在处理一个简单的 Panel
;你可以让它适应你的 OnMouseDown
..
Matrix.Multiply
有一个参数
Multiplies this Matrix by the matrix specified in the matrix parameter, by prepending the specified Matrix.
因此,您的矩阵序列正在以相反的顺序应用。
试试这个:
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);