[SC]スキャナーを使用します


過去数年間、同僚と私はオフィスで紙を少なくしようとしています。 従業員はデジタルドキュメントをより速く、より適切に操作します-ほこりはずっと少なくなります。

デジタルドキュメントに完全に切り替えるには、最初に紙をスキャンする必要があります。 スキャナーのデスクトップアプリケーションを開発するには、 .NET Frameworkを使用します 。 箱から出してすぐに、彼はスキャナーを操作するためのツールを提供しません。 .NETCOMと友だちなので、 WIA(Windows Imaging Architecture)コンポーネントを使用できます。

WIAとの便利な作業のために、多くのスキャンアプリケーションの基礎を形成するクラスを作成しました。 例としてScannerクラスを使用して、 スキャナーでの経験を共有したいと思います。

私たちの銀行のいくつかの非常に有用なアプリケーションで使用されています。 たとえば、銀行の顧客のすべてのアプリケーションと契約は、 スキャナークラスを使用するアプリケーションによってデジタル化されます。

次のようになります。


など:


すべてがシンプルであるというわけではありません


開発したアプリケーションからスキャナーの操作を開始するには、最初にスキャナーを選択してから構成し、スキャンする必要があります。 したがって、クラスのパブリックインターフェイスは、 ConfigurationScanの 2つのメソッドによって制限できます。

設定により、標準のスキャナー設定ダイアログが表示されます。 スキャンはドキュメントをスキャンし、 MemoryStreamに画像を返します。

今、詳細に...


WIAを介してスキャナーを操作します。 これを行うには、 Microsoft Windows Image Acquisition Library v2.0コンポーネントをCOMプロジェクトに接続します 。COMプロジェクトの実装はファイルC:\ Windows \ System32 \ wiaaut.dllにあります。

スキャナーを一度セットアップし、次のアプリケーションのロード時に、スキャナーが既に構成されていることが一般的です。 スキャナーを構成し、構成を保存および復元する機能が必要です。 Scannerクラスを作成し、 WIA使用してファイルの先頭に追加ます。 Scannerクラスのオブジェクトを作成するとき、設定から設定をロードしようとします。 うまくいかない場合は、スキャナーを手動でセットアップすることをお勧めします。

通常、1つのプログラムが1つのスキャナーで動作するため、シングルトンを作成できます。 私の場合、複数のスキャナーが同時に存在する場合があります。
コンストラクタは次のようになります。

public Scanner() { try { LoadConfig(); } catch (Exception) { MessageBox.Show(" ,    "); Configuration(); } } 

ここでMessageBoxは何をしていますか? ほとんどの場合、スキャンアプリケーションはGUIを意味します。 情報を表示するには、 MessageBoxを使用してメッセージを表示することもできます。さらに、標準のWIAダイアログを使用してスキャナーを構成します。

スキャナーをセットアップすることから始めましょう。 スキャナーは、 設定()機能を使用して設定されます。この機能は、プログラムの「スキャナー設定」ボタンを押すなど、いつでも呼び出すことができます。

 public void Configuration() { try { var commonDialog = new CommonDialogClass(); _scanDevice = commonDialog.ShowSelectDevice(WiaDeviceType.ScannerDeviceType, true); if (_scanDevice == null) return; var items = commonDialog.ShowSelectItems(_scanDevice); if (items.Count < 1) return; _scannerItem = items[1]; SaveProp(_scanDevice.Properties, ref _defaultDeviceProp); SaveConfig(); } catch (Exception e) { MessageBox.Show(e.Message, "   "); } } 

スキャナーには多くのパラメーターがあり、そのうちのいくつかは他から計算する必要があるため、問題を複雑にしないために、標準のWIAダイアログCommonDialogClassを使用して構成します。 まず、プログラムのユーザーがShowSelectDeviceスキャナー(WiaDeviceType。ScannerDeviceType、true)を選択することをお勧めします。 ダイアログは、 Deviceオブジェクトを返します 。デバイスが選択されていない場合はnullを返します


次に、受信したデバイスShowSelectItems(_scanDevice)を構成します。 設定では、 DPI 、用紙サイズ、カラーモード、その他のパラメーターを設定できます。

このセットアップウィンドウの主な欠点は、論理的なOKではなく、[スキャン]ボタンをクリックして確認する必要があることですが、この場合、スキャンは開始されません。


構成が正常に完了したら、SaveConfig()を呼び出して構成をファイルに保存します。

WIAは設定の保存/復元を提供しません。 私はこの部分を自分でしなければなりませんでした。 複雑なことは何もありませんでした。設定はIPropertyリストの形式で表示され、 DeviceItemにあります。

保存するために、パラメータ名、識別子、値がセミコロンで区切られ、 DeviceItemの設定が[device][item]ヘッダーで区切られた読みやすいテキスト形式を選択しました。

