I would recommend to do not multi-threading in GUI programming. Although, Qt provides multi-threading support in general, IMHO, the widgets are not well-prepared for this.
Thus, to achieve image loaders which run concurrently in separate threads I would suggest the following concept:
Each threaded image loader feeds a private buffer. The GUI inspects from time to time (using QTimer
) these buffers and updates its QPixmap
. As access to buffers should be possible from the resp. image loader thread as well as the GUI thread they have to be mutex guarded, of course.
My sample code testLoadImageMT.cc
:
#include <atomic>
#include <chrono>
#include <mutex>
#include <thread>
#include <QtWidgets>
// manually added types (normally provided by glib)
typedef unsigned guint;
typedef unsigned char guint8;
// the fluffy-cat image sample
struct Image {
guint width;
guint height;
guint bytes_per_pixel; /* 3:RGB, 4:RGBA */
guint8 pixel_data[1];
};
extern "C" const Image fluffyCat;
class ImageLoader {
private:
const Image &_img;
std::atomic<bool> _exit;
std::mutex _lock;
QImage _qImg;
std::thread _thread;
public: // main thread API
ImageLoader(const Image &img = fluffyCat):
_img(img),
_qImg(img.width, img.height, QImage::Format_RGB888),
_exit(false), _thread(&ImageLoader::loadImage, std::ref(*this))
{ }
~ImageLoader()
{
_exit = true;
_thread.join();
}
ImageLoader(const ImageLoader&) = delete;
void applyImage(QLabel &qLblImg)
{
std::lock_guard<std::mutex> lock(_lock);
qLblImg.setPixmap(QPixmap::fromImage(_qImg));
}
private: // thread private
void loadImage()
{
for (;;) {
{ std::lock_guard<std::mutex> lock(_lock);
_qImg.fill(0);
}
size_t i = 0;
for (int y = 0; y < (int)_img.height; ++y) {
for (int x = 0; x < (int)_img.width; ++x) {
const quint32 value
= _img.pixel_data[i + 2]
| (_img.pixel_data[i + 1] << 8)
| (_img.pixel_data[i + 0] << 16)
| (0xff << 24);
i += _img.bytes_per_pixel;
{ std::lock_guard<std::mutex> lock(_lock);
_qImg.setPixel(x, y, value);
}
if (_exit) return; // important: make thread co-operative
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // slow down CPU cooler
}
}
}
};
int main(int argc, char **argv)
{
// settings:
enum { N = 3 }; // number of images loaded/displayed
enum { Interval = 50 }; // update rate for GUI 50 ms -> 20 Hz (round about)
// build appl.
qDebug() << "Qt Version: " << QT_VERSION_STR;
QApplication app(argc, argv);
// build GUI
QWidget qMainWin;
QVBoxLayout qVBox;
QLabel *pQLblImgs[N];
for (int i = 0; i < N; ++i) {
qVBox.addWidget(
new QLabel(QString::fromUtf8("Image %1").arg(i + 1)));
qVBox.addWidget(
pQLblImgs[i] = new QLabel());
}
qMainWin.setLayout(&qVBox);
qMainWin.show();
// build image loaders
ImageLoader imgLoader[N];
// install timer
QTimer qTimer;
qTimer.setInterval(Interval); // ms
QObject::connect(&qTimer, &QTimer::timeout,
[&imgLoader, &pQLblImgs]() {
for (int i = 0; i < N; ++i) {
imgLoader[i].applyImage(*pQLblImgs[i]);
}
});
qTimer.start();
// exec. application
return app.exec();
}
Sorry, I used std::thread
instead of boost::thread
as I've no experience with the latter, nor a working installation. I believe (hope) the differences will be marginal. QThread
would have been the "Qt native" alternative but again – no experiences.
To keep things simple, I just copied data out of a linked binary image (instead of loading one from file or from anywhere else). Hence, a second file has to be compiled and linked to make this an MCVE – fluffyCat.cc
:
/* GIMP RGB C-Source image dump (fluffyCat.cc) */
// manually added types (normally provided by glib)
typedef unsigned guint;
typedef unsigned char guint8;
extern "C" const struct {
guint width;
guint height;
guint bytes_per_pixel; /* 3:RGB, 4:RGBA */
guint8 pixel_data[16 * 16 * 3 + 1];
} fluffyCat = {
16, 16, 3,
"x211s215232200gw`fx`at[cx^cw^fu\itZerWn|ap~cv204jnzedq^fr^kzfhv^Ra"
"GRbMWdR\jXer^qw_311256226271253235275264252315277260304255"
"231u~i213225207l{fly`jx\^nRlz_z206nlx`t~i221211s372276243375"
"336275376352340356312301235216212judgwcl~f212226u}206h212"
"224q231237z232236{216225v225230200306274244376360327376"
"361331376360341326275272253240244{203p202220xp~e{204^222"
"230n212217g240242{234236z214222r270271247360353340376370"
"336376363334375357336310254262232223234\gRfrX204220z212"
"225g225232j254255177252250{225226u304302265374365351376"
"375366376367341376361320374346324306241242237232235n{fj"
"xckyfu~fUX@VZCfnT231231207374374371377372354376376374376376"
"372376362332375340301341300264260253262jvdbq\XkVJTDNTCCG8O"
"TE322321313377377375376376373376377376376376375376374362"
"376360342344311306250244254R_PL^HXkT<@2OP@`dP217220177374374"
"370377377374376375371377377376376374360377367336376350"
"316342303274246236245jtbXdQTdNQYGU\KchV317315302377376372377"
"376367376373360377376367376366337376355312374331271323"
"263251216214214\hTP^HL\FR[LMXI^dW355352342376375366377374"
"360376374361376374361376356321374331264374330266330270"
"260200||Y`SLVE>K9BJ<CN?VYP347330322376366345376363330376367"
"337377372350374342314326243210375350314352317304shc^`TV`"
"RVbT>B4IS?PTD244232216374355320376354311376351306376362332"
"374344321267206u375362337326274272\POMNBT]LNZH:<*<A*TV>OI;242"
"222207340304243375335262372336272376361334320241212374"
"352322266233237c\WFH;MR>\`F~xP220214[pqE211202\g]=230214`313"
"266207344303240362336274323257201333304240305252204254"
"232p216206\206203U232224b234244b246257m220232`224227h~202"
"W206213]204210W227227i|177RvzNlsGrtJwtLz}N{204RlxF",
};
I compiled and tested in VS2013, with Qt 5.9.2 on Windows 10 (64 bit). This is how it looks: