Qt組件中的雙緩沖無閃爍繪圖


 

 

雙緩沖繪圖
在Qt4中,所有的窗口部件默認都使用雙緩沖進行繪圖。使用雙緩沖,可以減輕繪制的閃爍感。在有些情況下,用戶要關閉雙緩沖,自己管理繪圖。下面的語句設置了窗口部件的Qt::WA_PaintOnScreen屬性 ,就關閉了窗口部件的雙緩沖.
mywidget->setAttribute(Qt::WA_PaintOnScreen);

由於Qt4不再提供異或筆,組合模式QPainter::CompostionMode_Xor()並不是異或筆,Qt4只提供了QRubberBand實現矩形和直線的繪圖反饋。因此要實現在繪圖中動態
反饋必須使用其他方法。程序中使用雙環沖來解決這個問題。在繪圖過程中,一個緩沖區繪制臨時內存,一個緩沖區保存繪制好的內容,最后進行合並。
在交互繪圖過程中,程序將圖像緩沖區復制到臨時緩沖區,並在臨時緩沖區上繪制,繪制完畢在將結果復制到圖像緩沖區,如果沒有交互復制,則直接將圖像緩沖區繪制顯示到屏幕上。

 

Qt組件中的雙緩沖無閃爍繪圖
閃爍首先,要想把閃爍減弱,請設置組件的背景模式為NoBackground. 
setBackgroundMode(NoBackground);

其次,重載組件的paintEvent()函數,如下改寫: 
void MyWidget::paintEvent(QPaintEvent *e) 

QRect ur=e->rect();//得到組件尺寸 
QPixmap pix(ur.size());//以此為參數創建一個位圖變量 
pix.fill(this,ur.topLeft());//填充位圖 
QPainter p(&pic);//以位圖為參數創建一個QPainter 對象

p.translate(-ur.x(),-ur.y());//在QPainter 上繪畫 
//......//Drawing

p.End();//繪畫完畢

bitBlt(this,ur.topLeft().&pix);//把位圖貼到組件上

//注從qt4開始,bitBlt函數不在使用,取而代之的是drawPixmap。
}

 

 

   // 這是能隨機繪點的關鍵,沒有設置此屬性,默認相當於每次Qt都會完整的將上一次的屏幕擦除,

   // 新版的Qt中已經沒有了repaint(bool)接口了。

   w.setAttribute(Qt::WA_OpaquePaintEvent);

 

 

老電視機雪花效果中每次都需要擦除重繪避免點的疊加所以一下語句注釋掉

//    w.setAttribute(Qt::WA_OpaquePaintEvent);

 

 

 

