Qt例子学习笔记 - Examples/Qt-6.2.0/corelib/threads/queuedcustomtype

71 篇文章 4 订阅

//在自定义类型示例中,我们展示了如何将自定义类型与元对象系统集成
//使它们能够存储在 QVariant 对象中,写入调试信息并用于信号槽通信
//在这个例子中,我们创建了一个新的值类 Block
//并将它注册到元对象系统
//使我们能够使用排队的信号和槽在线程之间发送它的实例。

//The Block Class
//Block 类类似于自定义类型示例中描述的 Message 类
//它在元对象系统所需的类的公共部分提供默认构造函数
//复制构造函数和析构函数。 它描述了一个彩色矩形。

    class Block
    {
    public:
        Block();
        Block(const Block &other);
        Block(const QRect &rect,const QColor &color);

        QColor color() const;
        QRect rect() const;

    private:
        QRect m_rect;
        QColor m_color;
    };

    Q_DECLARE_METATYPE(Block);

//我们仍然需要在运行时通过调用 qRegisterMetaType()
//模板函数在元对象系统中注册它
//然后我们才能进行任何使用这种类型的信号槽连接。
//尽管我们不打算在这个例子中使用带有 QVariant 的类型
//但最好也用 Q_DECLARE_METATYPE() 声明新类型。
//Block 类的实现是微不足道的,所以我们避免在这里引用它。

//The Window Class
//我们定义了一个简单的 Window 类,它带有一个接受 Block 对象的公共槽。
//该类的其余部分与管理用户界面和处理图像有关。

    class Window : public QWidget
    {
        Q_OBJECT
    public:
        Window(QWidget *parent = nullptr);
        void loadImage(const QImage &image);
    public slots:
        void addBlock(const Block &block);
    public slots:
        void loadImage();
        void resetUi();
    private:
        QLabel *label;
        QPixmap pixmap;
        QPushButton *loadButton;
        QPushButton *resetButton;
        QString path;
        RenderThread *thread;
    };

//Window 类还包含一个工作线程
//由 RenderThread 对象提供
//这将发出信号以将 Block 对象发送到窗口的 addBlock(Block) 槽。
//Window 类还包含一个工作线程,由 RenderThread 对象提供。
//这将发出信号以将 Block 对象发送到窗口的 addBlock(Block) 槽。
//Window 类中最相关的部分是构造函数和 addBlock(Block) 槽。
//构造函数创建一个用于渲染图像的线程
//设置一个包含标签和两个按钮的用户界面,这些按钮连接到同一类中的插槽。

    Window::Window(QWidget *parent)
        :QWidget(parent),thread(new RenderThread(this))
    {
        label = new QLabel(this);
        label->setAlignment(Qt::AlignCenter);

        loadButton = new QPushButton(tr("&Load image ..."),this);
        resetButton = new QPushButton(tr("&Stop"),this);
        resetButton->setEnable(false);

        connect(loadButton,&QPushButton::clicked,
                this,QOverload<>::of(&Window::loadImage));
        connect(resetButton,&QPushButton::clicked,
                thread,&RenderThread::stopProcess);
        connect(thread,&RenderThread::finished,
                this,&Window::resetUi);
        connect(thread,&RenderThread::sendBlock,
                this,&Window::addBlock);
    }

//在最后一个连接中
//我们将 RenderThread 对象中的信号连接到窗口中的 addBlock(Block) 槽

//构造函数的其余部分只是设置窗口的布局。
//adBlock(Block) 插槽接收器通过构造函数中设置的信号插槽连接从渲染线程接收块:

    void Window::addBlock(const Block& block)
    {
        QColor color = block.color();
        color.setAlpha(64);

        QPainter painter;
        painter.begin(&pixmap);
        painter.fillRect(block.rect(),color);
        painter.end();
        label->setPixmap(pixmap);
    }

//我们只是在它们到达时将它们涂在标签上。

//RenderThread 类
//RenderThread 类处理图像
//创建 Block 对象并使用 sendBlock(Block) 信号将它们发送到示例中的其他组件。

    class RenderThread : public QThread 
    {
        Q_OBJECT
    public:
        RenderThread(QObject *parent = nullptr);
        ~RenderThread();

        void processImage(const QImage &image);
    signals:
        void sendBlock(const Block &block);
    public slots:
        void stopProcess();
    protected:
        void run();
    private:
        bool m_abort;
        QImage m_image;
        QMutex mutex;
    };

