十三、Qt 2D繪圖 之 塗鴉板 雙緩衝繪圖簡介 圖形視圖框架簡介

(1)塗鴉板

上面一節我們深入分析了一下Qt的座標系統,這一節我們在前面程序的基礎上稍加改動,設計一個塗鴉板程序。

簡單的塗鴉板:

1.我們再在程序中添加函數。

我們在dialog.h裏的public中再添加鼠標移動事件和鼠標釋放事件的函數聲明:

void mouseMoveEvent(QMouseEvent *);
void mouseReleaseEvent(QMouseEvent *);

在private中添加變量聲明:

QPixmap pix;
QPoint lastPoint;
QPoint endPoint;

因爲在函數裏聲明的QPixmap類對象是臨時變量,不能存儲以前的值,所以爲了實現保留上次的繪畫結果,我們需要將其設爲全局變量。

後兩個QPoint變量存儲鼠標指針的兩個座標值,我們需要用這兩個座標值完成繪圖。

2.在dialog.cpp中進行修改。

在構造函數裏進行變量初始化。

resize(600,500);    //窗口大小設置爲600*500
pix = QPixmap(200,200);
pix.fill(Qt::white);

然後進行其他幾個函數的定義:

void Dialog::paintEvent(QPaintEvent *)
{   
    QPainter pp(&pix);
    pp.drawLine(lastPoint,endPoint);   //根據鼠標指針前後兩個位置就行繪製直線
    lastPoint = endPoint;
   //讓前一個座標值等於後一個座標值,這樣就能實現畫出連續的線

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

void Dialog::mousePressEvent(QMouseEvent *event)
{
    if(event->button()==Qt::LeftButton) //鼠標左鍵按下
        lastPoint = event->pos();

}

void Dialog::mouseMoveEvent(QMouseEvent *event)
{
    if(event->buttons()&Qt::LeftButton) //鼠標左鍵按下的同時移動鼠標
    {
        endPoint = event->pos();
        update();
    }

}
void Dialog::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton) //鼠標左鍵釋放
    {
        endPoint = event->pos();
        update();
    }

}

這裏的update()函數,是進行界面重繪,執行該函數時就會執行那個重繪事件函數。

3.這時運行程序,效果如下。點擊圖片可將其放大




這樣簡單的塗鴉板程序就完成了。下面我們進行放大後的塗鴉。

放大後再進行塗鴉:

1.添加放大按鈕。

在dialog.h中添加頭文件聲明: #include <QPushButton>

在private中添加變量聲明:

int scale;
QPushButton *pushBtn;

然後再在下面寫上按鈕的槽函數聲明:

private slots:
    void zoomIn();

2.在dialog.cpp中進行更改。

在構造函數裏添加如下代碼:

scale =1;   //設置初始放大倍數爲1,即不放大
pushBtn = new QPushButton(this); //新建按鈕對象
pushBtn->setText(tr("zoomIn"));   //設置按鈕顯示文本
pushBtn->move(500,450);    //設置按鈕放置位置
connect(pushBtn,SIGNAL(clicked()),this,SLOT(zoomIn())); //對按鈕的單擊事件和其槽函數進行關聯

這裏我們利用代碼添加了一個按鈕對象,用它來實現放大操作。

再在構造函數以外進行zoomIn()函數的定義:

void Dialog::zoomIn() //按鈕單擊事件的槽函數
{
    scale *=2;
    update();

}

3.通過上一節的學習,我們應該已經知道想讓畫布的內容放大有兩個辦法,一個是直接放大畫布的座標,一個是放大窗口的座標。

我們主要講解放大窗口座標。

void Dialog::paintEvent(QPaintEvent *)
{   
    QPainter pp(&pix);
    pp.drawLine(lastPoint,endPoint);   //根據鼠標指針前後兩個位置就行繪製直線
    lastPoint = endPoint;   //讓前一個座標值等於後一個座標值,這樣就能實現畫出連續的線

    QPainter painter(this);
    painter.scale(scale,scale); //進行放大操作
    painter.drawPixmap(0,0,pix);
}

這時運行程序。

先隨意畫一個圖形,如下圖。

再按下“zoomIn”按鈕,進行放大兩倍。可以看到圖片放大了,效果如下。

這時我們再進行繪圖,繪製出的線條已經不能和鼠標指針的軌跡重合了。效果如下圖。



有了前面一節的知識,我們就不難理解出現這個問題的原因了。窗口的座標擴大了,但是畫布的座標並沒有擴大,而我們畫圖用的座標值是鼠標指針的,鼠標指針又是獲取的窗口的座標值。現在窗口和畫布的同一點的座標並不相等,所以就出現了這樣的問題。