(

以下是私有函數的實現:

void Plotter::updateRubberBandRegion()

{

    QRect rect = rubberBandRect.normalized();

    update(rect.left(), rect.top(), rect.width(), 1);
    update(rect.left(), rect.top(), 1, rect.height());
    update(rect.left(), rect.bottom(), rect.width(), 1);
    update(rect.right(), rect.top(), 1, rect.height());
}
函數updateRubberBand()在mousePressEvent(),mouseMoveEvent()和mouseReleaseEvent()中被調用,用來刪除或者重新繪制橡皮線。函數中調用了四次update(),用四個繪制事件完成由橡皮線(兩條垂直和水平的線)組成的四個小矩形的繪制。Qt也提供了一個類QRubberBand用來繪制橡皮線,但是控件自己提供的繪制函數會更好
void Plotter::refreshPixmap()
{
    pixmap = QPixmap(size());
    pixmap.fill(this, 0, 0);
    QPainter painter(&pixmap);
    painter.initFrom(this);
    drawGrid(&painter);
    drawCurves(&painter);
    update();
}
函數refreshPixmap()把plot繪制到圖片上,並且更新顯示。首先我們把圖片的大小調整為和當前控件大小相同,然后用控件的背景顏色填充整個圖片。這個顏色是當前調色版的“dark”部分,因為在Plotter構造函數中調用setBackgroundRole() 。如果背景用的刷子是非實心的(solid brush,刷子的樣式,只有顏色,沒有花紋的那種最簡單的),QPixmap::fill()需要知道控件中刷子的偏移量,以便圖片對齊刷子模式。這里圖片對應整個控件,因此偏移位置為(0,0)。
接下來我們創建了一個QPainter對象來繪制圖片,QPainter::initFrom()設置繪制圖片所需畫筆,背景和字體,參數this表示這些設置和Plotter控件的相應設置是一致的。然后我們調用drawGrid(),drawCurves()繪制網格和曲線。最后,update()函數安排整個控件的繪制事件,在painteEvent()函數中把圖片拷貝到控件上。
void Plotter::drawGrid(QPainter *painter)
{
   QRect rect(Margin, Margin,
               width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;
    PlotSettings settings = zoomStack[curZoom];
    QPen quiteDark = palette().dark().color().light();
    QPen light = palette().light().color();
    for (int i = 0; i <= settings.numXTicks; ++i) {
        int x = rect.left() + (i * (rect.width() - 1)
                                 / settings.numXTicks);
        double label = settings.minX + (i * settings.spanX()
                                           / settings.numXTicks);
        painter->setPen(quiteDark);
        painter->drawLine(x, rect.top(), x, rect.bottom());
        painter->setPen(light);
        painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
       painter->drawText(x - 50, rect.bottom() + 5, 100, 15,
                          Qt::AlignHCenter | Qt::AlignTop,
                          QString::number(label));
    }
    for (int j = 0; j <= settings.numYTicks; ++j) {
        int y = rect.bottom() - (j * (rect.height() - 1)
                                   / settings.numYTicks);
        double label = settings.minY + (j * settings.spanY()
                                          / settings.numYTicks);
        painter->setPen(quiteDark);
        painter->drawLine(rect.left(), y, rect.right(), y);
        painter->setPen(light);
        painter->drawLine(rect.left() - 5, y, rect.left(), y);
        painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20,
                          Qt::AlignRight | Qt::AlignVCenter,
                          QString::number(label));
    }
    painter->drawRect(rect.adjusted(0, 0, -1, -1));
}
函數drawGrid()在坐標軸和曲線的下面繪制網格。這個區域由一個矩形確定,如果控件太小,則不繪制立即返回。第一個循環繪制網格的垂直線,及沿x坐標軸的刻度。第二個循環繪制網格的水平線,及沿y坐標軸的刻度。最后,沿邊界繪制一個矩形。drawText()繪制數字,對應兩個坐標軸上刻度的標記。
函數painter->drawText()語法如下:
painter->drawText(x, y, width, height, alignment, text);
其中(x,y,width,height)所確定的矩形,alignment確定文字在矩形中的位置。
void Plotter::drawCurves(QPainter *painter)
{
    static const QColor colorForIds[6] = {
        Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow
    };
    PlotSettings settings = zoomStack[curZoom];
    QRect rect(Margin, Margin,
               width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;
    painter->setClipRect(rect.adjusted(+1, +1, -1, -1));
    QMapIterator<int, QVector<QPointF> > i(curveMap);
    while (i.hasNext()) {
        i.next();
        int id = i.key();
        const QVector<QPointF> &data = i.value();
        QPolygonF polyline(data.count());
        for (int j = 0; j < data.count(); ++j) {
            double dx = data[j].x() - settings.minX;
            double dy = data[j].y() - settings.minY;
            double x = rect.left() + (dx * (rect.width() - 1)
                                          / settings.spanX());
            double y = rect.bottom() - (dy * (rect.height() - 1)
                                           / settings.spanY());
            polyline[j] = QPointF(x, y);
        }
        painter->setPen(colorForIds[uint(id) % 6]);
        painter->drawPolyline(polyline);
    }
}
函數drawCurves()在網格的上層繪制曲線。調用了QPainter::setClipRect()函數設置QPainter的剪切區域為包含曲線的矩形區域(不包括四周的間隙和圖片的外框)。QPainter會忽略這個區域外的象素。
然后我們使用Java風格的迭代器,遍歷所有的曲線,對每一條曲線,遍歷它所有的QPointF點。函數key()得到曲線的id,value()函數得到曲線的QVector<QPointF>類型的數據。內層循環把每個QPointF記錄的plotter坐標轉換為控件坐標,把結果保存在polyline變量中。

轉換坐標后,我們設置畫筆的顏色(使用函數前面預定義的顏色),調用drawPolyline()繪制曲線,經過所有的曲線上的點。

 

http://blog.csdn.net/Last_Impression/archive/2008/05/20/2463647.aspx


免責聲明!

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



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