• QT 自定义QGraphicsItem 缩放后旋转 图形出现漂移问题


    实现自定义QGraphicsItem缩放和旋转时,遇到了这样一个问题:将item旋转一个角度,然后拖拽放大,再次进行旋转时图像会发生漂移。原本以为是放大后中心点位置没有改变,导致旋转时以原中心的旋转出现了偏移,但是重新设置旋转中心 setTransformOriginPoint(rect.center()); 并没有起作用,图像仍然出现漂移。通过查阅相关问题发现是item旋转后item坐标系与scene坐标系不再重叠出现的坐标换算问题。

    帮助文件中提到:

    1. 对QGraphicsItem进行变换不影响原点坐标 pos();
    2. 对QGraphicsItem进行变换不影响本地坐标系;
    3. 对QGraphicsItem进行变换顺序不同会导致最终结果不同;

    可以看出QGraphicsItem旋转后,item的坐标系也跟着旋转。QGraphicsItem旋转后再缩放,坐标变换如下图所示:

    当item放大后,pos() 在scene中的位置没有变,item的坐标系位置也没有变化。当再次旋转时,item进行重绘,重绘坐标原点仍然是原来的pos()坐标。重绘时先绘制图形,然后应用该图形的transform。而rect.center()的坐标已经不再是缩放时的center,所以会发生图形漂移。如下示意图,灰色方块为旋转前的图形,黑色方框为旋转后的图形,绿色方框为缩放后的图形,绿色方块为重绘时,放大后图形应绘制的位置,重绘后旋转时会以该方块的中心进行旋转,而旋转后与原来的位置出现偏差。

    参考文章QGraphicsItem旋转后,坐标变化机制解析,计算在原坐标系中TransformOriginPoint的实际位置可以解决旋转漂移的问题。具体计算方法比较复杂,但是我感觉有更简单的处理方法可以解决这个问题。

    解决思路:缩放后对pos()坐标进行更改,将新rect的中心点设置为新的pos(),这样当再次旋转触发重绘时,新rect的位置不会发生变化。此方法更简单,不用自己去计算新的旋转中心。

    建议:自定义QGraphicsItem时一定要将pos()设置为rect的中心点,即rect 的中心坐标为(0,0), 左topleft坐标为(-width/2, -height/2)

    部分实现代码:

    /**
     * 本示例采用的是给rect添加了调整控件,此代码是调整控件中的代码
     * from 为鼠标在scene上移动的起始位置
     * to 为鼠标移动的结束位置
     */
    void RectSelector::sizeAdjusterMove(const QPointF &from, const QPointF &to)
    {
        // 将坐标映射到item坐标系
        QPointF itemFrom = parentItem()->mapFromScene(from);
        QPointF itemTo = parentItem()->mapFromScene(to);
        QPointF moveOffset = itemTo - itemFrom;
        // 累计偏移量,图形缩放偏移小于1时不重绘
        sizeOffsetTotal += moveOffset;
        if(abs(sizeOffsetTotal.x()) < 1 && abs(sizeOffsetTotal.y()) < 1){
            return;
        }
        moveOffset = sizeOffsetTotal;
    
        AdjustPoint *point = (AdjustPoint *)sender();
        QRectF offset(0,0,0,0);
        // 判断是哪个控制点控制图形缩放,计算该控制点多图形的改变
        QPointF centerOffset(0,0);
        if (point->getId() == "topLeft") {
            offset.setTopLeft(moveOffset);
            centerOffset = moveOffset/2;
        } else if(point->getId() == "topMid"){
            offset.setTop(moveOffset.y());
            centerOffset.setY(moveOffset.y()/2);
        } else if(point->getId() == "topRight"){
            offset.setTopRight(moveOffset);
            centerOffset = moveOffset/2;
        } else if(point->getId() == "left"){
            offset.setLeft(moveOffset.x());
            centerOffset.setX(moveOffset.x()/2);
        } else if(point->getId() == "right"){
            offset.setRight(moveOffset.x());
            centerOffset.setX(moveOffset.x()/2);
        } else if(point->getId() == "bottomLeft"){
            offset.setBottomLeft(moveOffset);
            centerOffset = moveOffset/2;
        } else if(point->getId() == "bottomMid"){
            offset.setBottom(moveOffset.y());
            centerOffset.setY(moveOffset.y()/2);
        } else if(point->getId() == "bottomRight"){
            offset.setBottomRight(moveOffset);
            centerOffset = moveOffset/2;
        }
        // 更新选中框大小
        QRectF newRect = rect.adjusted(offset.left(), offset.top(), offset.right(), offset.bottom());
        if (newRect.width() <= 0 || newRect.height() <= 0){
            return;
        }
        refreshSelectRect(newRect);
        // 计算原点在scene上移动的距离
        QPointF src = parentItem()->mapToScene(0,0);
        QPointF dst = parentItem()->mapToScene(centerOffset);
        QPointF posOffset = dst - src;
        QPointF oldPos = parentItem()->pos();
        QPointF newPos = QPointF(oldPos.x() + posOffset.x(), oldPos.y() + posOffset.y());
        // 调整被控图形的pos坐标,可以保证在有旋转角度时图形位置不会跳动
        parentItem()->setPos(newPos);
        // 发出大小改变信号
        emit rectSizeChanged(offset);
        // 清空累计信息
        sizeOffsetTotal.setX(0);
        sizeOffsetTotal.setY(0);
    }
    
    void RectSelector::refreshSelectRect(const QRectF &newRect)
    {
        prepareGeometryChange();
        rect = newRect;
        update();
        // 重新定位调整点
        setSizeAdjusterPos(rect);
        setCornerAdjusterPos();
        setRotateAdjusterPos(rect);
    }
    
    /**
     * 角度旋转,from,to与sizeAdjusterMove相同
     */
    void RectSelector::rotateAdjusterMove(const QPointF &from, const QPointF &to)
    {
        // 找到原点
        QPointF origin = parentItem()->pos();
    
        emit rectRotateChanged(QLineF(origin, to).angleTo(QLineF(origin, from)));
    }
    

    参考文章:
    Qt中QTransform的translate和rotate实现过程
    QGraphicsRectItem美观实现缩放,旋转,平移
    QGraphicsItem鼠标拖动旋转(五)
    QGraphicsItem旋转后,坐标变化机制解析
    Qt:QGraphicsItem对象setPos(),setScale(),setRotation()操作后Item坐标和Scene坐标的变化

  • 相关阅读:
    统计目录下的文件数量
    使用队列集进行传递数据或信号同步
    深度优先(DFS) (例子:全排列,迷宫,pta龙龙送外卖)
    3. Caller 服务调用 - dapr
    通讯网关软件002——利用CommGate X2HTTP-U实现HTTP访问OPC UA Server
    2022-08-04 Brighthouse: An Analytic DataWarehouse for Ad-hoc Queries
    我要写整个中文互联网界最牛逼的JVM系列教程 | 「类加载子系统」章节:类的加载过程之三:Initialization
    如何编写lua扩展库
    java支持多任务之间的依赖协作关系
    c++编程实例
  • 原文地址:https://www.cnblogs.com/ITnoteforlsy/p/18073700