//这里没有引用构造函数和析构函数。
//它们负责设置线程的内部状态并在它被销毁时进行清理。
//处理从 processImage() 函数开始
//它调用 RenderThread 类对 QThread::run() 函数的重新实现:

    void RenderThread::processImage(const QImage &image)
    {
        if(image.isNull())
            return;

        m_image = image;
        m_abort = false;
        start();
    }

    void RenderThread::run()
    {
        int size = qMax(m_image.width() / 20, m_image.height()/20);
        for(int s = size; s > 0; --s)
        {
            for(int c = 0; c < 400; ++c)
            {
                //忽略图像处理方式的细节
                //我们看到包含块的信号以通常的方式发射
                //忽略图像处理方式的细节
                //我们看到包含块的信号以通常的方式发射 道路:
//....
                Block block(QRect(x1,y1,x2 - x1 + 1,y2 - y1 + 1),
                            QColor(red/n,green/n,blue/n));
                emit sendBlock(block);
                if(m_abort)
                    return;
                msleep(10);
        }
    }

//发出的每个信号都将排队并稍后传送到 windows adBlock(Block) 插槽。
//Registering the Type
//在示例的 main() 函数中
//我们通过调用 qRegisterMetaType() 模板函数将 Block 类注册为元对象系统的自定义类型:

    int main(int argc,char *argv[])
    {
        QApplication app(argc,argv);
        qRegisterMetaType<Block>();

        Window window;
        window.show();

        window.loadImage(createImage(256, 256));
        return app.exe();
    }

//这个调用放在这里是为了确保在使用它的任何信号槽连接之前注册类型。
//main() 函数的其余部分与为伪随机数生成器设置种子
//创建和显示窗口以及设置默认图像有关
//请参阅 createImage() 函数的实现源代码。
//Further Reading
//这个例子展示了如何在元对象系统中注册一个自定义类型
//以便它可以与线程之间的信号和槽连接一起使用
//对于涉及直接信号和槽的普通通信
//按照自定义类型示例中的描述声明类型就足够了。
//实际上,Q_DECLARE_METATYPE() 宏和 qRegisterMetaType()
//模板函数都可以用来注册自定义类型
//但是 qRegisterMetaType() 只有在需要与信号槽通信或创建和销毁自
//定义对象时才需要在运行时键入 类型。
//有关在 Qt 中使用自定义类型的更多信息,请参阅有关创建自定义 Qt 类型的文档

main.cpp

#include <QApplication>
#include <QPainter>
#include <QTime>
#include "block.h"
#include "window.h"

//Qt 提供了四个用于处理图像数据的类:QImage、QPixmap、QBitmap 和 QPicture。
//QImage 是为 I/O 以及直接像素访问和操作而设计和优化的
//而 QPixmap 是为在屏幕上显示图像而设计和优化的。
//QBitmap只是一个继承QPixmap的便利类,保证深度为1。
//最后,QPicture类是一个记录和重放QPainter命令的绘图设备。
//因为 QImage 是 QPaintDevice 的子类,
//所以可以使用 QPainter 直接在图像上绘制。
//在 QImage 上使用 QPainter 时,可以在当前 GUI 线程之外的另一个线程中执行绘制。
//QImage 类支持由 Format 枚举描述的几种图像格式。
//这些包括单色、8 位、32 位和 alpha 混合图像,
//这些图像在 Qt 4.x 的所有版本中都可用。
//QImage 提供了一组函数,可用于获取有关图像的各种信息。 还有几个功能可以转换图像。
//QImage 对象可以按值传递,因为 QImage 类使用隐式数据共享。
//QImage 对象也可以流式传输和比较。
//注意:如果您想在 Qt 的静态构建中加载 QImage 对象,请参阅插件 HowTo。
//警告:不支持使用 QImage::Format_Indexed8 格式在 QImage 上绘画。
//读取和写入图像文件
//QImage 提供了几种加载图像文件的方法:
//可以在构造 QImage 对象时加载文件,或者稍后使用 load() 或 loadFromData() 函数加载文件。
//QImage 还提供了静态的 fromData() 函数
//从给定的数据构造一个 QImage。
//加载图像时,文件名可以指磁盘上的实际文件
//也可以指应用程序的嵌入资源之一
//有关如何在应用程序的可执行文件中嵌入图像和其他资源文件的详细信息
//只需调用 save() 函数即可保存 QImage 对象。
//支持的文件格式的完整列表可通过 QImageReader::supportedImageFormats()
//和 QImageWriter::supportedImageFormats() 函数获得。
//新的文件格式可以作为插件添加。
//默认情况下,Qt 支持以下格式:

QImage createImage(int width, int height)
{
    QImage image(width, height, QImage::Format_RGB16);
    //QPainter 提供高度优化的功能来完成大多数绘图 GUI 程序所需的工作。
    //它可以绘制从简单线条到复杂形状(如馅饼和和弦)的所有内容。
    //它还可以绘制对齐的文本和像素图。
    //通常,它在“自然”坐标系中绘制,但它也可以进行视图和世界变换。
    //QPainter 可以对任何继承 QPaintDevice 类的对象进行操作。
    //QPainter 的常见用途是在小部件的绘制事件中:
    //构造和自定义(例如设置笔或画笔)画家。
    //然后画。 记得在绘制后销毁 QPainter 对象。 例如:
    ```cpp
        void SimpleExampleWidget::paintEvent(QPaintEvent *)
        {
            QPainter painter(this);
            painter.setPen(Qt::blue);
            painter.setFont(QFont("Arial",30));
            painter.drawText(rect(), Qt::AlignCenter,"Qt");
        }
    ```
    //QPainter 的核心功能是绘图
    //但该类还提供了几个功能
    //允许您自定义 QPainter 的设置及其渲染质量
    //以及其他启用剪辑的功能。
    //此外,您可以通过指定画家的合成模式来控制不同形状的合并方式。
    //QPainter 的核心功能是绘图
    //但该类还提供了几个功能
    //允许您自定义 QPainter 的设置及其渲染质量
    //以及其他启用剪辑的功能 .
    //外,您可以通过指定画家的构图模式来控制如何将不同的形状合并在一起。
    //isActive() 函数指示画家是否处于活动状态
    //画家由 begin() 函数和接受 QPaintDevice 参数的构造函数激活
    //end() 函数和析构函数将其停用。
    //与 QPaintDevice 和 QPaintEngine 类一起
    //QPainter 构成了 Qt 绘画系统的基础。
    //QPainter 是用于执行绘图操作的类。
    //QPaintDevice 表示可以使用 QPainter 绘制的设备。
    //QPaintEngine 提供了画家用来绘制不同类型设备的接口。
    //如果画家处于活动状态,则 device() 返回画家绘画的绘画设备
    //paintEngine() 返回画家当前正在操作的绘画引擎。 有关详细信息,请参阅绘制系统。
    //有时希望让其他人在不寻常的 QPaintDevice 上绘画
    //QPainter 支持一个静态函数来做到这一点,setRedirected()。

    QPainter painter;
    QPen pen;
    pen.setStyle(Qt::NoPen);
    QBrush brush(Qt::blue);

    //开始绘制绘制设备,如果成功则返回 true; 否则返回false。
    //请注意,调用 begin() 时,所有画家设置(setPen()、setBrush() 等)都将重置为默认值。
    //可能发生的错误是严重的问题,例如:
    ```cpp
        painter->begin(0); //impossible - paint device cannot be 0
        
        QPixmap image(0,0);
        painter->begin(&image); //impossible - image.isNull() == true

        painter->begin(myWidget);
        painter2->begin(myWidget); //impossible - only one painter at a time        
    ```
    painter.begin(&image);
    //void QPainter::fillRect(const QRectF &rectangle, const QBrush &brush)
    //用指定的画笔填充给定的矩形。
    //或者,您可以指定 QColor 而不是 QBrush;
    //QBrush 构造函数(采用 QColor 参数)将自动创建一个纯图案画笔。
    painter.fillRect(image.rect(), brush);
    brush.setColor(Qt::white);
    painter.setPen(pen);
    painter.setBrush(brush);

    //一个点由 x 坐标和 y 坐标指定,可以使用 x() 和 y() 函数访问它们。
    //为了精确起见,使用有限浮点数指定点的坐标。
    //如果 x 和 y 都设置为 0.0,则 isNull() 函数返回 true。
    //可以使用 setX() 和 setY() 函数设置(或更改)坐标
    //或者使用返回坐标引用的 rx() 和 ry() 函数(允许直接操作)。
    //给定一个点 p,以下语句都是等价的:
    ```cpp
        QPointF p;
        p.setX(p.x() + 1.0);
        p += QPointF(1.0,0.0);
        p.rx()++;
    ```
    //QPointF 对象也可以用作向量:
    //加法和减法的定义与向量相同(每个分量单独相加)。
    //QPointF 对象也可以除以或乘以 int 或 qreal。
    //此外,QPointF 类提供了一个将 QPoint 对象转换为 QPointF
    //对象的构造函数
    //以及相应的 toPoint() 函数,该函数返回该点的 QPoint 副本。
    //最后,QPointF 对象可以进行流式传输和比较。

    static const QPointF points1[3] = {
        QPointF(4, 4),
        QPointF(7, 4),
        QPointF(5.5, 1)
    };

    static const QPointF points2[3] = {
        QPointF(1, 4),
        QPointF(7, 4),
        QPointF(10, 10)
    };

    static const QPointF points3[3] = {
        QPointF(4, 4),
        QPointF(10, 4),
        QPointF(1, 10)
    };
    //绘画设备是二维空间的抽象
    //可以使用 QPainter 进行绘制。
    //其默认坐标系的原点位于左上角位置。
    //X 向右增加,Y 向下增加。 单位是一个像素。
    //QPaintDevice 的绘图功能目前由 QWidget、QImage、
    //QPixmap、QPicture 和 QPrinter 子类实现。
    //要实现对新后端的支持
    //您必须从 QPaintDevice 派生并重新实现虚拟 paintEngine() 函数
    //以告诉 QPainter 应该使用哪个绘制引擎在此特定设备上绘制。
    //请注意,您还必须创建相应的绘图引擎才能在设备上绘图
    //即从 QPaintEngine 派生并重新实现其虚拟功能。
    //警告:Qt 要求在创建任何绘制设备之前存在 QGuiApplication 对象。
    //绘制设备访问窗口系统资源
    //这些资源在创建应用程序对象之前不会被初始化。
    //QPaintDevice 类提供了几个返回各种设备指标的函数
    //depth() 函数返回其位深度(位平面的数量)。
    //height() 函数以默认坐标系单位(例如 QPixmap 和 QWidget 的像素)返回其高度
    //而 heightMM() 以毫米为单位返回设备的高度。
    //同样, width() 和 widthMM() 函数分别以默认坐标系单位和毫米为单位返回设备的宽度。
    //或者,protected metric() 函数可用于通过指定所需的 PaintDeviceMetric 作为参数来检索指标信息。
    //logicalDpiX() 和 logicalDpiY() 函数返回设备的水平和垂直分辨率(以每英寸点数为单位)。
    //physicalDpiX() 和 physicalDpiY() 函数还以每英寸点数为单位返回设备的分辨率
    //但请注意,如果逻辑分辨率和物理分辨率不同
    
    painter.setWindow(0, 0, 10, 10);

    int x = 0;
    int y = 0;
    int starWidth = image.width()/3;
    int starHeight = image.height()/3;

    QRect rect(x, y, starWidth, starHeight);

    for (int i = 0; i < 9; ++i) {

        painter.setViewport(rect);
        painter.drawPolygon(points1, 3);
        painter.drawPolygon(points2, 3);
        painter.drawPolygon(points3, 3);

        if (i % 3 == 2) {
            y = y + starHeight;
            rect.moveTop(y);

            x = 0;
            rect.moveLeft(x);

        } else {
            x = x + starWidth;
            rect.moveLeft(x);
        }
    }

    painter.end();
    return image;
}

//! [main function] //! [main start]
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
//! [main start] //! [register meta-type for queued communications]
    qRegisterMetaType<Block>();
//! [register meta-type for queued communications]

    Window window;
    window.show();

    window.loadImage(createImage(1024, 520));
//! [main finish]
    return app.exec();
}
//! [main finish] //! [main function]

window.h

#ifndef WINDOW_H
#define WINDOW_H

#include <QWidget>
#include "renderthread.h"

QT_BEGIN_NAMESPACE
class QLabel;
class QPushButton;
QT_END_NAMESPACE

//! [Window class definition]
class Window : public QWidget
{
    Q_OBJECT

public:
    Window(QWidget *parent = nullptr);
    void loadImage(const QImage &image);

public slots:
    void addBlock(const Block &block);

private slots:
    void loadImage();
    void resetUi();

private:
    QLabel *label;
    QPixmap pixmap;
    QPushButton *loadButton;
    QPushButton *resetButton;
    QString path;
    RenderThread *thread;
};
//! [Window class definition]

#endif
#include "window.h"
#include <QtWidgets>

//! [Window constructor start]
Window::Window(QWidget *parent)
    : QWidget(parent), thread(new RenderThread(this))
{
//! [Window constructor start] //! [set up widgets and connections]

    label = new QLabel(this);
    label->setAlignment(Qt::AlignCenter);

    loadButton = new QPushButton(tr("&Load image..."), this);
    resetButton = new QPushButton(tr("&Stop"), this);
    resetButton->setEnabled(false);

    connect(loadButton, &QPushButton::clicked,
            this, QOverload<>::of(&Window::loadImage));
    connect(resetButton, &QPushButton::clicked,
            thread, &RenderThread::stopProcess);
    connect(thread, &RenderThread::finished,
            this, &Window::resetUi);
//! [set up widgets and connections] //! [connecting signal with custom type]
    connect(thread, &RenderThread::sendBlock,
            this, &Window::addBlock);
//! [connecting signal with custom type]

    QHBoxLayout *buttonLayout = new QHBoxLayout;
    buttonLayout->addStretch();
    buttonLayout->addWidget(loadButton);
    buttonLayout->addWidget(resetButton);
    buttonLayout->addStretch();

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(label);
    layout->addLayout(buttonLayout);

//! [Window constructor finish]
    setWindowTitle(tr("Queued Custom Type"));
}
//! [Window constructor finish]

void Window::loadImage()
{
    QStringList formats;
    const QList<QByteArray> supportedFormats = QImageReader::supportedImageFormats();
    for (const QByteArray &format : supportedFormats)
        if (format.toLower() == format)
            formats.append(QLatin1String("*.") + QString::fromLatin1(format));

    QString newPath = QFileDialog::getOpenFileName(this, tr("Open Image"),
        path, tr("Image files (%1)").arg(formats.join(' ')));

    if (newPath.isEmpty())
        return;

    QImage image(newPath);
    if (!image.isNull()) {
        loadImage(image);
        path = newPath;
    }
}

void Window::loadImage(const QImage &image)
{
    QImage useImage;
    QRect space = QGuiApplication::primaryScreen()->availableGeometry();
    if (image.width() > 0.75*space.width() || image.height() > 0.75*space.height())
        useImage = image.scaled(0.75*space.width(), 0.75*space.height(),
                                Qt::KeepAspectRatio, Qt::SmoothTransformation);
    else
        useImage = image;

    pixmap = QPixmap(useImage.width(), useImage.height());
    pixmap.fill(qRgb(255, 255, 255));
    label->setPixmap(pixmap);
    loadButton->setEnabled(false);
    resetButton->setEnabled(true);
    thread->processImage(useImage);
}

//! [Adding blocks to the display]
void Window::addBlock(const Block &block)
{
    QColor color = block.color();
    color.setAlpha(64);

    QPainter painter;
    painter.begin(&pixmap);
    painter.fillRect(block.rect(), color);
    painter.end();
    label->setPixmap(pixmap);
}
//! [Adding blocks to the display]

void Window::resetUi()
{
    loadButton->setEnabled(true);
    resetButton->setEnabled(false);
}

renderthread.h


#ifndef RENDERTHREAD_H
#define RENDERTHREAD_H

#include <QImage>
#include <QMutex>
#include <QThread>
#include "block.h"
//QThread 对象管理程序中的一个控制线程
//QThreads 在 run() 中开始执行
//默认情况下,run() 通过调用 exec() 启动事件循环,并在线程内运行 Qt 事件循环。
//您可以通过使用 QObject::moveToThread() 将工作对象移动到线程来使用它们。
//注意:如果 QObject 没有线程关联(即,如果 thread() 返回零),或者
//如果它位于没有运行事件循环的线程中,则它无法接收排队信号或发布的事件。
//默认情况下,QObject 存在于创建它的线程中。
//可以使用 thread() 查询对象的线程关联,并使用 moveToThread() 进行更改。
/*
    class Worker : public QObject
    {
        Q_OBJECT
    public slots:
        void doWork(const QString &parameter)
        {
            QString result;
            emit resultReady(result);
        }
    signals:
        void resultReady(const QString &result);
    };

    class Controller : public QObject
    {
        Q_OBJECT
        QThread workerThread;
    public:
        Controller()
        {
            Worker *worker = new Worker;
            worker->moveToThread(&workThread);
            connect(&workerThread,&QThread::finished,worker,&QObject::deleteLater);
            connect(this,&Controller::operate,worker,&Worker::doWork);
            connect(worker,&Worker::resultReady,this,&Controller::handleResults);
            workerThread.start();
        }
        ~Controller()
        {
            workerThread.quit();
            workerThread.wait();
        }
    public slots:
        void handleResults(const QString &);
    signals:
        void operate(const QString& );
    };
*/
//Worker 插槽内的代码将在单独的线程中执行。
//但是,您可以自由地将 Worker 的插槽连接到来自任何对象、
//任何线程中的任何信号。
//由于一种称为排队连接的机制,跨不同线程连接信号和槽是安全的。
//Worker 槽中的代码将在单独的线程中执行
//但是,您可以自由地将 Worker 的插槽连接到来自任何对象、
//任何线程中的任何信号。
//由于一种称为排队连接的机制,跨不同线程连接信号和槽是安全的。
//使代码在单独的线程中运行的另一种方法是继承 QThread 并重新实现 run()。 例如
/*
    class WorkerThread : public QThread
    {
        Q_OBJECT
        void run() override
        {
            QString result;
            //here is the expensive or blocking operation
            emit resultReady(result);
        }
        signals:
            void resultReady(const QString &s);
    };

    void MyObject::startWorkInAThread()
    {
        WorkerThread *workerThread = new WorkerThread(this);
        connect(workerThread, &WorkerThread::resultReady,this,&MyObject::handleResults);
        connect(workerThread, &WorkerThread::finished,workerThread, &Object::deleteLater);
        workerThread->start();
    }
*/

