Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Scanning images in WPF via WIA

Pete Brown - 08 January 2010

Over the holidays, Scott assigned to me a project to wrap up some of the WPF-ness in the shoebox scanner he had built. He had the scanning in place, but not some of the cropping/resizing against the original image, and the UI was a dev UI.

The scanning part is pretty interesting, so I thought I'd share that with you.

The app Scott and I have put together is a WPF 3.5 application, but I thought I'd continue with sticking to the latest and pull us up into WPF 4 for this demo.

Introduction to Windows Image Acquisition/Architecture (WIA)

WIA was introduced in Windows 2000 as a way to enable access to scanners and cameras. In versions prior to Windows Vista, it also supported basic video. WIA is the still image acquisition platform in the Windows family of operating systems starting with Windows Millennium Edition (Windows Me) and Windows XP. [msdn]

Graphic showing the architecture of WIA and how it operates as a service. (source MSDN)

WIA does a decent job of abstracting away the details of interfacing with different scanners and other image acquisition devices. It includes functionality and common dialogs to help us with using those devices from script, COM and managed code (via COM).

Now, the COM interface does lean a little to the ugly side when compared to your normal .NET code. It's code you'll want to encapsulate in into something you can test, and then expose a clean interface to the rest of the app.

Project Setup

Create a new WPF 4 Windows application to start. We'll then need to add a reference to WIA and create a couple of the usual folders and ViewModel pattern support classes.

WIA Reference

WIA is a COM library, so you'll need to add a reference and create the interop DLL. Simply add a COM reference and select "Microsoft Windows Image Acquisition Library v2.0" and Visual Studio will create the interop wrapper for you.

image

If you want to avoid embedding errors with the FormatID class we'll discuss below, set Embed Interop Types to false in the reference properties once you have the reference in your solution. The other option, as the article mentions, is to create your own class of constants, but I'd rather not do that.

Structure

We'll take an approach I often take here, and implement a relatively simple version of the ViewModel pattern.

image

There's a dotted line from the ImageConverter to WIA just because it's not going back to WIA to get anything, it's using some stuff returned originally from the Scanner service. Not an important distinction, but one I wanted to make.

Scanner Service

The scanner code, due to the nature of WIA, includes both UI dialogs and functionality. For that reason and others, it makes sense to pull it out into its own service class. In the project, I created a Services folder and a new class named ScannerService.

Inside that same service class file, I added two exception types. The first, ScannerException, is used for anything we can't easily classify, but which comes from the scanner. The second is the ScannerNotFoundException which is used exactly when you think it would be based on the name.

public class ScannerException : ApplicationException
{
    public ScannerException()
        : base()
    { }

    public ScannerException(string message)
        : base(message)
    { }

    public ScannerException(string message, Exception innerException)
        : base(message, innerException)
    { }

}

public class ScannerNotFoundException : ScannerException
{
    public ScannerNotFoundException()
        : base("Error retrieving a list of scanners. Is your scanner or multi-function printer turned on?")
    {
    }
}




public class ScannerService
{
    public ImageFile Scan()
    {
        ImageFile image;

        try
        {
            CommonDialog dialog = new CommonDialog();

            image = dialog.ShowAcquireImage(
                    WiaDeviceType.ScannerDeviceType,
                    WiaImageIntent.ColorIntent,
                    WiaImageBias.MaximizeQuality,
                    WIA.FormatID.wiaFormatJPEG, 
                    false, 
                    true, 
                    false);

            return image;
        }
        catch (COMException ex)
        {
            if (ex.ErrorCode == -2145320939)
            {
                throw new ScannerNotFoundException();
            }
            else
            {
                throw new ScannerException("COM Exception", ex);
            }
        }
    }

}

The ScannerService.Scan method uses the WIA CommonDialog class to prompt the user for the scan. You can do the scan without a dialog, but you lose out on a bunch of useful UI.

ShowAcquireImage parameters

DeviceType Scanner, camera, or other device
Intent Whether you want color, black and white, text etc
Bias Quality
Format The format for the image, Jpeg in this case
AlwaysSelectDevice Whether or not to always prompt the user to select the device
UseCommonUI True or false to show the ui
CancelError If true, cancelling throws an error

 

