相关: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
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
// 参考: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
#include
#include
MyAsyncImageResponse::MyAsyncImageResponse(const QString &filepath, const QSize &requestedSize)
: mFilepath(filepath)
, mRequestedSize(requestedSize)
{
setAutoDelete(false);
qDebug()<<__FUNCTION__<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
}
}
}