QIconでSVGを表示する


はじめに

Qt widgetでアイコンを表すクラスQIconは、SVGをサポートしています

QIcon Classによれば、

Note: Since Qt 4.2, an icon engine that supports SVG is included.

早速やっていきましょう。

なぜSVGを使うのか

  • ベクター画像のため、サイズに依存しない高品質な描画が可能(特に高解像度ディスプレイで効果を発揮する)
  • アイコンにおいてもDRY1を推進できる(かもしれない)

前者はよく言われていますが、今回は後者の利点に着目してみます。

アイコンにおけるDRY

わかりやすさの点、あるいはデザインの省力化の点から、しばしばアイコンのデザインは流用されます。例えば、ある要素のアイコンに+xを重ねて"追加"、"削除"を表現したり、同じデザインのアイコンを回転させて方向を表現したりします。

ソースコードにおけるコピー&ペーストと同様、このような使い回しはデザインの変更を困難にします

SVGは<use>要素によって他のSVGファイルから図形を取り込むことができます。また、色やグラデーションといったスタイルに名前を付けて取り込むこともできます。

SVGをアイコンに使えば、これらの機能を活用してDRYをやっていくことができます

SVGをQIconに表示する

以下のコードでicon.svgをアクションのアイコンとしてツールバーに表示できます。

#include <QApplication>
#include <QWidget>
#include <QToolBar>
#include <QAction>
#include <QIcon>
#include <QMainWindow>
#include <memory>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    auto window = std::make_unique<QMainWindow>();

    QToolBar* toolbar = window->addToolBar("Tools");
    toolbar->addAction(new QAction(QIcon("icon.svg"), "action", window.get()));

    window->show();
    return app.exec();
}

use要素を使う

QIcon Classには書いていないですが、SVGの描画にはQt SVGが使われているはずです。

Rendering SVG Filesを見ると、Qt SVGはSVG 1.2 Tinyをサポートしていると書かれています。

Qt supports the static features of SVG 1.2 Tiny. ECMA scripts and DOM manipulation are currently not supported.

SVG 1.2 Tiny仕様の14.1.4 Reference restrictionsを見ると、SVG 1.2 Tinyで<use>は使える上に、外部ファイルのリンクも可能なようです。

Qt SVGは実装しているのでしょうか?

結論から言うと、同一ファイル内の要素は指定できますが、別のSVGファイルにある要素は指定できませんでした2

つまり、xlink:href="#id"のような指定は有効ですが、xlink:href="external.svg#id"のような指定は(SVG 1.2 Tinyとしては有効のはずだが)動かないという事です。

ソースを見てみよう

use要素のハンドラーを見てみると……

qsvghandler.cpp
static QSvgNode *createUseNode(QSvgNode *parent,
                               const QXmlStreamAttributes &attributes,
                               QSvgHandler *handler)
{
    QString linkId = attributes.value(QLatin1String("xlink:href")).toString().remove(0, 1);
    const QStringRef xStr = attributes.value(QLatin1String("x"));
    const QStringRef yStr = attributes.value(QLatin1String("y"));
    QSvgStructureNode *group = 0;
    /*略*/
}

1行目からして明らかに#idの形式しか想定していないですね。
QtでアイコンのDRYをやっていくのは現状では難しいようです

しかし、image要素の方はちゃんと外部ファイルを読み込むように実装してあるので、同じようにSVGを再帰的にロードすれば動かせるような気がします3

qsvghandler.cpp
static QSvgNode *createImageNode(QSvgNode *parent,
                                 const QXmlStreamAttributes &attributes,
                                 QSvgHandler *handler)
{
    /*略*/
    QStringRef filename = attributes.value(QLatin1String("xlink:href"));
    /*略*/
    if (filename.startsWith(QLatin1String("data"))) {
        /*略*/
    } else
        image = QImage(filename.toString());
    /*略*/
}

まとめ

  • QIconはSVGを扱えます
  • SVG 1.2 Tinyを部分的にサポートしています
  • 外部SVGファイルをリンクすることはできませんでした4
    • もしかしたら意外と簡単に実装できるかも

  1. Don't repeat yourself; 同じことを繰り返さない 

  2. static featuresではないということでしょうか? 

  3. 循環参照を排除したりといった例外処理は必要 

  4. コードから判断すると、useだけでなく全体的に外部のSVGを使うことはできない模様