QImage 和线程 [英] QImage and Threads

查看:55
本文介绍了QImage 和线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用 QImages 和 Qthreads 时遇到问题.我正在尝试在线程中加载大图像,然后在 QLabel 上将它们显示为 QPixmap.我的问题是,只要我不使用不同的线程来加载 QImages,一切都是完美的,但是只要我使用不同的线程,就没有渲染器.虽然我的 QImage 仍然有一个有效的大小.

I am having problems with QImages and Qthreads. I am trying to load big images in a Thread and then display them as QPixmap on a QLabel. My problem is that as long as I don't use a different thread to load the QImages, everything is perfect but as soon as I use a different thread, nothing is renderder. Though I still have a valid size for my QImage.

让我感到困惑的是,如果我只是评论 cpp 中将加载器移动到另一个线程的第 22 行,标签显示得很好.

The thing that puzzles me is that, if I just comment the 22nd line in the cpp that moves the loader to the other thread, the label displays nicely.

有人有想法吗?

这是我非常简化的代码:标题:

Here is my very simplified code: Header :

class Loader : public QObject
{
    Q_OBJECT
public:
    explicit Loader(QObject *parent = 0);

signals:
    void imageLoaded(QString, const QImage &);
public slots:
    void loadImage(const QString& fichier);
};

namespace Ui {
class MainWindow;
}
class LoaderImages;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

signals:
    void loadImage(const QString& dossier);
private slots:
    void imageAvailable(const QString& dossier, const QImage& img);

private:
    Ui::MainWindow *ui;
    //QString mDossier;
    Loader* mLoader;
    //QMap<QString, QImage*> mMapDesImages;
    int mWidth;
};

cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFile>
#include <QPixmap>
#include <QImage>
#include <QDir>
#include <QThread>
#include <QDebug>
#include <QLabel>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    mLoader(new Loader(NULL)),
    mWidth(0)
{
    ui->setupUi(this);

    QThread* thread = new QThread(this);
    mLoader->moveToThread(thread);
    thread->start();

    connect(this, SIGNAL(loadImage(QString)), mLoader, SLOT(loadImage(QString)));
    connect(mLoader, SIGNAL(imageLoaded(QString,QImage)), this, SLOT(imageAvailable(QString,QImage)));

    emit loadImage("C:/img.jpg");
}

void MainWindow::imageAvailable(const QString &dossier, const QImage& img)
{
    mWidth += (img.width() + 20);
    ui->mScrollContent->setMinimumSize(mWidth,img.height());
    QLabel* lab = new QLabel(ui->mScrollContent);
    lab->setFixedSize(img.width(), img.height());
    lab->setGeometry(mWidth - img.width() + 20, 0, img.width(), img.height());
    lab->setPixmap(QPixmap::fromImage(img));
}

MainWindow::~MainWindow()
{
    delete mLoader;
    delete ui;
}

Loader::Loader(QObject *parent) :
    QObject(parent)
{
}


void Loader::loadImage(const QString& fichier)
{
    QImage* image = new QImage(fichier);

    emit imageLoaded(fichier, *image);
}

谢谢!

推荐答案

有几个错误:

  1. 您没有显示标签.当图像加载器在 GUI 线程中时,图像被加载并且标签在主窗口显示之前被添加到内容窗格.由于显示了父级,因此子级变得可见.

  1. You're not showing the label. When the image loader is in the GUI thread, the image is loaded and the label added to the contents pane before the main window is shown. Since the parent is shown, the children become visible.

在另一个线程中完成加载后,您将向已显示的小部件添加图像标签.除非您明确show(),否则此类子小部件可见.

When the loading is done in another thread, you'll be adding image labels to a widget that's already shown. Such child widgets are not visible unless you explicitly show() them.

您在 loadImage 中泄露了图像.没有理由将 QImage 放在堆上.

You're leaking the image in loadImage. There's no reason to put that QImage on the heap.

您允许破坏正在运行的 QThread.这是一个常见的错误,因为 QThread 基本上被设计破坏了.理智的 C++ 类应该总是可破坏的.QThread 不是.因此,您需要一个解决方法.

You're allowing a running QThread to be destructed. That's a common error since QThread is essentially broken by design. Sane C++ classes should be always destructible. QThread isn't. Thus you need a workaround.

您还没有设置内容小部件的最小高度.

You're not setting the minimum height of the contents widget as well.

