閱讀537 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[Qt教程] 第16篇 2D繪圖(六)坐標係統

[Qt教程] 第16篇 2D繪圖(六)坐標係統

樓主
 發表於 2013-5-2 20:08:12 | 查看: 738| 回複: 0
坐標係統
版權聲明

該文章原創於Qter開源社區(www.qter.org),作者yafeilinux,轉載請注明出處!


導語

前麵一節我們講解了圖片的顯示,其中很多地方都用到了坐標的變化。這一節我們將講解Qt的坐標係統,分為兩部分來講解:第一部分主要講解前麵一節的那幾個函數,它們分別是translate()平移變換、scale()比例變換、rotate()旋轉變換、shear()扭曲變換。最後還會介紹兩個有用的函數save()和restore(),利用它們來保存和彈出坐標係的狀態,從而實現快速利用幾個變換函數來繪圖。
       第二部分會和大家一起來研究一下Qt的坐標係統,其中可能會涉及到多個坐標,大家一定要親自動手操作感悟一下,不然很難理解的!

環境:Windows Xp + Qt 4.8.4+Qt Creator2.6.2


目錄


第一部分 Qt坐標係統應用
一、坐標係統簡介
二、坐標係統變換
三、坐標係統的保存


第二部分 坐標係統深入研究
一、獲得坐標信息
二、研究變換後的坐標係統
三、研究繪圖設備的坐標係統






正文


第一部分 Qt坐標係統應用


一、坐標係統簡介
    Qt中每一個窗口都有一個坐標係統,默認的,窗口左上角為坐標原點,水平向右依次增大,水平向左依次減小,垂直向下依次增大,垂直向上依次減小。原點即為(0,0)點,以像素為單位增減。
下麵仍然在上一節的程序中進行代碼演示,更改paintEvent()的內容如下:


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)點繪製了一個同樣大小的**矩形。可以看到,我們隻能看到**矩形的四分之一部分。運行程序,效果如下圖所示。
16-1.jpg



二、坐標係統變換

默認的,QPainter在相關設備的坐標係統上進行繪製,在進行繪圖時,可以使用QPainter::scale()函數縮放坐標係統;使用QPainter::rotate()函數順時針旋轉坐標係統;使用QPainter::translate()函數平移坐標係統;還可以使用QPainter::shear()圍繞原點來扭曲坐標係統。如下圖所示。
16-2.jpg

       坐標係統的2D變換由QTransform類實現,我們可以使用前麵提到的那些便捷函數進行坐標係統變換,當然也可以通過QTransform類實現。

1.平移變換。將paintEvent()函數內容更改如下:


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);
}
       運行程序,效果如下圖所示。
16-3.jpg

       這裏先在原點(0, 0)繪製了一個寬、高均為50的正方形,然後使用translate()函數將坐標係統進行了平移,使(100, 100)點成為了新原點,所以我們再次進行繪製的時候,雖然drawRect()中的邏輯坐標還是(0, 0)點,但實際顯示出來的卻是在(100, 100)點的紅色正方形。可以再次使用translate()函數進行反向平移,使原點重新回到窗口左上角。


2.縮放變換。將paintEvent()函數中的內容更改如下:

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);
}
       運行程序,效果如下圖所示。
16-4.jpg

       可以看到,當我們使用scale()函數將坐標係統的橫、縱坐標都放大兩倍以後,邏輯上的(50 50)點變成了窗口上的(100, 100)點,而邏輯上的長度50,繪製到窗口上的長度卻是100



3.扭曲變換。將paintEvent()函數更改如下:

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);
}

       運行程序,效果如下圖所示。
16-5.jpg

       shear()有兩個參數,第一個是對橫向進行扭曲,第二個是對縱向進行扭曲,而取值就是扭曲的程度。比如程序中對縱向扭曲值為1,那麼就是紅色正方形左邊的邊下移一個單位,右邊的邊下移兩個單位,值為1就表明右邊的邊比左邊的邊多下移一個單位。大家可以更改取值,測試效果。



4.旋轉變換。將paintEvent()函數更改如下:

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);
}
       運行程序,效果如下圖所示。
16-6.jpg

       這裏先繪製了一條水平的直線,然後將坐標係統旋轉了30度,又繪製了一條直線。可以看到,默認是以原點(0, 0)為中心旋轉的。如果想改變旋轉中心,可以使用translate()函數,比如這裏將中心移動到了(100, 100)點,然後旋轉了30度,又繪製了一條直線。我們的本意是想在新的原點從水平方向旋轉30度進行繪製,可是實際效果卻超過了30度。這是由於前麵已經使用rotate()函數旋轉過坐標係統了,後麵的旋轉會在前麵的基礎上進行。

       下麵我們再次更改paintEvent()函數:


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);
}
       運行程序,效果如下圖所示。
