Qt C++ 在 GUI 线程(Boost 线程)之外显示图像
Posted
技术标签:
【中文标题】Qt C++ 在 GUI 线程(Boost 线程)之外显示图像【英文标题】:Qt C++ Displaying images outside the GUI thread (Boost thread) 【发布时间】:2017-11-23 13:41:20 【问题描述】:我正在使用 VS2015 开发一个通过 Qt 实现其接口的 C++ 库。在图书馆方面,3 个 boost 线程 不断地从 3 个文件夹加载图像。我试图在 3 个不同的 QLabel(或等效的QWidgets)中显示这些图像,因此线程主体包含此功能, 特别是通过利用 setPixmap 方法。尽管对该函数的调用受到 boost 互斥锁的保护,但我可能由于线程同步而遇到异常。在寻找解决方案时,我已经意识到 QPixmap 小部件不是“线程安全”(不可重入)。我也尝试使用QGraphicsView,但它又依赖于QPixmap,因此我遇到了同样的问题。 所以我的问题是:QPixmap 的替代品是否存在以线程安全的方式在 Qt 中显示图像 方式?
【问题讨论】:
我不会混合 GUI 编程和多线程 - 坏主意。相反,您可以尝试这样做:在提升映射中使用“私有”像素图(甚至更简单的缓冲区)。这个像素图/缓冲区必须由互斥体保存。 GUI 线程可以定期(QTimer)锁定该私有像素图/缓冲区,并在 GUI 中使用的相应像素图中复制其当前内容。 【参考方案1】:我建议不要在 GUI 编程中使用多线程。虽然 Qt 通常提供多线程支持,但恕我直言,小部件并没有为此做好充分准备。
因此,为了实现在不同线程中同时运行的图像加载器,我建议采用以下概念:
每个线程图像加载器都提供一个私有缓冲区。 GUI 不时检查(使用QTimer
)这些缓冲区并更新其QPixmap
。因为应该可以从 resp 访问缓冲区。当然,图像加载器线程以及 GUI 线程都必须受到互斥保护。
我的示例代码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();
抱歉,我使用 std::thread
而不是 boost::thread
,因为我没有使用后者的经验,也没有安装工作。我相信(希望)差异将是微不足道的。 QThread
本来是“Qt native”的替代品,但又一次——没有经验。
为了简单起见,我只是从链接的二进制图像中复制数据(而不是从文件或其他任何地方加载数据)。因此,必须编译和链接第二个文件以使其成为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,
"x\211s\215\232\200gw`fx`at[cx^cw^fu\\itZerWn|ap~cv\204jnzedq^fr^kzfhv^Ra"
"GRbMWdR\\jXer^qw_\311\256\226\271\253\235\275\264\252\315\277\260\304\255"
"\231u~i\213\225\207lfly`jx\\^nRlz_z\206nlx`t~i\221\211s\372\276\243\375"
"\336\275\376\352\340\356\312\301\235\216\212judgwcl~f\212\226u\206h\212"
"\224q\231\237z\232\236\216\225v\225\230\200\306\274\244\376\360\327\376"
"\361\331\376\360\341\326\275\272\253\240\244\203p\202\220xp~e\204^\222"
"\230n\212\217g\240\242\234\236z\214\222r\270\271\247\360\353\340\376\370"
"\336\376\363\334\375\357\336\310\254\262\232\223\234\\gRfrX\204\220z\212"
"\225g\225\232j\254\255\177\252\250\225\226u\304\302\265\374\365\351\376"
"\375\366\376\367\341\376\361\320\374\346\324\306\241\242\237\232\235nfj"
"xckyfu~fUX@VZCfnT\231\231\207\374\374\371\377\372\354\376\376\374\376\376"
"\372\376\362\332\375\340\301\341\300\264\260\253\262jvdbq\\XkVJTDNTCCG8O"
"TE\322\321\313\377\377\375\376\376\373\376\377\376\376\376\375\376\374\362"
"\376\360\342\344\311\306\250\244\254R_PL^HXkT<@2OP@`dP\217\220\177\374\374"
"\370\377\377\374\376\375\371\377\377\376\376\374\360\377\367\336\376\350"
"\316\342\303\274\246\236\245jtbXdQTdNQYGU\\KchV\317\315\302\377\376\372\377"
"\376\367\376\373\360\377\376\367\376\366\337\376\355\312\374\331\271\323"
"\263\251\216\214\214\\hTP^HL\\FR[LMXI^dW\355\352\342\376\375\366\377\374"
"\360\376\374\361\376\374\361\376\356\321\374\331\264\374\330\266\330\270"
"\260\200||Y`SLVE>K9BJ<CN?VYP\347\330\322\376\366\345\376\363\330\376\367"
"\337\377\372\350\374\342\314\326\243\210\375\350\314\352\317\304shc^`TV`"
"RVbT>B4IS?PTD\244\232\216\374\355\320\376\354\311\376\351\306\376\362\332"
"\374\344\321\267\206u\375\362\337\326\274\272\\POMNBT]LNZH:<*<A*TV>OI;\242"
"\222\207\340\304\243\375\335\262\372\336\272\376\361\334\320\241\212\374"
"\352\322\266\233\237c\\WFH;MR>\\`F~xP\220\214[pqE\211\202\\g]=\230\214`\313"
"\266\207\344\303\240\362\336\274\323\257\201\333\304\240\305\252\204\254"
"\232p\216\206\\\206\203U\232\224b\234\244b\246\257m\220\232`\224\227h~\202"
"W\206\213]\204\210W\227\227i|\177RvzNlsGrtJwtLzN\204RlxF",
;
我在 VS2013 中编译和测试,在 Windows 10(64 位)上使用 Qt 5.9.2。看起来是这样的:
【讨论】:
有趣的答案,谢谢!顺便说一句,我使用信号/插槽解决了!【参考方案2】:我使用信号/槽解决了:“非 GUI”线程发出信号而不是显示图像,并且被调用的槽在 GUI 线程内绘制 QLabel!
【讨论】:
以上是关于Qt C++ 在 GUI 线程(Boost 线程)之外显示图像的主要内容,如果未能解决你的问题,请参考以下文章
BOOST 如何在一个线程中发送信号并在另一个线程中执行相应的插槽?