其實解決辦法很簡單,窗口放大了多少倍,就將獲得的鼠標指針的座標值縮小多少倍就行了。

void Dialog::paintEvent(QPaintEvent *)
{   
    QPainter pp(&pix);
    pp.drawLine(lastPoint/scale,endPoint/scale);
    lastPoint = endPoint;

    QPainter painter(this);
    painter.scale(scale,scale); //進行放大操作
    painter.drawPixmap(0,0,pix);
}

運行程序,效果如下:



此時已經能進行正常繪圖了。

 

這種用改變窗口座標大小來改變畫布面積的方法,實際上是有損圖片質量的。就像將一張位圖放大一樣,越放大越不清晰。原因就是,它的像素的個數沒有變,如果將可視面積放大,那麼單位面積裏的像素個數就變少了,所以畫質就差了。

下面我們簡單說說另一種方法。

放大畫布座標。

void Dialog::paintEvent(QPaintEvent *)
{   
    QPainter pp(&pix);
    pp.scale(scale,scale);
    pp.drawLine(lastPoint/scale,endPoint/scale);

    lastPoint = endPoint;

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

效果如下:


此時,畫布中的內容並沒有放大,而且畫布也沒有變大,不是我們想要的,所以我們再更改一下函數。

void Dialog::paintEvent(QPaintEvent *)
{   
    if(scale!=1) //如果進行放大操作
    {
        QPixmap copyPix(pix.size()*scale); //臨時畫布,大小變化了scale倍
        QPainter pter(&copyPix);
        pter.scale(scale,scale);
        pter.drawPixmap(0,0,pix);   //將以前畫布上的內容複製到現在的畫布上
        pix = copyPix;     //將放大後的內容再複製回原來的畫布上,這樣只傳遞內容,不傳遞座標系
        scale =1; //讓scale重新置1
    }

    QPainter pp(&pix);
    pp.scale(scale,scale);
    pp.drawLine(lastPoint/scale,endPoint/scale);
    lastPoint = endPoint;

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

此時運行效果如下:

這樣就好了。可以看到,這樣放大後再進行繪製,出來的效果是不同的。

我們就講到這裏,如果你有興趣,可以接着研究!

怎麼應用上面講到的內容,你可以查看繪圖軟件的教程鏈接過去)。

 

 

 

(2)雙緩衝繪圖簡介

 

    上面一節我們實現了塗鴉板的功能,但是如果我們想在塗鴉板上繪製矩形,並且可以動態地繪製這個矩形,也就是說我們可以用鼠標畫出隨意大小的矩形,那該怎麼辦呢?

我們先進行下面的三步,最後引出所謂的雙緩衝繪圖的概念。

第一步:

我們更改上一節的那個程序的重繪函數。

void Dialog::paintEvent(QPaintEvent *)
{   
    QPainter painter(this);
    int x,y,w,h;
    x = lastPoint.x();
    y = lastPoint.y();
    w = endPoint.x() - x;
    h = endPoint.y() - y;
    painter.drawRect(x,y,w,h);

}

然後運行,效果如下。

這時我們已經可以拖出一個矩形了,但是這樣直接在窗口上繪圖,以前畫的矩形是不能保存住的。所以我們下面加入畫布,在畫布上進行繪圖。

 

第二步:

我們先在構造函數裏將畫布設置大點:pix = QPixmap(400,400);

然後更改函數,如下:

void Dialog::paintEvent(QPaintEvent *)
{   
    int x,y,w,h;
    x = lastPoint.x();
    y = lastPoint.y();
    w = endPoint.x() - x;
    h = endPoint.y() - y;
    QPainter pp(&pix);
    pp.drawRect(x,y,w,h);

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

這時運行程序,效果如下:

現在雖然能畫出矩形,但是卻出現了無數個矩形,這不是我們想要的結果,我們希望能像第一步那樣繪製矩形,所以我們再加入一個臨時畫布。

 

 

第三步:

首先,我們在dialog.h中的private裏添加變量聲明:

QPixmap tempPix; //臨時畫布
bool isDrawing;   //標誌是否正在繪圖

然後在dialog.cpp中的構造函數裏進行變量初始化:

isDrawing = false;

最後更改函數如下:

void Dialog::paintEvent(QPaintEvent *)
{   
    int x,y,w,h;
    x = lastPoint.x();
    y = lastPoint.y();
    w = endPoint.x() - x;
    h = endPoint.y() - y;

    QPainter painter(this);
    if(isDrawing)     //如果正在繪圖
    {
        tempPix = pix;    //將以前pix中的內容複製到tempPix中,這樣實現了交互繪圖
        QPainter pp(&tempPix);
        pp.drawRect(x,y,w,h);
        painter.drawPixmap(0,0,tempPix);
    }
    else
    {
        QPainter pp(&pix);
        pp.drawRect(x,y,w,h);
        painter.drawPixmap(0,0,pix);
    }
}

void Dialog::mousePressEvent(QMouseEvent *event)
{
    if(event->button()==Qt::LeftButton) //鼠標左鍵按下
    {
        lastPoint = event->pos();
        isDrawing = true;   //正在繪圖
    }
}

void Dialog::mouseMoveEvent(QMouseEvent *event)
{
    if(event->buttons()&Qt::LeftButton) //鼠標左鍵按下的同時移動鼠標
    {
        endPoint = event->pos();
        update();
    }
}
void Dialog::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton) //鼠標左鍵釋放
    {
        endPoint = event->pos();
        isDrawing = false;    //結束繪圖
        update();
    }
}

