Qt Quick で SVG を表示する

Qt では QtSVG モジュールを利用して SVG ファイルを扱うことが可能です。

(SVG Tiny 1.2 の static feature をサポートしていて、それ以外の機能には対応していません。)

Qt Quick では、Image エレメントを利用して画像の表示が可能で、PNG や JPEG のようなラスタ画像や、SVG 形式のベクター画像、PKM や KTX、ASTC といった圧縮テクスチャに対応しています。

ということで、ベクター形式の SVG 形式の画像を Image エレメントに指定をすれば、どんな大きさでも綺麗に描画をしてくれる…といいのですが、少々工夫が必要です。

SVG を表示する際の注意点

現在の Qt の設計と実装により、PNG, JPEG, SVG などの画像は、一度 CPU で描画されたものが Qt Quick 側に渡ってきて GPU に送られ表示されます。

最初の CPU 描画の時点で描画する画像の大きさが決まってしまい、それを Qt Quick 側で GPU で Image の大きさに合わせて拡大縮小することになります。

つまり、SVG 形式の画像だからといって、Qt Quick で自由なサイズで綺麗に表示できるわけではなく、SVG の大きさ(<svg> エレメントの width と height アトリビュート、もしくは viewBox の大きさ)で1度ラスタライズされたものが Qt Quick 側に渡ってくるため、小さなサイズの SVG を拡大表示した際には画像の解像度の低さが目立ちます。

解決方法1

これを解決する簡単な方法は、CPU 側でラスタライズする際のサイズを Qt Quick から指定する方法で、これは Image の sourceSize を利用します。

実サイズを指定する場合は、

sourceSize: Qt.size(width, height)
とするのが良いでしょう。

動的にサイズが変わる場合は、その度に SVG の画像化の処理が走りますので気をつけましょう。

解決方法2

Qt 5.10 で導入された Qt Quick の Shapes モジュール を利用することで、GPU でパスの描画に対応している場合には GPU での描画も可能になります。

簡単な SVG であれば、Shape エレメントに変換してしまいましょう。

これを自動的に行うプログラム もあるようですが、うまく動作しませんでした)

ソースコード

今回書いたソースコードは https://github.com/task-jp/svg を参照してください。

import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import QtQuick.Shapes 1.15

Window {
    id: window
    width: 800
    height: 200
    visible: true
    title: 'SVG'
    GridLayout {
        anchors.fill: parent
        columns: 4
        Text {
            text: 'Original'
            horizontalAlignment: Text.AlignHCenter
            Layout.preferredWidth: window.width / 4
            Layout.fillWidth: true
        }
        Text {
            text: 'Filled'
            horizontalAlignment: Text.AlignHCenter
            Layout.preferredWidth: window.width / 4
            Layout.fillWidth: true
        }
        Text {
            text: 'Filled (with sourceSize set)'
            horizontalAlignment: Text.AlignHCenter
            Layout.preferredWidth: window.width / 4
            Layout.fillWidth: true
        }
        Text {
            text: 'PathSvg'
            horizontalAlignment: Text.AlignHCenter
            Layout.preferredWidth: window.width / 4
            Layout.fillWidth: true
        }

        Item {
            Layout.preferredWidth: window.width / 4
            Layout.fillWidth: true
            Layout.fillHeight: true
            Image {
                anchors.centerIn: parent
                source: './triangle.svg'
            }
        }
        Image {
            Layout.preferredWidth: window.width / 4
            Layout.fillWidth: true
            Layout.fillHeight: true
            source: './triangle.svg'
        }
        Image {
            Layout.preferredWidth: window.width / 4
            Layout.fillWidth: true
            Layout.fillHeight: true
            source: './triangle.svg'
            sourceSize: Qt.size(width, height)
        }
        Shape {
            id: svg
            Layout.preferredWidth: window.width / 4
            Layout.fillWidth: true
            Layout.fillHeight: true
            ShapePath {
                fillColor: '#1B1E23'
                startX: 15.3 * svg.width / 39.2
                startY: 11.3 * svg.height / 39.2
                PathLine {
                    x: 29.2 * svg.width / 39.2
                    y: 19.6 * svg.height / 39.2
                }
                PathLine {
                    x: 15.3 * svg.width / 39.2
                    y: 27.9 * svg.height / 39.2
                }
            }
        }
    }
}
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In  -->
<svg version="1.1"
    xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
    x="0px" y="0px" width="39.2px" height="39.2px" viewBox="0 0 39.2 39.2" style="enable-background:new 0 0 39.2 39.2;"
    xml:space="preserve">
<style type="text/css">
    .st1{fill:#1B1E23;}
</style>
<defs>
</defs>
<polygon class="st1" points="15.3,11.3 29.2,19.6 15.3,27.9 "/>
</svg>