Magick.NETで画像一括変換ツールを作る


# はじめに

Qiitaやブログなどで画像リサイズや拡張子変更するのですが、

毎回1枚1枚指定するので面倒になってきたので、

まとめて同じ拡張子と同じQualityに指定して変換するツールを

Magick.NET使ってWinUI3で実装していきます！

（下記で使って以降、最近、WinUI3にはまっている）

https://qiita.com/shisojuice/items/cfe9bdeac44df851c82b

https://qiita.com/shisojuice/items/d4457bab83ab2a9e3e1c

https://qiita.com/shisojuice/items/7d46c77e67773f3e3de2

Magick.NET: .NET Framework や .NET (Core) アプリケーションから ImageMagick
の機能を簡単に利用するためのライブラリです。Magick.NET は ImageMagick
のラッパーとして機能します。

ImageMagick:
画像の読み込み、編集、保存など様々な操作を行うためのソフトウェア本体です。

(
※ImageMagickをサーバー等で利用する場合は脆弱性が修正されているかを確認の上で使用検討をおすすめします。
下記より

https://qiita.com/yoya/items/2076c1f5137d4041e3aa

)

# 今回作る成果物

こんな感じのアプリを作成する。

主な機能は

- 画面

![Magick.NETで画像一括変換ツールを作る_01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/aa7e360a-6844-173c-ac55-c00b09aca4bb.jpeg)

![Magick.NETで画像一括変換ツールを作る_02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/1f6e7a5f-f1dd-4532-6f9d-c40ecbce3f0c.jpeg)

![Magick.NETで画像一括変換ツールを作る_03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/6053132f-0e78-fa14-fe01-b463bb994848.jpeg)

![Magick.NETで画像一括変換ツールを作る_04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/5289a9dd-ee6e-b35d-3b1a-5059e3d40a44.jpeg)

![Magick.NETで画像一括変換ツールを作る_05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/9ff9db3f-ab45-3ffc-4853-a225888ccc52.jpeg)

![Magick.NETで画像一括変換ツールを作る_06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/9ca9e117-5dc1-8594-dd61-acad0d255d11.jpeg)

OSのダークモードの設定で背景色が変わります。

![Magick.NETで画像一括変換ツールを作る_07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/4296192d-9db8-9e54-d7dd-21e41a94cb83.jpeg)

# 対象

- OS　Windows

- WinUI3使用

# プロジェクトの作成

今回は、プロジェクト名は ImageConverter で作成する。

下記のように

ファイル情報を取得するFilePicker、

出力する拡張子のSelectBox、

品質を指定するQualityのInputのUIで実装する。

```xaml:MainWindow.xaml
    <Grid>
     <Grid.RowDefinitions>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
     </Grid.RowDefinitions>

     <Grid x:Name="AppTitleBar" Margin="0,0,0,8">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto"/>
             <ColumnDefinition/>
         </Grid.ColumnDefinitions>
         <Image Source="Assets/StoreLogo.png" Width="16" Height="16" Margin="8,8,8,8" VerticalAlignment="Center"/>
         <TextBlock x:Name="AppTitleTextBlock" Text="ImageConverter" Grid.Column="1" Style="{StaticResource CaptionTextBlockStyle}" VerticalAlignment="Center"/>
     </Grid>

     <StackPanel Grid.Row="1" Orientation="Vertical" Margin="8">
         <Button x:Name="PickFilesButton" Content="Open Multiple Files" Click="PickFilesButton_Click" Margin="0,10,0,10"/>
         <TextBlock x:Name="PickFilesOutputTextBlock" TextWrapping="Wrap" Margin="0,10,0,10" />
         <StackPanel Orientation="Horizontal" Margin="0,10,0,10">
             <ComboBox x:Name="ExtComboBox" Header="Output Extensions" PlaceholderText="Pick an extension" Width="200" Margin="0,0,8,0"/>
             <NumberBox x:Name="QualityNum" Header="Output Quality:" Value="100" Minimum="1" Maximum="100" SpinButtonPlacementMode="Inline"/>
         </StackPanel>
         <Button Style="{StaticResource AccentButtonStyle}" x:Name="ConvertButton" Content="Convert Files" Click="ConvertButton_Click"/>
     </StackPanel>
 </Grid>
```

初期化時にタイトルバーの設定、出力する拡張子のSelectBoxを設定する

