[Qt2D繪圖]-06QPainter的復合模式&&雙緩沖繪圖&&繪圖中的其他問題


本篇讀書筆記主要記錄QPainter的復合模式&&雙緩沖繪圖&&繪圖中的其他問題
 
大綱:
    復合模式
    雙緩沖繪圖
    繪圖中的其他問題
        重繪事件
        剪切
        讀入和寫入圖像
        播放GIF
        渲染SVG
 
復合模式
QPainter提供了復合模式(Composition Modes)來定義如何完成數字圖像的復合,即如何將源圖像的像素和目標圖像的像素進行合並。QPainter 提供的常用復合模式及其效果如下面截圖所示,所有的復合模式可以在QPainter的幫助文檔中進行查看。
 
最普通的類型是SourceOver(不設置的話默認是這個)(通常被稱為alpha混合),就是正在繪制的源像素混合在已經繪制的目標像素上,源像素的alpha分量定義了它的透明度,這樣源圖像就會以透明效果在目標圖像上進行顯示。
 
若繪圖設備是QImage,圖像的格式一定要指定為QImage::Format_ARGB32_Premultiplied或者Format_ARGB32,不然復合模式就不會產生任何效果。
當設置了復合模式,它就會應用到所有的繪圖操作中,如畫筆、畫刷、漸變和pixmap/image繪制等。
 
演示書上的代碼:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter;
 
QImage image(400,300,QImage::Format_ARGB32_Premultiplied);
// 使用繪圖設備,繪制到繪圖設備上
painter.begin(&image);
// 繪制一個矩形
painter.setBrush(Qt::green);
painter.drawRect(100,50,200,200);
 
//在四個角分別繪制一個矩形,使用不同的復合模式(composition)
painter.setBrush(QColor(0,0,255,150));
//composition沒有設置則使用默認的SourceOver
painter.drawRect(50,0,100,100);
//QPainter::CompositionMode_SourceIn
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.drawRect(250,0,100,100);
//QPainter::CompositionMode_DestinationOver
painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
painter.drawRect(50,200,100,100);
//QPainter::CompositionMode_Xor
painter.setCompositionMode(QPainter::CompositionMode_Xor);
painter.drawRect(250,200,100,100);
painter.end();
 
// 繪制到當前部件(當前繪圖設備是QWidget的子類,也就是部件)
painter.begin(this);
painter.drawImage(0,0,image);
}

 

效果圖:
 
 
雙緩沖繪圖
所謂雙緩沖 (double-buffers)繪圖,就是在進行繪制時,先將所有內容都繪制到一個繪圖設備(如QPixmap)上,然后再將整個圖像繪制到部件上顯示出來。
使用雙緩沖繪圖可以避免顯示時的閃爍現象。
從Qt4.0開始,QWidget部件的所有繪制都自動使用了雙緩沖,所以一般沒有必要在paintEvent()函數中使用雙緩沖代碼來避免閃爍。
雖然在一般的繪圖中無須手動使用雙緩沖繪圖,不過要想實現一些繪圖效果,還是要借助於雙緩沖的概念。
 
下面的程序實現使用鼠標在界面上繪制一個任意大小的矩形的功能。
這里需要兩張畫布,它們都是QPixmap實例。
其中一個tempPix用來作為臨時緩沖區,當鼠標正在拖動矩形進行繪制時,將內容先繪制到tempPix上,然后將tempPix繪制到界面上;
而另一個pix作為緩沖區,用來保存已經完成的繪制。當松開鼠標完成矩形的繪制后,則將tempPix的內容復制到pix上。
為了繪制時不顯示拖影,在移動鼠標過程中,每繪制一次都要在剛開始繪制這個矩形的圖像上進行繪制,所以需要在每次繪制tempPix之前,先將pix的內容復制到tempPix上。
#include "widget.h"
#include "ui_widget.h"
 
#include <QMouseEvent>
#include <QPainter>
 
 
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
  ui->setupUi(this);
  pix_buffer_ = QPixmap(400, 300);
  pix_buffer_.fill(Qt::white);
  temp_pix_buffer_ = pix_buffer_;
  is_drawing_ = false;
}
 
 
Widget::~Widget() { delete ui; }
 
 
void Widget::mousePressEvent(QMouseEvent *event) {
    if(event->button() == Qt::LeftButton){
        //當鼠標左鍵按下時獲取當前位置作為矩形的開始點
        start_point_ = event->pos();
        is_drawing_ = true;
    }
}
 
 
void Widget::mouseMoveEvent(QMouseEvent *event) {
    if(event->button() & Qt::LeftButton){
        //當按着鼠標左鍵進行移動時,獲取當前位置作為結束點,繪制矩形
        end_point_ = event->pos();
        //將緩沖區的內容復制到臨時緩沖區,這樣進行動態繪制時
        //每次都是在緩沖區圖像的基上進行繪制,就不會產生拖影現象了
        temp_pix_buffer_ = pix_buffer_;
        // 更新顯示
        update();
    }
}
 
 
void Widget::mouseReleaseEvent(QMouseEvent *event) {
    if(event->button()== Qt::LeftButton){
        //當鼠標左鍵松開時,獲取當前位置為結束點,完成矩形繪制
        end_point_ = event->pos();
        //標記已經結束繪圖
        is_drawing_ = false;
        update();
    }
}
 
 
void Widget::paintEvent(QPaintEvent *) {
    int x = start_point_.x();
    int y = start_point_.y();
    int width = end_point_.x();
    int height = end_point_.y();
 
 
    QPainter painter;
    painter.setPen(QColor(Qt::red));
    painter.begin(&temp_pix_buffer_);
    painter.drawRect(x,y,width,height);
    painter.end();
 
 
    painter.begin(this);
    painter.drawPixmap(0,0,temp_pix_buffer_);
    //如果已經完成了繪制,那么更新緩沖區
    if(!is_drawing_){
        pix_buffer_ = temp_pix_buffer_;
    }
}

 

