Qt GraphicsView如何实现完全精确的定点缩放?

您所在的位置:网站首页 如何把目录缩小一点 Qt GraphicsView如何实现完全精确的定点缩放?

Qt GraphicsView如何实现完全精确的定点缩放?

2024-07-13 09:26| 来源: 网络整理| 查看: 265

 一、前言

        目前网络博客上大多数关于Qt GraphicsView实现定点缩放的解决方法(第二节)都最高只能实现缩放中心整数级精度,直观表现为缩放过程中鼠标位置时刻改变。这在图像/图形缩放、精确取点等精度要求高的工程项目中无法使用。本博客给出一种实现缩放中心完全不变的解决方案。

二、一般方法

        Qt GraphicsView由scene和view组成,scene是一张画布,所有的图形绘制都在这上边完成;view是一个窗口,通过这个窗口观察scene上的一部分内容。在所有缩放、平移、旋转等操作中,都可以认为view不变,而scene变化。

        平移操作可以使用centerOn成员函数完成,该函数的功能是将scene上的某点平移至view的中心。

        缩放操作可以使用scale成员函数,该函数的功能是将scene放大或缩小(改变的是scene单位长度与view单位长度的比例关系,图形的scene坐标并未改变),这样view观察的区域就会相应的缩小或增大。

        理论上说,实现定点缩放可以按着以下思路完成:

        1.记录缩放中心的view位置

        2.计算缩放中心对应的scene位置

        3.计算view中心的scene位置

        4.在保证缩放中心位置不变的情况下,计算缩放后新的view中心的scene位置P

        5.调用scale进行缩放

        6.调用centerOn将P移至view中心

        具体代码如下:

void MainWindow::wheelEvent(QWheelEvent* pEvent) { //获取当前鼠标view位置; QPoint cursorViewPoint = pEvent->pos(); // 获取当前鼠标scene位置; QPointF cursorScenePos = mapToScene(cursorViewPoint ); //记录当前view中心的scene位置 QPointF vcScenePos = mapToScene(viewport()->width()/2, viewport()->height()/2); //计算新的view中心scene位置 QPointF dif =( cursorScenePos - vcScenePos ) / S; //S:缩放比例 QPointF vcScenePosNew = cursorScenePos - dif ; //缩放 scale(S,S); //定点 centerOn(vcScenePosNew ); }

        从数学上讲,上述步骤的逻辑是严密的,并没有什么问题。然而实际开发这么做是不行的,该方法只能实现缩放中心的整数级不变。原因是我们电脑屏幕的最小单位是像素,是离散的,这导致view位置的精度是整数级;而scene不受屏幕限制,位置是连续的。其结果是scene位置与view位置相互转换中出现了偏差。

        下节将具体分析。

三、整数view位置与实数scene位置的转换问题

        由于屏幕限制,view只能达到整数级精度。如果view一个单位(像素)对应scene上3个单位,那么可以想象,scene上从[0,3)的所有点都会被转换成view上的同一个点,而view上的一个点也只会被转换成scene上一个点,即使这个view点对应一个区间。为便于理解,如图所示:

         图中,左侧表示scene、右侧表示view、一个view单位对应三个scene单位。则黄色区域内的无穷个点都会被转换为红色view点;而反算时,红色view点只会被转为黄色区域中点。这就导致由view点计算对应scene点时,计算结果可能有误。因此第二节中第2、3步计算的并不是真正的scene位置。

 四、简单的解决方案

        1.记录光标的view与scene位置与视图中心的Scene位置。每次缩放时,若view位置不变,则使用上次的scene位置,不再实时计算。若view位置变化(光标移动),则更新其scene位置。缩放结束后,更新视图中心的scene位置,下次缩放时使用。

        2.缩放后,计算scene转view再转scene之间的偏差。如第3节图所示,若缩放前记录的cursor scene坐标为(1.5,1.5)(黑色点),那么由view反算得到的scene坐标为(2.5.2.5)(红色点),因此他俩的偏差为-1。那么缩放后反算view点的scene位置加上这个偏差即可。

        代码如下:

​ void MainWindow::wheelEvent(QWheelEvent* pEvent) { QPoint cursorViewPoint = pEvent->pos(); if(cursorViewPoint != cursorViewLast /*全局或成员变量*/) { cursorViewLast = cursorViewPoint ; cursorSceneLast/*全局或成员变量*/ = mapToScene(cursorViewLast); } QPointF cvSceneLastNew; //计算新的view中心scene位置 QPointF dif =( cursorScenePos - vcScenePos ) / S; //S:缩放比例 QPointF vcScenePosNew = cursorScenePos - dif ; //缩放 scale(S,S); //缩放后,反算缩放中心的view位置(缩放可能导致scene平移,此时的cuosor不再指向对应的scene 位置) QPointF curosorViewNew = mapFromScene(cursorViewPoint); //由新的view位置反算scene位置 QPointF cursorSceneNew = mapToScene(curosorViewNew.x(), curosorViewNew.y()); //缩放中心理论scene位置(上次记录的位置)与新的位置偏差 deltaCursorScene /*全局或成员变量*/= cursorSceneLast - cursorSceneNew; cvSceneLast/*全局或成员变量*/ = cvSceneLastNew; //定点 centerOn(vcScenePosNew ); } void showPos(QPoint anyViewPos) { QPointF scenePos = mapToScene(anyViewPos) + deltaCursorScene; //statusBar()->messageShow() } 五、更优的方案思路

      结果上加偏差是一种直观方法,但额外多了一步。可以从几何变换的本质出发,直接更改几何变换矩阵,这样可以与GraphicsView的坐标转换接口兼容,直接调用mapToScene即可求得正确的scene坐标。将在后续文章中给更改变换矩阵的解决方案,敬请期待...。有兴趣的小伙伴们也可自己尝试推导。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3