An introduction to using Windows Image Acquisition (WIA) via C#

Screenshot of the sample application
Screenshot of the sample application

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.

csharp
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.

csharp
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.

csharp
Device device;
CommonDialog dialog;

device = this.GetSelectedDevice();
dialog = new CommonDialog();

// display a common a dialogue

Selecting a device

Selecting a device
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.

csharp
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.

csharp
// error handling omitted for brevity
device = dialog.ShowSelectDevice(DeviceType: WiaDeviceType.ScannerDeviceType, AlwaysSelectDevice: true);

Displaying device properties

Scanner device properties
Scanner device properties
Camera device properties
Camera 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.

csharp
dialog.ShowDeviceProperties(device);

Selecting an item

Item selection via a camera
Item selection via a camera

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.

csharp
Items items;

items = dialog.ShowSelectItems(device, SingleSelect: false);

Acquiring an image

Selecting an item from a device with multiple sources
Selecting an item from a device with multiple sources

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.

csharp
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.

csharp
ImageFile image;

image = dialog.ShowTransfer(device.Items[1]);

if(image != null)
{
  // do something with the image
}

Printing images

Print Pictures dialogue
Print Pictures dialogue

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.

csharp
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

Viewing the properties of an item
Viewing the properties of an item

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.

csharp
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

csharp
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.

csharp
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.

Files


Comments

We'll never share your email with anyone else Styling with Markdown is supported