Thursday, July 29, 2010

Scanning images using WIA and TWAIN

Introduction

The development of windows imaging applications requires choosing an API to communicate with scanner s or cameras. The most used APIs are WIA and TWAIN.

The WIA (Windows Image Acquisition) platform enables imaging/graphics applications to interact with imaging hardware and standardizes the interaction between different applications and scanners. This allows those different applications to talk to and interact with those different scanners without requiring the application writers and scanner manufactures to customize their application or drivers for each application-device combination. (http://msdn.microsoft.com/en-us/library/ms630368(VS.85).aspx)

TWAIN is a standard software protocol and applications programming interface (API) that regulates communication between software applications and imaging devices such as scanners and digital cameras. (http://www.twain.org/)

Differences:

1. WIA uses a common dialog for all devices while TWAIN uses a dialog created by the device manufacturer. Practically speaking, this almost always means that the TWAIN dialog will provide more options and advanced control over the device.
2. TWAIN allows you to use custom capabilities that the device manufacturer has created even though they don't exist in the TWAIN specifications.
3. In general, when a device supports both Twain and WIA, TWAIN is better for scanners and WIA is better for acquiring images from cameras and video devices.
4. TWAIN has three transfer modes (Native, Memory, File) and WIA only has two (Memory, File).
5. Most TWAIN sources save the settings of the previous scan while WIA does not.
6. TWAIN supports options for each page when scanning in duplex mode but WIA uses the same settings for both sides.

Problem

Usually, older scanners (drivers) are build for TWAIN and does not supported by WIA platform, moreover newer devices which created under Microsoft standard does not supported by TWAIN API.


In order to build scanning application that will communicate with both types of devices I combined those APIs under a uniform interface which will provide the easy access to both type of devices.

ScannerAdapterBase
public abstract class ScannerAdapterBase
{
protected List<Image> m_ImagesList = null;
protected ScannerDeviceData m_ScannerDeviceData = null;
// Initializes the Adapter
public abstract void InitAdapter(nessScanning control, IMessageFilter messageFilter, IntPtr handle);
// Selects scanning device, and returns the indicator that the device selected
// Returns the indicator that the device selected
public abstract bool SelectDevice();
// Acquires images from scanning device and fills ImagesLis
public abstract void AcquireImages(bool showUI);
}
Factory Method
public static ScannerAdapterBase GetScannerAdapter(nessScanning control, IMessageFilter messageFilter, IntPtr handle)
{
lock (locker)
{
bool isTwainDeviceSelected = false;

if (m_ScannerAdapterBase != null)
{
return m_ScannerAdapterBase;
}

try
{
//Checks WIA Devices
m_ScannerAdapterBase = new WiaScannerAdapter();
m_ScannerAdapterBase.InitAdapter(control, messageFilter, handle);
isWiaDeviceSelected = m_ScannerAdapterBase.SelectDevice();
if (isWiaDeviceSelected)
{
return m_ScannerAdapterBase;
}

//Checks TWAIN Devices
m_ScannerAdapterBase = new TwainScannerAdapter();
m_ScannerAdapterBase.InitAdapter(control, messageFilter, handle);
isTwainDeviceSelected = m_ScannerAdapterBase.SelectDevice();
if (isTwainDeviceSelected)
{
return m_ScannerAdapterBase;
}
}
catch (ScannerException ex)
{
throw ex;
}

return null;
}
}
Wia Adapter AcquireImages method
public override void AcquireImages(bool showUI)
{
CommonDialogClass wiaCommonDialog = new CommonDialogClass();

//Select Device
if (m_DeviceID == null)
{
Device device = null;
try
{
device = wiaCommonDialog.ShowSelectDevice(WiaDeviceType.ScannerDeviceType, false, false);
if (device != null)
{
m_DeviceID = device.DeviceID;
FillDeviceData(device);
}
else
{
return;
}
}
catch (COMException ex)
{
if ((WiaScannerError)ex.ErrorCode == WiaScannerError.ScannerNotAvailable)
{
return;
}
else
{
WiaScannerException se = BuildScannerException(device, ex);
throw se;
}
}
}

//Create DeviceManager
DeviceManager manager = new DeviceManagerClass();
Device WiaDev = null;
foreach (DeviceInfo info in manager.DeviceInfos)
{
if (info.DeviceID == m_DeviceID)
{
WIA.Properties infoprop = null;
infoprop = info.Properties;
//connect to scanner
WiaDev = info.Connect();
break;
}
}

//Get Scanning Properties
WIA.Item scanningItem = null;
WIA.Items items = null;
if (showUI)
{
items = wiaCommonDialog.ShowSelectItems(WiaDev, WiaImageIntent.TextIntent, WiaImageBias.MinimizeSize, false, true, false);
}
else
{
items = WiaDev.Items;
}

if (items != null && items.Count > 0)
{
scanningItem = items[1] as WIA.Item;
}

WIA.ImageFile imgFile = null;
WIA.Item item = null;

//Prepare ImagesList
if (m_ImagesList == null)
{
m_ImagesList = new List<Image>;
}
//Start Scan
while (HasMorePages(WiaDev))
{
item = scanningItem;

try
{
//Scan Image
imgFile = (ImageFile)wiaCommonDialog.ShowTransfer(item, ImageFormat.Jpeg.Guid.ToString("B")/* wiaFormatJPEG*/, false);
byte[] buffer = (byte[])imgFile.FileData.get_BinaryData();
MemoryStream ms = new MemoryStream(buffer);
m_ImagesList.Add(Image.FromStream(ms));
imgFile = null;
}
catch (COMException ex)
{
if ((WiaScannerError)ex.ErrorCode == WiaScannerError.PaperEmpty)
{
break;
}
else
{
WiaScannerException se = BuildScannerException(WiaDev, ex);
throw se;
}
}
catch (Exception ex)
{
WiaScannerException se = BuildScannerException(WiaDev, ex);
throw se;
}
finally
{
item = null;
}
}
}
Wia Adapter HasMorePages method
private bool HasMorePages(Device WiaDev)
{
try
{
bool hasMorePages = false;
//determine if there are any more pages waiting
Property documentHandlingSelect = null;
Property documentHandlingStatus = null;
foreach (Property prop in WiaDev.Properties)
{
if (prop.PropertyID == WIA_PROPERTIES.WIA_DPS_DOCUMENT_HANDLING_SELECT)
documentHandlingSelect = prop;
if (prop.PropertyID == WIA_PROPERTIES.WIA_DPS_DOCUMENT_HANDLING_STATUS)
documentHandlingStatus = prop;
}
if (documentHandlingSelect != null) //may not exist on flatbed scanner but required for feeder
{
//check for document feeder
if ((Convert.ToUInt32(documentHandlingSelect.get_Value()) & WIA_DPS_DOCUMENT_HANDLING_SELECT.FEEDER) != 0)
{
hasMorePages = ((Convert.ToUInt32(documentHandlingStatus.get_Value()) & WIA_DPS_DOCUMENT_HANDLING_STATUS.FEED_READY) != 0);
}
}
return hasMorePages;
}
catch (COMException ex)
{
WiaScannerException se = BuildScannerException(WiaDev, ex);
throw se;
}
}
TWAIN Adapter AcquireImages method
public ArrayList TransferPictures()
{
ArrayList pics = new ArrayList();
if (srcds.Id == IntPtr.Zero)
{
return pics;
}
TwRC rc;
IntPtr hbitmap = IntPtr.Zero;
TwPendingXfers pxfr = new TwPendingXfers();

do
{
pxfr.Count = 0;
hbitmap = IntPtr.Zero;

TwImageInfo iinf = new TwImageInfo();
rc = DSiinf(appid, srcds, TwDG.Image, TwDAT.ImageInfo, TwMSG.Get, iinf);
if (rc != TwRC.Success)
{
CloseSrc();
return pics;
}
rc = DSixfer(appid, srcds, TwDG.Image, TwDAT.ImageNativeXfer, TwMSG.Get, ref hbitmap);
if (rc != TwRC.XferDone)
{
CloseSrc();
return pics;
}
rc = DSpxfer(appid, srcds, TwDG.Control, TwDAT.PendingXfers, TwMSG.EndXfer, pxfr);
if (rc != TwRC.Success)
{
CloseSrc();
return pics;
}
pics.Add(hbitmap);
}
while (pxfr.Count != 0);

rc = DSpxfer(appid, srcds, TwDG.Control, TwDAT.PendingXfers, TwMSG.Reset, pxfr);
return pics;
}