An introduction to using Windows Image Acquisition (WIA) via C#
In this post I'm going to cover a basic crash course for using the Microsoft Windows Image Acquisition library (WIA) in a C# WinForms application. It's mostly based around using the various built-in dialogues in order to easily add image acquisition and printing to your application, I don't go into deeper functionality such as command execution.
I started writing this post in September 2019 before burning out in a fairly large fashion. Is it normally my policy that I don't write a new development blog post until the previous is finished, even if not published, but after staring blankly at it for 9 months whenever I opened it, I had to shelve it and move on. It is now approaching 14 months and so I'm pushing it out as is.
Getting started
Adding a reference to WIA
- Open the Add Reference dialogue
- Select COM from the left-hand sidebar
- Find Microsoft Windows Image Acquisition Library 2.0 from
the list and check it (you may find it easier just to type
image
into the search box) - Click OK to add the reference
Using COM Interop
If you aren't used to using COM references in .NET, it may be worth reading my previous article Resolving compile error "Interop type cannot be embedded. Use the applicable interface instead" to avoid some potential pitfalls.
A note on collections
WIA offers various collection interfaces, such as DeviceInfos
,
Vector
and Properties
. All of these collections are
one-based just like Visual Basic of old, not the zero-based
collections of .NET.
A note on image formats
Although several functions allow you to specify an image format, it is not guaranteed that the result will use that format. For example, when scanning an image I always ask for an image in PNG format, but the result I get is always BMP on both the old Canon flatbed I used for most of this article and on a more recent Brother MFC-L2710DW.
Working with devices
Before you can do anything with WIA, you need a device. We can
use the DeviceManager
interface to enumerate and access
devices.
Listing Devices
The DeviceManager
interface has a DeviceInfos
property which
can be used to enumerate devices. Each device is returned via
the DeviceInfo
interface which allows us to query the device
type and enumerate its properties.
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 supports 3 different types of devices - Scanners, Cameras and Video Cameras. I have tested WIA with a flatbed scanner, a DLSR camera, and a mobile phone. Although the latter two can both take pictures and record video, they appear in WIA as the Camera type. I do not have a dedicated video device I was able to test with.
In the months that this article has been sitting in draft form I also acquired a Brother laser printer that includes both a flatbed and an automated document feeder (ADF) scanner, I have briefly tested the flatbed aspect with WIA but not the ADF.
The WIA device type enumeration is not flag based. Therefore, isn't possible to mix and match device types when using WIA functions that take a type - you either have the choice of listing all devices, or devices belong to a single type.
Connecting to a device
While the DeviceInfo
allows you to query the information
related to a specific device, to actually use it we first need
to obtain a Device
instance. This can be done via the
Connect
method of a given DeviceInfo
.
DeviceInfo deviceInfo;
Device device;
deviceInfo = this.GetSelectedDeviceInfo();
device = deviceInfo.Connect();
// do something with the device instance
Using WIA dialogues
Common dialogues in Windows are a fantastic piece of
functionality - who wants to have to implement the same file or
folder pickers in every application? WIA also provides a number
of common dialogues via the CommonDialog
interface.
Device device;
CommonDialog dialog;
device = this.GetSelectedDevice();
dialog = new CommonDialog();
// display a common a dialogue
Selecting a device
The ShowSelectDevice
method displays a dialogue box for
choosing a device. By default it will show all devices, but you
can limit it to showing devices of a specific type.
There is one caveat with this method however - if no devices are
currently present, it will throw an exception. If you plan on
using this method you should add a handler for a hResult of
WIA_S_NO_DEVICE_AVAILABLE
with value of 0x80210015
or
-2145320939
.
Device device;
try
{
device = dialog.ShowSelectDevice(AlwaysSelectDevice: true);
}
catch (COMException ex) when (ex.ErrorCode == WIA_S_NO_DEVICE_AVAILABLE)
{
// handle no devices available
}
Alternatively, if you wanted to select a device of a specific
type, then you can use the optional DeviceType
parameter.
// error handling omitted for brevity
device = dialog.ShowSelectDevice(DeviceType: WiaDeviceType.ScannerDeviceType, AlwaysSelectDevice: true);
Displaying device properties
Although this is probably something you are unlikely to need to
call very often, it is possible to display the native
properties dialogue box for a given device via the
ShowDeviceProperties
method. The dialogue displayed by this
method will vary by device.
dialog.ShowDeviceProperties(device);
Selecting an item
The ShowSelectItems
method displays a UI for selecting one or
more items from a device. In the case of a camera, it will
display a selection dialogue box that allows you choose one or
more photographs. For a scanner, it allows you to scan a
document or photo and for a camera it allows selection of a
previous taken photograph (as noted, I have no dedicated video
device to test with). It returns anItems
collection describing
the user's selection, or null
if cancelled.
The SingleSelect
parameter defaults to true
which only
allows the selection of a single item. Setting it to false
allows multiple selection for the Get Pictures dialogue, but has
no effect for the Scan dialogue.
Items items;
items = dialog.ShowSelectItems(device, SingleSelect: false);
Acquiring an image
The ShowAcquireImage
will display one or more dialogues for
selecting an image. If successful, it will return an ImageFile
object containing the acquired data, otherwise null
.
Calling it without any parameters means it will allow an image
to be selected from any supported device, or you can use the
DeviceType
parameter to constrain the acquisition to a
specific category.
Regardless of if you allow for all device types or limit to a single type, if multiple devices exist it will first prompt for a device as described in Selecting a Device above. If only a single device is present it will automatically use that device without presenting a UI.
Once a device has been selected, the appropriate Select Item dialogue is displayed allowing an existing image to be selected (in the case of a camera) or a new image created (in the case of a scanner).
After selecting/creating an image, the image details are
returned wrapped in an ImageFile
.
One other parameter which may be of interest is FormatID
. If
you specify this, it will try and return the image result in
that format. In practice however, I haven't found this to work -
I always get a Windows Bitmap back regards of what I actually
ask for.
ImageFile image;
image = dialog.ShowAcquireImage();
if(image != null)
{
// do something with the image
}
See Converting WIA.ImageFile into a Bitmap below for how to
get a .NET Image
or Bitmap
from the ImageFile
instance.
Transferring an image
As well as using ShowAcquireImage
to obtain an image via a
dialogue, you can also directly obtain an image. This is useful
when you have configured the properties and don't want the user
to be able to change them, for example when repeating a scan
with the same dimensions as the previous scan.
For this, you can use the ShowTransfer
method. This will still
display a dialogue, but one that only shows the progress of the
transfer and that allows it to be cancelled. As with
ShowAcquireImage
, this method will return an ImageFile
containing the results of the operation, and you can also
attempt to specify the image format you wish returned.
ImageFile image;
image = dialog.ShowTransfer(device.Items[1]);
if(image != null)
{
// do something with the image
}
Printing images
By using the ShowPhotoPrintingWizard
you can print one or more
image files, which is a handy way of adding print functionality
to your application with only a few lines of code.
Although you aren't able to control these programmatically, the Wizard allows the user to choose how images are laid out on the page and configure assorted options.
Vector files;
files = new Vector();
files.Add("Z:\samples\20190811 0349 7.png")
files.Add("Z:\samples\\tower-of-london.jpg")
dialog.ShowPhotoPrintingWizard(files);
There are a number of caveats with this method that I have noticed:
- The Wizard is not modal, meaning a user can click back and continue working with your application leaving the Wizard open or open multiple copies of the Wizard
- File names must be fully qualified
- The Wizard will fail if any of the image files don't have a
recognised image extension. For example, if you created a
.tmp
file containing a bitmap and tried to print, it would fail (and with a very unhelpful "file not found" message)
Device and item properties
Both the Device
and Item
interfaces have a Properties
collection that allows you to manipulate the item. Properties
are self-describing, so in addition to staples like ID, name and
value, you can also query the type of the property (both the
value type and also a sub type that describes how the properties
works, for example range or flags). For range based properties,
the minimum and maximum values are accessible, as well as the
step. For properties that are a list of values, you are able to
read these as well. You could use this information to build your
own UI elements rather than using the built in ones, or for
validating user input.
The Properties
collection has an indexer that accepts an
index, the ID of a property or the name. However, given that
both the index and ID are integers, if you want to pass an ID
you need to convert it to a string first. To save on confusion,
I ended up adding extension methods as helpers.
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()];
}
Assuming I wanted to configure the DPI and quality of a device prior to initiating a scan, I could call the extension as follows
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);
Converting WIA.ImageFile into a Bitmap
The ImageFile
interface has an ARGBData
property that
returns a Vector
containing color data for an image. I tried
using this first to set the pixels of a new bitmap, but even
when manipulating the bitmap pixels directly it was a little
slow.
An alternative is to use the FileData
property. This returns a
series of bytes that represent the image in the output format
returned by the device. This means we can dump this into a
MemoryStream
and call Image.FromFile
. However, as .NET likes
to keep the stream open that could lead to complications
and so I usually clone the resulting image. The following
extension method will take a given ImageFile
and return a
standalone Bitmap
from it.
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;
}
Note that this extension method isn't suitable for images with
multiple frames (e.g. a multi-page TIFF file) as it will only
keep the first frame. I don't usually work with multi-page
images so I don't have a concrete recommendation for this
use-case. I think I'd lean towards saving the result to a
temporary file and then using Image.FromFile
.
Closing thoughts
WIA is very useful for adding basic image printing support to your application, and perfect if you want to add the ability for your applications to capture images from various devices. It is capable of more than I've demonstrated here, but this article covers the basics and some common uses.
The source code for the demonstration as it was at the time of publication can be downloaded from the links on this page, any updates can be found on the GitHub repository.
Downloads
Filename | Description | Version | Release Date | |
---|---|---|---|---|
ScannerTest.zip
|
A sample application demonstrating the basic use of WIA from C# |
06/11/2020 | Download |
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
Pascal Lemba
#
I try use WIA c# using Web Application from a server, I have the problem, but if I use the sql from my computer where I do the programmation it work correct, How I must change in WIA Properties?