QML Image 通过 QQuickAsyncImageProvider 异步加载图片

这篇具有很好参考价值的文章主要介绍了QML Image 通过 QQuickAsyncImageProvider 异步加载图片。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

相关:QML Image 通过 QQuickImageProvider 加载图片-CSDN博客

前言

根据 QQuickImageProvider 的文档说明,该类其实是支持在独立线程中异步加载的,但是 Async 类提供了专用于异步加载的接口,操作起来也不复杂,对于大文件或者网络文件,都可以采用异步方式加载图片。

操作流程

继续 QQuickAsyncImageProvider 只需要实现一个虚函数,但是返回的 QQuickImageResponse 需要我们继承重写部分接口

class MyAsyncImageProvider : public QObject, public QQuickAsyncImageProvider
{
    Q_OBJECT
public:
    explicit MyAsyncImageProvider(QObject *parent = nullptr);

    // 通过该接口给 QML Image 提供数据
    // 文档注释:此方法可能由多个线程调用,因此请确保此方法的实现是可重入的
    QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
};

若 Image url 为 "image://Provider/imageTag/0",那么 id 就是 "imageTag/0"。注意这个 id  是 url 编码之后的,如果你有一些会被 url 转义的字符记得转回来。 

class MyAsyncImageResponse : public QQuickImageResponse
{
    Q_OBJECT
public:
    MyAsyncImageResponse(const QString &filepath, const QSize &requestedSize);
    ~MyAsyncImageResponse();

    // Factory 提供接口从 QML 加载自定义纹理
    QQuickTextureFactory *textureFactory() const override;
    // 加载失败可以给报错提示
    QString errorString() const override;

public slots:
    // Image 没有引用该图了,还没返回就需要取消
    void cancel() override;
};

textureFactory 返回最终结果,可以直接由 QImage 构造:

QQuickTextureFactory::textureFactoryForImage(mImage);

此时还有一个问题,怎么异步加载呢,通过在接口内部打印线程 ID,都是在 QQuickPixmapReader 这个线程。

最后在 github 一个叫 mixxx 的项目上找到了解答,MyAsyncImageResponse 多继承 QQuickImageResponse 和 QRunnable,利用 QRunnable 的 run 接口去加载图像。

实现代码

github 链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20240118_AsyncImageProvider文章来源地址https://www.toymoban.com/news/detail-805458.html

#pragma once
#include <QObject>
#include <QQuickImageResponse>
#include <QQuickAsyncImageProvider>
#include <QImage>
#include <QTimer>
#include <QThreadPool>
#include <QRunnable>
#include <atomic>


// 参考:https://github.com/mixxxdj/mixxx.git 项目 src/qml/asyncimageprovider.h
class MyAsyncImageResponse : public QQuickImageResponse, public QRunnable
{
    Q_OBJECT
public:
    MyAsyncImageResponse(const QString &filepath, const QSize &requestedSize);
    ~MyAsyncImageResponse();

    // QRunnable 线程中加载图片
    void run() override;
    // Factory 提供接口从 QML 加载自定义纹理
    QQuickTextureFactory *textureFactory() const override;
    // 加载失败可以给报错提示
    QString errorString() const override;

public slots:
    // Image 没有引用该图了,还没返回就需要取消
    void cancel() override;

private:
    QString mFilepath;
    QSize mRequestedSize;
    QImage mImage;
    std::atomic_bool mRunning{true};
};

// 如果需要信号槽的话可以继承 QObject
class MyAsyncImageProvider : public QObject, public QQuickAsyncImageProvider
{
    Q_OBJECT
public:
    explicit MyAsyncImageProvider(QObject *parent = nullptr);

    // 通过该接口给 QML Image 提供数据
    // 文档注释:此方法可能由多个线程调用,因此请确保此方法的实现是可重入的
    QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;

signals:
    // 测试刷新
    void imageChanged(const QString &imageTag);

private:
    // 加载图片的线程池
    QThreadPool pool;
    // 测试定时刷新
    QTimer *timer;
};
#include "MyAsyncImageProvider.h"
#include <QImageReader>
#include <QThread>
#include <QDebug>

