Qt GraphicsView如何实现完全精确的定点缩放?——一种直观的方法

 一、前言

        目前网络博客上大多数关于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坐标。将在后续文章中给更改变换矩阵的解决方案,敬请期待...。有兴趣的小伙伴们也可自己尝试推导。

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值