16-7.jpg

       這次我們在移動原點以前先將坐標係統反向旋轉,可以看到,第二次旋轉也是從水平方向開始的。
       其實,前麵講到的這幾個變換函數都是如此,他們改變了坐標係統以後,如果不進行逆向操作,坐標係統是無法自動複原的。針對這個問題,下麵我們將講解兩個非常實用的函數來實現坐標係統的保存和還原。


三、坐標係統的保存


我們可以先利用save()函數來保存坐標係現在的狀態,然後進行變換操作,操作完之後,再用restore()函數將以前的坐標係狀態恢複,其實就是一個入棧和出棧的操作。下麵來看一個具體的例子,更改paintEvent()函數如下:


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);
}
       運行程序,效果如下圖所示。利用好這兩個函數,可以實現坐標係快速切換,繪製出不同的圖形。
16-8.jpg




第二部分 坐標係統深入研究


       在第一部分,我們主要學習了常用的一些坐標變換,雖然在編程中,這些變換已經可以滿足大部分的應用需求。不過,大家是否也感覺到現在對坐標的變換依然很模煳,沒有一個透徹的認識。下麵咱們就一點一點來研究一下坐標係統的變換。



一、獲得坐標信息

       前麵圖形的變換都是我們眼睛看到的,為了更具有說服力,下麵將獲取具體的坐標數據,通過參考數據來進一步了解坐標變換。

1.首先在dialog.h文件中添加頭文件包含:


#include <QMouseEvent>

然後添加一個protected鼠標事件處理函數聲明:


void mousePressEvent(QMouseEvent *);


2.dialog.cpp文件中,先添加頭文件包含:


#include <QDebug>


然後添加函數定義:


void Dialog::mousePressEvent(QMouseEvent *event)
{    
         qDebug() << event->pos();
}
       這裏應用了qDebug()函數,該函數可以在程序運行時將程序中的一些信息輸出到控製麵板,在QtCreator中會將信息輸出到其下麵的“應用程序輸出”窗口。這個函數很有用,在進行簡單的程序調試時,都可以利用該函數進行。我們這裏利用它將鼠標指針的坐標值輸出出來。


3.paintEvent()函數更改如下:


void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawRect(0, 0, 50, 50);
}
       現在運行程序,然後將鼠標在繪製的正方形右下角頂點處點擊,在QtCreator的應用程序輸出窗口就會輸出相應點的坐標信息。如下圖所示。大家也可以點擊一下其他的地方,查看輸出信息。
16-9.jpg



二、研究變換後的坐標係統


1.首先研究放大後的坐標係統,將paintEvent()函數更改如下:

void Dialog::paintEvent(QPaintEvent *)
{
    // 放大
    QPainter painter(this);
    painter.drawRect(0, 0, 50, 50);
    painter.scale(1, 2);
    painter.drawRect(50, 50, 50, 50);
}
       這裏,我們將縱坐標放大了兩倍,而橫坐標沒有改變。運行程序,效果如下圖所示。
16-10.jpg

       大家可以查看一下第二個矩形的各個頂點的坐標,左上角是(50, 100)也就是說縱坐標擴大了兩倍,查看其它點,會發現左右兩條邊長都變成了100


2.研究旋轉後的坐標係統。修改paintEvent()函數如下:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawLine(0, 0, 100, 0);
    painter.rotate(45);
    painter.setPen(Qt::red);
    painter.drawLine(0, 0, 100, 0);
}
       這裏我們先繪製了一條水平的直線,然後將坐標係統旋轉45度,再次繪製了一條相同的紅色直線。運行程序,效果如下圖所示。
16-11.jpg

大家可以查看一下各處的坐標,雖然旋轉後直線位置發生了變化,但是坐標其實是沒有變化的。
我們也可以利用這種方法來測試一下應用其他變換函數後坐標的變化,這裏就不再敖述。


三、研究繪圖設備的坐標係統


       除了可以在QWidget等窗口部件上進行繪製以外,還可以在QPixmapQImage等上麵進行繪製,這些均稱為繪圖設備。下麵我們就以QPixmap為例,來研究一下它的坐標係統。


1.首先更改paintEvent()函數如下:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200, 200);
    pix.fill(Qt::red);   //背景填充為紅色
    painter.drawPixmap(0, 0, pix);
}
    在前麵我們已經講過,QPixmap可以用來顯示圖片。其實QPixmap本身就是一個繪圖設備,可以在它上麵直接繪圖。這裏先生成了一個寬和高都是200像素的QPixmap類對象(注意,必須在構建時指定其大小),然後為其填充了紅色,最後在窗口的原點進行了繪製。為了表述方麵,下麵將QPixmap對象稱為畫布,這裏就是先繪製了一個紅色畫布。
    我們運行程序,並在紅色畫布的左上角和右下角分別點擊,查看輸出的坐標。如下圖所示。因為點擊位置的誤差,所以兩個點可能不是頂點。