ViewModel and Command Structure

Observable Base Class

ViewModel and model classes typically need to have some goo inserted to notify when properties changed. The way I usually handle that in real applications is to create an Observable abstract base class in a project accessible to both the ViewModels and Models. Since everything's in on project in this demo, just put it in the root of your project.

public abstract class Observable : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

}

 

Main ViewModel

As is often the case in demo-land, I have only one form in this particular application, so I have only one ViewModel. Still, create a ViewModels folder and add the ScanViewModel class to it.

In the past, I've used the RelayCommand class from this MSDN article. I'll use that again here. Rather than repeat the code, simply refer to the link and look at Figure 3 for the RelayCommand source code.

class ScanViewModel : Observable
{
    private BitmapSource _scannedImage;
    public BitmapSource ScannedImage
    {
        get { return _scannedImage; }
        set { _scannedImage = value; NotifyPropertyChanged("ScannedImage"); }
    }

    // Command for scanning
    private RelayCommand _scanCommand;
    public ICommand ScanCommand
    {
        get
        {
            if (_scanCommand == null)
            {
                _scanCommand = new RelayCommand(param => this.Scan(), param => this.CanScan);
            }

            return _scanCommand;
        }
    }

    // implemented for the command pattern completeness. We don't currently
    // have any situations where we'd disable the ability to scan
    public bool CanScan
    {
        get { return true; }
    }

    // method to do the actual scanning
    public void Scan()
    {
        var scanner = new ScannerService();

        try
        {
            ImageFile file = scanner.Scan();

            if (file != null)
            {
                var converter = new ScannerImageConverter();

                ScannedImage = converter.ConvertScannedImage(file);
            }
            else
            {
                ScannedImage = null;
            }

        }
        catch (ScannerException ex)
        {
            // yeah, I know. Showing UI from the VM. Shoot me now.
            MessageBox.Show(ex.Message, "Unable to Scan Image");
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, "Error");
        }
    }
}

The method that does the heavy lifting is the Scan method. It isn't called directly from outside the VM (but certainly could be). Instead, it is called via the ScanCommand.

The ScannedImage property exposes a WPF-friendly version of the scanned image.

The CanScan property is there just to support the relay command. In this application, there aren't any points when scanning would be disallowed, so I always return true.

ScannerImageConverter Service

In the Scan method of the ViewModel above, you can see that I call out to an image conversion class to do the conversion. I felt that code had no real business being in the VM, as it was reusable and seemed a little heavy for that class. I also felt it shouldn't be in the Scanner service, as that would introduce (through the BitmapImage type) a dependency on WPF that I wanted to avoid. The Scanner service should live on its own.

For that reason, I created a separate class to handle the image conversion. It takes a dependency both on the scanner and on WPF.

In this service, I implemented two ways to handle the image conversion. I only use one in the code, but I wanted to share both with you. The in-memory approach is great, until you get into large scans, such as 1200dpi full-pagers. Then, the COM library chokes on the conversion because it can't handle transferring that many bytes across the COM interface using the variant types it has.

The second approach uses a temp file. I save the image using WIA then load it back in via WPF. It's simpler, but does assume the code will have permission to read and write files.

public class ScannerImageConverter
{
    // this could be in the ScannerService, but then that service
    // takes a dependency on WPF, which I didn't want. Better to have
    // the dependencies wrapped into this service instead. Requires
    // FileIOPermission
    public BitmapSource ConvertScannedImage(ImageFile imageFile)
    {
        if (imageFile == null)
            return null;

        // save the image out to a temp file
        string fileName = Path.GetTempFileName();

        // this is pretty hokey, but since SaveFile won't overwrite, we 
        // need to do something to both guarantee a unique name and
        // also allow SaveFile to write the file
        File.Delete(fileName);

        // now save using the same filename
        imageFile.SaveFile(fileName);

        BitmapFrame img;

        // load the file back in to a WPF type, this is just 
        // to get around size issues with large scans
        using (FileStream stream = File.OpenRead(fileName))
        {
            img = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);

            stream.Close();
        }
        
        // clean up
        File.Delete(fileName);

