WPFで画像ファイルのデコード速度を比べてみた

コンピュータ

WPF標準のBitmapImage、Magic.NET(ImageMagic)、SkiaSharp、OpenCVSharpでPNGファイルのデコード速度を比べて見ました

プロジェクトの作成

cd (mkdir SpeedLoader01)
dotnet new wpf -f net8.0
rm *.xaml
rm MainWindow.xaml.cs
dotnet add package SkiaSharp --version 3.119.0
dotnet add package SkiaSharp.Views.WPF --version 3.119.0
dotnet add package SkiaSharp.Views.WPF
dotnet add package Magick.NET-Q8-AnyCPU --version 14.6.0
dotnet add package Magick.NET.SystemWindowsMedia --version 8.0.6
dotnet add package OpenCvSharp4
dotnet add package OpenCvSharp4.Extensions
dotnet add package OpenCvSharp4.WpfExtensions

ソースコード

ファイル名:SpeedLoader01.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
    <StartupObject>SpeedLoader01.App</StartupObject>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.6.0" />
    <PackageReference Include="Magick.NET.SystemWindowsMedia" Version="8.0.6" />
    <PackageReference Include="SkiaSharp" Version="3.119.0" />
    <PackageReference Include="SkiaSharp.Views.WPF" Version="3.119.0" />
  </ItemGroup>

</Project>

ファイル名:App.xaml.cs

using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using SkiaSharp;
using SkiaSharp.Views.WPF;
using ImageMagick;
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;

namespace SpeedLoader01;

public class App : Application
{
    [STAThread]
    public static void Main()
    {
        var imagePath = "X:\\testdata\\00001un.png"; // 実際のパスに置き換えてください
        byte[] buffer = File.ReadAllBytes(imagePath); // ファイルはUIスレッドで一括読み込み

        var imageControl = new Image
        {
            Stretch = Stretch.Uniform
        };

        var window = new System.Windows.Window
        {
            Title = "画像を表示",
            Width = 400,
            Height = 300,
            Content = new Grid { Children = { imageControl } }
        };

        window.Loaded += async (_, _) =>
        {
            var bitmap = await LoadBitmapAsync(buffer);
            //var bitmap = await LoadWithSkiaSharpAsync(buffer);
            //var bitmap = await LoadWithMagickAsync(buffer);
            //var bitmap = await LoadWithOpenCVSharpAsync(buffer);
            imageControl.Source = bitmap;
        };

        var app = new App();
        app.Run(window);
    }

    // デコード処理(BitmapImage)
    private static Task<BitmapSource> LoadBitmapAsync(byte[] buffer)
    {
        return Task.Run(() =>
        {
            using var memStream = new MemoryStream(buffer);

            var sw = Stopwatch.StartNew();

            var bitmap = new BitmapImage();
            bitmap.BeginInit();
            bitmap.CacheOption = BitmapCacheOption.OnLoad;
            bitmap.StreamSource = memStream;
            bitmap.EndInit();
            bitmap.Freeze();

            sw.Stop();
            Console.WriteLine($"BitmapImage Decode time: {sw.ElapsedMilliseconds} ms");

            return (BitmapSource)bitmap;
        });
    }
    // デコード処理(SkiaSharp)
    private static Task<BitmapSource> LoadWithSkiaSharpAsync(byte[] buffer)
    {
        return Task.Run(() =>
        {
            var sw = Stopwatch.StartNew();

            using var skStream = new SKManagedStream(new MemoryStream(buffer));
            using var skBitmap = SKBitmap.Decode(skStream);

            var source = skBitmap.ToWriteableBitmap();
            source.Freeze();

            sw.Stop();
            Console.WriteLine($"SkiaSharp decode time: {sw.ElapsedMilliseconds} ms");

            return (BitmapSource)source;
        });
    }
    // デコード処理(ImageMagick)
    private static Task<BitmapSource> LoadWithMagickAsync(byte[] buffer)
    {
        return Task.Run(() =>
        {
            var sw = Stopwatch.StartNew();

            using var memStream = new MemoryStream(buffer);
            using var image = new MagickImage(memStream);

            var source = image.ToBitmapSource(); // WPF で表示可能
            source.Freeze();

            sw.Stop();
            Console.WriteLine($"Magick.NET decode time: {sw.ElapsedMilliseconds} ms");

            return source;
        });
    }
    // デコード処理(OpenCV)
    private static Task<BitmapSource> LoadWithOpenCVSharpAsync(byte[] buffer)
    {
        return Task.Run(() =>
        {
            var sw = Stopwatch.StartNew();

            using var memStream = new MemoryStream(buffer);
            using var mat = Cv2.ImDecode(memStream.ToArray(), ImreadModes.Unchanged); // Unchanged: 透過も保持

            var bitmapSource = mat.ToBitmapSource();
            bitmapSource.Freeze();

            sw.Stop();
            Console.WriteLine($"OpenCVSharp decode time: {sw.ElapsedMilliseconds} ms");

            return bitmapSource;
        });
    }


}

PNG
BitmapImage Decode time: 376 ms
SkiaSharp decode time: 412 ms
Magick.NET decode time: 669 ms
OpenCVSharp decode time: 512 ms

JPG
BitmapImage Decode time: 26 ms
SkiaSharp decode time: 83 ms
Magick.NET decode time: 108 ms
OpenCVSharp decode time: 45 ms

コメント