Qml中實現多視圖,多圖像源(QImage / QPixmap)

【寫在前面】

在 Qml 中,實現多視圖很容易,無非是多個 Image 而已。

但是,如果需要動態刷新,則變得困難。

再或者,來自多個不同的圖像源,將更加複雜。

實際上,這在 Qt ( Widgets ) 中實現卻很容易,究其原因,是 Qml 中缺少對 QImage ( 或者說 原始圖像 ) 的支持 。

即便如此,Qt 仍提供了一種解決方法。

本篇主要內容:

1、QML 中支持 QImage / QPixmap 。

2、QML 中實現多視圖。

3、QML 中實現多圖像源視圖。


【正文開始】

首先,展示一下效果圖。

多視圖的:

多圖像源的:

  •  想要在 QML 中實現上面的效果,必須學會在 QML 中使用 QImage / QPixmap

其中,核心的 Class 爲 QQuickImageProvider

QQuickImageProvider is used to provide advanced image loading features in QML applications. It allows images in QML to be:

Loaded using QPixmaps rather than actual image files.

Loaded asynchronously in a separate thread.

To specify that an image should be loaded by an image provider, use the "image:" scheme for the URL source of the image, followed by the identifiers of the image provider and the requested image.

翻譯:

QQuickImageProvider 用於在 QML 應用程序中提供高級圖像加載功能。 它允許 QML 中的圖像爲:

使用 QPixmaps 而不是實際的圖像文件加載。

在單獨的線程中異步加載。

要指定圖像提供者應加載圖像,請對圖像的URL源使用“ image:”方案,然後使用圖像提供者的標識符和請求的圖像。

根據文檔以及示例,很容易明白 QQuickImageProvider 的工作方式:

1、寫一個自己的 QQuickImageProvider

2、使用 void QQmlEngine::addImageProvider(const QString &providerId, QQmlImageProviderBase *provider),添加到 Qml 引擎中,並且擁有其所有權。

3、Qml 中 Image source 前綴爲:"image://" 來請求圖像。

4、請求將調用 QQuickImageProvider 中的 requestImage() / requestPixmap() / requestTexture(),藉此返回 C++ 提供的圖像 ( QImage / QPixmap / QQuickTextureFactory )。

  • 現在我們來實現第一個:多視圖。

Qml 部分關鍵代碼如下:

Page {
    id: page1
    width: 860
    height: 660

    Connections {
        target: view
        onViewNeedUpdate: {
            /**
              * 1. index爲視圖索引
              * 2. ###用於分隔
              * 3. Date.now()用於更新
              */
            viewRepeater.itemAt(index).source = "image://MultiView/" + index + "###" + Date.now();
        }
    }

    Grid {
        rows: 3
        columns: 3
        anchors.fill: parent

        Repeater {
            id: viewRepeater
            model: 9

            Image {
                mipmap: true
                width: page1.width / 3
                height: page1.height / 3

                Text {
                    anchors.left: parent.left
                    anchors.leftMargin: 12
                    anchors.top: parent.top
                    anchors.topMargin: 12
                    font.bold: true
                    font.pointSize: 30
                    color: "red"
                    text: index
                }

                Rectangle {
                    anchors.fill: parent
                    border.width: 2
                    border.color: "yellow"
                    color: "transparent"
                }
            }
        }
    }
}

其中,viewRepeater.itemAt(index).source = "image://MultiView/" + index + "###" + Date.now() 將調用 requestImage()

然後,我們實現自己的 ImageProvider ( 這裏是 ViewProvider ):

#ifndef VIEWPROVIDER_H
#define VIEWPROVIDER_H

#include <QQuickImageProvider>

class ViewProvider : public QQuickImageProvider
{
public:
    ViewProvider();
    void updateView(int index, const QImage &view);
    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;

private:
    /**
     * @note 爲什麼使用 hash 而不是 vector
     *       因爲這樣可以擴展到更廣泛的使用場景
     */
    QHash<int, QImage> m_views;
};

#endif // VIEWPROVIDER_H
#include "viewprovider.h"

ViewProvider::ViewProvider()
    : QQuickImageProvider(QQuickImageProvider::Image)
{

}

void ViewProvider::updateView(int index, const QImage &view)
{
    m_views[index] = view;
}

QImage ViewProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
    //這裏可以id.left(1);
    int index = id.left(id.indexOf("###")).toInt();
    QImage view = m_views[index];

    if (!view.isNull()) {
        view.scaled(requestedSize);

        if (size) *size = requestedSize;
    }

    return view;
}

