搜档网
当前位置:搜档网 › 使用WIA获取扫描仪数据(C#)

使用WIA获取扫描仪数据(C#)

目录

1在C#中使用WIA获取扫描仪数据 (3)

界面 (3)

使用WIA (4)

打开扫描对话框 (5)

获取图像 (6)

2WIA Automation Layer (8)

关于WIA Automation Layer (9)

对象分级结构 (9)

改进的例子 (9)

3利用Filter处理图片 (9)

代码 (9)

FilterID (10)

RotateFlip (11)

Crop (11)

Scale (11)

Stamp (11)

Exif (12)

Frame (12)

ARGB (12)

Convert (12)

小节 (13)

4通过编程方式扫描图像 (13)

WIAAL模型 (13)

获取Device对象 (13)

扫描图像 (14)

关于ShowTransfer方法 (14)

5注册事件 (15)

注册事件 (16)

枚举设备事件 (17)

按下按钮扫描图像 (17)

在C#中使用WIA获取扫描仪数据?

1在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应用程序,在上面添加一个按钮和一个图片框,点击按钮时启动扫描进程,然后在图片框中显示图像,应用程序界面如下:

使用WIA

Visual Studio 2008有一个好处,可以自动装配Com组件,在工程中添加一个WIA的COM引用:

点击确定后,会在工程引用中添加一个WIA.Interop.dll的文件,可以在对象浏览器中查看它:

打开扫描对话框

接下来可以利用WIA来进行扫描了,步骤很简单,首先引用命名空间:

using WIA;

接下来,在button的Click事件中,添加如下代码:

ImageFile imageFile = null;

CommonDialogClass cdc = new https://www.sodocs.net/doc/2814741647.html,monDialogClass();

try

{

imageFile= cdc.ShowAcquireImage(WIA.WiaDeviceType.ScannerDeviceType,

WIA.WiaImageIntent.TextIntent,

WIA.WiaImageBias.MaximizeQuality,

"{00000000-0000-0000-0000-000000000000}",

true,

true,

false);

}

catch(https://www.sodocs.net/doc/2814741647.html,Exception)

{

imageFile= null;

}

WIA会自动弹出标准扫描对话框,进行扫描操作:

获取图像

调用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");

}

结果如下:

2WIA Automation Layer

前文说过,在WIA 2.0 里,有一个叫Automation Layer的东西,来负责WIA和应用程序交互。既然被命名为Automation了,那么意味着比直接试用WIA接口,WIAAL更容易、更方便。实际上的确如此。

关于WIA Automation Layer

文档上说,WIA Automation Layer是一个高级的,全能的图像操作组件,能为应用程序(例如ASP,C#)提供首尾相连的处理能力。利用WIAAL,在程序中可以很容易地从诸如数码相机、扫描仪等图像设备中捕获图像,以及进行简单处理(缩放、旋转)。

对象分级结构

WIAAL的对象不多,总的来说分成来两块,第一块是可以被创建的类(例如在c#里我们用关键字new 来创建),另一部分是不能被创建的类(在c#里,这些类虽然也有构造函数,不过即使创建了,也没有任何东西),它们必须由第一种类创建。如下图:

可见,上面有我们熟悉的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);

}

}

3利用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;

ip.Filters.Add(ip.FilterInfos.get_Item(ref filterName).FilterID, 0);

ip.Filters[1].Properties.get_Item(ref propertyName).set_Value(ref propertyValue);

var buffer =ip.Apply(imageFile).FileData.get_BinaryData() as byte[];

using(MemoryStream ms = new MemoryStream())

{

ms.Write(buffer, 0, buffer.Length);

pictureBox1.Image = Image.FromStream(ms);

}

}

FilterID

以下是可用的FilterID

RotateFlip

以90 度增量旋转,以及水平或垂直翻转。

●RotationAngle - 如果希望旋转,可将RotationAngle 属性设置为90、180 或270,否则设置为

0 [默认值]

●FlipHorizontal - 如果希望水平翻转图像,可将FlipHorizontal 属性设置为True,否则设置为

False [默认值]

●FlipVertical - 如果希望垂直翻转图像,可将FlipVertical 属性设置为True,否则设置为False

[默认值]

●FrameIndex - 如果希望修改除ActiveFrame 之外的帧,可将FrameIndex 属性设置为帧的索

引,否则设置为0 [默认值]

Crop

以指定的左、右、上、下边距裁剪图像。

●Left - 如果希望沿左侧裁剪,可将Left 属性设置为左边距(单位为像素),否则设置为0 [默

认值]

●Top - 如果希望沿顶部裁剪,可将Top 属性设置为上边距(单位为像素),否则设置为0 [默

认值]

●Right - 如果希望沿右侧裁剪,可将Right 属性设置为右边距(单位为像素),否则设置为0

[默认值]

●Bottom - 如果希望沿底部裁剪,可将Bottom 属性设置为下边距(单位为像素),否则设置为0

[默认值]

●FrameIndex - 如果希望修改除ActiveFrame 之外的帧,可将FrameIndex 属性设置为帧的索引,

否则设置为0 [默认值]

Scale

将图像缩放到指定的最大宽度和最大高度,如有必要,保留纵横比。

●MaximumWidth - 将MaximumWidth 属性设置为希望将图像缩放到的宽度(单位为像

素)。

●MaximumHeight - 将MaximumHeight 属性设置为希望将图像缩放到的高度(单位为像

素)。

●PreserveAspectRatio - 如果希望保持图像当前的纵横比,可将PreserveAspectRatio 属性设置

