WPFで画像にSVGファイルを利用する

はじめに

アイコン描画でビットマップ画像を使うと,メニュー表示を拡大するとwikipedia:ジャギー画出てしまいます.wikipedia:Windows Presentation Foundation(以下,WPF)にはスケーラブルな描画機能がありますから,これを活用しない手はありません.
ここではSVG形式のアイコンをWPFで表示する方法をまとめます.

SVGファイルを表示するコントロールはあるか? ないみたい.

SVG形式のファイルを,Imageクラス(System.Windows.Controls)のSourceプロパティに直接指定して表示ができれば話は簡単です.
そこでSVGファイルへのパスを,Imageクラス(System.Windows.Controls)のSourceプロパティ,Frame クラス((System.Windows.Controls)のSourceプロパティ)に設定してみましたが,"リソースを変換できない"という例外が発生しました.
またWebBrowserクラス(System.Windows.Controls)のSourceプロパティにSVGファイルのパスを指定してみると,XMLファイルとしてソースが表示されてしまいました.
SVGファイルを直接表示できるコントロールは,なさそうです.

SVGファイルをXAMLファイルに変換する

直接SVGが扱えないため,SVGファイルをXAMLファイルに変換して表示することにします.
SVGXMLで記述されたベクターグラフィックです.ここではXMLの形式を変換するXSL技術を使います.手順は以下のとおりになります:

  1. 画像ファイルを入手する
  2. SVG形式をXAML形式に変換するXSLTファイルを入手する
  3. SVG形式をXAML形式に変換する

まずSVGファイルを入手します.ここではUNIXディスクトップ環境向けのオープンソースなアイコン集 SVG Icons * BlueSphere icon theme for KDE and GNOMEを用いました.
XSLファイルは,Having fun with XAML (Silverlight) and SVGhttp://members.chello.nl/~a.degreef/xaml/svg2xaml.xslを用います.
変換ソフトウェアには好きなソフトウェアを使えばいいのですが,ここではわんくまツールXSLTransformerの詳細情報 : Vector ソフトを探す!を使いました.
それぞれのファイルをダウンロードして変換作業をしている画面が左図です.上から,変換したいSVGファイル,XSLファイル,そして変換先であるXAMLファイルのパスをそれぞれ設定して,処理実行ボタンを押します.変換には数秒かかります.

XAMLファイルを表示する


では変換したXAMLファイルを表示するWindowを作ります.実行すると左図のようになります.

Frameコントロールを使う

まずFrameコントロールXAMLファイルを読み込み表示する方法を述べます.
まずVisual studio 2008(Expressでも何でも)でWPFアプリケーションのプロジェクトを作製します.
次に変換したXAMLファイルを"既存ファイルの読み込み"でプロジェクトに読み込み,そのプロパティを左図のようにコンテンツに設定します.ファイルをリソースとして読み込んでもよいですが,そうするとXAMLファイルがアセンブラに含まれるために,XAMLファイルを変更するたびにアセンブラを再配布することになりますWindows Presentation Foundation のアプリケーション データ ファイル | Microsoft Docs.単なる見栄えの変更でアセンブラのバージョンまで変えてしまうと,アプリケーション配布を面倒にするので,ここはコンテンツに指定しました.
最後に,FrameコントロールのSourceプロパティに読み込んだXAMLファイルを指定します.変換したXAMLファイルに含まれるCanvasコントロールはWidthおよびHeightプロパティが指定されているために,このままFrameコントロールを表示すると,とても大きな画像がそのまま表示されてしまいます.そこでViewboxコントロールを使い適当な大きさに縮小表示しています.XAMLの記述は下記のようになります.

<Viewbox Width="32">
   <Frame Source="1downarrow.xaml" />
</Viewbox>

わざわざViewboxコントロールを使わなくてもLayoutTransformで同じことができないかと思い,以下のコードを試してみました.ですが,大きなフレームが表示されるだけで,意図した縮小表示はできませんでした.

<Frame Source="1downarrow.xaml">
  <Frame.LayoutTransform>
    <ScaleTransform ScaleX="64"/>
  </Frame.LayoutTransform>
</Frame>
Imageコントロールを使う

画像リソースを再利用するのですから,Frameコントロールのようにリソース名を都度してするよりも,リソースで定義しておくほうがスマートです.Frameコントロール自体をリソースで定義して利用するにも,リソースで定義したFrameコントロールインスタンスは1つですから,複数のコントロールで利用すると論理ツリーに加えることができず,例外が発生します.
例えばですが,SVGをGeometryDrawingに変換するXSLファイルを作成すれば以下のコードで再利用が可能になります.

<Window.Resource>
<GeometryDrawing x:Key="_icon1">
            <GeometryDrawing.Geometry>
                <!-- Create a composite shape. -->
                <GeometryGroup>
                    <EllipseGeometry Center="50,50" RadiusX="45" RadiusY="20" />
                    <EllipseGeometry Center="50,50" RadiusX="20" RadiusY="45" />
                </GeometryGroup>
            </GeometryDrawing.Geometry>
            <GeometryDrawing.Brush>

                <!-- Paint the drawing with a gradient. -->
                <LinearGradientBrush>
                    <GradientStop Offset="0.0" Color="Blue" />
                    <GradientStop Offset="1.0" Color="#CCCCFF" />
                </LinearGradientBrush>
            </GeometryDrawing.Brush>
            <GeometryDrawing.Pen>

                <!-- Outline the drawing with a solid color. -->
                <Pen Thickness="10" Brush="Black" />
            </GeometryDrawing.Pen>
        </GeometryDrawing>
