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/2009 6:59:47 AM
使用WIA
Visual Studio 2008有一個好處,可以自動裝配Com組件,在工程中添加一個WIA的COM引用:
image-thumb40.png(48.10 K)
5/24/2009 6:59:47 AM
點擊確定后,會在工程引用中添加一個WIA.Interop.dll的文件,可以在對象瀏覽器中查看它:
image-thumb41.png(9.83 K)
5/24/2009 6:59:47 AM
打開掃描對話框
接下來可以利用WIA來進行掃描了,步驟很簡單,首先引用命名空間:
using WIA;接下來,在button的Click事件中,添加如下代碼:
- ImageFile imageFile = null;
- CommonDialogClass cdc = new WIA.CommonDialogClass();
- 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.Runtime.InteropServices.COMException)
15. {
- 16. imageFile = null;
17. }
復制代碼
WIA會自動彈出標准掃描對話框,進行掃描操作:
image-thumb42.png(31.45 K)
5/24/2009 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/2009 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/2009 7:08:54 AM
FilterID
以下是可用的FilterID
RotateFlip
以 90 度增量旋轉,以及水平或垂直翻轉。 |
Crop
以指定的左、右、上、下邊距裁剪圖像。 |
Scale
將圖像縮放到指定的最大寬度和最大高度,如有必要,保留縱橫比。 |
Stamp
在指定的 Left 和 Top 坐標處標記指定的 ImageFile。 |
Exif
添加/刪除指定的 Exif 屬性。 |
ARGB
ARGBData - 將 ARGBData 屬性設置為表示指定 FrameIndex 的ARGB 數據的 Longs 的矢量(寬度和高度必須匹配) |
Convert
將得到的 ImageFile 轉換為指定的類型。 |
小節
總的來說,在c#中利用Automation Layer中的Filter非常麻煩(要寫一堆Object),這些簡單的圖像處理操作還不如用GDI+來實現。
在C#中使用WIA獲取掃描儀數據(四、通過編程方式掃描圖像)
在前面幾節,我通過調用CommonDialog對象的ShowAcquireImage方法來掃描圖像,這是一個彈出選擇設備對話框,讓用戶自己掃描的過程。有時候,我們不想把過程弄得那么復雜,只想用戶點擊按鈕后,自動開始掃描。本節我將嘗試這個需求。
WIAAL模型
在開始代碼前,再回顧以下WIAAL模型,這里選取其中的一小部分:
image-thumb49.png(12.37 K)
5/24/2009 7:13:49 AM
和
image-thumb50.png(15.66 K)
5/24/2009 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 WIA.CommonDialogClass();
- 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/2009 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 System.ComponentModel;
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 WIA.CommonDialogClass();
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#里通過編程掃描圖像了。還不滿足?對,在前面的例子里,需要掃描的時候總是要按下一個掃描按鈕,既傻又費事。現在的掃描儀,上面往往會多幾個額外的按鈕用來和用戶交互,例如我是用的HP G2410上就有兩個按鈕:掃描及復制。那么,能不能用這兩個按鈕來代替程序里的那個難看的按鈕呢?
image-thumb52.png(29.40 K)
5/24/2009 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 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}";
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/2009 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 WIA.CommonDialogClass();
- 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. };