当前位置: 首页 > news >正文

实现 QQuickImageProvider 的若干问题的思路

使用 QQuickImageProvider

在 Qml 中使用 QQuickImageProvider 的方式:

Image {
    property url originSource
    property real cornerRadius: 15
    source: {
        return "image://Effect/clip/" + encodeURIComponent(originSource)
                + "?size=" + width + "x" + height
                + "&cornerRadius=" + cornerRadius
    }
}

使用 image:// 开头的 url,实际上调用了自定义的 ImageProvider。

下面这两个例子体现了这种机制的用武之地:

Qt/QML 实现图片圆角剪切效果

在 Qt 中实现变色的图标(tintColor)

实现 QQuickImageProvider

实现 QQuickImageProvider 一个关键步骤是选择工作模式。QQuickImageProvider 可以是下面三种模式:

  • 同步模式
  • 独立线程模式(ForceAsynchronousImageLoading)
  • 纯异步模式(ImageResponse)

因为我们需要先从网络上获取原始图片,同步模式会阻塞 UI 线程执行,所以显然不适合。

那么独立线程模式是否可行呢?因为它是在独立的线程处理网络下载,应该说理论上是可以的,但是有些麻烦。 

注意到 QNetworkAccessManager 是基于信号槽工作的,并且是多线程异步的,所以需要一个 Qt 事件线程来处理其信号。但是 QQuickImageProvider 的独立线程并没有事件循环,所以需要借助其他线程(比如 UI 线程)来处理信号,然后将结果同步到 QQuickImageProvider 的线程中(借助 QCondition)。

这样,不仅实现复杂,并且独立线程没有并发处理能力,所以不建议使用独立线程模式。

剩下的就是纯异步模式了,接下来我们就看看如何实现。

实现 QQuickAsyncImageProvider

Qt 提供了 QQuickAsyncImageProvider 帮助实现纯异步模式。通过查看源代码,发现 QQuickAsyncImageProvider 只是简单的设置了默认模式:

QQuickAsyncImageProvider::QQuickAsyncImageProvider()
 : QQuickImageProvider(ImageResponse, ForceAsynchronousImageLoading)
 , d(nullptr) // just as a placeholder in case we need it for the future
{
    Q_UNUSED(d);
}
QQuickAsyncImageProvider::~QQuickAsyncImageProvider()
{
}

这里默认设置了 ForceAsynchronousImageLoading(独立线程模式),也就是对 ImageProvier 的请求是在独立线程(不是 UI 线程)发出的,但是我们希望最好有个 Qt 事件线程来启动网络请求。

能不能不要这个 Flag 呢?我们可以通过复写(override)Flags flags() const 方法,去除这个 flag,但是实测并不能工作。进一步查看源代码,发现这种方式是设计时就默认了的,所以还需要费些周章将启动网络的任务 post 到 UI 线程执行。

static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions, bool *ok)
{
    ...
            case QQuickImageProvider::ImageResponse:
            {
                // Fall through, ImageResponse providers never get here
                Q_ASSERT(imageType != QQuickImageProvider::ImageResponse && "Sync call to ImageResponse provider");
            }
    ...
}

实现 QQuickAsyncImageProvider,主要的工作就是实现下面的方法,准确的说是实现 QQuickImageResponse 派生类:

QQuickImageResponse * MyImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
{
    return new MyImageResponse(network_, id, requestedSize);
}

实现 QQuickImageResponse

在 MyImageResponse,需要完成网络图片的下载,对图片的整形,其大概的实现框架是这样的:

class MyImageResponse : public QQuickImageResponse
{
public:
    MyImageResponse(QNetworkAccessManager * network, QString const & id, QSize const &requestedSize);

public:
    QQuickTextureFactory *textureFactory() const override { return QQuickTextureFactory::textureFactoryForImage(image_); }
    QString errorString() const override { return errorString_; }

public slots:
    void cancel() override {
        if (reply_)
            reply_->abort();
    }

private:
    void parseSettings(QString const & id);

    void setImage(QImage image);

private:
    QSize requestedSize_;
    QUrl realUrl_;
    qreal borderWidth_ = 0.;
    QColor borderColor_;
    qreal cornerRadius_ = 0.;
    QVector<qreal> cornerRadii_;

    QImage image_;
    QString errorString_;
    QNetworkReply * reply_;
};

