2010-12-09 10 views
3

Ich habe eine Anwendung, die Pixel für Pixel mit einer bestimmten Bildrate (Simulation einer alten Maschine) zu zeichnen. Eine Einschränkung besteht darin, dass die Hauptmaschine in einem Hintergrundthread ausgeführt wird, um sicherzustellen, dass die Benutzeroberfläche während der Simulation reaktionsfähig und verwendbar bleibt.Zeichnen Sie pixelbasierte Grafiken zu einem QWidget

Derzeit bin ich liebäugelt mit so etwas wie dies mit:

class QVideo : public QWidget { 
public: 
    QVideo(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) { 

    } 

    void draw_frame(void *data) { 
     // render data into screen_image_ 
    } 

    void start_frame() { 
     // do any pre-rendering prep work that needs to be done before 
     // each frame 
    } 

    void end_frame() { 
     update(); // force a paint event 
    } 

    void paintEvent(QPaintEvent *) { 
     QPainter p(this); 
     p.drawImage(rect(), screen_image_, screen_image_.rect()); 
    } 

    QImage screen_image_; 
}; 

Diese meist wirksam ist, und überraschenderweise nicht sehr langsam. Es gibt jedoch ein Problem. Die Update-Funktion Pläne a paintEvent, kann es nicht sofort happen. In der Tat kann ein Bündel von paintEvent 'gemäß der Qt-Dokumentation "kombiniert" werden.

Der negative Effekt, den ich sehe, ist, dass nach ein paar Minuten der Simulation der Bildschirm aufhört zu aktualisieren (Bild wird eingefroren, obwohl die Simulation noch läuft), bis ich etwas mache, das eine Bildschirmaktualisierung erzwingt zum Beispiel Fenster umschalten und aus maximiert.

Ich habe experimentiert mit QTimer 's und anderen ähnlichen Mechanismus, um die Wirkung des Rendering im GUI - Thread zu haben, so dass ich sofortige Updates erzwingen kann, aber dies führte zu inakzeptablen Leistungsprobleme.

Gibt es eine bessere Möglichkeit, Pixel in einem festen Intervall konstant auf ein Widget zu zeichnen. Reine Qt-Lösungen sind bevorzugt.

BEARBEITEN: Seit some people wählen Sie eine Haltung statt die ganze Frage zu lesen, werde ich das Problem klären. Ich kann QWidget::repaint nicht verwenden, da es eine Einschränkung hat, dass es von demselben Thread wie die Ereignisschleife aufgerufen werden muss. Andernfalls kommt es kein Update und stattdessen erhalte ich qDebug Meldungen wie diese:

QPixmap: It is not safe to use pixmaps outside the GUI thread 
QPixmap: It is not safe to use pixmaps outside the GUI thread 
QWidget::repaint: Recursive repaint detected 
QPainter::begin: A paint device can only be painted by one painter at a time. 
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent 
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent 

EDIT: das Problem zu demonstrieren, habe ich diesen einfachen Beispielcode erstellt:

QVideo.h

#include <QWidget> 
#include <QPainter> 

class QVideo : public QWidget { 
    Q_OBJECT; 
public: 
    QVideo(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) { 

    } 

    void draw_frame(void *data) { 
     // render data into screen_image_ 
     // I am using fill here, but in the real thing I am rendering 
     // on a pixel by pixel basis 
     screen_image_.fill(rand()); 
    } 

    void start_frame() { 
     // do any pre-rendering prep work that needs to be done before 
     // each frame 
    } 

    void end_frame() { 
     //update(); // force a paint event 
     repaint(); 
    } 

    void paintEvent(QPaintEvent *) { 
     QPainter p(this); 
     p.drawImage(rect(), screen_image_, screen_image_.rect()); 
    } 

    QImage screen_image_; 
}; 

main.cc:

#include <QApplication> 
#include <QThread> 
#include <cstdio> 
#include "QVideo.h" 


struct Thread : public QThread { 

    Thread(QVideo *v) : v_(v) { 
    } 

    void run() { 
     while(1) { 
      v_->start_frame(); 
      v_->draw_frame(0); // contents doesn't matter for this example 
      v_->end_frame(); 
      QThread::sleep(1); 
     } 
    } 

    QVideo *v_; 
}; 

int main(int argc, char *argv[]) { 
    QApplication app(argc, argv); 
    QVideo w; 
    w.show(); 

    Thread t(&w); 
    t.start(); 

    return app.exec(); 
} 

ich bin de endlich bereit, Optionen zu erkunden, die keine temporäre QImage zum Rendern verwenden. Es ist nur die einzige Klasse in Qt, die eine direkte Pixelschreibschnittstelle zu haben scheint.

+1

Sie könnten eine Schlange-Signal-Slot-Verbindung verwenden, um sicherzustellen, dass update() vom GUI-Thread aufgerufen wird. – ChrisV

+0

@Noah: Zuallererst, du bist derjenige, der mit einer Einstellung begann.Was ich tun muss, ist Pixel zu zeichnen, weshalb ich darüber gesprochen habe. Also, wenn es eine alternative Lösung zu dem gibt, was ich mache, bin ich interessiert. Der Code, den ich verwende, verwendet keine 'QPixmap', so dass er eindeutig in Qt verwendet wird. Ihr Vorschlag scheint bei meinem Beispiel nicht durchführbar zu sein. Ich werde versuchen, ein einfaches Beispiel dafür zu liefern. –

+0

@Noah: Ihr Vorschlag scheint nicht zu funktionieren, wenn Sie eine Demonstration vorfinden, bei der das Beispiel funktioniert, nehme ich Ihre Antwort gerne an. –

Antwort

1

In ähnlichen Fällen benutzte ich immer noch einen QTimer, aber ich machte mehrere Simulationsschritte anstelle von nur einem. Sie können sogar das Programm so einstellen, dass die Anzahl der Simulationsschritte automatisch angepasst wird, um die gewünschten Frames pro Sekunde für die Bildschirmaktualisierung zu erhalten.

+0

Der QTimer-Ansatz scheint praktikabel zu sein, aber ich hatte Leistungsprobleme damit, ich werde es noch einmal untersuchen. Vielleicht habe ich etwas übersehen. –

2

Versuchen Sie, ein Signal vom Thread an einen Slot im Event-Loop-Widget zu senden, das repaint() aufruft, das dann sofort ausgeführt wird. Ich mache so etwas in meinem Grafikprogramm, das die Hauptberechnungen in einem Thread ausführt und dann dem Widget mitteilt, wann es Zeit ist, die Daten neu zu zeichnen().

Verwandte Themen