        return img;
    }


    // this will choke on large images (like 1200dpi scans)
    // for that reason, you may want to do the conversion by
    // saving the ImageFile to a temp file and then loading it
    // in to convert it, as we do in the revised method above
    public BitmapSource InMemoryConvertScannedImage(ImageFile imageFile)
    {
        if (imageFile == null)
            return null;

        Vector vector = imageFile.FileData;

        if (vector != null)
        {
            byte[] bytes = vector.get_BinaryData() as byte[];

            if (bytes != null)
            {
                var ms = new MemoryStream(bytes);
                return BitmapFrame.Create(ms);
            }
        }

        return null;
    }

}

 

Creating the UI

The user interface is pretty simple. We need a place to show the scanned image, and a button to start the scan. Here's the resulting screen in design view.

image

And here's the xaml. I wired up the ViewModel using the data context for the window, and bound the Scan Button to the ScanCommand in the VM. The image is bound to the ScannerImage property of the same VM.

<Window x:Class="WpfScannerDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModels="clr-namespace:WpfScannerDemo.ViewModels"
        Title="MainWindow" Height="466" Width="600">
    <Window.DataContext>
        <viewModels:ScanViewModel />
    </Window.DataContext>
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="150" />
        </Grid.ColumnDefinitions>

        <Image x:Name="ScannerImage"
               Stretch="Uniform"
               Grid.Column="0" 
               Source="{Binding ScannedImage}"
               />

        <Button x:Name="Scan"
                Grid.Column="1"
                Margin="10"
                Height="25"
                Width="75"
                HorizontalAlignment="Right"
                VerticalAlignment="Top"
                Content="Scan"
                Command="{Binding ScanCommand}" />
    </Grid>
</Window>

 

Testing it Out

Run the application and hit the Scan button. You'll get the WIA dialog that lets you configure a bunch of parameters such as resolution, type of scan etc.

image

The first time you scan, the scanner will need to warm up, which will make the WIA status dialog do nothing useful for a bit. Then it will start the scan.

image

Once it finishes, the image will be scanned in and available to your WPF application. Pretty neat, eh?

image

Yes, that image is me and my son just before Christmas, at the Toys for Tots fundraiser. Donate a toy, get your photo taken with a bunch of Star Wars dudes. He loved it :) It's just for the kids, you know? ;)

Conclusion

That covers the basics of using the scanner from WPF. I'll post some follow-up information on how we did cropping, rotation and other image manipulation goodies.

         

Source Code and Related Media

Download /media/55896/petebrownwpfscannerdemo.zip
posted by Pete Brown on Friday, January 8, 2010
filed under:          