```csharp:MainWindow.xaml.cs
      Dictionary<MagickFormat, string> exts = new Dictionary<MagickFormat, string>();
      IReadOnlyList<StorageFile> files = new List<StorageFile>();
      public MainWindow()
      {
          this.InitializeComponent();
          ExtendsContentIntoTitleBar = true;
          SetTitleBar(AppTitleBar);

          List<ComboBoxItem> items = new List<ComboBoxItem>();
          exts.Add(MagickFormat.Jpeg, "jpeg");
          exts.Add(MagickFormat.WebP, "webp");
          exts.Add(MagickFormat.Png, "png");
          foreach (var ext in exts)
          {
              ComboBoxItem item = new ComboBoxItem();
              item.Content = ext.Value;
              item.Tag = ext.Key;
              items.Add(item);
          }
          ExtComboBox.ItemsSource = items;
          ExtComboBox.SelectedIndex = 0;
      }
```

FilePickerのボタン押下イベント処理を追加する。

ユーザーのDLフォルダーをデフォルトのOpen先に指定し、拡張子をフィルターする。

```csharp:MainWindow.xaml.cs
      private async void PickFilesButton_Click(object sender, RoutedEventArgs e)
      {
          PickFilesOutputTextBlock.Text = "";
          var openPicker = new Windows.Storage.Pickers.FileOpenPicker();
          var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
          WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd);

          openPicker.ViewMode = PickerViewMode.List;
          openPicker.SuggestedStartLocation = PickerLocationId.Downloads;
          openPicker.FileTypeFilter.Add(".jpg");
          openPicker.FileTypeFilter.Add(".jpeg");
          openPicker.FileTypeFilter.Add(".png");
          openPicker.FileTypeFilter.Add(".webp");
          openPicker.FileTypeFilter.Add(".heic");

          files = await openPicker.PickMultipleFilesAsync();
          if (files.Count > 0)
          {
              StringBuilder output = new StringBuilder("Picked files:");
              foreach (StorageFile file in files)
              {
                  output.Append(file.Name + ",");
              }
              PickFilesOutputTextBlock.Text = output.ToString();
          }
          else
          {
              PickFilesOutputTextBlock.Text = "Operation cancelled.";
          }
      }
```

Convertのボタン押下イベント処理を追加する。

成果物の出力先をユーザーのDLフォルダーのimageconverter_yyyyMMddhhmmssにして、作成する。

Magick.NETを使用して指定された出力拡張子と品質でConvertする。

```csharp:MainWindow.xaml.cs
      private async void ConvertButton_Click(object sender, RoutedEventArgs e)
      {
          if (files.Count > 0)
          {
              try
              {
                  string downloadsPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\Downloads";
                  string newFolderPath = Path.Combine(downloadsPath, "imageconverter_" + DateTime.Now.ToString("yyyyMMddhhmmss"));
                  if (!Directory.Exists(newFolderPath))
                  {
                      Directory.CreateDirectory(newFolderPath);
                  }
                  var selectedItemTag = (ExtComboBox.SelectedItem as FrameworkElement)?.Tag;
                  foreach (StorageFile file in files)
                  {
                      string newExtPath = Path.ChangeExtension(file.Path, "." + ExtComboBox.SelectionBoxItem);
                      string outputPath = Path.Combine(newFolderPath, Path.GetFileName(newExtPath));
                      using (MagickImage image = new MagickImage(file.Path))
                      {
                          image.Format = (MagickFormat)selectedItemTag!;
                          image.Quality = (uint)QualityNum.Value;
                          image.Write(outputPath);
                      }
                  }

                  ContentDialog dialog = new ContentDialog
                  {
                      Title = "Success:Open " + newFolderPath,
                      CloseButtonText = "OK",
                      XamlRoot = this.Content.XamlRoot
                  };
                  await dialog.ShowAsync();
                  PickFilesOutputTextBlock.Text = "";
              }
              catch (Exception ex)
              {
                  ContentDialog dialog = new ContentDialog
                  {
                      Title = ex.Message.ToString(),
                      CloseButtonText = "OK",
                      XamlRoot = this.Content.XamlRoot
                  };
                  await dialog.ShowAsync();
              }
          }
          else
          {
              ContentDialog dialog = new ContentDialog
              {
                  Title = "Choose Files",
                  CloseButtonText = "OK",
                  XamlRoot = this.Content.XamlRoot
              };
              await dialog.ShowAsync();
              PickFilesButton.Focus(FocusState.Programmatic);
          }
      }
```

完成！

# 今回の成果物ソース

ソース

https://github.com/shisojuice/winui_ImageConverter

# まとめ

Magick.NET(ImageMagick)で遊んでみたが、

様々な拡張子のサポートがあり、操作も簡単で扱いやすいライブラリだった。

特にHEIC拡張子をサポートしているライブラリは少ないので

脆弱性などを理解したうえで、今後も注目していきたいと思う。