十二、Qt 2D繪圖 之 座標系統

前面一節我們講解了圖片的顯示,其中很多都用到了座標的變化,這一節我們簡單講一下Qt的座標系統,其實也還是主要講上一節的那幾個函數。這裏我們先講解一下Qt的座標系,然後講解那幾個函數,它們分別是:

translate()函數,進行平移變換;scale()函數,進行比例變換;rotate()函數,進行旋轉變換;shear()函數,進行扭曲變換。

最後介紹兩個有用的函數save()和restore(),利用它們來保存和彈出座標系的狀態,從而實現快速利用幾個變換來繪圖。

一、座標系簡介。

Qt中每一個窗口都有一個座標系,默認的,窗口左上角爲座標原點,然後水平向右依次增大,水平向左依次減小,垂直向下依次增大,垂直向上依次減小。原點即爲(0,0)點,然後以像素爲單位增減。

例如:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setBrush(Qt::red);
    painter.drawRect(0,0,100,100);
    painter.setBrush(Qt::yellow);
    painter.drawRect(-50,-50,100,100);
}

我們先在原點(0,0)繪製了一個長寬都是100像素的紅色矩形,又在(-50,-50)點繪製了一個同樣大小的黃色矩形。可以看到,我們只能看到黃色矩形的一部分。效果如下圖。

二、座標系變換。

座標系變換是利用變換矩陣來進行的,我們可以利用QTransform類來設置變換矩陣,因爲一般我們不需要進行更改,所以這裏不在涉及。下面我們只是對座標系的平移,縮放,旋轉,扭曲等應用進行介紹。

1.利用translate()函數進行平移變換。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setBrush(Qt::yellow);
    painter.drawRect(0,0,50,50);

   painter.translate(100,100); //將點(100,100)設爲原點

    painter.setBrush(Qt::red);
    painter.drawRect(0,0,50,50);

   painter.translate(-100,-100);

    painter.drawLine(0,0,20,20);
}
效果如下。

這裏將(100,100)點作爲了原點,所以此時(100,100)就是(0,0)點,以前的(0,0)點就是

(-100,-100)點。要想使原來的(0,0)點重新成爲原點,就是將(-100,-100)設爲原點。

2.利用scale()函數進行比例變換,實現縮放效果。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setBrush(Qt::yellow);
    painter.drawRect(0,0,100,100);

    painter.scale(2,2); //放大兩倍

    painter.setBrush(Qt::red);
    painter.drawRect(50,50,50,50);
}
效果如下。

可以看到,painter.scale(2,2),是將橫縱座標都擴大了兩倍,現在的(50,50)點就相當於以前的

(100,100)點。

3.利用shear()函數就行扭曲變換。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setBrush(Qt::yellow);
    painter.drawRect(0,0,50,50);

    painter.shear(0,1); //縱向扭曲變形
    painter.setBrush(Qt::red);
    painter.drawRect(50,0,50,50);
}
效果如下。

這裏,painter.shear(0,1),是對縱向進行扭曲,0表示不扭曲,當將第一個0更改時就會對橫行進行扭曲,關於扭曲變換到底是什麼效果,你觀察一下是很容易發現的。

4.利用rotate()函數進行比例變換,實現縮放效果。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawLine(0,0,100,0);

    painter.rotate(30); //以原點爲中心,順時針旋轉30度
    painter.drawLine(0,0,100,0);

   painter.translate(100,100);
    painter.rotate(30);
    painter.drawLine(0,0,100,0);
}
效果如下。

因爲默認的rotate()函數是以原點爲中心進行順時針旋轉的,所以我們要想使其以其他點爲中心進行旋轉,就要先進行原點的變換。這裏的painter.translate(100,100)將(100,100)設置爲新的原點,想讓直線以其爲中心進行旋轉,可是你已經發現效果並非如此。是什麼原因呢?我們添加一條語句,如下:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawLine(0,0,100,0);

    painter.rotate(30); //以原點爲中心,順時針旋轉30度
    painter.drawLine(0,0,100,0);

   painter.rotate(-30);

    painter.translate(100,100);
    painter.rotate(30);
    painter.drawLine(0,0,100,0);
}