为True [默认值],否则设置为False,图像将被拉伸到MaximumWidth 和MaximumHeight

●FrameIndex - 如果希望修改除ActiveFrame 之外的帧,可将FrameIndex 属性设置

为帧的索引,否则设置为0 [默认值]

Stamp

在指定的Left 和Top 坐标处标记指定的ImageFile。

●ImageFile - 将ImageFile 属性设置为希望标记的ImageFile 对象

●Left - 将Left 属性设置为希望将ImageFile 标记到的从左侧开始的偏移(单位为像

素)[默认值为0]

●Top - 将Top 属性设置为希望将ImageFile 标记到的从顶部开始的偏移(单位为像

素)[默认值为0]

●FrameIndex - 如果希望修改除ActiveFrame 之外的帧,可将FrameIndex 属性设置为帧的索

引,否则设置为0[默认值]

Exif

添加/删除指定的Exif 属性。

●Remove - 如果希望删除指定的Exif 属性,可将Remove 属性设置为True,否则设置为

False [默认值]以添加指定的exif 属性

●ID - 将ID 属性设置为希望添加或删除的PropertyID

●Type - 设置Type 属性以指示希望添加的Exif 属性的WiaImagePropertyType(对于删

除则忽略)

●Value - 将Value 属性设置为希望添加的Exif 属性的值(对于删除则忽略)

●FrameIndex - 如果希望修改除ActiveFrame 之外的帧,可将FrameIndex 属性设置为帧的索

引,否则设置为0[默认值]

Frame

●Remove - 如果希望删除指定的FrameIndex,可将Remove 属性设置为True,否则设置

为False [默认值]以在指定的FrameIndex 之前插入ImageFile

●ImageFile - 将ImageFile 属性设置为希望添加其ActiveFrame 的ImageFile 对象(对于删

除则忽略)

●FrameIndex - 对于删除,将FrameIndex 属性设置为希望删除的帧的索引,对于添加,将

FrameIndex 设置为要在其之前插入ImageFile 的帧的索引,否则设置为0 [默认值]以从指定的ImageFile 追加帧

ARGB

●ARGBData -将ARGBData 属性设置为表示指定FrameIndex 的ARGB 数据的Longs的矢量

(宽度和高度必须匹配)

●FrameIndex - 将FrameIndex 属性设置为希望修改其ARGB 数据的帧的索引,否则设置为

0[默认值]以修改ActiveFrame

Convert

将得到的ImageFile 转换为指定的类型。

●FormatID - 将FormatID 属性设置为所需支持的光栅图像格式,当前可选择的格式有

wiaFormatBMP、wiaFormatPNG、wiaFormatGIF、wiaFormatJPEG 或wiaFormatTIFF

●Quality - 对于JPEG 文件,可将Quality 属性设置为从1 到100 [默认值]之间的任何值,

以指定JPEG 压缩的质量

Compression - 对于TIFF 文件,可将Compression 属性设置为CCITT3、CCITT4、RLE 或Uncompressed 以指定压缩方案,否则可设置为LZW [默认值]

小节

总的来说,在c#中利用Automation Layer中的Filter非常麻烦(要写一堆Object),这些简单的图像处理操作还不如用GDI+来实现。

4通过编程方式扫描图像

在前面几节,我通过调用CommonDialog对象的ShowAcquireImage方法来扫描图像,这是一个弹出选择设备对话框,让用户自己扫描的过程。有时候,我们不想把过程弄得那么复杂,只想用户点击按钮后,自动开始扫描。本节我将尝试这个需求。

WIAAL模型

在开始代码前,再回顾以下WIAAL模型,这里选取其中的一小部分:

从上图不难想象,一台扫描仪,实际上就是一个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 https://www.sodocs.net/doc/2814741647.html,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())

{

ms.Write(buffer, 0, buffer.Length);

pictureBox1.Image = Image.FromStream(ms);

}

}

关于ShowTransfer方法

CommonDialog的ShowTransfer方法,实际上就是ShowAcquireImage方法的最后一个步骤,显示一个获取图片的进度条:

声明如下:

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

5注册事件

好了,现在我们能在c#里通过编程扫描图像了。还不满足?对,在前面的例子里,需要扫描的时候总是要按下一个扫描按钮,既傻又费事。现在的扫描仪,上面往往会多几个额外的按钮用来和用户交互,例如我是用的HP G2410上就有两个按钮:扫描及复制。那么,能不能用这两个按钮来代替程序里的那个难看的按钮呢?

注意左上角那个难看的按钮了吗?

在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 devices

?Enumerating devices

?Querying properties of installed devices

?Creating device objects

?Monitoring device events

?Acquiring images

?Registering 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}";

public const string wiaEventScanImage3 = "{154E27BE-B617-4653-ACC5-0FD7BD4C65CE}";

public const string wiaEventScanImage4 = "{A65B704A-7F3C-4447-A75D-8A26DFCA1FDF}";

public const string wiaEventScanOCRImage = "{9D095B89-37D6-4877-AFED-62A297DC6DBE}";

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, https://www.sodocs.net/doc/2814741647.html,, eve.Description);

}

运行后,发现该扫描仪仅仅支持wiaEventDeviceConnected 和wiaEventDeviceDisconnected ,以及两个HP自定义的事件:按下扫描按钮、按下拷贝按钮。OOXX!

按下按钮扫描图像

修改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 https://www.sodocs.net/doc/2814741647.html,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())

{

ms.Write(buffer, 0, buffer.Length);

pictureBox1.Image = Image.FromStream(ms);

}

}

};

相关主题