2

QT 自定义QGraphicsItem 缩放后旋转 图形出现漂移问题 - 永不停转

 6 months ago
source link: https://www.cnblogs.com/ITnoteforlsy/p/18073700
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

实现自定义QGraphicsItem缩放和旋转时,遇到了这样一个问题:将item旋转一个角度,然后拖拽放大,再次进行旋转时图像会发生漂移。原本以为是放大后中心点位置没有改变,导致旋转时以原中心的旋转出现了偏移,但是重新设置旋转中心 setTransformOriginPoint(rect.center()); 并没有起作用,图像仍然出现漂移。通过查阅相关问题发现是item旋转后item坐标系保持不变、原点保持不变,item重绘时先恢复未应用transform的坐标系然后再应用transform,为保证item坐标系相对item没有变化,坐标系会按照新的旋转中心进行旋转,从而导致图形发生漂移。

1596700-20240314185337372-1891176394.png

帮助文件中提到:

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

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

1596700-20240314190046382-103777659.png

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

1596700-20240314200427150-2013127135.png

参考文章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坐标的变化


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK