.NETCoreでマルチプラットフォームを対象にした画像処理

# はじめに

画像の縮小やグレースケールなどの画像の変換処理をC#で行う場合、System.Drawing名前空間のクラスを使うことが多かったと思います。

.NET6以降でこれらのメソッドを使う場合、Visual Studioでは下記のようなWindows環境以外では問題があるという警告が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/15de97da-0e7c-ff6d-3783-f0817edfc6d3.png)
```
警告	CA1416	この呼び出しサイトはすべてのプラットフォームで到達可能です。'Bitmap' は 'windows' でのみサポートされています。
警告	CA1416	この呼び出しサイトはすべてのプラットフォームで到達可能です。'Image.FromStream(Stream)' は 'windows' でのみサポートされています。
```
これは、System.Drawing.CommonがWindows以外で依存しているlibgdiplusというネイティブモジュールの品質が問題視され、.NET 6.0以降ではサポートされなくなったためです。

https://learn.microsoft.com/ja-jp/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only

今回は、System.Drawing.Commonと、↑のページで代替えとして紹介されている下記2つのライブラリを使って画像の拡大処理の例を見ていきます（Microsoft.Maui.Graphicsはうまく動かせなかったので今回はスキップ）。

- System.Drawing.Common
- ImageSharp
- SkiaSharp

# System.Drawing.Common

非推奨だけれど大人の事情などでどうしても使いたいという場合は、次の手順で利用できる。

System.Drawing.Commonとlibgdiplusをインストールし、

```sh
$ dotnet add package System.Drawing.Common
$ sudo apt install libgdiplus
```

.runtimeconfig.json用のテンプレートを作り

```json:runtimeconfig.template.json
{
    "configProperties": {
       "System.Drawing.EnableUnixSupport": true
    }
}
```

プロジェクトファイルでビルド時に.runtimeconfig.jsonを生成するように構成する。

```diff_xml:csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
+    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Drawing.Common" Version="6.0.0" />
  </ItemGroup>
</Project>
```

あとはお好みで警告を黙らせてあげれば実行できます。

```csharp:Program.cs
using System.Drawing;

public static void Resize(string input, string output)
{
    var imageData = File.OpenRead(input);
#pragma warning disable CA1416
    var resized = new Bitmap(Image.FromStream(imageData), new Size(1280, 720));
    resized.Save(output);
#pragma warning restore
}
```

# ImageSharp

.NET ネイティブな画像処理ライブラリです。

https://github.com/SixLabors/ImageSharp

追加のネイティブモジュールを利用しないので扱いやすいし、コードも直感的でわかりやすいのですが、ライセンス形態が独自になっていて、100万ドル以上の売り上げがある場合は有償ライセンスが必要になるので注意してください。

https://github.com/SixLabors/ImageSharp/blob/main/LICENSE

利用する場合はNuGetで `SixLabors.ImageSharp` を追加し

```sh
dotnet add package SixLabors.ImageSharp
```

Image.LoadAsyncした後に画像を操作して保存してあげればよいです。

```csharp:Program.cs
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;

public static async Task ResizeAsync(string input, string output)
{
    using var image = await Image.LoadAsync(input);

    image.Mutate(x => x
        .Resize(1280, 720));
    await image.SaveAsync(output);
}
```

# SkiaSharp

SkiaSharpはMonoプロジェクトが管理する画像ライブラリで、Googleが提供するネイティブモジュールであるSkiaのラッパーとして提供されています。

https://github.com/mono/SkiaSharp

Linuxで動作させる場合は、SkiaSharpのほかにSkiaSharp.NativeAssets.Linuxもインストールするようにしてください。

```sh
dotnet add package SkiaSharp
dotnet add package SkiaSharp.NativeAssets.Linux
```

ネイティブモジュールのラッパーということでusingが沢山出てきますね。

```csharp:Program.cs
using SkiaSharp;

public static async Task ResizeAsync(string input, string output)
{
    await using var imageData = File.OpenRead(input);
    using var bitmap = SKBitmap.Decode(imageData);
    using var resizedBitmap = bitmap.Resize(new SKSizeI(1280, 720), SKFilterQuality.Medium);
    using var data = resizedBitmap.Encode(SKEncodedImageFormat.Png, 10);

    await File.WriteAllBytesAsync(output, data.ToArray());
}
```

# ベンチマーク

BenchmarkDotNetでベンチマークしたんだけれど、Linux環境でSystem.Drawing.Commonがどうしても失敗するのでN/Aにしてます。
単品で動かす分には動くんだけれどな。

Windows環境

|           Method |     Mean |    Error |   StdDev |   Median |
|----------------- |---------:|---------:|---------:|---------:|
| RunDrawingCommon | 146.9 ms |  5.78 ms | 17.05 ms | 143.3 ms |
|    RunImageSharp | 648.9 ms | 29.18 ms | 86.05 ms | 608.0 ms |
|     RunSkiaSharp | 510.0 ms | 10.14 ms | 16.66 ms | 507.7 ms |

Linux環境

|           Method |     Mean |    Error |   StdDev |   Median |
|----------------- |---------:|---------:|---------:|---------:|
| RunDrawingCommon |       NA |       NA |       NA |       NA |
|    RunImageSharp | 585.7 ms | 10.47 ms |  8.74 ms | 590.8 ms |
|     RunSkiaSharp | 539.3 ms | 12.10 ms | 34.52 ms | 526.8 ms |

System.Drwaing が圧倒的に早い、さすがWindows環境ではMSのサポートがあるライブラリ、、、

# おわりに

ImageSharpが一番好みではあるのですが、ライセンス問題があるのでまず考えるのはSkiaSharpですかねぇ。