试着想有这么一个场景,当你滚动滚轮时,图像会以你的鼠标中心为缩放中心进行缩放


代码很简单,就是在缩放时,获取鼠标对元素的相对坐标,调用ScaleAt,然后添加到它现有的RenderTransform

var position = e.GetPosition(TestGrid);
var scale = 1 + e.Delta / (double)Math.Abs(e.Delta) * 0.1;     
var matrix = TestGrid.RenderTransform.Value;
matrix.ScaleAt(scale, scale, position.X, position.Y);
TestGrid.RenderTransform = new MatrixTransform(matrix);

然而结果却出现了偏差,除了开始的几次正常之外,后续的行为都不正常。

意外的,只要将matrix.ScaleAt(scale, scale, position.X, position.Y);改为

matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);就能正常使用。

我们都知道ScaleAtPrepend意味着矩阵左乘。

那为什么叠加不是右乘而是左乘呢?

再仔细看实际上,错误原因出在ScaleAtPrependScaleAt都是以RenderTransform之前的位置坐标进行的缩放,而我们期望的GetPosition(TestGrid)却是以RenderTransform之后的坐标。

了解了原因,我们只需要将position乘以现有的矩阵就可以了

var position = e.GetPosition(TestGrid);
var scale = 1 + e.Delta / (double)Math.Abs(e.Delta) * 0.1;     
var matrix = TestGrid.RenderTransform.Value;
var position2 = position * matrix;
matrix.ScaleAt(scale, scale, position2.X, position2.Y);
TestGrid.RenderTransform = new MatrixTransform(matrix);

现在也符合期望了。

但是我们还有一个问题,为什么以原来”错误”的坐标进行左乘也能得到正确的结果呢?

我做一个推导,假设$M$为原有的变化矩阵,$P_0$为变化前的点,$P_1$为变化后的点,

那么有$P_1=P_0M$

令$S_{p_0}$表示以$P_0$为中心的缩放矩阵,$S_{p_1}$表示以$P_1$为中心的缩放矩阵

那么对于任意点$P_n$

有$\begin{array}{lr} P_nMS_{P_1}=(P_nM+P_1)S-P_1\=(P_nM+P_0M)S-P_0M\=((P_n+P_0)S-P_0)M\=P_nS_{P_0}M\end{array} $

得证两种方式的结果是一致的。

参考项目仓库 https://github.com/xinyuehtx/ScaleWithPointer


本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/%E5%9C%A8RenderTransform%E4%B8%8A%E5%8F%A0%E5%8A%A0%E4%B8%80%E4%B8%AAScaleAt.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系