ゲームのスクリーンショットの作成を監視してフォーマット変更して別の場所に移す

# はじめに
この記事はThe Elder ScrollsのMorrowindを遊んでいる最中に、ゲームの様子などをTwtterに投稿する際に、bmpファイルをpngに変換するのが手動では面倒なので、簡単なツールを作成したものについて説明しています。

## 追記 [2020-07-24]
フォームが邪魔だったので次の記事を参考にタスクトレイに収納するようにした。
[【C#】最小化時のみタスクトレイにアイコンを表示 - TakBoy's Programing Note](https://csharpmagazine.hatenablog.com/entry/2019/07/14/%E3%80%90C%23%E8%AC%9B%E5%BA%A7%E3%80%91%E6%9C%80%E5%B0%8F%E5%8C%96%E6%99%82%E3%81%AE%E3%81%BF%E3%82%BF%E3%82%B9%E3%82%AF%E3%83%88%E3%83%AC%E3%82%A4%E3%81%AB%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3)

ついでにフォームに貼り付けているコントロールの事も追記

# 要件
- 指定した場所にて bmp ファイルが Create された事を検知する
- 検知したファイルを png に変換し指定した場所にコピーする
- コピー先はさらに `yyyy-MM-dd` 形式のフォルダを作成してそこにコピーする
- 元の bmp ファイルは削除する
- 結果をログ表示する
- 最小化時にタスクトレイに収納する（追記）

# ポイント
## ファイル監視
`System.IO.FileSystemWatcher` を利用する。
次を参考にした。
[フォルダ、ファイルの変更を監視する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/file/filesystemwatcher.html)

## bmp から png の変換
`System.Drawing.Bitmap` を利用する。
次を参考にした。
[方法: BMP イメージから PNG イメージへの変換 - Windows Forms | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-convert-a-bmp-image-to-a-png-image)

## ログ表示
非同期処理ではフォームのコントロールを直接操作できない。いつも忘れる。
次を参考にした。
[コントロールに対してスレッドセーフな呼び出しを行う - Windows Forms | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls)

## タスクトレイへの収納
`System.Windows.Forms.NotifyIcon` コントロールを追加して、アイコン指定して必要なコードを記載する。

# フォームデザイン
追加しているのは次の通り。

```C#
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox3;
private System.Windows.Forms.NotifyIcon notifyIcon1;
```

フォームはサイズ変更可能としているので `Anchor` だけ調整。

# コード
汚いが、自分用に動けばいいので。

```C#
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace RImageConverter
{
    public partial class Form1 : Form
    {
        private delegate void SafeCallDelegate(string text);
        private FileSystemWatcher watcher = new FileSystemWatcher();
        private string moveFolder;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            watcher.Filter = "*.bmp";
            watcher.Created += new FileSystemEventHandler(watcher_Created);
        }

        private void AddLog(string text)
        {
            if (textBox1.InvokeRequired)
            {
                var d = new SafeCallDelegate(AddLog);
                textBox1.Invoke(d, new object[] { text });
            }
            else
            {
                textBox3.Text = $"{text}\r\n{textBox3.Text}";
            }
        }

        private void watcher_Created(object sender, FileSystemEventArgs args)
        {
            try
            {
                Thread.Sleep(1000);
                using (var bmp = new Bitmap(args.FullPath))
                {
                    var dateFolder = DateTime.Now.ToString("yyyy-MM-dd");
                    Directory.CreateDirectory($@"{textBox2.Text}\{dateFolder}");
                    var path = $@"{moveFolder}\{dateFolder}\{args.Name.Replace(".bmp", ".png")}";
                    bmp.Save(path, ImageFormat.Png);
                    AddLog(path);
                }
                File.Delete(args.FullPath);
            }
            catch (Exception ex)
            {
                AddLog(ex.Message);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (button1.Text.Equals("監視開始"))
            {
                watcher.Path = textBox1.Text;
                moveFolder = textBox2.Text;
                watcher.EnableRaisingEvents = true;
                button1.Text = "監視停止";
            }
            else
            {
                watcher.EnableRaisingEvents = false;
                button1.Text = "監視開始";
            }
        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                this.Visible = false;
                notifyIcon1.Visible = true;
            }
        }

        private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.Visible = true;
            this.WindowState = FormWindowState.Normal;
            notifyIcon1.Visible = false;
        }

    }
}
```

## 画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156225/534b4254-678a-7da4-928c-fc3fb54d07d2.png)

# 課題
`Create` が即検知されて、Morrowindが生成途中であろう状況を読み取ってしまうようなので、一旦致し方なく `Thread.Sleep(1000);` を配置した。
カッコよくするなら、ちゃんとアクセスできる状態をチェックするなどだが、とりあえずはこれでいい。

# 参考にしたサイト
- [フォルダ、ファイルの変更を監視する - .NET Tips (VB.NET,C#...)](https://dobon.net/vb/dotnet/file/filesystemwatcher.html)
- [方法: BMP イメージから PNG イメージへの変換 - Windows Forms | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-convert-a-bmp-image-to-a-png-image)
- [コントロールに対してスレッドセーフな呼び出しを行う - Windows Forms | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls)
- [【C#】最小化時のみタスクトレイにアイコンを表示 - TakBoy's Programing Note](https://csharpmagazine.hatenablog.com/entry/2019/07/14/%E3%80%90C%23%E8%AC%9B%E5%BA%A7%E3%80%91%E6%9C%80%E5%B0%8F%E5%8C%96%E6%99%82%E3%81%AE%E3%81%BF%E3%82%BF%E3%82%B9%E3%82%AF%E3%83%88%E3%83%AC%E3%82%A4%E3%81%AB%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3)