Qt繪圖(使用QPainter)翻轉圖像的兩種方法


我想要創造一個小人,它可以向四個方向走。我用定時器實現了繪圖的循環執行,並從這個圖片中把各個幀裁切下來並畫出來。

但是,我發現小人的行走動畫是向右的。當小人向左走的時候就非常不自然。我想要在這種時候把圖像翻轉。

於是我嘗試着在QPainter中找到一個flip函數——結果居然沒有!翻轉這樣一個非常常用的功能居然沒有!

方法一

沒有的話,我們就只能自己實現了。我所熟悉的對坐標系進行變形的函數四個:

void shear(qreal sh, qreal sv);
void scale(qreal sx, qreal sy);
void rotate(qreal angle);
void translate(qreal dx, qreal dy)

其中最麻煩的是剪切函數shear。經過探索,它對於右手系的改變相當於乘上一個矩陣:(使用笛卡爾坐標系而不是默認坐標系)

\[\left[ \begin{array}{l} 1 &-sh\\ -sv &1 \end{array} \right] \]

而對於左手系的改變則是:

\[\left[ \begin{array}{l} 1 & sh\\ sv &1 \end{array} \right] \]

這是四個函數中唯一一個可以改變坐標系兩個基底的夾角的函數。因為翻轉需要我們改變整個坐標系的手性,所以這個函數是必不可少的。

對於放縮函數scale,顯然它對坐標系的改變相當於乘上一個矩陣:

\[\left[ \begin{array}{l} sx &0 \\ 0 & sy \end{array} \right] \]

要是sx可以為負數,我的問題其實就已經解決了。但是這個函數不支持負數。

對於旋轉函數rotate,它對坐標系的改變相當於乘上一個矩陣:

\[\left[ \begin{array}{l} cos \alpha & -sin \alpha \\ sin \alpha & cos \alpha \end{array} \right] \]

translate的作用是改變原點位置,不是對坐標系進行線性變換,所以與矩陣無關。

於是下面就是數學推導。把初始兩個基底向量\((1, 0)\)\((0, 1)\)變成豎向,放在矩陣里(參考3b1b的線性代數相關視頻):

\[\left[ \begin{array}{l} 1 & 0 \\ 0 & 1 \end{array} \right] \]

不妨進行shear(2, 2)

\[\left[ \begin{array}{l} 1 & -2\\ -2 & 1 \end{array} \right] \]

此時坐標系就已經變成左手系了。還需要進行一些轉換。進行rotate(-90)

\[\left[ \begin{array}{l} -2 & -1\\ 1 & 2 \end{array} \right] \]

記它為矩陣\(A\)

我們的目標是水平翻轉,所以目標坐標系的兩個基底向量是\((-1, 0)\)\((0, 1)\)。所以現在我們要找到一個矩陣\(X\),使得\(X \cdot A = \left[\begin{array}{l}-1&0\\0&1\end{array}\right]\)

求出\(A\)的逆元為:

\[A^{-1}= \left[ \begin{array}{l} -\frac{2}{3} & -\frac{1}{3}\\ \frac{1}{3} & \frac{2}{3} \end{array} \right] \]

所以可得

\[X = \left[\begin{array}{l}-1&0\\0&1\end{array}\right] \cdot A^{-1} = \left[ \begin{array}{l} \frac{2}{3} & \frac{1}{3}\\ \frac{1}{3} & \frac{2}{3} \end{array} \right] = \left[ \begin{array}{l} \frac{2}{3} & 0\\ 0 & \frac{2}{3} \end{array} \right] \cdot \left[ \begin{array}{l} 1 & \frac{1}{2}\\ \frac{1}{2} & 1 \end{array} \right] \]

所以再分別進行一次shear(0.5, 0.5)scale(2.0/3, 2.0/3)就可以完成翻轉了。

綜上所述,要在一個以\((cx,cy)\)為左上角的區域內輸出一個寬wid的被水平翻轉的QPixmap變量frame,使用以下代碼:

void drawPixmapFlippedHorizentally(QPainter &qpainter, int cx, int cy, int wid, const QPixmap &frame) {
        qpainter.translate(cx + wid - 1, cy);
        qpainter.shear(2, 2);
        qpainter.rotate(-90);
        qpainter.shear(0.5, 0.5);
        qpainter.scale(2.0/3, 2.0/3);

        qpainter.drawPixmap(QPoint(0, 0), frame);

        qpainter.shear(2, 2);
        qpainter.rotate(-90);
        qpainter.shear(0.5, 0.5);
        qpainter.scale(2.0/3, 2.0/3);
        qpainter.translate(-cx - wid + 1, -cy);
}

打包成一個函數就可以了。真是不容易啊。

方法二

使用QPainter自帶的setViewport函數,對展示的坐標系進行轉換。

void setViewport(int x, int y, int width, int height)

作用是把畫布轉換為以\((x,y)\)為原點,\(width\)為寬,\(height\)為高的畫布。

原始的畫布的原點是\((0,0)\),寬是\(width()\),高是\(height()\)(注:這兩個函數返回的是窗口的寬和高)。所以,執行函數qpainter.setViewport(0, 0, width(), height())就等於把畫布還原到默認狀態。

這個函數也能實現坐標系的變換。改變原點位置與矩陣無關,故只有后面的兩個參數對坐標系需要納入考慮。相當於下面這個矩陣:

\[\left[ \begin{array}{l} \frac{width}{width()} & 0\\ 0 & \frac{height}{height()} \end{array} \right] \]

其中\(width\)\(height\)是函數的參數,而\(width()\)\(height()\)則是窗口的大小。

所以,只需要以下代碼就可以輸出一個翻轉的圖像:

void drawPixmapFlippedHorizentally(QPainter &qpainter, int cx, int cy, int wid, const QPixmap &frame) {
        qpainter.setViewport(cx + wid - 1, cy, -width(), height());
        qpainter.drawPixmap(QPoint(0, 0), frame);
        qpainter.setViewport(0, 0, width(), height());
}

這是我第一次在生活實踐中大量使用線性代數的知識。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM