在C#中使用WIA获取扫描仪数据(一)
WIA(Windows Image Acquire,最新版本2.0)是Windows中一组从设备中捕获图像的标准API集合,它可以从设备(例如扫描仪、数码相机)中获取静态图像,以及管理这些设备。它既是API,又是DDI(Device Driver Interface)。因此,只要是满足这个规范的设备,都能够利用WIA直接和应用程序交互,而不是通过驱动。WIA甚至提供了统一的对话框来获取图片。
WIA是基于Com的,有两种使用方式:
c++:使用WIA自定义接口其他:使用WIAAL(WIA Automation Layer)。
注:在Windows XP sp1以前的版本,WIAAL还不存在,因此第二种方式用的是WIA Scripting Model。
在.Net中使用WIA,我们用的是第二种方法。接下来做一个简单的图像扫描程序:
界面
新建一个WinForm应用程序,在上面添加一个按钮和一个图片框,点击按钮时启动扫描进程,然后在图片框中显示图像,应用程序界面如下:
image-thumb39.png(42.89 K)
5/24/ 6:59:47 AM
使用WIA
Visual Studio 有一个好处,可以自动装配Com组件,在工程中添加一个WIA的COM引用:
image-thumb40.png(48.10 K)
5/24/ 6:59:47 AM
点击确定后,会在工程引用中添加一个WIA.Interop.dll的文件,可以在对象浏览器中查看它:
image-thumb41.png(9.83 K)
5/24/ 6:59:47 AM
打开扫描对话框
接下来可以利用WIA来进行扫描了,步骤很简单,首先引用命名空间:
using WIA;接下来,在button的Click事件中,添加如下代码:
ImageFile imageFile = null;CommonDialogClass cdc = new monDialogClass();try{imageFile = cdc.ShowAcquireImage(WIA.WiaDeviceType.ScannerDeviceType, WIA.WiaImageIntent.TextIntent, WIA.WiaImageBias.MaximizeQuality, "{00000000-0000-0000-0000-000000000000}",10. true,11. true,12. false);
13. }
14. catch (System.Exception)
15. {
16. imageFile = null;
17. }
复制代码
WIA会自动弹出标准扫描对话框,进行扫描操作:
image-thumb42.png(31.45 K)
5/24/ 6:59:47 AM
获取图像
调用ShowAcquireImage后,扫描后的数据就保存在ImageFile对象里了。用以下方法读取ImageFile中的数据(该方法很傻很傻……很傻)
if (imageFile != null){imageFile.SaveFile(@"c:\1.bmp"); using (FileStream stream = new FileStream(@"c:\1.bmp", FileMode.Open, FileAccess.Read, FileShare.Read)) {pictureBox1.Image = Image.FromStream(stream); } File.Delete(@"c:\1.bmp");
10. }
复制代码
结果如下:
在C#中使用WIA获取扫描仪数据(二、WIA Automation Layer)
前文说过,在WIA 2.0 里,有一个叫Automation Layer的东西,来负责WIA和应用程序交互。既然被命名为Automation了,那么意味着比直接试用WIA接口,WIAAL更容易、更方便。实际上的确如此。
关于WIA Automation Layer
文档上说,WIA Automation Layer是一个高级的,全能的图像操作组件,能为应用程序(例如ASP,C#)提供首尾相连的处理能力。利用WIAAL,在程序中可以很容易地从诸如数码相机、扫描仪等图像设备中捕获图像,以及进行简单处理(缩放、旋转)。
对象分级结构
WIAAL的对象不多,总的来说分成来两块,第一块是可以被创建的类(例如在c#里我们用关键字new来创建),另一部分是不能被创建的类(在c#里,这些类虽然也有构造函数,不过即使创建了,也没有任何东西),它们必须由第一种类创建。如下图:
image-thumb44.png(51.69 K)
5/24/ 7:03:03 AM
可见,上面有我们熟悉的CommonDialog(在Interop后,这些类后面都加上了Class表示实现,例如CommonDialogClass就是CommonDialog的实现)。
改进的例子
在前面的文章中,我用了一个很“囧”的方法来保存图片,实际上大可不必如此,从上面的关系图可以看到,ImageFile对象有一个Vector的对象,该对象保存了图片的像素值。修改代码如下:
if (imageFile != null){var buffer =imageFile.FileData.get_BinaryData() as byte[]; using (MemoryStream ms = new MemoryStream()) {ms.Write(buffer, 0, buffer.Length); pictureBox1.Image = Image.FromStream(ms); }}
在C#中使用WIA获取扫描仪数据(三、利用Filter处理图片)
WIA Automation Layer不仅能从设备中捕获照片,还能进行简单的处理。当WIA Automation Layer从设备中捕获照片,保存为一个ImageFile对象,我们可以通过访问该ImageFile对象来访问照片的属性。然而,为了保护原来的照片,不能直接通过修改该ImageFile对象的方法修改图片。代替的方法是,使用ImageProcess和一个或多个Filter对象创建一个副本,修改图片。
代码
以下代码把扫描得到的图片顺时针旋转90度:
if (imageFile != null){ImageProcess ip = new ImageProcessClass(); object filterName="RotateFlip"; Object propertyName = "RotationAngle"; Object propertyValue = 90;10. ip.Filters.Add(ip.FilterInfos.get_Item(ref filterName).FilterID, 0);11. ip.Filters[1].Properties.get_Item(ref propertyName).set_Value(ref propertyValue);12. 13. var buffer =ip.Apply(imageFile).FileData.get_BinaryData() as byte[];14. using (MemoryStream ms = new MemoryStream())15. {16.ms.Write(buffer, 0, buffer.Length);17.pictureBox1.Image = Image.FromStream(ms);18. }19.
20. }
复制代码
image-thumb45.png(256.10 K)
5/24/ 7:08:54 AM
FilterID
以下是可用的FilterID
RotateFlip
Crop
Scale
Stamp
Exif
ARGB
Convert
小节
总的来说,在c#中利用Automation Layer中的Filter非常麻烦(要写一堆Object),这些简单的图像处理操作还不如用GDI+来实现。
在C#中使用WIA获取扫描仪数据(四、通过编程方式扫描图像)
在前面几节,我通过调用CommonDialog对象的ShowAcquireImage方法来扫描图像,这是一个弹出选择设备对话框,让用户自己扫描的过程。有时候,我们不想把过程弄得那么复杂,只想用户点击按钮后,自动开始扫描。本节我将尝试这个需求。
WIAAL模型
在开始代码前,再回顾以下WIAAL模型,这里选取其中的一小部分:
image-thumb49.png(12.37 K)
5/24/ 7:13:49 AM
和
image-thumb50.png(15.66 K)
5/24/ 7:13:49 AM
从上图不难想象,一台扫描仪,实际上就是一个Device对象,因此,我们可以通过DeviceManager来“获取”这台设备的“引用”,然后通过得到的Device对象,执行相应的扫描工作。从而跳过了使用ShowAcquireImage方法带来的一系列“多余的鼠标操作问题”。
获取Device对象
按照上面思路,首先需要建立一个DeviceManager对象:
DeviceManager manager = new DeviceManagerClass();然后获取Device对象,在这里,我假设我的电脑上只有一台扫描仪,因此不做诸如“判断使用哪台扫描仪进行扫描”之类的操作。
Device device = null;foreach (DeviceInfo info in manager.DeviceInfos){if (info.Type != WiaDeviceType.ScannerDeviceType) continue; device = info.Connect(); break;}
复制代码
扫描图像
WIA把Device设备的图像数据看做一个个Item对象,可以通过方法GetItem(ItemID)来实现。不过,对于扫描仪做种东西,和数码相机不同,一般只有一个Item对象,因此可以简单的使用数组的方法(注意:index是从1开始的,而不是从0):
Item item = device.Items[1];
最后,调用CommonDialog的ShowTransfer方法,用一个进度条,来显示扫描过程:
CommonDialogClass cdc = new monDialogClass();ImageFile imageFile = cdc.ShowTransfer(item, "{B96B3CAB-0728-11D3-9D7B-0000F81EF32E}", true) as ImageFile;if (imageFile != null){var buffer = imageFile.FileData.get_BinaryData() as byte[]; using (MemoryStream ms = new MemoryStream())10. {11.ms.Write(buffer, 0, buffer.Length);12.pictureBox1.Image = Image.FromStream(ms);13. }
14. }
复制代码
关于ShowTransfer方法
CommonDialog的ShowTransfer方法,实际上就是ShowAcquireImage方法的最后一个步骤,显示一个获取图片的进度条:
image-thumb51.png(18.47 K)
5/24/ 7:13:49 AM
声明如下:
public virtual object ShowTransfer(Item Item, string FormatID, bool CancelError);对于第二个参数,FormatID,可以使用以下值:
wiaFormatBMP ({B96B3CAB-0728-11D3-9D7B-0000F81EF32E})wiaFormatPNG ({B96B3CAF-0728-11D3-9D7B-0000F81EF32E})wiaFormatGIF ({B96B3CB0-0728-11D3-9D7B-0000F81EF32E})wiaFormatJPEG ({B96B3CAE-0728-11D3-9D7B-0000F81EF32E})wiaFormatTIFF ({B96B3CB1-0728-11D3-9D7B-0000F81EF32E})
============================================================================
有人提问
真是好文. wia的sample很多都是vb各c++的. c#的真的很小. 先謝謝樓主.
但小弟有一個問題. 請先看小弟的PROGRAM.
using System;
using System.Collections.Generic;
using ponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using WIA;
using System.IO;
namespace ScanDoc
{
public partial class Form1 : Form
{
String wiaFormatJPEG = "{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}";
String wiaFormatTIFF = "{B96B3CB1-0728-11D3-9D7B-0000F81EF32E}";
DeviceManager manager;
Device device;
CommonDialogClass cdc;
public Form1()
{
InitializeComponent();
manager = new DeviceManager();
cdc = new monDialogClass();
device = cdc.ShowSelectDevice(WiaDeviceType.ScannerDeviceType, true, true);
manager.RegisterEvent("{6AA1C701-F2FD-11D2-99F9-006008CF3572}", device.DeviceID);
manager.OnEvent += new _IDeviceManagerEvents_OnEventEventHandler(OnScanImageButtonPress);
}
private void scanTest()
{
ImageFile imageFile = cdc.ShowTransfer(device.Items[1], wiaFormatTIFF, true) as ImageFile;
displayScanedImage(imageFile);
}
private void displayScanedImage(ImageFile imageFile)
{
if (imageFile != null)
{
byte[] buffer;
buffer = imageFile.FileData.get_BinaryData() as byte[];
using (MemoryStream ms = new MemoryStream())
{
ms.Write(buffer, 0, buffer.Length);
pictureBox1.Image = Image.FromStream(ms);
}
}
}
private void OnScanImageButtonPress(string eventId, string deviceId, string itemId)
{
scanTest();
}
}
}
這程序一開始會請使用者選擇 掃瞄器.
以后每按一下 scan 的鍵就會運行 ScanTest 這個 procedure.
這支程序在一般掃瞄器無什麼問題. 問題是如果我用有送紙器的掃瞄器,問題就出玩. 問題如下:
1.) 掃瞄格式如果是 wiaFormatJPEG. 每按一下掃瞄器上的掃瞄按鈕,就只掃瞄一張.
2.) 掃瞄格式如果是 wiaFormatTIFF. 每按一下掃瞄器上的掃瞄按鈕,可以把送紙器的文件都全掃瞄到一個 多頁的TIFF檔. 但再把文件放回送紙器,再按掃瞄按鈕. 這情況就只會出現 進導條 而掃瞄器則沒有動作.
如果我修改 scanTest() 則可以正常掃瞄. 但會再要求重就選擇掃瞄器.
private void scanTest()
{
device = cdc.ShowSelectDevice(WiaDeviceType.ScannerDeviceType, true, true);
ImageFile imageFile = cdc.ShowTransfer(device.Items[1], wiaFormatTIFF, true) as ImageFile;
displayScanedImage(imageFile);
}
不知是什麼問題.請樓主指教.
============================================================================
在C#中使用WIA获取扫描仪数据(五、注册事件)
好了,现在我们能在c#里通过编程扫描图像了。还不满足?对,在前面的例子里,需要扫描的时候总是要按下一个扫描按钮,既傻又费事。现在的扫描仪,上面往往会多几个额外的按钮用来和用户交互,例如我是用的HP G2410上就有两个按钮:扫描及复制。那么,能不能用这两个按钮来代替程序里的那个难看的按钮呢?
image-thumb52.png(29.40 K)
5/24/ 7:17:45 AM
注意左上角那个难看的按钮了吗?
在WIAAL里,我们可以同过注册设备事件,监听事件等方式和设备上的按钮交互。
注册事件
还记得我们在上节提到的DeviceManager对象吗?MSDN官方文档描述:
The Microsoft Windows Image Acquisition (WIA) Device Manager is an extension of the Still Image (STI) Event Monitor. The WIA Device Manager provides objects, methods, and interfaces for the following:
Installing devicesEnumerating devicesQuerying properties of installed devicesCreating device objectsMonitoring device eventsAcquiring imagesRegistering destination applications.
和传统.Net编程不同,WIA的事件,需要先通过DeviceManager的RegisterEvent的方法注册,才能使用。RegisterEvent定义如下:
void RegisterEvent(string EventID, string DeviceID);其中,EventID是事件的GUID,DeviceID是扫描仪的GUID。在类EventID里,WIA定义了几种基本的事件类型,从定义上不难理解这些ID的所代表的具体事件:
public const string wiaEventDeviceConnected = "{A28BBADE-64B6-11D2-A231-00C04FA31809}";public const string wiaEventDeviceDisconnected = "{143E4E83-6497-11D2-A231-00C04FA31809}";public const string wiaEventItemCreated = "{4C8F4EF5-E14F-11D2-B326-00C04F68CE61}";public const string wiaEventItemDeleted = "{1D22A559-E14F-11D2-B326-00C04F68CE61}";public const string wiaEventScanEmailImage = "{C686DCEE-54F2-419E-9A27-2FC7F2E98F9E}";public const string wiaEventScanFaxImage = "{C00EB793-8C6E-11D2-977A-0000F87A926F}";public const string wiaEventScanFilmImage = "{9B2B662C-6185-438C-B68B-E39EE25E71CB}";public const string wiaEventScanImage = "{A6C5A715-8C6E-11D2-977A-0000F87A926F}";public const string wiaEventScanImage2 = "{FC4767C1-C8B3-48A2-9CFA-2E90CB3D3590}";
10. public const string wiaEventScanImage3 = "{154E27BE-B617-4653-ACC5-0FD7BD4C65CE}";
11. public const string wiaEventScanImage4 = "{A65B704A-7F3C-4447-A75D-8A26DFCA1FDF}";
12. public const string wiaEventScanOCRImage = "{9D095B89-37D6-4877-AFED-62A297DC6DBE}";
13. public const string wiaEventScanPrintImage = "{B441F425-8C6E-11D2-977A-0000F87A926F}";
复制代码
例如,我们可以使用以下来吗来注册一个事件,并监听它:
manager.RegisterEvent(EventID.wiaEventScanImage, device.DeviceID);manager.OnEvent += (eventID, deviceID, itemID) =>{//…………}
复制代码
枚举设备事件
如果你向我这般,兴冲冲地在OnEvent里加入扫描处理逻辑,然后按下HP G2410上的扫描按钮,你一定会像我一样,在漫长的等待中渐渐失望:扫描仪根本没有按我所想的那样扫描图片。也就是说,wiaEventScanImage这个事件根本不起作用。
幸好能够通过Device类来枚举设备支持的事件,我写了以下一段代码:
Console.WriteLine("Events:");foreach (DeviceEvent eve in device.Events){Console.WriteLine("{0}:{1}:{2}", eve.EventID, eve.Name, eve.Description);}
复制代码
运行后,发现该扫描仪仅仅支持wiaEventDeviceConnected 和 wiaEventDeviceDisconnected ,以及两个HP自定义的事件:按下扫描按钮、按下拷贝按钮。OOXX!
image-thumb53.png(36.57 K)
5/24/ 7:17:45 AM
按下按钮扫描图像
修改manager.RegisterEvent方法,使用HP提供的EventID:
manager.RegisterEvent("{0C5E2143-FD9B-490B-9AD5-7637A403566B}", device.DeviceID);
最终我们可以通过按下扫描仪上的扫描按钮来扫描数据了!:)
manager.OnEvent += (eventID, deviceID, itemID) =>{Item item = device.Items[1]; CommonDialogClass cdc = new monDialogClass(); ImageFile imageFile = cdc.ShowTransfer(item, "{B96B3CAB-0728-11D3-9D7B-0000F81EF32E}", true) as ImageFile;10. if (imageFile != null)11. {12.var buffer = imageFile.FileData.get_BinaryData() as byte[];13.using (MemoryStream ms = new MemoryStream())14.{15. ms.Write(buffer, 0, buffer.Length);16. pictureBox1.Image = Image.FromStream(ms);17.}18. }
19. };