Vector Graphics in Qt 6.8

Two-dimensional vector graphics has been quite prevalent in recent Qt release notes, and it is something we have plans to continue exploring in the releases to come. This blog takes a look at some of the options you have, as a Qt developer.

In Qt 6.6 we added support for a new renderer in Qt Quick Shapes, making it possible to render smooth, anti-aliased curves without enabling multisampling. The renderer was generalized to also support text rendering in Qt 6.7, and, in the same release, Qt SVG was expanded to support a bunch of new features.

And there is no end in sight yet: In Qt 6.8 we are bringing even more vector graphics goodies to the Qt APIs. In this blog, I will share some details on the different ways vector graphics can be used in Qt, as well as the benefits and drawbacks of each.

QPainter, Qt SVG and the Image component

At the core of the Qt Widgets module lies QPainter. This is a stand-alone C++ API that can be used to render 2D vector graphics with pixel perfect anti-aliasing. By itself, it enables your application to draw primitives such as rectangles, ellipses, and text, as well as arbitrary shapes and curves described by QPainterPaths.

The Qt SVG module sits on top of this and supports parsing complex SVG files and rendering them using QPainter. As a baseline, Qt SVG supports the static parts of SVG Tiny 1.2 profile. Since Qt 6.7, it also supports some additional features from the full SVG profile, such as filters and masks.

Together, Qt SVG and QPainter are powerful tools for rendering 2D vector graphics in Qt applications.

Decorative Angel Polyprismatic Pattern by OpenClipart rendered with Qt SVG

Here is the old SVG Viewer example in Qt SVG used to render a public domain prismatic angel SVG made by OpenClipart, as an example of an SVG image with a lot of curves and individual gradients.

While Qt SVG primarily has a C++ API, its renderer is also exposed through an image format plugin. This means that anywhere an image can be loaded in Qt, it can also load SVG files. One such place is the Image component in Qt Quick. Using this, SVGs can also be rendered in Qt Quick applications.

One thing to note about QPainter is that it is a software rasterizer, meaning that all the paint commands will be executed on the CPU. When used from Qt Quick, the image will first be created in software and then uploaded to the GPU to be displayed with the rest of the Qt Quick scene.

This has some caveats: Rendering the image can sometimes be expensive, as there is no hardware-acceleration of the paint commands themselves. And the rendered image has to be uploaded to GPU memory when it is done, which can be slow on certain hardware. The rasterized image is also stored in memory at the requested size; this both means that in order to zoom the image further, it will have to be re-rendered at a larger scale, and that memory consumption will increase as the target size increases.

That said, once the SVG image is done rendering and is stored in GPU memory, then blitting it onto the screen is very fast. This will, in fact, perform the same as pre-rendering the SVG to a PNG file (or similar) at build time and displaying this pre-rasterized image instead. Pre-rendering the SVG would typically be the most performant option, but it requires you to know the target size at build time, which is not always possible, for instance if the target size depends on the resolution of the screen.

So in circumstances where the image is rendered once at a specific size and then rarely updated, Qt SVG can be a flexible and performant option.

Qt Quick Shapes, VectorImage and svgtoqml

Qt Quick Shapes is the module for showing arbitrary curves and shapes in Qt Quick, similar to how QPainter and QPainterPath can be used to draw shapes. It supports manually building shapes from curves and lines, and even from SVG's path descriptions, but has not had any support for loading files created by third-party applications. Until now.

In Qt 6.8, we are introducing a new type to fill this gap: The VectorImage component can load SVG files and render these using the types we already have in the Qt Quick tool chest. This is similar to how Qt SVG parses SVG files and converts them to a sequence of QPainter commands. Instead of QPainter commands, VectorImage will create a scene of Qt Quick items that represents the SVG. If the SVG contains a text element, for instance, then the VectorImage will create a Text component in its place. And Qt Quick Shapes will be used to support shapes and curves.