您可能希望考虑使用 QtConcurrent::run 而不是专用线程.当您进行的操作或多或少是单班轮时,这尤其值得.我已经展示了两者,实现在运行时交替.注意需要在项目文件中添加concurrent模块和CONFIG += c++11.

You might wish to consider the use QtConcurrent::run instead of a dedicated thread. This is especially worthwhile when the operation you're undertaking is a one liner, more or less. I've shown both, the implementations are alternated between at runtime. Note that you need to add the concurrent module and CONFIG += c++11 to the project file.

风格错误:

  • 没有理由为已经为零的默认值参数传递 NULL.

  • There's no reason to pass NULL for default-valued parameters that are already zero.

没有理由将具有父对象生命周期的 QObject 成员保留在堆上,如果这些成员是与父对象一起构造的.

There's no reason to keep QObject members that have the lifetime of the parent object on the heap, if such members are constructed along with the parent object.

仅仅因为 Qt Creator 带有愚蠢的模板文件并不意味着您不应该使用 std::unique_ptrQScopedPointer 来保存ui 成员.裸指针几乎永远不应该是成员,除非它们是指向带有父对象的 QObjects 的指针.

Just because Qt Creator comes with silly template files doesn't mean that you shouldn't be using a std::unique_ptr or QScopedPointer to hold the ui member. Naked pointers should almost never be members unless they're pointers to QObjects with parents.

由于缺少相当多的代码,我真的不知道还有什么可能是错误的.下面是一个完整的例子.

As quite a bit of the code is missing, I can't really tell what else might be wrong. Below is a complete example.

// https://github.com/KubaO/stackoverflown/tree/master/questions/image-loader-24853687
#include <QtWidgets>
#include <QtConcurrent>

class Thread final : public QThread {
public:
    ~Thread() { quit(); wait(); }
};

class Loader : public QObject
{
    Q_OBJECT
public:
    explicit Loader(QObject *parent = nullptr) : QObject(parent) {}
    Q_SIGNAL void imageLoaded(const QString &, const QImage &);
    Q_SLOT void loadImage(const QString& fichier) {
        QImage img(fichier);
        if (! img.isNull()) emit imageLoaded(fichier, img);
    }
};

class MainWindow : public QWidget
{
    Q_OBJECT
    Loader m_loader;
    Thread m_loaderThread;
    QGridLayout m_layout{this};
    QPushButton m_open{"Open"};
    QScrollArea m_view;
    QWidget m_content;
    int m_width{};
    bool m_threadImpl = true;
    Q_SIGNAL void loadImage(const QString &);
    Q_SIGNAL void imageLoaded(const QString &, const QImage & img);
    Q_SLOT void imageAvailable(const QString &, const QImage & img) {
        int spacing = 20;
        if (m_width) m_width += spacing;
        auto lab = new QLabel(&m_content);
        lab->setFixedSize(img.width(), img.height());
        lab->setGeometry(m_width, 0, img.width(), img.height());
        lab->setPixmap(QPixmap::fromImage(img));
        lab->show();
        m_width += img.width();
        m_content.setMinimumWidth(m_width);
        m_content.setMinimumHeight(qMax(m_content.minimumHeight(), img.height()));
    }
    Q_SLOT void open() {
        auto dialog = new QFileDialog(this);
        dialog->setAttribute(Qt::WA_DeleteOnClose);
        dialog->show();
        if (m_threadImpl)
            connect(dialog, &QFileDialog::fileSelected, this, &MainWindow::loadImage);
        else
            connect(dialog, &QFileDialog::fileSelected, [this](const QString & fichier){
                QtConcurrent::run([this, fichier]{
                    QImage img(fichier);
                    if (! img.isNull()) emit this->imageLoaded(fichier, img);
                });
            });
        m_threadImpl = !m_threadImpl;
    }
public:
    explicit MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        m_layout.addWidget(&m_open);
        m_layout.addWidget(&m_view);
        m_view.setWidget(&m_content);
        m_loader.moveToThread(&m_loaderThread);
        m_loaderThread.start();
        connect(&m_open, &QPushButton::clicked, this, &MainWindow::open);
        connect(this, &MainWindow::loadImage, &m_loader, &Loader::loadImage);
        connect(this, &MainWindow::imageLoaded, this, &MainWindow::imageAvailable);
        connect(&m_loader, &Loader::imageLoaded, this, &MainWindow::imageAvailable);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

#include "main.moc"

这篇关于QImage 和线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