Monday, June 2, 2008

Qt example : generating a single QImage in a separate thread to the GUI

Here we consider the situation when a GUI-driven application needs to generate a QImage, however, due to the time required to generate the QImage, the GUI would freeze unless the QImage is generated in a separate thread. This example is similar to the Mandelbrot example code in the Qt documentation, however the code presented here is much simpler and easier to follow.

It is important to remember that while a QImage can be made outside the GUI thread, this cannot be done for QPixmaps or QWidgets, for example (QPixmaps and QWidgets can only be used in the GUI thread).

The example consists of the following files: main.C, window.C, window.h, thread.C, thread.h, imagewindow.C and imagewindow.h.

main.C

#include <QApplication>
#include "window.h"

//----------------------------------------------------------------------------
//
// main program
//
//----------------------------------------------------------------------------

int main (int argc, char **argv)
{
QApplication app(argc, argv);
Window window;
window.show();
window.setMaximumSize(window.minimumWidth(),
window.minimumHeight());
return app.exec();
}


window.C

Here we make a simple GUI containing just a single button. When the button is clicked, an abstract image is generated in another thread when QThread::start() is called. Using Qt's signals and slots, when the image has been generated, it is sent to the GUI thread and then displayed on the screen.
#include "window.h"

//----------------------------------------------------------------------------
//
// the main GUI
//
//----------------------------------------------------------------------------

//------------
// constructor
//------------

Window::Window(QWidget *parent) : QWidget(parent)
{
setWindowTitle("Qt example 02");
runButton = new QPushButton("Generate image...");
connect(runButton, SIGNAL(clicked()),
this, SLOT(run_thread()));

QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(runButton);
setLayout(layout);

// window to display the image

imageWindow = new ImageWindow();

// the QImage generation thread

qRegisterMetaType<QImage>("QImage");
otherThread = new timeConsumingThread();

// slot to respond to the QImage generation thread
// - display the image in a window

connect(otherThread, SIGNAL(theImage(const QImage &)),
this, SLOT(displayImage(const QImage &)));
}

//--------------------
// slot to run thread
//-------------------

void Window::run_thread()
{
if (!otherThread->isRunning())
{
otherThread->start();
}
}

//-------------------
// display the QImage
//-------------------

void Window::displayImage(const QImage &image)
{
imageWindow->plot = QPixmap::fromImage(image);
imageWindow->show();
imageWindow->update();
imageWindow->raise();
imageWindow->activateWindow();
}


window.h

#ifndef WINDOW_H_
#define WINDOW_H_

#include <QPushButton>
#include <QThread>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QMetaType>

#include "thread.h"
#include "imagewindow.h"

class Window : public QWidget
{
Q_OBJECT

public:
Window(QWidget *parent = 0);

private:
QPushButton *runButton;
timeConsumingThread *otherThread;
ImageWindow *imageWindow;

public slots:
void run_thread();
void displayImage(const QImage &);
};

#endif /*WINDOW_H_*/


thread.C

Here we have the time-consuming image generation in the run() reimplementation. In this example, the rendering of the image doesn't actually take too long and is just a very simple abstract image. After the image has been generated, we emit a signal containing the image.
#include "thread.h"

//----------------------------------------------------------------------------
//
// thread which runs the time-consuming image generation
//
//----------------------------------------------------------------------------

//------------
// constructor
//------------

timeConsumingThread::timeConsumingThread(QObject *parent)
: QThread(parent)
{

}

//-----------------------
// run - paint the QImage
//-----------------------

void timeConsumingThread::run()
{
QImage myQImage(600, 600, QImage::Format_RGB32);
QPainter painter(&myQImage);
for (int i=0; i<600; i++)
{
for (int j=0; j<600; j++)
{
double hue = (double)(i + j + i*j)/361200.0;
QColor myColor;
myColor.setHsvF(hue, 1.0, 1.0, 1.0);
painter.setPen(myColor);
painter.drawPoint(i, j);
}
}
emit theImage(myQImage);
}


thread.h
#ifndef THREAD_H_
#define THREAD_H_

#include <QThread>
#include <QImage>
#include <QColor>
#include <QPainter>

class timeConsumingThread : public QThread
{
Q_OBJECT

public:
timeConsumingThread(QObject *parent = 0);

signals:
void theImage(const QImage &);

protected:
void run();

};

#endif /*THREAD_H_*/


imagewindow.C

Here we setup a simple window which is used for displaying the image. In paintEvent() we draw the QPixmap containing our image onto the window.
#include "imagewindow.h"

//----------------------------------------------------------------------------
//
// image window
//
//----------------------------------------------------------------------------

//------------
// constructor
//------------

ImageWindow::ImageWindow(QWidget *parent) : QWidget(parent)
{
setFixedSize(600, 600);
setWindowTitle("Qt Example 02: Image");
}

//-----------------------------------------------------------------------------
//
// paint the image
//
//-----------------------------------------------------------------------------

void ImageWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.drawPixmap(0, 0, plot);
}


imagewindow.h

#ifndef IMAGEWINDOW_H_
#define IMAGEWINDOW_H_

#include <QWidget>
#include <QPixmap>
#include <QPainter>

class ImageWindow : public QWidget
{
Q_OBJECT

public:
ImageWindow(QWidget *parent = 0);
QPixmap plot;

private:

protected:
void paintEvent(QPaintEvent *event);

};

#endif /*IMAGEWINDOW_H_*/
This example can be compiled by the following:

qmake -project
qmake
make