16-12.jpg



2.下麵我們接著更改paintEvent()的代碼:


void Dialog::paintEvent(QPaintEvent *)
{    
      QPainter painter(this);    
      QPixmap pix(200, 200);    
      pix.fill(Qt::red);   //背景填充為紅色    
      painter.drawPixmap(100, 100, pix);
}
       這次我們在(100, 100)點重新繪製了畫布,現在運行程序,發現畫布左上角坐標確實為(100,100),這個就是我們窗口中的坐標,是沒有什麼疑問的。效果如下圖所示。
16-13.jpg


窗口和畫布都是繪圖設備,那麼畫布本身有沒有自己的坐標係統呢?我們接著研究!


3.我們繼續更改paintEvent()函數:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200, 200);
    pix.fill(Qt::red);
    //新建QPainter類對象,在pix上進行繪圖
    QPainter pp(&pix);
    //在pix上的(0,0)點和(50,50)點之間繪製直線
    pp.drawLine(0, 0, 50, 50);
    painter.drawPixmap(100, 100, pix);
}
       這裏我們為畫布pix創建了一個QPainter對象pp,注意這個pp隻能在畫布上繪畫,然後我們在畫布上繪製了一條從原點(0, 0)開始的直線。運行程序,效果如下圖所示。
16-14.jpg

       可以看到,直線是從畫布的左上角開始繪製的,也就是說,畫布也有自己的坐標係統,坐標原點在畫布的左上角。


    下麵補充說明一下:QPainter painter(this) ,this就表明了是在窗口上進行繪圖,所以利用painter進行的繪圖都是在窗口部件上的,painter進行的坐標變換,是變化的窗口的坐標係;而利用pp進行的繪圖都是在畫布上進行的,如果它進行坐標變化,就是變化的畫布的坐標係。

       而通過坐標數值,我們可以得出下麵兩條結論:
第一,QWidget和QPixmap各有一套坐標係統,它們互不影響。可以看到,無論畫布在窗口的什麼位置,它的坐標原點依然在左上角,為(0,0)點,沒有變。

第二,我們所得到的鼠標指針的坐標值是窗口坐標係統的,不是畫布的坐標。


4.下麵這個例子將對比分析擴大窗口坐標或畫布坐標的異同。

首先將paintEvent()函數更改如下:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);
    //放大前輸出畫布的大小
    qDebug() << pix.size();
    pix.fill(Qt::red);
    QPainter pp(&pix);
    //畫布的坐標擴大2倍
    pp.scale(2, 2);
    //在畫布上的(0,0)點和(50,50)點之間繪製直線
    pp.drawLine(0, 0, 50, 50);
    //放大後輸出畫布的大小
    qDebug() << pix.size();
    painter.drawPixmap(0, 0, pix);
}

       這裏我們將畫布坐標係統放大了兩倍,然後從原點開始繪製了一條直線,並分別輸出了畫布放大前後的大小。運行程序,效果如下圖所示。
16-15.jpg



下麵再次更改paintEvent()函數:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix(200,200);
    qDebug() << pix.size();
    //窗口坐標擴大2倍
    painter.scale(2,2);
    pix.fill(Qt::red);
    QPainter pp(&pix);
    pp.drawLine(0, 0, 50, 50);
    qDebug() << pix.size();
    painter.drawPixmap(0, 0, pix);
}
       這裏與前麵唯一的不同是:這裏放大了窗口的坐標係統,而前麵放大的是畫布的坐標係統。運行程序,效果如下圖所示。
16-16.jpg


       可以看到,整個畫布的可見麵積變大了。直線雖然長度依然是100,但是這次的效果跟前麵明顯不同,因為是窗口坐標變大,所以在上麵繪出的線條有了明顯的顆粒感。
       上麵兩個程序雖然最終輸出的數據是一樣的,但實際效果還是有很大不同的。大家可以根據需要進行選擇性應用。


結語

       在這一節中我們講述了坐標相關的多個知識點,經過本節的學習,大家應該已經對Qt2D繪圖有了一個淺顯的認識,下一節我們將做一個比較實用的塗鴉板例子。
       Qt的坐標係統是很有必要好好研究的,它對深入學習應用Qt繪圖很有幫助。如果大家想更係統的學習Qt坐標係統,可以參考《Qt Creator快速入門》的第10章相關內容。




涉及到的源碼:
 painter_2_1.zip (6.91 KB, 下載次數: 5) 
 painter_2_2.zip (6.68 KB, 下載次數: 3) 
 painter_2_3.zip (6.83 KB, 下載次數: 3) 

最後更新:2017-04-03 14:54:15

  上一篇:go [Qt教程] 第45篇 進階(五)Qt樣式表
  下一篇:go [Qt教程] 第19篇 2D繪圖(九)圖形視圖框架(上)