TwainDotNetによるスキャン

#1. はじめに

[TwainDotNet](https://github.com/tmyroadctfig/twaindotnet)を使用してC#のWinFormからTWAINでスキャンしました。これを元に、[スキャンアプリケーション ITScan - Qiita](https://qiita.com/mayo00/items/01a22bbe1e9283fce6cb)を作成しています。

#2. 画面

「SelectSource」でスキャナを選択します。
「Scan」でTWAINドライバのダイアログを表示してスキャンします。

![20200517.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534247/abfc1087-064a-00ce-b54e-304d834633db.png)

#3. TwainDotNetの準備

TwainDotNetはnugetで取得できます。バージョン1.0.0を取得しました。
nugetする前にx86でビルドするように変更しないと、警告が表示されます。nugetした後に変更しても大丈夫です。

#4. ソース

```Form1.cs
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;
using TwainDotNet;
using TwainDotNet.WinFroms;

namespace TwainDotNetTest1
{
    public partial class Form1 : Form
    {
        private const string SCAN_DIR = @"C:\scan";

        private Twain twain = null;
        private ScanSettings settings = null;
        private Bitmap resultImage = null;
        private int scanCount = 1;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            twain = new Twain(new WinFormsWindowMessageHook(this));
            twain.TransferImage += twain_TransferImage;
            twain.ScanningComplete += twain_ScanningComplete;

            settings = new ScanSettings();
            settings.ShowTwainUI = true;
        }

        private void btnSelectSource_Click(object sender, EventArgs e)
        {
            twain.SelectSource();
        }

        private void btnScan_Click(object sender, EventArgs e)
        {
            Enabled = false;
            try
            {
                twain.StartScanning(settings);
            }
            catch (TwainException ex)
            {
                Enabled = true;
            }
        }

        private void twain_TransferImage(object sender, TransferImageEventArgs e)
        {
            resultImage = e.Image;

            string filename = String.Format("{0:D6}", scanCount++) + ".png";
            string fullname = Path.Combine(SCAN_DIR, filename);
            resultImage.Save(fullname, ImageFormat.Png);
            resultImage.Dispose();
        }

        private void twain_ScanningComplete(object sender, ScanningCompleteEventArgs e)
        {
            Enabled = true;
        }
    }
}
```

#5. 解説

WinForm用のTwainオブジェクトを作成し、イベントハンドラをセットします。WPF用もあります。

```cs
            twain = new Twain(new WinFormsWindowMessageHook(this));
            twain.TransferImage += twain_TransferImage;
            twain.ScanningComplete += twain_ScanningComplete;
```

後述のStartScanning()に渡すScanSettingsオブジェクトを作成します。StartScanning()のタイミングでTWAINドライバのダイアログを表示するためにShowTwainUIにtrueを代入します。

```cs
            settings = new ScanSettings();
            settings.ShowTwainUI = true;
```

ソース選択ダイアログを表示します。

```cs
            twain.SelectSource();
```
![20200517-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534247/5011f07d-432b-6daa-f4d5-87eff97400c8.png)
ソース選択ダイアログ

スキャンを開始します。
最初にFormをdisableにしています。
ShowTwainUIをtrueにしているため、TWAINドライバのダイアログが表示され、スキャンを開始するボタンを押下するとスキャンが開始されます。ShowTwainUIがfalseのままだと、TWAINドライバのダイアログが表示されずにスキャンが開始されます。
他のアプリケーションでTWAINドライバが使用中の場合には、TWAINドライバがエラーダイアログを表示して(TWAINドライバの実装に依存する)、ダイアログを閉じた後に例外が発生します。Twain関連の例外をcatchして握りつぶしています。(全ての例外をcatchしてEnable = trueしたほうがより安全ではありますが)

```cs
            Enabled = false;
            try
            {
                twain.StartScanning(settings);
            }
            catch (TwainException ex)
            {
                Enabled = true;
            }
```

![20200517-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534247/c747da58-6cc0-d938-1c74-7fc3503c42ab.png)
TWAINドライバのダイアログ(Canon DR-G1130)

1画像転送される毎にTransferImageが発生します。ADFスキャナの場合には1画像毎に連続して呼ばれます。ここでは0パディング6桁連番でPNGファイルとして保存しています。無条件上書きなので注意して下さい。
resultImage.Dispose()はTwainDotNetのテストプログラムでは行われていませんが、必要です。これがないと数ページ～十数ページ程度処理した後に、TwainDotNet内のnew Bitmap()で「使用されたパラメータが有効ではありません。」の例外が発生し、連続スキャンが止まります。TwainDotNet側ではDispose()していないのと、TransferImageEventArgsで渡されたBitmapをClone()せずに利用することを考えると、イベントハンドラ内でDispose()しないとメモリリークするからです。

```cs
        private void twain_TransferImage(object sender, TransferImageEventArgs e)
        {
            resultImage = e.Image;

            string filename = String.Format("{0:D6}", scanCount++) + ".png";
            string fullname = Path.Combine(SCAN_DIR, filename);
            resultImage.Save(fullname, ImageFormat.Png);
            resultImage.Dispose();
        }
```

スキャン完了時にScanningCompleteが発生します。ADFスキャナの場合には、連続したスキャンが完了した時に1回呼ばれます。また、TWAINドライバのダイアログを閉じたときにも呼ばれます。
Formをenableに戻しています。

```cs
        private void twain_ScanningComplete(object sender, ScanningCompleteEventArgs e)
        {
            Enabled = true;
        }
```