接着,我們只需送入 QImage 並提示 Qml 更新即可:

    //多視圖圖像生成器
    d->m_generator = new QTimer(this);
    connect(d->m_generator, &QTimer::timeout, this, [this]() {
        static int index = 0;
        QImage image = QGuiApplication::primaryScreen()->grabWindow(0).toImage();
        d->m_multiView->updateView(index, image);
        emit viewNeedUpdate(index);

        if (++index == 9) index = 0;
    });
    d->m_generator->start(1000 / 9);

這裏的圖像源爲桌面截圖,間隔 111ms,送給九個視圖進行顯示。

  • 然後我們來實現第二個:多圖像源視圖。

在上面,我們已經成功實現了一個圖像源的多視圖展示,現在更進一步,提供多個圖像源。

Qml 部分關鍵代碼如下:

Page {
    id: page2
    width: 860
    height: 660

    Connections {
        target: view
        onSourceNeedUpdate: {
            /**
              * 1. source爲圖像源索引
              * 2. ###用於分隔
              * 3. Date.now()用於更新
              */
            sourceRepeater.itemAt(source).source = "image://MultiSource/" + source + "###" + Date.now();
        }
    }

    Grid {
        rows: 2
        columns: 2
        anchors.fill: parent

        Repeater {
            id: sourceRepeater
            model: 4

            Image {
                mipmap: true
                width: page2.width / 2
                height: page2.height / 2
                fillMode: Image.PreserveAspectFit

                property bool running: true

                Image {
                    width: 80
                    height: 80
                    anchors.centerIn: parent
                    opacity: 0.7
                    mipmap: true
                    source: parent.running ? "" : "qrc:/play.png"
                }

                Text {
                    anchors.left: parent.left
                    anchors.leftMargin: 12
                    anchors.top: parent.top
                    anchors.topMargin: 12
                    font.bold: true
                    font.pointSize: 30
                    color: "red"
                    text: index
                }

                Rectangle {
                    anchors.fill: parent
                    border.width: 2
                    border.color: "#89f2f5"
                    color: "transparent"
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        if (parent.running) {
                            view.pauseSource(index);
                            parent.running = false;
                        } else {
                            view.resumeSource(index);
                            parent.running = true;
                        }
                    }
                }
            }
        }
    }
}

這裏基本類似,只是多了暫停 / 繼續而已。

C++ 部分關鍵代碼:

    /**
     * @note 多圖像源
     * @source[0-4] gif[0-4]
     */
    for (int i = 0;  i < 4; i++) {
        d->m_sources[i] =  new QMovie(":/" + QString::number(i) + ".gif", "GIF", this);
        qDebug() << d->m_sources[i]->fileName() << d->m_sources[i]->isValid();
        connect(d->m_sources[i], &QMovie::frameChanged, this, [i, this](int) {
            d->m_multiSource->updateView(i, d->m_sources[i]->currentImage());
            emit sourceNeedUpdate(i);
        });
    }

如你所見,四個圖像源來自四張不同的 GIF 圖像,之所以用這種形式,是因爲可以讓人聯想到其他類似的場景:

1、多個監視器( 多個攝像頭 )。

2、多個圖像源( 多個視頻源 )。 

而對於這些場景,我覺得是相當有用的。


【結語】

實際上,在 Qml 使用 QImage 確實有些麻煩。

當然,對我來說,因爲用得多了,反倒是挺習慣的。

並且也漸漸明白了,Qml 只做前端顯示,複雜的工作都由後端的 C++ 來完成。

然後一點題外話要說,注意:

Image Caching:

Images returned by a QQuickImageProvider are automatically cached, similar to any image loaded by the QML engine. When an image with a "image://" prefix is loaded from cache, requestImage() and requestPixmap() will not be called for the relevant image provider. If an image should always be fetched from the image provider, and should not be cached at all, set the cache property to false for the relevant Image, BorderImage or AnimatedImage object.

翻譯:圖像緩存

QQuickImageProvider 返回的圖像將自動緩存,類似於 QML 引擎加載的任何圖像。 當從緩存中加載帶有 "image://" 前綴的圖像時,相關圖像提供者將不會調用 requestImage() requestPixmap() 。 如果應該始終從圖像提供者獲取圖像,並且根本不應該對其進行緩存,則將相關 ImageBorderImage AnimatedImage 對象的 cache 屬性設置爲 false。

然而,將 cache 設置爲 false 並沒有卵用,仍然需要使用 Date.now() 來請求一個不同的圖像,才能造成刷新的效果( 也許這是一個 Bug),但不管怎麼說,這個坑必須小心。

最後,附上項目鏈接(多多star呀..⭐_⭐):

CSDN的:https://download.csdn.net/download/u011283226/12126178

Github的:https://github.com/mengps/QmlExamples

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章