Since VectorImage builds up a Qt Quick scene, it means that the geometry of the shapes will be uploaded directly to the GPU memory instead of being drawn into a rasterized image first. For large target sizes, this can consume less memory; but more importantly, it means that curve rasterization and anti-aliasing is hardware-accelerated and happens when the scene is rendered onto the screen.

As I mentioned earlier, using a simple Image component means rasterizing at a specified size. If we try to scale the results of this when we draw the scene, we can get fuzzy results, as the renderer only has access to the pre-rasterized pixels and not the actual curve data.

SVG rendered with QPainter and then scaled to show upscaling artifacts

Here is a zoomed-in view of the prismatic angel from earlier. Smooth pixmap filtering is applied and the details of the image are lost. In order to get a high quality rendering of it, we would have to update the sourceSize property of the Image component instead. That would trigger Qt SVG to create a new rendering of the image at the requested size. Re-rendering every time the scale changes can be quite expensive and, for instance, doing an animated zoom of the image will often be out of the question.

The VectorImage component triangulates the shapes instead and keeps the information on the GPU. When the image is scaled, it will simply update a matrix uniform and the fragment shader will take care of rendering the curve at the requested level of detail.

Same SVG rendered with VectorImage and scaled, showing that details are retained

The same image is here rendered with VectorImage. Since the information about curvature is known by the shader program, it can produce a transformed image with details intact.

The trade-off is that the geometry of the scene and the fragment shader are both more complex than when we're simply showing the image as a textured quad. Therefore, using an Image component can often be the right choice for images that are only ever rendered at a single size.

Qt 6.8 also comes with a new tool called "svgtoqml" (tech preview in Qt 6.7). Essentially this serves the same purpose as VectorImage, but can be used to convert the SVG to a QML file ahead of time. If the SVG is part of the application assets, then this will be faster to load than parsing and converting the image at run-time.

Other

This blog has been mostly focused on SVG and on the additions we have made to Qt in the most recent releases. Before finishing up, I think it's worth mentioning a few other vector graphics formats that exist and can be used together with Qt.

One is the Lottie format, supported in Qt by the Qt Lottie Animation module. This is a JSON-based format originally conceived by developers at AirBnB. While it is a general vector graphics format, its main purpose was originally to support animations created in Adobe After Effects. Since Qt SVG only has very limited support for animations so far, Qt Lottie Animation can be a way for an app to get animated vector icons.

The implementation in Qt does come with certain limitations (see the overview in the documentation) and it hasn't been updated to support any new features since it was originally made. This means that some Lottie files will not work as the creator intended. We are interested in hearing feedback on this, though, and what specific support you might want to see added in the future. Please file suggestions in Qt's bug tracker if you have ideas on what the priorities should be.

On the technical side, Qt Lottie Animation works similarly to Qt SVG, in that it renders the vector image in software using QPainter. So far, there is no equivalent to VectorImage for the Lottie format.

Another format that seems to have been gaining traction lately is Rive. This is also an animated vector graphics format and comes with its own open source run-time. There is a project by basysKom which connects this run-time with Qt's RHI to create a hardware-accelerated renderer that can be used with Qt Quick. Note that this is not an officially supported part of Qt and is released by basysKom under the LGPLv3 license.

And last (but not least), my personal favorite topic: fonts. While primarily used to display text, the Truetype (and Opentype) font file format is a vector graphics format in its own right. Most fonts are monochrome, but multicolor vector font formats also exist, and both are quite often also used to contain vector icons. One example of an icon font is the popular Font Awesome, as well as Microsoft's classic Wingdings. And with the more recent introduction of "variable fonts", certain aspects of the icons can even be animated, although within strict boundaries. Qt supports fonts through system libraries and Freetype. While font support is optimized for text, reusing it for vector graphics can be useful, since a font renderer is typically always present on a system and does not need additional libraries or renderers.

 


Blog Topics:

Comments