原文地址:www.cyotek.com/blog/an-int…
原文作者:www.cyotek.com/
发布时间:2020年11月6日
在这篇文章中,我将介绍在C# WinForms应用程序中使用微软Windows图像采集库(WIA)的一个基本速成课程。它主要是围绕着使用各种内置的对话框,以便轻松地将图像采集和打印添加到你的应用程序中,我不去研究更深层次的功能,如命令执行。
我在2019年9月开始写这篇文章,然后以相当大的方式烧掉了。通常情况下,我的政策是在前一篇完成之前不写新的开发博文,即使没有发表,但在9个月里,每当我打开它时,都会茫然地盯着它,我不得不搁置它,继续前进。现在已经接近14个月了,所以我把它原封不动地推了出来。
开始工作
在WIA中添加一个参考文献
- 打开 "添加引用 "对话框
- 从左边的侧边栏选择COM
- 从列表中找到Microsoft Windows Image Acquisition Library 2.0,并选中它(你可能会发现在搜索框中输入图像会更容易)。
- 点击 "确定 "来添加参考资料
使用COM互操作
如果你不习惯在.NET中使用COM引用,可能值得阅读我之前的文章《解决编译错误 "Interop类型不能被嵌入。使用适用的接口代替"》,以避免一些潜在的陷阱。
关于集合的说明
WIA提供了各种集合接口,如DeviceInfos、Vector和Properties。所有这些集合都是基于一的,就像以前的Visual Basic一样,而不是.NET的基于零的集合。
关于图像格式的说明
尽管有几个函数允许你指定一种图像格式,但并不保证结果会使用这种格式。例如,在扫描图像时,我总是要求提供PNG格式的图像,但在我用于本文大部分内容的旧佳能平板电脑和较新的Brother MFC-L2710DW上得到的结果总是BMP。
与设备一起工作
在你能用WIA做任何事情之前,你需要一个设备。我们可以使用DeviceManager接口来列举和访问设备。
列出设备
DeviceManager接口有一个DeviceInfos属性,可以用来列举设备。每个设备都是通过DeviceInfo接口返回的,它允许我们查询设备类型和列举其属性。
DeviceManager deviceManager;
DeviceInfos devices;
deviceManager = new DeviceManager();
devices = deviceManager.DeviceInfos;
for (int i = 1; i <= devices.Count; i++)
{
DeviceInfo device;
device = devices[i];
if (device.Type == WiaDeviceType.ScannerDeviceType)
{
// found a scanner device, do something with it
}
}
WIA支持3种不同类型的设备--扫描仪、照相机和录像机。我曾用一台平板扫描仪、一台DLSR相机和一部手机测试过WIA。虽然后两者都能拍照和录像,但它们在WIA中显示为相机类型。我没有一个专门的视频设备,我能够用它来测试。
在这篇文章以草稿形式存在的几个月里,我还购买了一台Brother激光打印机,它包括一个平板和一个自动送纸器(ADF)扫描仪,我用WIA简单地测试了平板方面,但没有测试ADF。
WIA设备类型的枚举不是基于标志的。因此,在使用需要类型的WIA功能时,不可能混合和匹配设备类型--你可以选择列出所有的设备,或者设备属于一个单一的类型。
连接到一个设备
虽然DeviceInfo允许你查询与特定设备相关的信息,但要实际使用它,我们首先需要获得一个设备实例。这可以通过一个给定的DeviceInfo的连接方法来完成。
DeviceInfo deviceInfo;
Device device;
deviceInfo = this.GetSelectedDeviceInfo();
device = deviceInfo.Connect();
// do something with the device instance
使用 WIA 对话框
Windows中的通用对话框是一个很好的功能--谁想在每个应用程序中都实现相同的文件或文件夹选择器呢?WIA也通过CommonDialog接口提供了一些常用对话框。
Device device;
CommonDialog dialog;
device = this.GetSelectedDevice();
dialog = new CommonDialog();
// display a common a dialogue
选择一个设备
ShowSelectDevice方法显示一个选择设备的对话框。默认情况下,它将显示所有的设备,但你可以限制它显示特定类型的设备。
然而,这个方法有一个注意事项--如果当前没有设备存在,它将抛出一个异常。如果你打算使用这个方法,你应该为一个值为0x80210015或-2145320939的WIA_S_NO_DEVICE_AVAILABLE的hResult添加一个处理器。
Device device;
try
{
device = dialog.ShowSelectDevice(AlwaysSelectDevice: true);
}
catch (COMException ex) when (ex.ErrorCode == WIA_S_NO_DEVICE_AVAILABLE)
{
// handle no devices available
}
另外,如果你想选择一个特定类型的设备,那么你可以使用可选的DeviceType参数。
// error handling omitted for brevity
device = dialog.ShowSelectDevice(DeviceType: WiaDeviceType.ScannerDeviceType, AlwaysSelectDevice: true);
显示设备属性
虽然这可能是你不太可能需要经常调用的东西,但通过ShowDeviceProperties方法可以为一个给定的设备显示本地属性对话框。这个方法所显示的对话将因设备而异。
dialog.ShowDeviceProperties(device);
选择一个项目
ShowSelectItems方法显示一个用户界面,用于从一个设备中选择一个或多个项目。如果是相机,它将显示一个选择对话框,允许你选择一个或多个照片。对于扫描仪,它允许你扫描一个文件或照片,对于相机,它允许选择以前拍摄的照片(如前所述,我没有专门的视频设备来测试)。它返回一个描述用户选择的Items集合,如果取消则为空。
SingleSelect参数默认为true,只允许选择一个项目。把它设置为false允许在获取图片对话中进行多选,但对扫描对话没有影响。
Items items;
items = dialog.ShowSelectItems(device, SingleSelect: false);
获取图片
ShowAcquireImage将显示一个或多个选择图像的对话框。如果成功,它将返回一个包含获取数据的ImageFile对象,否则为空。
在没有任何参数的情况下调用它意味着它将允许从任何支持的设备中选择图像,或者你可以使用DeviceType参数将采集限制在一个特定的类别。
不管你是允许所有的设备类型还是限制在一个单一的类型,如果存在多个设备,它将首先提示一个设备,如上面选择设备中所述。如果只有一个设备存在,它将自动使用该设备而不显示用户界面。
一旦选择了一个设备,就会显示相应的选择项目对话框,允许选择一个现有的图像(如果是相机)或创建一个新的图像(如果是扫描仪)。
在选择/创建一个图像后,图像的细节会以一个图像文件的形式返回。
另外一个可能感兴趣的参数是FormatID。如果你指定了这个参数,它将尝试以该格式返回图像结果。然而在实践中,我发现这并不奏效--我总是得到一个Windows位图的返回,而不是我实际要求的那样。
ImageFile image;
image = dialog.ShowAcquireImage();
if(image != null)
{
// do something with the image
}
关于如何从ImageFile实例中获得一个.NET图像或位图,请参见下面的将WIA.ImageFile转换为位图。
转移图像
除了使用ShowAcquireImage通过对话获得图像外,你还可以直接获得图像。当您已经配置了属性,并且不希望用户能够改变它们时,这是非常有用的,例如,在重复扫描时,尺寸与之前的扫描相同。
对于这一点,您可以使用ShowTransfer方法。这仍然会显示一个对话,但这个对话只显示转移的进度,并允许取消转移。和ShowAcquireImage一样,这个方法将返回一个包含操作结果的ImageFile,你也可以尝试指定你希望返回的图像格式。
ImageFile image;
image = dialog.ShowTransfer(device.Items[1]);
if(image != null)
{
// do something with the image
}
打印图片
通过使用ShowPhotoPrintingWizard,你可以打印一个或多个图片文件,这是一个方便的方法,只需几行代码就可以为你的应用程序添加打印功能。
虽然你不能够以编程方式控制这些,但该向导允许用户选择图像在页面上的布局方式,并配置各种选项。
Vector files;
files = new Vector();
files.Add("Z:\samples\20190811 0349 7.png")
files.Add("Z:\samples\\tower-of-london.jpg")
dialog.ShowPhotoPrintingWizard(files);
我注意到这个方法有一些注意事项。
- 向导不是模态的,这意味着用户可以点击返回并继续使用你的应用程序,而不打开向导或打开多个向导的副本
- 文件名必须是完全合格的
- 如果任何图像文件没有一个公认的图像扩展名,"向导 "就会失败。例如,如果你创建了一个包含位图的.tmp文件并试图打印,就会失败(而且会出现非常无助的 "找不到文件 "的信息)。
设备和项目属性
设备和项目接口都有一个属性集合,允许你对项目进行操作。属性是自我描述的,所以除了像ID、名字和值这样的主要内容外,你还可以查询属性的类型(包括值类型和描述属性工作方式的子类型,例如范围或标志)。对于基于范围的属性,可以访问最小和最大的值,以及步骤。对于作为值的列表的属性,你也能够读取这些值。你可以使用这些信息来建立你自己的UI元素,而不是使用内置的元素,或者用于验证用户输入。
属性集合有一个索引器,它接受一个索引、一个属性的ID或名称。然而,鉴于索引和ID都是整数,如果你想传递一个ID,你需要先把它转换成一个字符串。为了避免混乱,我最后添加了扩展方法作为帮助工具。
public static void SetPropertyValue<T>(this Properties properties, WiaPropertyId id, T value)
{
Property property;
property = properties[((int)id).ToString()];
property.let_Value(value);
}
public static Property GetProperty(this Properties properties, WiaPropertyId id)
{
return properties[((int)id).ToString()];
}
假设我想在启动扫描之前配置设备的DPI和质量,我可以按以下方式调用扩展程序
Properties properties = device.Properties;
properties.SetPropertyValue(WiaPropertyId.WIA_IPS_CUR_INTENT, _settings.ImageIntent); // set this first as it resets a bunch of other properties
properties.SetPropertyValue(WiaPropertyId.WIA_IPS_XRES, _settings.ScanDpi);
properties.SetPropertyValue(WiaPropertyId.WIA_IPS_YRES, _settings.ScanDpi);
将WIA.ImageFile转换为位图
ImageFile接口有一个ARGBData属性,可以返回一个包含图像颜色数据的向量。我首先尝试用它来设置一个新的位图的像素,但即使直接操作位图的像素,也有点慢。
另一个选择是使用FileData属性。这将返回一系列字节,这些字节以设备返回的输出格式表示图像。这意味着我们可以将其转储到MemoryStream中并调用Image.FromFile。然而,由于.NET喜欢保持流的开放,这可能会导致复杂的情况,所以我通常会克隆所得到的图像。下面的扩展方法将接受一个给定的ImageFile并从中返回一个独立的Bitmap。
public static Bitmap ToBitmap(this ImageFile image)
{
Bitmap result;
byte[] data;
data = (byte[])image.FileData.get_BinaryData();
using (MemoryStream stream = new MemoryStream(data))
{
using (Image scannedImage = Image.FromStream(stream))
{
result = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(result))
{
g.Clear(Color.Transparent);
g.PageUnit = GraphicsUnit.Pixel;
g.DrawImage(scannedImage, new Rectangle(0, 0, image.Width, image.Height));
}
}
}
return result;
}
请注意,这个扩展方法并不适合多帧的图像(比如多页的TIFF文件),因为它只保留第一帧。我通常不处理多页图像,所以我对这个用例没有具体的建议。我想我更倾向于将结果保存到一个临时文件,然后使用Image.FromFile。
结束语
WIA对于为你的应用程序添加基本的图像打印支持非常有用,如果你想为你的应用程序添加从各种设备捕获图像的能力,它也是完美的。它的功能比我在这里演示的要多,但这篇文章涵盖了基础知识和一些常见用途。
发表时的演示源代码可以从本页的链接中下载,任何更新可以在GitHub仓库中找到。
通过www.DeepL.com/Translator(免费版)翻译