Using an Image Provider to Supply Images to QML Applications from Qt Resources

Your hybrid QML/C++ application can load its QML from a Qt resource, but what about images? If you load the QML from a Qt resource, then all of your images are loaded from the Qt resource segment as well. But what if you want to supply your QML as files (or deliver them over the network)?

Your C++ application can provide images to the Qt declarative runtime through an image provider, so that in your QML you can write something like

Image {
    source: "image://myprovider/image.png"
}

and the QML runtime will query your application’s image provider for a provider registered to the name myprovider, asking that provider for the image named image.png. Using the image provider framework, it’s easy to load images from your application’s resource segment: all you need to do is provide a custom image provider that loads images from the resource segment and register it with the Qt declarative runtime.

Qt image providers are subclasses of QDeclarativeImageProvider, a class that has methods to return a QPixmap or a QImage given the image’s name. It’s up to you to decide how to organize and fetch those images when you implement an image provider; an obvious way when fetching images from the resource segment is to use the resource name. Our resource-based image provider looks like this:

class ResourceImageProvider : public QDeclarativeImageProvider
{
public:
    ResourceImageProvider(QDeclarativeImageProvider::ImageType type);
    ~ResourceImageProvider();
    QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize);
    QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize);
};

The constructor accepts an indicator as to whether the returned images provided are pixmaps or images; while pixmaps are faster to draw, they reside in the graphics subsystem’s memory, which is typically a scarce resource, so you should always be sure to use this judiciously.

The image provider gives images to the Qt declarative runtime through the requestImage and requestPixmap methods, which return a QImage or QPixmap, respectively. Each of these methods take the identifier of the image to return, a desired size for the image, and a pointer where to place the actual size of the image that’s returned. The image provider should try to match the requested size if it’s able, although depending on the QML, can accept a smaller or larger image and reflow the QML layout—that depends, of course, on the QML element’s anchors property, of course.

The actual image provider is simple:

ResourceImageProvider::ResourceImageProvider(QDeclarativeImageProvider::ImageType type) :
    QDeclarativeImageProvider(type)
{
    // This space intentionally left blank.
}

ResourceImageProvider::~ResourceImageProvider()
{
    // This space intentionally left blank.
}

QImage ResourceImageProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize)
{
    QString rsrcid = ":/" + id;
    QImage image(rsrcid);
    QImage result;

    if (requestedSize.isValid()) {
        result = image.scaled(requestedSize, Qt::KeepAspectRatio);
    } else {
        result = image;
    }
    *size = result.size();
    return result;
}

QPixmap ResourceImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize)
{
    QString rsrcid = ":/" + id;
    QPixmap image(rsrcid);
    QPixmap result;

    if (requestedSize.isValid()) {
        result = image.scaled(requestedSize, Qt::KeepAspectRatio);
    } else {
        result = image;
    }
    *size = result.size();
    return result;
}

The image and pixmap loaders do essentially the same thing: create a Qt resource identifier from the incoming image id, load the image, scale it to the requested size, and return the requested image (as a pixmap in the requestPixmap case) along with its size.

Once you’ve created an image provider, you need to make it available to the QDeclarativeView displaying your QML. In a previous post, I showed you how to create a QDeclarativeView that displays your QML in a QMainWindow; you can use the same code plus the following snippet to add your image provider to the Qt declarative runtime:

    QDeclarativeView* view = new QDeclarativeView(window);
    ...
    view->engine()->addImageProvider(QLatin1String("qrc"), new ResourceImageProvider(QDeclarativeImageProvider::Pixmap));

This code pokes at the QDeclarativeView‘s QDeclarativeEngine, giving it an instance of your image provider that returns pixmaps for images in the namespace beginning with qrc. Once you’ve done this, you can now load images in your QML from your Qt resource segment using things like:

Image {
    source: "image://qrc/image.png"
}

In a later post, I’ll show you how you can use an image provider for another common C++ integration point: loading images directly from a C++-hosted data model.

Although you probably already know this, it’s worth pointing out an important detail about Qt resources: they’re in your application’s read-only segment. If your application is running on a memory-contrained platform like a mobile device, it’s worth choosing carefully whether you place your resources in a Qt resource segment or as additional files on the file system packaged with your application. If they’re in the data segment, it’s possible they’ll be loaded into memory when your application is launched (for example, when started from a memory card on Symbian, which only performs demand-paging from the internal store). Consequently, putting a lot of images in your application’s resources can lead to slower load times and even out-of-memory errors on some platforms, so it’s best to do this with small resources that simply must be loaded quickly like bitmaps for small user interface controls and the like.

You can download the code, too!.

Leave a Reply