<bdo id="4g88a"><xmp id="4g88a">
  • <legend id="4g88a"><code id="4g88a"></code></legend>

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

    實現自定義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)));
    }
    

    該代碼可以解決圖形漂移問題,但是由于縮放時在不斷調整pos()位置,導致縮放時圖形有一些閃爍,看起來不是很舒服。要解決閃爍的問題就需要在縮放時只記錄pos()的偏移量,不更改pos(),在圖形旋轉時修改pos()到實際位置,并將偏移量置空。修改后的代碼如下:

    /**
     * 本示例采用的是給rect添加了調整控件,此代碼是調整控件中的代碼
     * from 為鼠標在scene上移動的起始位置
     * to 為鼠標移動的結束位置
     */
    void RectSelector::sizeAdjusterMove(const QPointF &from, const QPointF &to)
    {
        QPointF itemFrom = parentItem()->mapFromScene(from);
        QPointF itemTo = parentItem()->mapFromScene(to);
        QPointF moveOffset = itemTo - itemFrom;
        // 累計偏移量
        sizeOffsetTotal += moveOffset;
        if(abs(sizeOffsetTotal.x()) < 1 && abs(sizeOffsetTotal.y()) < 1){
            return;
        }
        moveOffset = sizeOffsetTotal;
    
        AdjustPoint *point = (AdjustPoint *)sender();
        QRectF offset(0,0,0,0);
        if (point->getId() == "topLeft") {
            offset.setTopLeft(moveOffset);
        } else if(point->getId() == "topMid"){
            offset.setTop(moveOffset.y());
        } else if(point->getId() == "topRight"){
            offset.setTopRight(moveOffset);
        } else if(point->getId() == "left"){
            offset.setLeft(moveOffset.x());
        } else if(point->getId() == "right"){
            offset.setRight(moveOffset.x());
        } else if(point->getId() == "bottomLeft"){
            offset.setBottomLeft(moveOffset);
        } else if(point->getId() == "bottomMid"){
            offset.setBottom(moveOffset.y());
        } else if(point->getId() == "bottomRight"){
            offset.setBottomRight(moveOffset);
        }
        // 更新選中框大小
        QRectF newRect = rect.adjusted(offset.left(), offset.top(), offset.right(), offset.bottom());
        if (newRect.width() <= 0 || newRect.height() <= 0){
            return;
        }
        refreshSelectRect(newRect);
    
        // 發出大小改變信號
        emit rectSizeChanged(offset);
        // 清空累計信息
        sizeOffsetTotal.setX(0);
        sizeOffsetTotal.setY(0);
    }
    
    /**
     * 創建矩形選擇控制框
     */
    void RectItem::createSelectRect()
    {
        selector = new RectSelector(getPaintRect(), borderWidth, this);
        connect(selector, SIGNAL(rectSizeChanged(QRectF)), this, SLOT(sizeChanged(QRectF)));
        if (rounded){
            selector->showCornerAdjuster(true);
            selector->setRadius(arcSize);
            connect(selector, SIGNAL(rectCornerChanged(qreal)), this, SLOT(cornerChanged(qreal)));
        }
        connect(selector, SIGNAL(rectRotateChanged(qreal)), this, SLOT(rotateChanged(qreal)));
    }
    
    /**
     * 在自定義矩形大小改變時記錄新中心點的偏移量
     * @param offsetValue 矩形4個邊的偏移量
     */
    void RectItem::sizeChanged(QRectF offsetValue)
    {
        // 通知 scene item 大小即將改變
        prepareGeometryChange();
        rect.adjust(offsetValue.left(), offsetValue.top(),
                    offsetValue.right(), offsetValue.bottom());
    
        centerOffset = mapToParent(rect.center()) - pos();
    
    }
    /**
     * 重新設定矩形位置
     */
    void RectItem::redefineRect()
    {
        // 定義矩形位置,保持(0,0) 為矩形中心位置
        QPointF pt(rect.width()/2, rect.height()/2);
        rect.setTopLeft(-pt);
        rect.setBottomRight(pt);
        // 更新控制點位置
        if(!selector.isNull()){
            selector->refreshSelectRect(rect);
        }
    }
    
    /**
     * 響應矩形旋轉事件
     * @param offsetValue 角度偏移量
     */
    void RectItem::rotateChanged(qreal offsetValue)
    {
        qreal oldAngle = rotation();
        qreal angle = oldAngle + offsetValue;
        if (angle > 360){
            angle -= 360;
        }
        if (angle < -360){
            angle += 360;
        }
    
        prepareGeometryChange();
        // 發現中心偏移時,調整中心位置
        if(centerOffset.x() != 0 || centerOffset.y() != 0){
            // 調整中心點位置
            setPos(centerOffset + pos());
            // 調整矩形位置
            redefineRect();
            // 偏移量置空
            centerOffset.setX(0);
            centerOffset.setY(0);
        }
    
        setRotation(angle);
    }
    

    參考文章:
    Qt中QTransform的translate和rotate實現過程
    QGraphicsRectItem美觀實現縮放,旋轉,平移
    QGraphicsItem鼠標拖動旋轉(五)
    QGraphicsItem旋轉后,坐標變化機制解析
    Qt:QGraphicsItem對象setPos(),setScale(),setRotation()操作后Item坐標和Scene坐標的變化

    posted @ 2024-03-14 20:30  永不停轉  閱讀(110)  評論(0編輯  收藏  舉報
    免费视频精品一区二区_日韩一区二区三区精品_aaa在线观看免费完整版_世界一级真人片
    <bdo id="4g88a"><xmp id="4g88a">
  • <legend id="4g88a"><code id="4g88a"></code></legend>