【C#】SkiaSharpを使えばLinux上でSVGとJPEGを合成できるよ

技術

最初はWindowsサーバーでやってました

とあるWebアプリの開発の中で

ブラウザ上で画像の上にSVGでお絵描きして、出来上がった画像を別サーバーへ転送したい

という要件がありました。

具体的にはこんな流れです。

  1. ブラウザ上で背景画像を指定したsvgタグにrectpathのようなタグを配置してお絵描きします。
  2. サーバーへは画像パスとsvgに配置したタグのリストを送信します。
  3. サーバーはブラウザから送信されたsvgタグのリストをSVGファイルとして保存します。
  4. 任意のタイミングで画像とSVGファイルを合成してJPEGファイルを生成し、別サーバーへ転送します。

今回はこの「画像とSVGファイルを合成してJPEGファイルを生成」の部分の話題です。

この時、開発していたのは.NET6で作ったWebアプリケーションで、WindowsサーバーのIISでホストされて動くものでした。

ということで、https://github.com/svg-net/SVGで公開されているSVG.NETというライブラリを使ってSVGとJPEGの合成を実装したのですが、これはSystem.Drawing名前空間を使っていて「Windows限定」みたいな実装になってしまいました。

System.Drawing名前空間あたりはWindows以外では使えないこと(もしくはWindows以外での使用は非推奨?)になっているらしいく、Linuxで同じことをしようと思ったら別の方法でやらないといけないみたいです。

少し調べて試してみたところSkiaSharp.Svgというライブラリが使えそうでした。
SkiaSharp.Svgはhttps://github.com/mono/SkiaSharp.Extendedで公開されているオープンソースのライブラリです。

画像とSVGファイルを準備

今回、サンプル用に作ったプログラムで使う画像はこちらのサイトからダウンロードさせてもらいました。
こんな画像です。
この画像をベースにSVGを合成させてみます。

woman.jpg

合成するSVGファイルはこの2つです。
最初にlayer1.svgを合成して、その上にさらにlayer2.svgを合成することにしました。

layer1.svg
layer2.svg

SVGファイルの中身はこんな感じです。

<svg xmlns="http://www.w3.org/2000/svg">
    <circle cx="2250" cy="1300" r="800" fill="none" stroke="#FF0000" stroke-width="20"></circle>
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
    <path d="M2300 1800 L2500 2000 L2400 2000 L2400 2300 L2200 2300 L2200 2000 L2100 2000 Z" style="fill:#0F0" />
</svg>

そしてこのwoman.jpgにlayer1.svg、layer2.svgを合成してこんな画像が出来上がる予定です。

まずはWindows限定で動く「SVG.NET」版

SkiaSharpの実装を紹介する前に、先にSVG.NETで実装したコードをご紹介します。

SVG.NETは下記のコマンドでNugetから参照追加します。
※ 下記は「C:\work\SvgNetSample」プロジェクトにSVG.NETを参照追加する場合

cd C:\work\SvgNetSample
dotnet add package Svg

SVG.NETを使って画像ファイルとSVGファイルを合成する処理を下記のように実装しました。
ちなみに画像の合成を試すだけなので、今回はコンソールアプリです。

using Svg;
using System.Drawing;
using System.Drawing.Imaging;

var orgFile = @"C:\work\woman.jpg";
var svgFile1 = @"C:\work\layer1.svg";
var svgFile2 = @"C:\work\layer2.svg";
var outputFile = @"C:\work\result.jpg";

Overlay(outputFile, orgFile, svgFile1, svgFile2);

static void Overlay(string outputPath, string baseImagePath, params string[] layerSvgs)
{
    var svgDocs = layerSvgs.Select(svgPath => SvgDocument.Open(svgPath));

    var orgImage = Image.FromFile(baseImagePath);
    using (var graphics = Graphics.FromImage(orgImage))
    {
        foreach (var svgDoc in svgDocs)
        {
            graphics.DrawImage(svgDoc.Draw(), 0, 0);
        }
    }

    using (var stream = File.OpenWrite(outputPath))
    {
        orgImage.Save(stream, ImageFormat.Jpeg);
    }
}

Overlayメソッドが画像の合成を実装している部分になります。

最初にSvgDocument.OpenメソッドでSVGファイルを読んでいます。
そして、ベースとなるJpegファイル(この場合は「woman.jpg」)のImageからGraphicsを作って、読み取ったSVGを描画しているだけです。

SvgDocument.Draw() メソッドは Bitmapクラスのインスタンスを返すので、そのままGraphicsにDrawできる、というわけです。

このプログラムの中で使用しているImage Graphics ImageFormatのあたりがSystem.Drawing名前空間の中に定義されているクラスになっていて、この辺りを使っているためにWindows限定で動作するプログラムということになってしまっていました。

Linuxでも動く「SkiaSharp.Svg」版

ということで、次はSkiaSharp.Svgでの実装をご紹介します。
同じ .NET6 ですが、 今度は Linux 上に作ります。

まずは参照追加。
SkiaSharp.Svgを使うには「SkiaSharp.NativeAssets.Linux.NoDependencies」も必要らしいので同時に参照追加しています。
※ 下記は「/usr/src/SkiaSharpSample」プロジェクトの場合

cd /usr/src/SkiaSharpSample
dotnet add package SkiaSharp.Svg
dotnet add package SkiaSharp.NativeAssets.Linux.NoDependencies

SkiaSharp.Svg版の場合、Imageクラスの代わりにSKBitmapを使い、Graphicsの代わりにSKCanvasを使う感じです。

using SkiaSharp;
using SKSvg = SkiaSharp.Extended.Svg.SKSvg;

var orgFile = "/usr/src/woman.jpg";
var svgFile1 = "/usr/src/layer1.svg";
var svgFile2 = "/usr/src/layer2.svg";
var outputFile = "/usr/src/result.jpg";

Overlay(outputFile, orgFile, svgFile1, svgFile2);

static void Overlay(string outputPath, string baseImagePath, params string[] layerSvgs)
{
    var orgBitmap = SKBitmap.Decode(baseImagePath);
    var canvas = new SKCanvas(orgBitmap);
    foreach (var layer in layerSvgs)
    {
        canvas.DrawPicture(new SKSvg().Load(layer));
    }

    using (var stream = SKFileWStream.OpenStream(outputPath))
    {
        orgBitmap.Encode(stream, SKEncodedImageFormat.Jpeg, 90);
    }
}

SVG.NET版と同様にOverlayメソッドで画像を合成する作りになっています。
OverlayメソッドはシグニチャもSVG.NET版と同じにしました。

SKBitmap.Decode()メソッドでベースとなるJPEGファイルを読んで、SKBitmapのインスタンスを作ります。
このSKBitmapからSKCanvasを作れば、SKCanvas.DrawPicture()メソッドでSKPictureを画像の上に描画できることになっています。

SKPictureのインスタンスはSKSvgクラスのインスタンスメソッドLoad()でSVGファイルから簡単に作成できました。

最後にSKBitmap.EncodeメソッドでJPEGファイルにしてファイル出力するだけです。

まとめ

画像処理ってちょっと面倒なイメージがあったんですが、画像とSVGの合成がめっちゃ簡単に実装できちゃいました。

ちなみにSVG.NETはMicrosoft Public License、SKSharp.SvgはMIT Licenseになっていて、どちらも使いやすそうです。

SVGは無料のエディタも多数ありますし、SVG自体がテキストベースなのでプログラムで動的に作ったりなんかもできるので、色々なサービスに組み込みやすいかもしれないなと思いました。
なんか考えてみようかな♪

今回の記事が、画像合成のアイデアを何かお持ちの方の参考になればうれしいと思います。

協力が必要って方は、ぜひシステムクラフトへご相談ください。

コメント

タイトルとURLをコピーしました