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