我們使用兩個畫布,就解決了繪製矩形等圖形的問題。

其中tempPix = pix;一句代碼很重要,就是它,才實現了消除那些多餘的矩形。

 

雙緩衝繪圖簡介:

根據我的理解,如果將第一步中不用畫布,直接在窗口上進行繪圖叫做無緩衝繪圖,那麼第二步中用了一個畫布,將所有內容都先畫到畫布上,在整體繪製到窗口上,就該叫做單緩衝繪圖,那個畫布就是一個緩衝區。這樣,第三步,用了兩個畫布,一個進行臨時的繪圖,一個進行最終的繪圖,這樣就叫做雙緩衝繪圖。

我們已經看到,利用雙緩衝繪圖可以實現動態交互繪製。其實,Qt中所有部件進行繪製時,都是使用的雙緩衝繪圖。就算是第一步中我們沒有用畫布,Qt在進行自身繪製時也是使用的雙緩衝繪圖,所以我們剛纔那麼說,只是爲了更好地理解雙緩衝的概念。

 

----------------------------------------------------------------------------------------------------------

到這裏,我們已經可以進行一些Qt 2D繪圖方面的設計了。我這裏有兩個例子,一個是那個繪圖軟件(鏈接到那裏),它是實踐了我所講的這幾節的內容,可以說是對這幾節內容的一個綜合。還有一個例子,就是俄羅斯方塊程序(鏈接到那裏),那個是對這些知識的應用。如果你有興趣,可以看一下。

 

 

(3)圖形視圖框架簡介

 

   我們前面用基本的繪圖類實現了一個繪圖軟件,但是,我們無法做出像Word或者Flash中那樣,繪製出來的圖形可以作爲一個元件進行任意變形。我們要想很容易地做出那樣的效果,就要使用Qt中的圖形視圖框架。

The QGraphics View Framework(圖形視圖框架),在Qt Creator中的幫助裏可以查看它的介紹,當然那是英文的,這裏有一篇中文的翻譯,大家可以看一下:

http://hi.baidu.com/yafeilinux/blog/item/f7040630723a0612eac4af20.html

如果你的程序中要使用大量的2D圖元,並且想要這些圖元都能進行單獨或羣組的控制,你就要使用這個框架了。比方說像Flash一樣的矢量繪圖軟件,各種遊戲軟件。但是因爲這裏涉及的東西太多了,不可能用一兩篇文章就介紹清楚,所以這裏我們只是提及一下,讓一些剛入門的朋友知道有這樣一個可用的框架。

最簡單的使用:

The QGraphics View Framework包含三個大類:QGraphicsItem 項類(或者叫做圖元類),QGraphicsScene 場景類,和QGraphicsView 視圖類。

QGraphicsItem 用來繪製你所要用到的圖形,QGraphicsScene 用來包含並管理所有的圖元,QGraphicsView 用來顯示所有場景。而他們三個都擁有自己各自的座標系統。我們下面就來建立一個工程,完成一個最簡單的例子。

1.新建空的Qt工程:

2.更改工程名和存放路徑。

3.然後新建C++類。

4.更改類名爲MyItem,基類填寫爲QGraphicsItem,如下圖:

5.可以看到新建的類默認已經添加到了工程裏。

6.新建C++ Source File,更改名字爲main.cpp,如下圖:

7.然後更改各文件的內容。

更改完成後,myitem.h文件內容如下:


myitem.cpp文件的內容如下:


main.cpp的內容如下:

運行程序,最終效果如下:

這裏我們只是演示了一下使用這個框架完成最簡單的程序的過程,只起到拋磚引玉的作用。

這個框架很複雜,但是功能也很強大,Qt Creator中自帶了幾個相關的例子(在幫助中查找Graphics View Examples即可),你可以參考一下。因爲篇幅問題,我們就只講這麼多,如果以後有機會,我會推出一個相關的專題來講述這個框架。

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