MyAsyncImageResponse::MyAsyncImageResponse(const QString &filepath, const QSize &requestedSize)
    : mFilepath(filepath)
    , mRequestedSize(requestedSize)
{
    setAutoDelete(false);
    qDebug()<<__FUNCTION__<<QTime::currentTime();
}

MyAsyncImageResponse::~MyAsyncImageResponse()
{
    qDebug()<<__FUNCTION__;
}

void MyAsyncImageResponse::run()
{
    do {
        // 测试用延时
        QThread::msleep(15);
        if (mFilepath.isEmpty())
            break;
        QImageReader reader(this->mFilepath);
        if (!reader.canRead())
            break;
        mImage = reader.read();
        // 只缩小,不放大
        bool do_scale = mRequestedSize.width() < mImage.size().width() &&
                        mRequestedSize.height() < mImage.size().height();
        if (!mImage.isNull() && mRequestedSize.isValid() && do_scale) {
            // 如果用 QML 中的缩放设置,就不在这里进行缩放了
            // Qt::FastTransformation
            mImage = mImage.scaled(mRequestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
        }
    }
    while(false);
    // 在父类构造函数关联,发出之后才会进入下一步
    emit finished();
}

QQuickTextureFactory *MyAsyncImageResponse::textureFactory() const
{
    return QQuickTextureFactory::textureFactoryForImage(mImage);
}

QString MyAsyncImageResponse::errorString() const
{
    if (!mRunning && mImage.isNull()) {
        return "Image Load Error";
    }
    return "";
}

void MyAsyncImageResponse::cancel()
{
    // 这里设置 run 的退出条件
    qDebug()<<__FUNCTION__;
}

MyAsyncImageProvider::MyAsyncImageProvider(QObject *parent)
    : QObject{parent}
    , timer{new QTimer(this)}
{
    pool.setMaxThreadCount(8);

    // 测试刷新
    connect(timer, &QTimer::timeout, this, [this](){
        emit imageChanged("imageTag");
    });
    timer->start(20);
}

QQuickImageResponse *MyAsyncImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
{
    // 指定 Image 的 sourceSize 会作为 requestedSize 参数,默认是 QSize(-1, -1)
    // 若 url 为 "image://Provider/imageTag/0",那么 id 就是 "imageTag/0"
    // 注意 url 的编码问题,如果有特殊符号之类的不能直接从中截取 QML 中设置的路径
    // id 到实际路径的转换根据实际需求来,此处取固定值
    Q_UNUSED(id)
    QString path = ":/flower.jpg";
    auto response = new MyAsyncImageResponse(path, requestedSize);
    pool.start(response);
    return response;
}
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Async Image Provider")

    Image {
        id: image
        anchors.centerIn: parent
        asynchronous: true
        cache: false
        // image://provider_id/image_id
        source: "image://Provider/imageTag/0"
        // 指定 Image 的 sourceSize 会作为 requestedSize 参数,默认是 QSize(-1, -1)
        // sourceSize: Qt.size(120, 120)
    }
    // 加一个 Shader 是为了防止刷新时闪烁,Qt 6.5 之后也可以用 Image 的 layer.live
    // https://bugreports.qt.io/browse/QTBUG-66713
    ShaderEffectSource {
        id: texture
        anchors.fill: image
        sourceItem: image
        live: image.status === Image.Ready
        hideSource: true
    }

    property int updateNum: 0
    Connections {
        target: provider
        function onImageChanged(imageTag) {
            // Image 的 load 接口不是 public 的,所以只能切换 url 来重新加载数据
            // 异步加载时间更长,刷新会闪烁
            updateNum++
            image.source = "image://Provider/" + imageTag + "/" + updateNum
        }
    }
}

到了这里,关于QML Image 通过 QQuickAsyncImageProvider 异步加载图片的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包