试着想有这么一个场景,当你滚动滚轮时,图像会以你的鼠标中心为缩放中心进行缩放
代码很简单,就是在缩放时,获取鼠标对元素的相对坐标,调用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
意味着矩阵左乘。
那为什么叠加不是右乘而是左乘呢?
再仔细看实际上,错误原因出在ScaleAtPrepend
和ScaleAt
都是以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 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。