QT 自定義QGraphicsItem 縮放後旋轉 圖形出現漂移問題

永不停转發表於2024-03-14

實現自定義QGraphicsItem縮放和旋轉時,遇到了這樣一個問題:將item旋轉一個角度,然後拖拽放大,再次進行旋轉時影像會發生漂移。原本以為是放大後中心點位置沒有改變,導致旋轉時以原中心的旋轉出現了偏移,但是重新設定旋轉中心 setTransformOriginPoint(rect.center()); 並沒有起作用,影像仍然出現漂移。透過查閱相關問題發現是item旋轉後item座標系保持不變、原點保持不變,item重繪時先恢復未應用transform的座標系然後再應用transform,為保證item座標系相對item沒有變化,座標系會按照新的旋轉中心進行旋轉,從而導致圖形發生漂移。

幫助檔案中提到:

  1. 對QGraphicsItem進行變換不影響原點座標 pos();
  2. 對QGraphicsItem進行變換不影響本地座標系;
  3. 對QGraphicsItem進行變換順序不同會導致最終結果不同;

可以看出QGraphicsItem旋轉後,item的座標系也跟著旋轉。QGraphicsItem旋轉後再縮放,座標變換如下圖所示:

當item放大後,pos() 在scene中的位置沒有變,item的座標系位置相對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座標的變化

相關文章