37 comments for “Scanning images in WPF via WIA”

  1. Ramesh Nagulsays:
    Would be interested in being able to script a scanner without a UI and possibly passing in some parameters, like for .e.g

    ScanDocument(ScannertoUse, OptiosObject)

    ScannertoUse = Scanner ID of some sort, if there is more than one scanner on the system

    OptionsObject = Object or structure allowing to specify things like DPI, color, etc..
  2. Yuvalsays:
    Thank you for you post. It is very helpful.

    Is there a way to scan without showing the dialog and without any user intervention?
    I tried setting "UseCommonUI" to false but it didn't work.

    Thanks


  3. Petesays:
    @Yuval

    I haven't tried the options yet, but I know they are there. Maybe in an upcoming post.

    @(post before Yuval) Multi-page scanning would require either repeated requests to the user to add the next page and hit enter, or integration with scanners that have a built-in feeder. I don't believe WMI surfaces the latter, but I haven't actually tried.
  4. Petesays:
    @Jitbit

    It's all there in the post. I'm working on a way to let me share zip files more easily from my blog. Until then, email me through the contact link here, and I'll send you the zip when I get back from Redmond next week.

    Pete
  5. Rolandsays:
    Hi Pete

    Very intresting article. Could I get both source (WPF / Silverlight). We are evaluating, which technology we should use with WIA API
    thanks very much

    Roland
  6. Ramesh Mishrasays:
    Hi Scott,
    I am getting following error

    Error retrieving a list of scanners. Is your scanner or multi-function printer turned on

    could u help me .

    Thanks
  7. marionnysays:
    Did anyone succeded in disabling user interface: setting "UseCommonUI" to false. I make it false but it didn't work. It still shows the : What do you want to scan window. I've putted the WiaImageIntent to grayscale and disable UI but it still show me that window. Have anyone any idea?
  8. Kamilsays:
    Hi!
    Very neat code. Im working on similar solution, but without UI. I finally got it working, but there's a problem with setting custom DPI. I don't know why, but WIA doesn't allow to set all supported DPIs. For example even though my scanner can handle up to 1200, Paint exposes only 100 and 300. So how can I get a list of available DPIs, because I only found a property which stands for max optical dpi.
  9. Arinsays:
    Hi,

    Thanks so much for the great article. The code works great. However, I was wondering if you could help me figure out how I can make the code work for the multi-page scanning.

    I have tried it on Silverlight and also WPF and so far no luck.

    Any help would greatly appreciated.

    Thanks,
    Arin
  10. Michaelsays:
    When an image is scanned it is stored as a .tmp file, is there any way to save the file as a jpeg?

    I had thought that "WIA.FormatID.wiaFormatJPEG," would have saved it as a jpeg.

    Thanks
  11. mohammadsays:
    hi...
    When I want to refrence windows Image acquisition v 2.0 i have
    windows Image acquisition v 1.1
    i vcan using from WiaLib not Wia;
    i have win xp sp3 and vs 2010
  12. Petesays:
    @Kamil

    I believe you can grab device capability information, but I haven't tried it. I'm going to do some more work with scanning in the near future, so I'll post additional information when I get there.

    @Arin, @Nick

    No idea on multi-page scanning. I don't believe WIA is going to help you there. You'll likely need to look to TWAIN or something else.

    @Michael

    The format issues were a real problem. It seemed to store in the same format regardless of what I provided it.

    @emrah

    I recommend not using WPF browser applications. I wrote another post on using WIA from Silverlight which may help you.

    @mohammad

    Upgrade to an OS that isn't a decade old ;) Windows 7 is an excellent operating system.

    You can download WIA 2.0 from the download center. http://www.microsoft.com/downloads/en/details.aspx?FamilyID=a332a77a-01b8-4de6-91c2-b7ea32537e29 (I found that by going to the download center and typing "WIA Automation"

    Pete
  13. adityasays:
    hey pete, i actually wanted to integrate a webcam with my wpf project, and after a bit of research on wia i stublem across this page, looks cool to me except i wanted to use a webcam instead of a scanner, is there any possible way of doing it, i looked at other options like wpfmediakit, n webcam capture but they all seem to have compatibility issues with older systems. I insist on using wia as it is a native windows built api.

    are there any options i can look at or do you suggest me anything??
  14. Sami Al Damirisays:
    Hi Pete Wia can help in multiple pages i found code in wia 1 and i tried it and i make the loop for it . i Scanned many papers that time i used the Win XP when i turn it to Win 7 my headache begins and until now am still searching ... if u want i can paste the wia 1 code .. and i found wia 2 code but for single paper .. i want to make loop for multiple pages . i Program on Vb.net .. i wish u can help me .....
  15. Sami Al Damirisays:
    My Conclusion About Wia 1 & Wia 2 :
    Wia 1 is For Windows XP and previous Versions and its Uses Wiascr.dll (Can Found on Windows\System 32\)
    Wia 2 is For Windows Vista And later Verions and its Uses Wiaaut.dll ( Can Found on windows\System 32\)
  16. Sami Al Damirisays:
    Wis 2 Scan Code .. Work Perfect On Windows 7 on VS 2008 & 2010 But I Need loop On It i Hope u Can Help Me
    --------------------------------------------------------------------------------------------------------------
    Code .......................................

    _filename = "Page"
    JpgFilename = Path2 & _filename & Format(Image_No, "00") & ".Jpg"
    BmpFilename = Path1 & _filename & Format(Image_No, "00")
    Try
    Dim Dialog As New WIA.CommonDialog
    Dim _ImageFile As New ImageFile
    Dim _FileExtension As String = "."
    Dim itmX As Integer = 1
    _ImageFile = Dialog.ShowAcquireImage(WiaDeviceType.ScannerDeviceType, WiaImageIntent.ColorIntent, WiaImageBias.MaximizeQuality, , False, False, False)
    _FileExtension = _ImageFile.FileExtension
    BmpFilename &= "." & _FileExtension
    _ImageFile.SaveFile(BmpFilename)
    Dim ScannedImage As Image = Image.FromFile(BmpFilename)
    ScannedImage.Save(JpgFilename, System.Drawing.Imaging.ImageFormat.Jpeg)
    ScannedImage.Dispose()
    ScannedImage = Nothing
    Application.DoEvents()
    Dialog = Nothing
    _ImageFile = Nothing
    With PictureBox1
    .BackgroundImage = Image.FromFile(JpgFilename)
    .Tag = JpgFilename
    End With
    Path2 = JpgFilename
    My.Computer.FileSystem.DeleteFile(BmpFilename)
    End Code ------------------------------------------------------------------------------------------------
    --------------------------------------------------------------------------------------------------------------------------
    Hope u Can Help In loop .... Thanks
  17. Dirk Bourgeoissays:
    @ Anyone!

    This post is in line with the one from "marionny" on "Friday, July 02, 2010 at 11:33:34 AM" ..
    I have the same problem! For some reason i can not get the UI to bugger off..

    I have a Canon LIDE 210 scanner, pretty basic cheap-o scanner.. but works fine with WIA..
    Code is cleancut:
    ---------------------------------------
    Public Function Scan() As Bitmap
    Try
    ' Scan Image using WIA
    Dim dlgCommon As New CommonDialog()
    Dim WIAImage As ImageFile = dlgCommon.ShowAcquireImage(WiaDeviceType.ScannerDeviceType, WiaImageIntent.ColorIntent, WiaImageBias.MaximizeQuality, WIA.FormatID.wiaFormatJPEG, False, False, False)
    If (WIAImage Is Nothing) Then _
    Return Nothing

    ' Convert WIA Image to Bitmap
    Dim Bitmap As New Bitmap(New MemoryStream(CType(WIAImage.FileData.BinaryData, Byte())))
    Return Bitmap
    Catch ex As COMException
    Return Nothing
    End Try
    End Function
    ---------------------------------------

    As you can see i clearly set the ImageIntent, aswell as disabling the UI, still i am getting the "What do you want to Scan" window which allows you to select ImageIntent and preview your scan..
    Hence, the freaking UI.. I wish to make the scanner just SCAN with no input required.

    Does anyone have the slightest clue on why in gods name i am still getting an UI?
    Otherwise i will have to start a thread before envoking "ShowAcquireImage" which waits for the window handle, and sends an Enter Key.
    But simply put ofc i do not want to even start making this work-arround!

    Please someone enlighten me ...
  18. yelinnasays:
    With this tutorial, and using wiaaut.dll version 6.1.7600 (the version for win7 placed in the same folder of my solution, NOT in the system folder) and Interop you can make an app compatible witn both winXP and win7
    Thanks a lot!
  19. Nipunasays:
    I want to do the scanning of a image without displaying any UI. therefore I implemented this solution and made UseCommonUI to false to hide the UI. but this code seems to be not working. Has anyone succeeded implementing this.
  20. Mike Sayeghsays:
    Hello Peter,

    This is a really cool code! I incorperated your code into one of my project. However, when I press the scan button it does not respond at all. I tried to make sense of how the button in your code works without luck!
    Can you please guide me on how to trigger the scanner?

    Thanks.
  21. Mike Sayeghsays:
    Hello Pete,

    This is a really cool code! I incorperated your code into one of my project. However, when I press the scan button it does not respond at all. I tried to make sense of how the button in your code works without luck!
    Can you please guide me on how to trigger the scanner?

    Thanks.

Comment on this Post

Remember me

1 trackback for “Scanning images in WPF via WIA”

  1. uberVU - social commentssays:
    This post was mentioned on Twitter by Pete_Brown: Blogged: Scanning images in WPF 4 via WIA http://bit.ly/8yLJbt #scanner #viewmodel #wpf