效果如下。

這時就是我們想要的效果了。我們加的一句代碼爲painter.rotate(-30),這是因爲前面已經將座標旋轉了30度,我們需要將其再旋轉回去,才能是以前正常的座標系統。不光這個函數如此,這裏介紹的這幾個函數均如此,所以很容易出錯。下面我們將利用兩個函數來很好的解決這個問題。

三、座標系狀態的保護。

我們可以先利用save()函數來保存座標系現在的狀態,然後進行變換操作,操作完之後,再用restore()函數將以前的座標系狀態恢復,其實就是一個入棧和出棧的操作。

例如:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.save(); //保存座標系狀態
    painter.translate(100,100);
    painter.drawLine(0,0,50,50);

    painter.restore(); //恢復以前的座標系狀態
    painter.drawLine(0,0,50,50);
}
效果如下。

利用好這兩個函數,可以實現快速的座標系切換,繪製出不同的圖形。

 

 

 

Qt座標系統深入

接着上面一節,前面只是很簡單的講解了一下Qt座標系統的概念,通過對幾個函數的應用,我們應該已經對Qt的座標系統有了一個模糊的認識。那麼現在就來讓我們更深入地研究一下Qt窗口的座標。希望大家把這一節的例子親手做一下,不要被我所說的東西搞暈了!

        我們還是在以前的工程中進行操作。

獲得座標信息:

爲了更清楚地獲得座標信息,我們這裏利用鼠標事件,讓鼠標點擊左鍵時輸出該點的座標信息。

1.在工程中的dialog.h文件中添加代碼。

添加頭文件: #include <QMouseEvent>

在public中添加函數聲明:void mousePressEvent(QMouseEvent *);

然後到dialog.cpp文件中:

添加頭文件: #include <QDebug>

定義函數:

void Dialog::mousePressEvent(QMouseEvent *event)
{
   qDebug() << event->pos();
}

這裏應用了qDebug()函數,利用該函數可以在程序運行時將程序中的一些信息輸出,在Qt Creator中會將信息輸出到其下面的Application Output窗口。這個函數很有用,在進行簡單的程序調試時,都是利用該函數進行的。我們這裏利用它將鼠標指針的座標值輸出出來。

2.然後更改重繪事件函數。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawRect(0,0,50,50);

}

我們繪製了一個左上頂點爲(0,0),寬和高都是50的矩形。

3.這時運行程序。並在繪製的矩形左上頂點點擊一下鼠標左鍵。效果如下。(點擊可看大圖)


因爲鼠標點的不夠準確,所以輸出的是(1,0),我們可以認爲左上角就是原點(0,0)點。你可以再點擊一下矩形的右下角,它的座標應該是(50,50)。這個方法掌握了以後,我們就開始研究這些座標了。

 

研究放大後的座標

1.我們現在進行放大操作,然後查看其座標的變化。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.scale(2,2);    //橫縱座標都擴大2倍
    painter.drawRect(0,0,50,50);
}

我們將橫縱座標都擴大2倍,然後運行程序,查看效果:


我們點擊矩形右下頂點,是(100,100),比以前的(50,50)擴大了2倍。

 

研究QPixmap或QImage的座標

對於QWidget,QPixmap或QImage等都是繪圖設備,我們都可以在其上利用QPainter進行繪圖。現在我們研究一下QPixmap的座標(QImage與其效果相同)。

1.我們更改重繪事件函數如下。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);
    pix.fill(Qt::red);   //背景填充爲紅色
    painter.drawPixmap(0,0,pix);
}

這裏新建了一個寬、高都是200像素的QPixmap類對象,並將其背景顏色設置爲紅色,然後從窗口的原點(0,0)點添加該QPixmap類對象。爲了表述方便,在下面我們將這個QPixmap類對象pix稱爲畫布。