实现 textureFactory 想要返回一个 QQuickTextureFactory 对象,Texture 是图形硬件中的二维纹理,看起来不太好实现,但是 QQuickTextureFactory 提供了从 QImage 构造 TextureFactory 的辅助方法,这就很简单了。注意,不要缓存 QQuickTextureFactory 对象,而是让 Qml 自己去管理这些对象。

我们要实现用圆角矩形去剪切图片,所以需要有 cornerRadius、borderWith 这些配置变量,它们来源于外部(requestImageResponse 方法的参数)输入的 id。

从网络下载图片

通常网络下载文件的代码是这样的:

QNetworkRequest request(realUrl_);
reply_ = network->get(request);
QObject::connect(reply_, &QNetworkReply::finished, [this] () {
    ....
});

在  QQuickAsyncImageProvider 中,这段代码不能生效,因为信号-槽的跨线程调用,依赖事件分发,普通的线程并没有事件分发。

因此,需要将这段代码 post 到带有事件分发的线程中,其实就是 post 一个 QEvent 对象,像下面这样的:

template<typename F>
struct AsyncEvent : QEvent
{
    AsyncEvent(F f) : QEvent(None), f_(f) {}
    ~AsyncEvent() { f_(); }
private:
    F f_;
};

 这个 AsyncEvent post 给哪个 QObject 并不重要,我们主要利用事件的析构方法来执行某一段代码。我们 post 给 network (QNetworkManager)对象,事件不会有什么代码处理,经过分派后就会被删除,从而调用了析构方法。

QCoreApplication::postEvent(network, new AsyncEvent([this, network] {
   ...
});

然后下载图片数据需要转换为 QImage 对象,我们通过 QImageReader 来实现:

if (reply_->error() == QNetworkReply::NoError) {
    QImageReader reader(reply_, nullptr);
    QImage image = reader.read();
    if (image.isNull()) {
        errorString_ = reader.errorString();
    } else {
        setImage(image);
    }
}

注意,直接通过 QImage::load(data, format) 方法解析图片不能取得期望的效果,可能是需要调用者指定数据格式(format),不能够自动识别。

从另一个 ImageProvider 获取图片

相关文章:

  • 算法——查找
  • C/C++语言100题练习计划 82——加勒比海盗船(贪心算法实现)
  • RHCE(四)--- DNS服务的正反向解析配置
  • VUE的侦听器watch
  • ROS1云课→15主题与坐标系
  • 【1. GPIO】
  • Netty——NIO(Selector处理read事件)代码示例
  • 计算机与软件技术系毕业设计(论文)实施意见-计算机毕业设计论文怎么写
  • C/C++语言100题练习计划 83——背包问题(贪心算法实现)
  • JS:数组类型及常用的方法使用
  • Oracle-job跑批变慢案例
  • java/php/python在线求助救援网站vue+elementui
  • Vivado关联Vscode,解决Vscode自动保存和卡顿问题
  • Java基础用Date类编写万年历
  • 前端面试题2
  • 【技术性】Search知识
  • Asm.js的简单介绍
  • C++11: atomic 头文件
  • eclipse的离线汉化
  • JSDuck 与 AngularJS 融合技巧
  • opencv python Meanshift 和 Camshift
  • PHP CLI应用的调试原理
  • session共享问题解决方案
  • socket.io+express实现聊天室的思考(三)
  • Vue小说阅读器(仿追书神器)
  • 大整数乘法-表格法
  • 工作手记之html2canvas使用概述
  • 规范化安全开发 KOA 手脚架
  • 码农张的Bug人生 - 见面之礼
  • 使用Swoole加速Laravel(正式环境中)
  • 手写双向链表LinkedList的几个常用功能
  • 数组的操作
  • 说说动画卡顿的解决方案
  • 为视图添加丝滑的水波纹
  • 用 vue 组件自定义 v-model, 实现一个 Tab 组件。
  • 在weex里面使用chart图表
  • puppet连载22:define用法
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • ​queue --- 一个同步的队列类​
  • # Java NIO(一)FileChannel
  • #Linux(make工具和makefile文件以及makefile语法)
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (4)(4.6) Triducer
  • (ibm)Java 语言的 XPath API
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (译)2019年前端性能优化清单 — 下篇
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .net framework4与其client profile版本的区别
  • .NET Framework与.NET Framework SDK有什么不同?