Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
655 views
in Technique[技术] by (71.8m points)

multithreading - Qt C++ Displaying images outside the GUI thread (Boost thread)

I am developing a C++ library realizing its interface by means of Qt, using VS2015. On the library side, 3 boost threads continously load images from 3 folders. I am trying to display these images in 3 different QLabel (or equivalent QWidgets), so the thread body consists of this functionality, in particular by exploiting the setPixmap method. Although the call to the function is protected by a boost mutex, I got exceptions probably due to threads synchronization. Looking for a solution, I already awared that the QPixmap widget is not "thread-safe" (non-reentrant). I also tried to use QGraphicsView but it in turn relies on QPixmap, thus I came across the same problem. So my question is: does an alternative to QPixmap exist to display images in Qt in a thread-safe manner?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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 MCVEfluffyCat.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:

Snapshot of testLoadImageMT


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...