//在该示例中,线程将在 run 函数返回后退出。
//除非您调用 exec(),否则线程中不会运行任何事件循环。
//重要的是要记住 QThread 实例存在于实例化它的旧线程中
//而不是调用 run() 的新线程中。
//这意味着所有 QThread 的排队槽和调用的方法都将在旧线程中执行。
//因此,希望在新线程中调用槽的开发人员必须使用工作对象方法;
//不应直接在子类 QThread 中实现新插槽。
//与排队插槽或调用方法不同,
//直接在 QThread 对象上调用的方法将在调用该方法的线程中执行。
//在继承 QThread 时,请记住构造函数在旧线程中执行,而 run() 在新线程中执行。
//如果从两个函数访问成员变量,则从两个不同的线程访问该变量。
//检查这样做是否安全。
//注意:跨不同线程与对象交互时必须小心。
//作为一般规则,除非文档另有说明
//否则只能从创建 QThread 对象本身的线程(例如 setPriority())调用函数。
//有关详细信息,请参阅同步线程。

//管理线程
//QThread 会在线程开始() 和完成() 时通过信号通知您
//或者您可以使用 is Finished() 和 is Running() 来查询线程的状态。
//您可以通过调用 exit() 或 quit() 来停止线程。
//在极端情况下,您可能希望强制终止()正在执行的线程。
//然而,这样做是危险的,也是令人沮丧的。
//请阅读 terminate() 和 setTerminationEnabled() 的文档以获取详细信息。
//从Qt 4.8 开始,可以通过将finished() 信号连接到
//QObject::deleteLater() 来释放刚刚结束的线程中的对象。
//使用 wait() 阻塞调用线程,直到另一个线程完成执行(或直到指定的时间过去)
//QThread 还提供静态的、平台独立的睡眠函数:sleep()、
//msleep() 和 usleep() 分别允许完整的秒、毫秒和微秒分辨率。 这些函数在 Qt 5.0 中公开。
//注意:通常来说,wait() 和 sleep() 函数应该是不必要的
//因为 Qt 是一个事件驱动的框架。
//而不是wait(),考虑监听finished() 信号。
//考虑使用 QTimer,而不是 sleep() 函数。