構成例
[device]
DeviceID;{6BDD1FC6-810F-11D0-BEC7-08002BE2092F}\0003
Item Name;4098;Root
Full Item Name;4099;0003\Root
Item Flags;4101;76
Unique Device ID;2;{6BDD1FC6-810F-11D0-BEC7-08002BE2092F}\0003
Manufacturer;3;FUJITSU
Description;4;fi-6140dj
Type;5;65537
Port;6;\\.\Usbscan0
Name;7;fi-6140dj #2
Server;8;local
Remote Device ID;9;
UI Class ID;10;{C2A237CB-9CEF-4fd6-B989-E82E1DB0F0C9}
Hardware Configuration;11;0
BaudRate;12;
STI Generic Capabilities;13;51
WIA Version;14;2.0
Driver Version;15;2.1.4.3
PnP ID String;16;\\?\usb#vid_04c5&pid_114d#8&184ab430&0&2#{6bdd1fc6-810f-11d0-bec7-08002be2092f}
STI Driver Version;17;2
Horizontal Bed Size;3074;8500
Vertical Bed Size;3075;11692
Access Rights;4102;3
Horizontal Optical Resolution;3090;600
Vertical Optical Resolution;3091;600
Firmware Version;1026;0500
Max Scan Time;3095;180000
Preview;3100;0
Show preview control;3103;1
Horizontal Sheet Feed Size;3076;8500
Vertical Sheet Feed Size;3077;14000
Document Handling Capabilities;3086;21
Document Handling Status;3087;5
Document Handling Select;3088;1
Pages;3096;1
Sheet Feeder Registration;3078;1
Horizontal Bed Registration;3079;0
Vertical Bed Registration;3080;0
Document Handling Option;38914;0
[item]
ItemID;0003\Root\Top
Item Name;4098;Top
Full Item Name;4099;0003\Root\Top
Item Flags;4101;67
Color Profile Name;4120;sRGB Color Space Profile.icm
Horizontal Resolution;6147;200
Vertical Resolution;6148;200
Horizontal Extent;6151;1653
Vertical Extent;6152;2338
Horizontal Start Position;6149;23
Vertical Start Position;6150;0
Data Type;4103;2
Bits Per Pixel;4104;8
Brightness;6154;0
Contrast;6155;0
Current Intent;6146;0
Pixels Per Line;4112;1653
Number of Lines;4114;2338
Preferred Format;4105;{B96B3CAA-0728-11D3-9D7B-0000F81EF32E}
Item Size;4116;3872792
Threshold;6159;128
Format;4106;{B96B3CAA-0728-11D3-9D7B-0000F81EF32E}
Media Type;4108;128
Channels Per Pixel;4109;1
Bits Per Channel;4110;8
Planar;4111;0
Bytes Per Line;4113;1656
Buffer Size;4118;65535
Access Rights;4102;3
Compression;4107;0
Photometric Interpretation;6153;0
Lamp Warm up Time;6161;180000
3100;3100;0


 private void SaveConfig() { var settings = new List<string>(); settings.Add("[device]"); settings.Add(String.Format("DeviceID;{0}", _scanDevice.DeviceID)); foreach (IProperty property in _scanDevice.Properties) { var propstring = string.Format("{1}{0}{2}{0}{3}", ";", property.Name, property.PropertyID, property.get_Value()); settings.Add(propstring); } settings.Add("[item]"); settings.Add(String.Format("ItemID;{0}", _scannerItem.ItemID)); foreach (IProperty property in _scannerItem.Properties) { var propstring = string.Format("{1}{0}{2}{0}{3}", ";", property.Name, property.PropertyID, property.get_Value()); settings.Add(propstring); } File.WriteAllLines(Config, settings.ToArray()); } private enum loadMode {undef, device, item}; private void LoadConfig() { var settings = File.ReadAllLines(Config); var mode = loadMode.undef; foreach (var setting in settings) { if (setting.StartsWith("[device]")) { mode = loadMode.device; continue; } if (setting.StartsWith("[item]")) { mode = loadMode.item; continue; } if (setting.StartsWith("DeviceID")) { var deviceid = setting.Split(';')[1]; var devMngr = new DeviceManagerClass(); foreach (IDeviceInfo deviceInfo in devMngr.DeviceInfos) { if (deviceInfo.DeviceID == deviceid) { _scanDevice = deviceInfo.Connect(); break; } } if (_scanDevice == null) { MessageBox.Show("    "); return; } _scannerItem = _scanDevice.Items[1]; continue; } if (setting.StartsWith("ItemID")) { var itemid = setting.Split(';')[1]; continue; } var sett = setting.Split(';'); switch (mode) { case loadMode.device: SetProp(_scanDevice.Properties, sett[1], sett[2]); break; case loadMode.item: SetProp(_scannerItem.Properties, sett[1], sett[2]); break; } } SaveProp(_scanDevice.Properties, ref _defaultDeviceProp); } private static void SetProp(IProperties prop, object property, object value) { try { prop[property].set_Value(value); } catch (Exception) { //       return; } } 

スキャン手順は簡単です。 ピクチャとともにMemoryStreamを返すか、スキャンが失敗した場合はnullを返します。

 public MemoryStream MemScan() { try { var result = _scannerItem.Transfer(FormatID.wiaFormatJPEG); var wiaImage = (ImageFile)result; var imageBytes = (byte[])wiaImage.FileData.get_BinaryData(); using (var ms = new MemoryStream(imageBytes)) { using (var bitmap = Bitmap.FromStream(ms)) { bitmap.Save(stream, ImageFormat.Jpeg); } } } catch (Exception) { return null; } return stream; } 

私の意見では、スキャンするときは進行状況バーを描画する方が便利なので、標準の進行状況を非表示にします。 進行状況を表示せずにスキャンするには、 Transferメソッドが使用されます。

両面/シンプレックス


多くの長距離スキャナーは、設定ダイアログから選択できる2ページスキャンモードをサポートしています。


ただし、標準ダイアログでは、 二重モードの設定を忘れることがあります。したがって、 二重モードを強制的に有効にして元の設定に戻すことができると便利です。

 public void SetDuplexMode(bool isDuplex) { // WIA property ID constants const string wiaDpsDocumentHandlingSelect = "3088"; const string wiaDpsPages = "3096"; // WIA_DPS_DOCUMENT_HANDLING_SELECT flags const int feeder = 0x001; const int duplex = 0x004; if (_scanDevice == null) return; if (isDuplex) { SetProp(_scanDevice.Properties, wiaDpsDocumentHandlingSelect, (duplex | feeder)); SetProp(_scanDevice.Properties, wiaDpsPages, 1); } else { try { SetProp(_scanDevice.Properties, wiaDpsDocumentHandlingSelect, _defaultDeviceProp[wiaDpsDocumentHandlingSelect]); SetProp(_scanDevice.Properties, wiaDpsPages, _defaultDeviceProp[wiaDpsPages]); } catch (Exception e) { MessageBox.Show(String.Format("   :{0}{1}", Environment.NewLine, e.Message)); } } } 

2ページモードでスキャンするには、1ページ目と2ページ目でMemScan()を呼び出す必要があります。 最初の呼び出しはシートをスキャンして最初のページの画像を返し、2番目の呼び出しは2ページ目のスキャンを返します。 フィーダースキャナーを使用する場合、 MemScan()が戻るまでループでスキャンすると便利です!= Null-これは、スキャナーが紙切れになったことを意味します。 スキャナーがどのモードで動作するかを考える必要はありません。文書はとにかくスキャンされます。 カチンコスキャナーのループでスキャンを開始する場合、スキャナーが接続されている間、スキャンプロセスは停止しません。ソフトウェアを開発するとき、このニュアンスを考慮する必要があります。

スキャナーを操作するためのフルクラスのソースコード
 using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Windows.Forms; using WIA; namespace Scanning { public class Scanner { public const string Config = "scanner.cfg"; private Device _scanDevice; private Item _scannerItem; private Random _rnd = new Random(); private Dictionary<string, object> _defaultDeviceProp; public bool IsVirtual; public Scanner() { try { LoadConfig(); } catch (Exception) { MessageBox.Show(" ,    "); Configuration(); } } public void Configuration() { try { var commonDialog = new CommonDialogClass(); _scanDevice = commonDialog.ShowSelectDevice(WiaDeviceType.ScannerDeviceType, true); if (_scanDevice == null) return; var items = commonDialog.ShowSelectItems(_scanDevice); if (items.Count < 1) return; _scannerItem = items[1]; SaveProp(_scanDevice.Properties, ref _defaultDeviceProp); SaveConfig(); } catch (Exception e) { MessageBox.Show(e.Message, "   "); } } private void SaveProp(WIA.Properties props, ref Dictionary<string, object> dic) { if (dic == null) dic = new Dictionary<string, object>(); foreach (Property property in props) { var propId = property.PropertyID.ToString(); var propValue = property.get_Value(); dic[propId] = propValue; } } public void SetDuplexMode(bool isDuplex) { // WIA property ID constants const string wiaDpsDocumentHandlingSelect = "3088"; const string wiaDpsPages = "3096"; // WIA_DPS_DOCUMENT_HANDLING_SELECT flags const int feeder = 0x001; const int duplex = 0x004; if (_scanDevice == null) return; if (isDuplex) { SetProp(_scanDevice.Properties, wiaDpsDocumentHandlingSelect, (duplex | feeder)); SetProp(_scanDevice.Properties, wiaDpsPages, 1); } else { try { SetProp(_scanDevice.Properties, wiaDpsDocumentHandlingSelect, _defaultDeviceProp[wiaDpsDocumentHandlingSelect]); SetProp(_scanDevice.Properties, wiaDpsPages, _defaultDeviceProp[wiaDpsPages]); } catch (Exception e) { MessageBox.Show(String.Format("   :{0}{1}", Environment.NewLine, e.Message)); } } } public MemoryStream MemScan() { if ((_scannerItem == null) && (!IsVirtual)) { MessageBox.Show("  ,   !", "Info"); //return null; IsVirtual = true; } var stream = new MemoryStream(); if (IsVirtual) { if (_rnd.Next(3) == 0) { return null; } var btm = GetVirtualScan(); btm.Save(stream, ImageFormat.Jpeg); return stream; } try { var result = _scannerItem.Transfer(FormatID.wiaFormatJPEG); var wiaImage = (ImageFile)result; var imageBytes = (byte[])wiaImage.FileData.get_BinaryData(); using (var ms = new MemoryStream(imageBytes)) { using (var bitmap = Bitmap.FromStream(ms)) { bitmap.Save(stream, ImageFormat.Jpeg); } } } catch (Exception) { return null; } return stream; } private Bitmap GetVirtualScan() { const int imgSize = 777; var defBtm = new Bitmap(imgSize, imgSize); var g = Graphics.FromImage(defBtm); var r = new Random(); g.FillRectangle(new SolidBrush(Color.FromArgb(r.Next(0, 50), r.Next(0, 50), r.Next(0, 50))), 0, 0, imgSize, imgSize); // bg for (int i = 0; i < r.Next(1000, 3000); i++) { var den = r.Next(200, 255); var br = new SolidBrush(Color.FromArgb(den, den, den)); den -= 100; var pr = new Pen(Color.FromArgb(den, den, den), 1); var size = r.Next(1, 8); var x = r.Next(0, imgSize - size); var y = r.Next(0, imgSize - size); g.FillEllipse(br, x, y, size, size); g.DrawEllipse(pr, x, y, size, size); } g.DrawString(" ", new Font(FontFamily.GenericSerif, 25), Brushes.Aqua, new RectangleF(0, 0, imgSize, imgSize)); g.Flush(); return defBtm; } private void SaveConfig() { var settings = new List<string>(); settings.Add("[device]"); settings.Add(String.Format("DeviceID;{0}", _scanDevice.DeviceID)); foreach (IProperty property in _scanDevice.Properties) { var propstring = string.Format("{1}{0}{2}{0}{3}", ";", property.Name, property.PropertyID, property.get_Value()); settings.Add(propstring); } settings.Add("[item]"); settings.Add(String.Format("ItemID;{0}", _scannerItem.ItemID)); foreach (IProperty property in _scannerItem.Properties) { var propstring = string.Format("{1}{0}{2}{0}{3}", ";", property.Name, property.PropertyID, property.get_Value()); settings.Add(propstring); } File.WriteAllLines(Config, settings.ToArray()); } private enum loadMode {undef, device, item}; private void LoadConfig() { var settings = File.ReadAllLines(Config); var mode = loadMode.undef; foreach (var setting in settings) { if (setting.StartsWith("[device]")) { mode = loadMode.device; continue; } if (setting.StartsWith("[item]")) { mode = loadMode.item; continue; } if (setting.StartsWith("DeviceID")) { var deviceid = setting.Split(';')[1]; var devMngr = new DeviceManagerClass(); foreach (IDeviceInfo deviceInfo in devMngr.DeviceInfos) { if (deviceInfo.DeviceID == deviceid) { _scanDevice = deviceInfo.Connect(); break; } } if (_scanDevice == null) { MessageBox.Show("    "); return; } _scannerItem = _scanDevice.Items[1]; continue; } if (setting.StartsWith("ItemID")) { var itemid = setting.Split(';')[1]; continue; } var sett = setting.Split(';'); switch (mode) { case loadMode.device: SetProp(_scanDevice.Properties, sett[1], sett[2]); break; case loadMode.item: SetProp(_scannerItem.Properties, sett[1], sett[2]); break; } } SaveProp(_scanDevice.Properties, ref _defaultDeviceProp); } private static void SetProp(IProperties prop, object property, object value) { try { prop[property].set_Value(value); } catch (Exception) { return; } } } } 


もちろん、 スキャナースキャナーを操作するための本格的なツールとは言えませんが、さまざまなタイプのスキャナーを操作するためのターンキーソリューションです。 この例は、下からWIAを操作する方法を示しています。 NETであり、スキャンプログラムを構築するための基礎となります。

そして、人事部の注文をスキャンします;)


ご清聴ありがとうございました、ハブレでお会いしましょう!

Source: https://habr.com/ru/post/J310562/


All Articles