Qt 從 QTransform 逆向解出 Translate/Scale/Rotate(平移/縮放/旋轉)分析

永不停转發表於2024-04-22

QTransform 用於圖形繪製,它定義瞭如何平移(translate)、縮放(scale)、切變(shear)、旋轉(rotate)或投射(project)座標系。注意:QTransform 是作用於座標系,不是直接作用於圖形。實際運用中我們可以透過 QPainter 、 QGraphicsView、QGraphicsItem 實現圖形的平移、縮放、旋轉等操作,但是需要從當前圖形物件中獲取當前旋轉的角度、縮放比例時無法找到合適的函式直接獲取。有很多建議是從 QTransform 中獲取,因為我們總是能透過 transform 獲取到 QTransform ,而且QT幫助文件中也給出了對應矩陣的說明。透過實踐發現
QTransform 的矩陣說明是指執行單一變換時矩陣中每個數值的含義,當執行了一系列的平移、縮放、旋轉後,該矩陣已經經過多次運算,所包含的數值已經不是我們當時設定的值,也就是說這些值是平移、縮放、旋轉相互作用後的數值。如果想得到當初我們設定的數值,就需要經過逆向的計算。

首先,我們需要了解 QTransform 矩陣是如何運算的。為了幫助我們快速瞭解矩陣的運算過程,我設計了一個小程式,每次平移、縮放、旋轉後都會輸出 QTransform 計算後的矩陣資訊。參考專案地址

QTransform 的定義

QTransform 只是定義了一個變換矩陣,實際的圖形變換是由 QPainter 中的繪製程式來實現。QPainter 使用的標準座標系是:以左上角為原點,X 軸向右,Y 軸向下。QTransform 物件包含一個 3 x 3 矩陣。m31 (dx) 、 m32 (dy) 表示水平與垂直位移。m11 、 m22 表示水平與垂直縮放比。m21 、 m12 表示水平與垂直切變。m13 、 m23 表示水平與垂直投影, m33 是投影因子。 3 x 3 矩陣如下圖所示

平面上的一個點透過 QTransform 變換到另外一個點的計算公式如下, (x, y) 為原始點, (x', y')是變換後的點。

 x' = m11*x + m21*y + dx
 y' = m22*y + m12*x + dy
 if (!isAffine()) {
     w' = m13*x + m23*y + m33
     x' /= w'
     y' /= w'
 }

對於一個恆等矩陣(identity matrix),m11、m22、m33 的值為 1,其它值為0。恆等矩陣作用於圖形時不會產生任何變化。m12、m21 控制切變,它們會扭曲座標系。座標系的旋轉(Rotation)是透過同時設定切變(Shear)和縮放(scale)來實現的。座標系的透視(Perspective)是透過同時設定投影(projection)和縮放(scale)實現的。

很顯然,當我們設定了縮放,又設定了旋轉,m11、m22 就不再僅表示縮放,m12、m21 也不再僅表示切變。如果要得到原始值,需要經過計算。

Transform 計算與組合

1. Transform 計算

當我們執行如下程式碼時,最終的 transform 是如何計算出來的呢?

     const double a = qDegreesToRadians(45.0);
     double sina = sin(a);
     double cosa = cos(a);

     QTransform scale(0.5, 0, 0, 1.0, 0, 0);
     QTransform rotate(cosa, sina, -sina, cosa, 0, 0);
     QTransform translate(1, 0, 0, 1, 50.0, 50.0);

     QTransform transform = scale * rotate * translate;

變數 scale 表示的矩陣為:

變數 rotate 表示的矩陣為:

變數 translate 表示的矩陣為:

座標系變換執行順序如下:translate -> rotate -> scale

先計算 temp = rotate * translate:

再計算 transform = scale * temp :

注意:後執行的變換在做矩陣乘法時要放在左邊,即左乘

2. Transform 組合

座標系的旋轉會同時影響 m11 m12 m21 m22,座標系透視會同時影響 m11 m13 m22 m23 m33,所以瞭解 Transform 的組合必須要了解座標系的旋轉與透視。我們可以先從看似互不影響的操作(實際順序不同時產生影響)來看。

(1)translate 與 scale

當按照 translate -> scale 的順序變換時,transform 矩陣中的值就是我們設定的值。

當安照 scale -> translate 的順序變換時,transform 矩陣中dx dy的值受到 scale 的影響不再是我們設定的值。

這跟前面講的矩陣的運算順序有關。

(2)translate 與 rotate

當按照 translate -> rotate的順序變換時,transform 矩陣中的值就是我們設定的值。

當按照 rotate -> translate 的順序變換時,transform 矩陣中的dx dy的值受到 rotate 的影響不再是我們設定的值。

dx dy的計算如下圖所示:

(3)scale 與 rotate

當按照 scale -> rotate的順序變換時,transform 矩陣中的值的計算方式如下

當按照 rotate -> scale 的順序變換時,transform 矩陣中的值的計算方式如下

(4)shear 與 translate

當按照 translate -> shear 的順序變換時,transform 矩陣中的值的計算方式如下

當按照 shear -> translate 的順序變換時,transform 矩陣中的值的計算方式如下

(5)shear 與 scale

當按照 scale -> shear 的順序變換時,transform 矩陣中的值的計算方式如下

當按照 shear -> scale 的順序變換時,transform 矩陣中的值的計算方式如下

(6)translate scale rotate

按照 translate -> scale -> rotate 的順序變換時,transform 矩陣中的值的計算方式如下

按照 translate -> rotate -> scale 的順序變換時,transform 矩陣中的值的計算方式如下

按照 rotate -> translate -> scale 的順序變換時,transform 矩陣中的值的計算方式如下

按照 rotate -> scale -> translate 的順序變換時,transform 矩陣中的值的計算方式如下

按照 scale -> rotate -> translate 的順序變換時,transform 矩陣中的值的計算方式如下

按照 scale -> translate -> rotate 的順序變換時,transform 矩陣中的值的計算方式如下

(7)shear translate scale rotate

按照 translate -> scale -> rotate -> shear 的順序變換時,transform 矩陣中的值的計算方式如下

當 4 種變換混合在一起時,計算非常複雜,這裡不再一一列舉。可以看出當變換越多時,從transform 的矩陣中反推出原始值非常困難,如果使用過程中僅限於少數的變換,從上面的推導中是可以逆推出原始值的。另外,即使是同樣的一組變換,執行順序不同得到的結果也會不同,更直觀的可以執行上面的程式仔細觀察一下。

相關文章