我們運行程序,並在畫布的左上角和右下角分別點擊一下,效果如下:

可以看到其左上角爲(0,0)點,右下角爲(200,200)點,是沒有問題的。

2.我們再將函數更改如下。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);
    pix.fill(Qt::red);   //背景填充爲紅色
    painter.drawPixmap(100,100,pix);
}

這時我們從窗口的(100,100)點添加該畫布,那麼此時我們再點擊畫布的右上角,其座標會是多少呢?

可以看到,它是(100,100),沒錯,這是窗口上的座標,那麼這是不是畫布上的座標呢?

3.我們接着更改函數。

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);
    pix.fill(Qt::red);   //背景填充爲紅色
    QPainter pp(&pix);   //新建QPainter類對象,在pix上進行繪圖
    pp.drawLine(0,0,50,50);     //在pix上的(0,0)點和(50,50)點之間繪製直線
    painter.drawPixmap(100,100,pix);
}

這裏我們又新建了一個QPainter類對象pp,其中pp(&pix)表明,pp所進行的繪圖都是在畫布pix上進行的。

現在先說明一下:

QPainter painter(this) ,this就表明了是在窗口上進行繪圖,所以利用painter進行的繪圖都是在窗口上的,painter進行的座標變化,是變化的窗口的座標系;而利用pp進行的繪圖都是在畫布上進行的,如果它進行座標變化,就是變化的畫布的座標系。

我們在畫布上的(0,0)點和(50,50)點之間繪製了一條直線。這時運行程序,點擊這條直線的兩端,看看其座標值。

結果是直線的兩端的座標分別是(100,100),(150,150)。我們從中可以得出這樣的結論:

第一,QWidget和QPixmap各有一套座標系統,它們互不影響。可以看到,無論畫布在窗口的什麼位置,它的座標原點依然在左上角,爲(0,0)點,沒有變。

第二,我們所得到的鼠標指針的座標值是窗口提供的,不是畫布的座標。

下面我們繼續研究:

4.比較下面兩個例子。

例子一:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);

    qDebug() << pix.size();   //放大前輸出pix的大小

    pix.fill(Qt::red);   
    QPainter pp(&pix);   
    pp.scale(2,2);           //pix的座標擴大2倍
    pp.drawLine(0,0,50,50);     //在pix上的(0,0)點和(50,50)點之間繪製直線

    qDebug() << pix.size();    //放大後輸出pix的大小

    painter.drawPixmap(0,0,pix);
}

例子二:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);

    qDebug() << pix.size();   //放大前輸出pix的大小

    painter.scale(2,2);     //窗口座標擴大2倍

    pix.fill(Qt::red);
    QPainter pp(&pix);
    pp.drawLine(0,0,50,50);     //在pix上的(0,0)點和(50,50)點之間繪製直線

    qDebug() << pix.size();    //放大後輸出pix的大小

    painter.drawPixmap(0,0,pix);
}



兩個例子中都使直線的長度擴大了兩倍,但是第一個例子是擴大的畫布的座標系,第二個例子是擴大的窗口的座標系,你可以看一下它們的效果。

你仔細看一下輸出,兩個例子中畫布的大小都沒有變。

如果你看過了我寫的那個繪圖軟件的教程鏈接過去),現在你就能明白我在其中講“問題一”時說的意思了:雖然畫布看起來是大了,但是其大小並沒有變,其中座標也沒有變。變的是像素的大小或者說像素間的距離。

但是,有一點你一定要搞明白,這只是在QPixmap與QWidget結合時纔出現的,是相對的說法。其實利用scale()函數是會讓座標變化的,我們在開始的例子已經證明了。

結論:

現在是不是已經很亂了,一會兒是窗口,一會兒是畫布,一會兒座標變化,一會兒又不變了,到底是怎麼樣呢?

其實只需記住一句話:

所有的繪圖設備都有自己的座標系統,它們互不影響。

發佈了31 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章