這里先在臨時緩沖區中進行繪圖,然后將其繪制到界面上。最后判斷是否已經完成了繪制,如果是,則將臨時緩沖區中的內容復制到緩沖區中,這樣就完成了整個矩形的繪制。這個例子中的關鍵是pix和tempPix的相互復制,如果想將這個程序進行擴展,可以查看一下網站上的塗鴉板序。
 
與這個例子很相似的一個應用是橡皮筋線,就是我們在Windows桌面上拖動鼠標出現的橡皮筋選擇框。Qt中提供了QRubberBand 類來實現橡皮筋線,使用它只需要在幾個鼠標事件處理函數中進行設置即可,具體應用可以查看該類的幫助文檔。
 
繪圖中的其他問題
重繪事件 paintEvent(QPaintEvent *event)override; protected
前面講到的所有繪制操作都是 在重繪事件處理函數paintEvent()中完成的, 它是QWidget類中定義的函數。也就意味着QtWidgets都是繪圖設備
一個重繪事件用來重繪一個部件的全部或者部分區域,下面幾個原因中的任意一個都會發生重繪事件:
  • repaint()函數或者update()函數被調用;
  • 被隱藏的部件現在被重新顯示;
  • other
大部分部件可以簡單地重繪它們的全部界面, 但是一些繪制比較慢的部件需要進行優化而只繪制需要的區域(可以使用 QPaintEvent::region()來獲取該區域),這種速度上的優化不會影響結果。
Qt也會通過合並多個重繪事件為一個事件來加快繪制,當update()函數被調用多次,或者窗口系統發送了多個重繪事件時,那么Qt就會合並這些事件成為一個事件,而這個事件擁有最大的需要重繪的區域。
update()函數不會立即進行重繪,要等到Qt返回主事件循環后才會進行,所以多次調用update()函數一般只會引起一次paintEvent()函數調用。
 
但是調用repaint()函數會立即調用paintEvent()函數來重繪部件,只有在必須立即進行重繪操作的情況下(比如在動畫中),才使用repaint()函數。
update()允許Qt優化速度和減少閃爍,但是repaint()函數不支持這樣的優化,所以建議一般情況下盡可能使用update()函數。
還要說明一下,在程序開始運行時就會自動發送重繪事件而調用paintEvent()函數。另外,不要在paintEvent()函數中調用update( )或者repaint()函數。
 
當重繪事件發生時,要更新的區域一般會被擦除,然后在部件的背景上進行繪制。
部件的背景一般可以使用setBackgroundRole( )來指定,然后使用setAutoFillBackground(true)來啟用指定的顏色。
例如,使界面顯示比較深的顏色, 則可以在部件的構造函數中添加如下代碼:
 
setBackgroundRole(QPalette::Dark);
setAutoFillBackgroud(true);
 
剪切
QPainter可以剪切任何的繪制操作,它可以剪切一個矩形、一個區域或者一個路徑中的內容,
這分別可以使用setClipRect()、setClipRegion()和setClipPath()函數來實現。
剪切會在QPainter的邏輯坐標系統中進行。下面的代碼實現了剪切一個矩形中的文字:
 
QPainter painter(this);
// 剪切矩形中的內容
painter.setClipRect(10,0,20,10);
painter.drawText(10,10,"tudouTestTestTest");
 
讀入和寫入圖像
要讀取圖像,最普通的方法是使用QImage或者QPixmap的構造函數,或者調用Qlmage: :load()和QPixmap::load()函數。
 
Qt中還有一個 QImageReader類,該類提供了一個格式無關的接口,可以從文件或者其他設備中讀取圖像。
QImageReader 類可以在讀取圖像時提供更多的控制,例如,可以使用setScaledSize()函數將圖像以指定的大小進行讀取,還可以使用setClipRect()讀取圖像的一個區域。
由於依賴於圖像格式底層的支持,QImageReader的這些操作可以節省內存和加快圖像的讀取
另外,Qt還提供了 QImageWriter類來存儲圖像,它支持設置圖像格式的特定選項,比如伽瑪等級、壓縮等級和品質等。
當然,如果不需要設置這些選項,那么可以直接使用Qlmage: :save()和QPixmap::save( )函數。
 
播放GIF
QMovie類是使用QImageReader來播放動畫的便捷類,使用它可以播放不帶聲音的簡單的動畫,比如gif文件格式。
這個類提供了很方便的函數來進行動畫的開始、暫停和停止等操作。
 
渲染SVG
可縮放矢量圖形( Scalable Vector Graphics,SVG)是一個使用XML來描述二維圖形和圖形應用程序的語言。
在Qt中可以使用QSvgWidget類加載一個SVG文件,而使用QSvgRenderer類在QSvgWidget中進行SVG文件的渲染。
這兩個類的使用很簡單,可以參考SVG Generator Example和SVG Viewer Example示例程序。
 
Qt參考示例和文檔
文檔:
示例:Image Composition Example , Composition Modes
 
本篇源代碼
https://github.com/tudouloveloli/QtExampleCode


免責聲明!

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



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