</Window.Resource>
...中略...

<Image.Source>
  <DrawingImage Drawing="{StaticResource _icon1}"/> 
</Image.Source>
サンプルコード

今回のサンプルコードです.

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Width="300" >

    <Window.Resources>
        <GeometryDrawing x:Key="_icon1">
            <GeometryDrawing.Geometry>

                <!-- Create a composite shape. -->
                <GeometryGroup>
                    <EllipseGeometry Center="50,50" RadiusX="45" RadiusY="20" />
                    <EllipseGeometry Center="50,50" RadiusX="20" RadiusY="45" />
                </GeometryGroup>
            </GeometryDrawing.Geometry>
            <GeometryDrawing.Brush>

                <!-- Paint the drawing with a gradient. -->
                <LinearGradientBrush>
                    <GradientStop Offset="0.0" Color="Blue" />
                    <GradientStop Offset="1.0" Color="#CCCCFF" />
                </LinearGradientBrush>
            </GeometryDrawing.Brush>
            <GeometryDrawing.Pen>

                <!-- Outline the drawing with a solid color. -->
                <Pen Thickness="10" Brush="Black" />
            </GeometryDrawing.Pen>
        </GeometryDrawing>
        <Frame x:Key="_downIcon" 
               Name="pageFrame" 
               Source="1downarrow.xaml" />
    </Window.Resources>


    <DockPanel>                    
        <StackPanel>
        <TextBlock HorizontalAlignment="Center" >GeometryDrawing使用</TextBlock>
        <Button>
            <DockPanel>  
                <Image Width="8">
                    <Image.Source>
                        <DrawingImage Drawing="{StaticResource _icon1}">
                        </DrawingImage>
                    </Image.Source>
                </Image>
                <TextBlock VerticalAlignment="Center">size 8</TextBlock>
            </DockPanel>
        </Button>

        <Button>
            <DockPanel>
                <Image Width="16">
                    <Image.Source>
                        <DrawingImage Drawing="{StaticResource _icon1}">
                        </DrawingImage>
                    </Image.Source>
                </Image>
                <TextBlock VerticalAlignment="Center">size 16</TextBlock>
            </DockPanel>
        </Button>

        <Button>
            <DockPanel>
                <Image Width="32">
                    <Image.Source>
                        <DrawingImage Drawing="{StaticResource _icon1}">
                        </DrawingImage>
                    </Image.Source>
                </Image>
                <TextBlock VerticalAlignment="Center">size 32</TextBlock>
            </DockPanel>
        </Button>

        <Button>
            <DockPanel>
                <Image Width="64">
                    <Image.Source>
                        <DrawingImage Drawing="{StaticResource _icon1}">
                        </DrawingImage>
                    </Image.Source>
                </Image>
                <TextBlock VerticalAlignment="Center">size 64</TextBlock>
            </DockPanel>
        </Button>
    </StackPanel>  
        
        <StackPanel>
            <TextBlock HorizontalAlignment="Center">Frame使用</TextBlock>
            <Button>
                <DockPanel>
                    <Viewbox Width="8">
                        <Frame Source="1downarrow.xaml" />
                    </Viewbox>
                    <TextBlock VerticalAlignment="Center">size 8</TextBlock>                                   
                </DockPanel>
            </Button>

            <Button>
                <DockPanel>
                    <Viewbox Width="16">
                        <Frame Source="1downarrow.xaml" />
                    </Viewbox>                    
                    <TextBlock VerticalAlignment="Center">size 16</TextBlock>
                </DockPanel>
            </Button>

            <Button>
                <DockPanel>
                    <Viewbox Width="32">
                        <Frame Source="1downarrow.xaml" />
                    </Viewbox>                    
                    <TextBlock VerticalAlignment="Center">size 32</TextBlock>
                </DockPanel>
            </Button>

            <Button>
                <DockPanel>
                    
                        <Frame Source="1downarrow.xaml">
                            <Frame.LayoutTransform>
                                <ScaleTransform ScaleX="64"/>
                            </Frame.LayoutTransform>
                        </Frame>

                    
                    <TextBlock VerticalAlignment="Center">size 64</TextBlock>
                </DockPanel>
            </Button>

        </StackPanel>
        </DockPanel>
</Window>

まとめ

SVGの画像をWPFでアイコンなどとして利用する方法を述べました.
SVGXAMLに変換して,それをFrameコントロールで表示することができました.またStaticResourceとして利用する方法として,ImageDrawingをStaticResourceに登録する方法を述べました.ここで必要になるImageDrawingに変換するXSLファイルは別途作成する必要があります.
スケーラブル画像をそのまま利用することで,解像度に関わらず画像ファイル1つを再利用することができました.ビットマップ・アイコンでは,ジャギーを抑えて見やすいアイコンを表示するために,解像度ごとにビットマップ・ファイルを作製していた手間が不要になります.
一方で,スケーラブル画像を表示するために複数のコントロールを利用せねばならず,また描画処理の負荷がビットマップ描画に比べれば多少大きいだろうとも考えられます.この負荷がどの程度かの評価が必要なのですが,ここでは評価していません.
開発速度とよい実行速度を両立させるためには,アプリケーション開発開始時点ではSVGファイル読み込みで進めて,開発が完了して見栄えと処理速度を作りこむ段階で,SVGファイルを解像度に合わせて変換したビットマップ画像を読み込むコードに書き換える,のがよいのかもしれません.