📜 ⬆️ ⬇️

[SC] We work with the scanner


For the past few years, my colleagues and I have been trying to make less paper in the office. Employees work with digital documents faster and with better quality - and the dust is several times smaller.

To fully switch to digital documents, you first need to scan paper documents. We use the .NET Framework to develop desktop scanner applications. Out of the box, he does not provide the means to work with scanners. Since .NET is friendly with COM , you can use the WIA component (Windows Imaging Architecture) .

For easy work with WIA, I wrote a class that formed the basis of many scanning applications. I want to share our experience with scanners using the example of the Scanner class.

It is used in several very useful applications of our bank. For example, all applications and contracts of bank customers are digitized by applications using the Scanner class.
')
It looks like this:


so:


Not everything is simple ingenious.


To start working with a scanner from a developed application, you must first select it, then configure it, and then you can scan. Therefore, the public class interface can be limited to two methods - Configuration and Scan .

Configuration will display the standard scanner setup dialog. Scan will scan the document and return a MemoryStream with a picture.

Now in detail ...


We will work with the scanner through WIA . To do this, we connect the Microsoft Windows Image Acquisition Library v2.0 component to the COM project, the implementation of which is located in the file C: \ Windows \ System32 \ wiaaut.dll .

It’s customary to set up the scanner once and to have an already configured scanner at the next application load. We will need functions to configure the scanner and to save and restore the configuration. Create a Scanner class and add using WIA at the beginning of the file. When creating an object of the Scanner class, we try to load the settings from the config. If it does not work out, we suggest setting up the scanner manually.

Usually one program works with one scanner, so a singleton could be made. In my case there may be several scanners at the same time.
Here is what our constructor will look like:

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

What does the MessageBox do here? In most cases, scanning applications involve a GUI . To display information, it is quite possible to show the message via MessageBox , the more we will configure the scanner through the standard WIA dialog.

Let's start with the scanner settings. The scanner will be configured by the Configuration () function, which can be called at any convenient time, for example, by pressing the "Scanner Setup" button in the program.

 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, "   "); } } 

The scanner has many parameters, some of which must be calculated from the others, therefore, in order not to complicate things, we used the standard WIA-dialog CommonDialogClass for setting. First, we suggest that the user of the program select the ShowSelectDevice scanner (WiaDeviceType. ScannerDeviceType, true) . The dialog will return a Device object or null if no device is selected.


Then we set up the resulting ShowSelectItems device (_scanDevice) . In the settings you can set the DPI , paper size, color mode and other parameters.

The main drawback of this configuration window is that in order to confirm instead of the logical OK , you will have to click on the “Scan” button, although in our case scanning will not be launched.


After successful setup, save the configuration to a file by calling SaveConfig () .

WIA does not provide any save / restore settings. I had to do this part on my own. There was nothing complicated here, the settings are presented as a list of IProperty , which Device and Item have .

To save, we chose a readable text format in which the name of the parameters, identifiers and values ​​are separated by a semicolon, and the settings for Device and Item are separated by the headers [device] and [item] .

Config example
[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; } } 

The scanning procedure is trivial. It returns either a MemoryStream with a picture, or null if the scan failed.

 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; } 

In my opinion, when scanning, it is more convenient to draw your progress bar, so we hide the standard progress. For scanning without progress display, the Transfer method is used.

Duplex / Simplex


Many long-distance scanners support the double-page scan mode, which can be selected through the settings dialog.


However, in the standard dialog, you can forget to set the Duplex mode, so it is useful to be able to forcefully turn on the Duplex mode and return to the original settings.

 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)); } } } 

To scan in double-page mode, you need to call MemScan () for the first and second pages. The first call will scan the sheet and return the image of the first page, the second call will return the scan of the second page. When working with feeder scanners, it is convenient to scan in a loop while MemScan () returns! = Null - this often means that the scanner is out of paper. You can not think in which mode the scanner works - the documents will be scanned in any case. If you run a scan in a loop for the cracker scanner, the scanning process will not stop while the scanner is connected to an outlet; this nuance should be taken into account when developing software.

Full class source code for working with a scanner
 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; } } } } 


Of course, Scanner cannot be called a full-fledged tool for working with a scanner, but it is a turnkey solution for working with various types of scanners. This example illustrates the principle of working with WIA from under. NET , and may be the basis for the construction of scanning programs.

And so we scan the orders of the HR department;)


Thank you for your attention, until we meet again at Habré!

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


All Articles