//静态函数 currentThreadId() 和 currentThread() 返回当前正在执行的线程的标识符
//前者返回线程的平台特定 ID; 后者返回一个 QThread 指针。
//要选择您的线程的名称(例如,由 Linux 上的命令 ps -L 标识)
//您可以在启动线程之前调用 setObjectName()。
//如果您不调用 setObjectName()
//则为您的线程指定的名称将是您的线程对象的运行时类型的类名
//(例如,在 Mandelbrot 示例中为“RenderThread”,因为这是 QThread 子类)
//请注意,目前这不适用于 Windows 上的发布版本。
//要选择您的线程的名称(例如,由 Linux 上的命令 ps -L 标识)
//您可以在启动线程之前调用 setObjectName()。
//果您不调用 setObjectName(),则为您的线程指定的名称将是您的线程对象的运行时类型的类名


//! [RenderThread class definition]
class RenderThread : public QThread
{
    Q_OBJECT

public:
    RenderThread(QObject *parent = nullptr);
    ~RenderThread();

    void processImage(const QImage &image);

signals:
    void sendBlock(const Block &block);

public slots:
    void stopProcess();

protected:
    void run();

private:
    bool m_abort;
    QImage m_image;
    QMutex mutex;
};
//! [RenderThread class definition]

#endif

renderthread.cpp


#include "renderthread.h"

#include <QRandomGenerator>

RenderThread::RenderThread(QObject *parent)
    : QThread(parent)
{
    m_abort = false;
}

RenderThread::~RenderThread()
{
    mutex.lock();
    m_abort = true;
    mutex.unlock();

    wait();
}

//![processing the image (start)]
void RenderThread::processImage(const QImage &image)
{
    if (image.isNull())
        return;

    m_image = image;
    m_abort = false;
    start();
}

void RenderThread::run()
{
    int size = qMax(m_image.width()/20, m_image.height()/20);
    for (int s = size; s > 0; --s) {
        for (int c = 0; c < 400; ++c) {
//![processing the image (start)]
            int x1 = qMax(0, QRandomGenerator::global()->bounded(m_image.width()) - s/2);
            int x2 = qMin(x1 + s/2 + 1, m_image.width());
            int y1 = qMax(0, QRandomGenerator::global()->bounded(m_image.height()) - s/2);
            int y2 = qMin(y1 + s/2 + 1, m_image.height());
            int n = 0;
            int red = 0;
            int green = 0;
            int blue = 0;
            for (int i = y1; i < y2; ++i) {
                for (int j = x1; j < x2; ++j) {
                    QRgb pixel = m_image.pixel(j, i);
                    red += qRed(pixel);
                    green += qGreen(pixel);
                    blue += qBlue(pixel);
                    n += 1;
                }
            }
//![processing the image (finish)]
            Block block(QRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1),
                        QColor(red/n, green/n, blue/n));
            emit sendBlock(block);
            if (m_abort)
                return;
            msleep(10);
        }
    }
}
//![processing the image (finish)]

void RenderThread::stopProcess()
{
    mutex.lock();
    m_abort = true;
    mutex.unlock();
}

block.h

#ifndef BLOCK_H
#define BLOCK_H

#include <QColor>
#include <QMetaType>
#include <QRect>

//! [custom type definition and meta-type declaration]
class Block
{
public:
    Block();
    Block(const Block &other);
    ~Block();

    Block(const QRect &rect, const QColor &color);

    QColor color() const;
    QRect rect() const;

private:
    QRect m_rect;
    QColor m_color;
};

Q_DECLARE_METATYPE(Block);
//! [custom type definition and meta-type declaration]

#endif

block.cpp

#include "block.h"

Block::Block()
{
}

Block::Block(const Block &other)
    : m_rect(other.m_rect), m_color(other.m_color)
{
}

Block::~Block()
{
}

Block::Block(const QRect &rect, const QColor &color)
    : m_rect(rect), m_color(color)
{
}

QColor Block::color() const
{
    return m_color;
}

QRect Block::rect() const
{
    return m_rect;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值