Click here to Skip to main content
15,906,816 members
Articles / Programming Languages / C#
Article

.NET TWAIN image scanner

Rate me:
Please Sign up or sign in to vote.
4.91/5 (227 votes)
12 May 2002Public Domain2 min read 7.4M   132.3K   421   996
Using TWAIN API to scan images

Sample Screenshot

Abstract

In Windows imaging applications, the most used API for scanning is TWAIN www.twain.org. Unfortunately, the new .NET Framework has no built-in support for TWAIN. So we have to work with the interop methods of .NET to access this API. This article doesn't explain this interop techniques, and good knowledge of the TWAIN 1.9 specifications is assumed! The sample code included doesn't present a finished library, only some essential steps for a minimal TWAIN adaption to .NET applications.

Details

First step was to port the most important parts of TWAIN.H, these are found in TwainDefs.cs. The real logic for calling TWAIN is coded in the class Twain, in file TwainLib.cs.. As the TWAIN API is exposed by the Windows DLL, twain_32.dll, we have to use the .NET DllImport mechanism for interop with legacy code. This DLL has the central DSM_Entry(), ordinal #1 function exported as the entry point to TWAIN. This call has numerous parameters, and the last one is of variable type! It was found to be best if we declare multiple variants of the call like:

C#
[DllImport("twain_32.dll", EntryPoint="#1")]
private static extern TwRC DSMparent(
    [In, Out] TwIdentity origin,
    IntPtr zeroptr,
    TwDG dg, TwDAT dat, TwMSG msg,
    ref IntPtr refptr );

The Twain class has a simple 5-step interface:

C#
class Twain
{
    Init();
    Select();
    Acquire();
    PassMessage();
    TransferPictures();
}

For some sort of 'callbacks', TWAIN uses special Windows messages, and these must be caught from the application-message-loop. In .NET, the only way found was IMessageFilter.PreFilterMessage(), and this filter has to be activated with a call like Application.AddMessageFilter(). Within the filter method, we have to forward each message to Twain.PassMessage(), and we get a hint (enum TwainCommand) back for how we have to react.

Sample App

The sample is a Windows Forms MDI-style application. It has the two TWAIN-related menu items Select Source... and Acquire... Once an image is scanned in, we can save it to a file in any of the GDI+ supported file formats (BMP, GIF, TIFF, JPEG...)

Limitations

All code was only tested on Windows 2000SP2, with an Epson Perfection USB scanner and an Olympus digital photo camera. The scanned picture is (by TWAIN spec) a Windows DIB, and the sample code has VERY little checking against error return codes and bitmap formats. Unfortunately, no direct method is available in .NET to convert a DIB to the managed Bitmap class... Some known problems may show up with color palettes and menus.

Note, TWAIN has it's root in 16-Bit Windows! For a more modern API supported on Windows ME/XP, have a look at Windows Image Acquisition (WIA).

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Web Developer
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Usage of Twain without a Form Pin
bgunes11-Mar-07 22:04
bgunes11-Mar-07 22:04 
GeneralRe: Usage of Twain without a Form Pin
m@u12-Mar-07 12:46
m@u12-Mar-07 12:46 
GeneralRe: Usage of Twain without a Form Pin
gabegabe25-Apr-07 10:31
gabegabe25-Apr-07 10:31 
QuestionRe: Usage of Twain without a Form Pin
shaggy847520-Mar-07 4:18
shaggy847520-Mar-07 4:18 
GeneralRe: Usage of Twain without a Form Pin
josepaulino22-Mar-07 18:26
josepaulino22-Mar-07 18:26 
GeneralRe: Usage of Twain without a Form Pin
m@u23-Mar-07 3:32
m@u23-Mar-07 3:32 
QuestionRe: Usage of Twain without a Form Pin
josepaulino25-Mar-07 19:18
josepaulino25-Mar-07 19:18 
AnswerRe: Usage of Twain without a Form Pin
m@u25-Mar-07 22:13
m@u25-Mar-07 22:13 
ok..

i think you could get it to work with something like this:


<br />
using System;<br />
using System.Threading;<br />
using System.Collections<br />
public class MTScan<br />
{<br />
    private Thread scanThread;<br />
    TwainMan scanner;<br />
    public MTScan()<br />
    {<br />
        scanThread = new Thread(startScanApp);<br />
    }<br />
    private void startScanApp()<br />
    {<br />
        Application.Idle = new EventHandler(Application_Idle);<br />
        Application.Start();<br />
    }<br />
    private void Application_Idle(object sender, EventArgs e)<br />
    {<br />
        scanner = new TwainMan();<br />
        scanner.ImageScanned += new ImageScannedEventHandler(scanner_ImageScanned);<br />
    }<br />
    private void scanner_ImageScanned(object sender, ImageScannedEventArgs e)<br />
    {<br />
        onImageScanned(e);<br />
    }<br />
    protected virtual void onImageScanned(ImageScannedEventArgs e)<br />
    {<br />
        if (ImageScanned != null)<br />
        {<br />
            ImageScanned(this, e);<br />
        }<br />
    }<br />
    public void Select()<br />
    {<br />
        scanner.Invoke(new ThreadStart(scanner.Select));<br />
    }<br />
    public void Scan()<br />
    {<br />
        scanner.Invoke(new ThreadStart(scanner.Scan));<br />
    }<br />
    public void Dispose()<br />
    {<br />
        scanner.Invoke(new ThreadStart(dispose));<br />
    }<br />
    private void dispose()<br />
    {<br />
        scanner.Dispose();<br />
        Application.ExitThread();<br />
    }<br />
}<br />
public class ImageScannedEventArgs:EventArgs<br />
{<br />
    private Bitmap image;<br />
    public Bitmap Image<br />
    {<br />
        get<br />
        {<br />
            return image;<br />
        }<br />
    }<br />
    public ImageScannedEventArgs(Bitmap Image)<br />
    {<br />
        image = Image;<br />
    }<br />
}<br />
public delegate void ImageScannedEventHandler(object sender, ImageScannedEventArgs e);<br />
[StructLayout(LayoutKind.Sequential, Pack=2)]<br />
internal class BITMAPINFOHEADER<br />
{<br />
    public int biSize;<br />
    public int biWidth;<br />
    public int biHeight;<br />
    public short biPlanes;<br />
    public short biBitCount;<br />
    public int biCompression;<br />
    public int biSizeImage;<br />
    public int biXPelsPerMeter;<br />
    public int biYPelsPerMeter;<br />
    public int biClrUsed;<br />
    public int biClrImportant;<br />
}<br />
/// <br />
/// Zusammenfassung für TwainMan.<br />
/// <br />
public class TwainMan:NativeWindow,IDisposable<br />
{<br />
    private class InvokeHolder<br />
    {<br />
        private Delegate target;<br />
        private object[] args;<br />
        public InvokeHolder(Delegate Target, object[] Args)<br />
        {<br />
            target = Target;<br />
            args = Args;<br />
        }<br />
        public void Run()<br />
        {<br />
            target.DynamicInvoke(args);<br />
        }<br />
    }<br />
    [DllImport("kernel32.dll", ExactSpelling=true)]<br />
    internal static extern IntPtr GlobalLock( IntPtr handle );<br />
    [DllImport("kernel32.dll", ExactSpelling=true)]<br />
    internal static extern IntPtr GlobalFree( IntPtr handle );<br />
    private const int WM_USER = 0x400;<br />
    private const int WM_Invoke = WM_USER+5;<br />
    [DllImport("user32.dll", EntryPoint="PostThreadMessageA")]<br />
    public static extern int PostThreadMessage ( <br />
        int idThread,<br />
        int msg,<br />
        int wParam,<br />
        int lParam);<br />
    private Twain tw;<br />
    private BITMAPINFOHEADER bmi;<br />
    private Rectangle bmprect;<br />
    private bool ready = false;<br />
    public event ImageScannedEventHandler ImageScanned;<br />
    private int threadID;<br />
    private Queue delegateQueue;<br />
    public bool Ready<br />
    {<br />
        get<br />
        {<br />
            return ready;<br />
        }<br />
    }<br />
    public TwainMan()<br />
    {<br />
        CreateParams cp = new CreateParams();<br />
        cp.Parent = new IntPtr(-3);<br />
        //cp.ClassName = "Window";<br />
        cp.Caption = "";<br />
        this.CreateHandle(cp);<br />
        tw = new Twain();<br />
        tw.Init(Handle);<br />
        threadID = AppDomain.GetCurrentThreadId();<br />
        delegateQueue = new Queue();<br />
    }<br />
    public void Invoke(Delegate Target, params object[] args)<br />
    {<br />
        delegateQueue.Enqueue(new InvokeHolder(Target, args);<br />
        PostThreadMessage(threadID, WM_Invoke,0,0);<br />
    }<br />
    public void Select()<br />
    {<br />
        ready = tw.Select();<br />
    }<br />
    public void Scan()<br />
    {<br />
        if (ready)<br />
        {<br />
            tw.Acquire();<br />
        }<br />
    }<br />
    protected IntPtr GetPixelInfo( IntPtr bmpptr )<br />
    {<br />
        bmi = new BITMAPINFOHEADER();<br />
        Marshal.PtrToStructure( bmpptr, bmi );<br />
        bmprect = Rectangle.Empty;<br />
        bmprect.X = bmprect.Y = 0;<br />
        bmprect.Width = bmi.biWidth;<br />
        bmprect.Height = bmi.biHeight;<br />
<br />
        if( bmi.biSizeImage == 0 )<br />
            bmi.biSizeImage = ((((bmi.biWidth * bmi.biBitCount) + 31) & ~31) >> 3) * bmi.biHeight;<br />
<br />
        int p = bmi.biClrUsed;<br />
        if( (p == 0) && (bmi.biBitCount <= 8) )<br />
            p = 1 << bmi.biBitCount;<br />
        p = (p * 4) + bmi.biSize + (int) bmpptr;<br />
        return (IntPtr) p;<br />
    }<br />
    protected virtual void onImageScanned(ImageScannedEventArgs e)<br />
    {<br />
        if (ImageScanned != null)<br />
        {<br />
            ImageScanned(this,e);<br />
        }<br />
    }<br />
    protected override void WndProc(ref Message m)<br />
    {<br />
        if (tw != null)<br />
        {<br />
            TwainCommand cmd = tw.PassMessage( ref m );<br />
            if( cmd != TwainCommand.Not )<br />
            {<br />
                switch( cmd )<br />
                {<br />
                    case TwainCommand.CloseRequest:<br />
                    {<br />
                        tw.CloseSrc();<br />
                        ready = false;<br />
                        break;<br />
                    }<br />
                    case TwainCommand.CloseOk:<br />
                    {<br />
                        tw.CloseSrc();<br />
                        ready = false;<br />
                        break;<br />
                    }<br />
                    case TwainCommand.DeviceEvent:<br />
                    {<br />
                        break;<br />
                    }<br />
                    case TwainCommand.TransferReady:<br />
                    {<br />
                        ArrayList pics = tw.TransferPictures();<br />
                        tw.CloseSrc();<br />
                        for( int i = 0; i < pics.Count; i++ )<br />
                        {<br />
                            IntPtr img = (IntPtr) pics[ i ];<br />
                            IntPtr bmpt = GlobalLock(img);<br />
                            IntPtr pi = GetPixelInfo(bmpt);<br />
                            IntPtr imgH = IntPtr.Zero;<br />
                            int Ret = GdiPlusLib.Gdip.GdipCreateBitmapFromGdiDib(bmpt,pi,ref imgH);<br />
                            if (img != IntPtr.Zero)<br />
                            {<br />
                                Bitmap bmp = (Bitmap)typeof(Bitmap).InvokeMember("FromGDIplus", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, null, new object[] { imgH });<br />
                                onImageScanned(new ImageScannedEventArgs(bmp.Clone() as Bitmap));<br />
                            }<br />
                        }<br />
                        break;<br />
                    }<br />
                    case WM_Invoke:<br />
                    {<br />
                        if (invokeQueue.Count != 0)<br />
                        {<br />
                            do<br />
                            {<br />
                                InvokeHolder h = invokeQueue.Dequeue() as InvokeHolder;<br />
                                h.Run();<br />
                            }while(invokeQueue.Count != 0);<br />
                        }<br />
                        break;<br />
                    }<br />
                }<br />
            }<br />
        }<br />
    base.WndProc(ref m);<br />
}<br />
#region IDisposable Member<br />
<br />
public void Dispose()<br />
{<br />
    try<br />
    {<br />
        tw.CloseSrc();<br />
    }<br />
    catch{}<br />
    tw = null;<br />
}<br />
<br />
#endregion<br />
}<br />

this code should theoretically start a different thread where the whole scanning should be handled.
it's not testet, so maybe it contains some syntax / logical errors. but i think the solution for scanning in a different thread is something like that..

greets
m@u
QuestionRe: Usage of Twain without a Form Pin
chandresh.softobiz17-Sep-09 23:50
chandresh.softobiz17-Sep-09 23:50 
GeneralRe: Usage of Twain without a Form [modified] Pin
dahalbibek26-Dec-11 22:54
dahalbibek26-Dec-11 22:54 
GeneralSoftware Spalsh Screen Pin
MauSav3121-Dec-06 8:38
MauSav3121-Dec-06 8:38 
QuestionFrame using TWFix32 instead of Int32 (capture 8.5x11) Pin
pelstone30-Nov-06 5:16
pelstone30-Nov-06 5:16 
GeneralClose after scanning / saving Pin
qjay23-Nov-06 22:05
qjay23-Nov-06 22:05 
QuestionCreating an ActiveX and calling it from Web Browser Pin
IndoDev8-Nov-06 21:42
IndoDev8-Nov-06 21:42 
AnswerRe: Creating an ActiveX and calling it from Web Browser Pin
SikeMullivan28-Dec-06 3:51
SikeMullivan28-Dec-06 3:51 
GeneralRe: Creating an ActiveX and calling it from Web Browser Pin
IndoDev5-Feb-07 2:28
IndoDev5-Feb-07 2:28 
QuestionRe: Creating an ActiveX and calling it from Web Browser Pin
Scott G26-Feb-07 16:33
Scott G26-Feb-07 16:33 
GeneralRe: Creating an ActiveX and calling it from Web Browser Pin
bharathkashyap4-Oct-07 21:10
bharathkashyap4-Oct-07 21:10 
GeneralRe: Creating an ActiveX and calling it from Web Browser Pin
LupinTheThird23-Oct-11 1:50
LupinTheThird23-Oct-11 1:50 
GeneralRe: Creating an ActiveX and calling it from Web Browser Pin
LupinTheThird23-Oct-11 2:12
LupinTheThird23-Oct-11 2:12 
QuestionPreview image stream without standart window [modified] Pin
Victor VP6-Nov-06 0:32
Victor VP6-Nov-06 0:32 
QuestionProgrammatically capturing the image Pin
lifesabirch.org31-Oct-06 3:31
lifesabirch.org31-Oct-06 3:31 
QuestionHow can I display the scanner settings dialog without starting the scan process? Pin
DJM16-Oct-06 0:36
DJM16-Oct-06 0:36 
AnswerRe: How can I display the scanner settings dialog without starting the scan process? Pin
gabegabe25-Apr-07 10:32
gabegabe25-Apr-07 10:32 
GeneralAutocrop Pin
MrPieGuy12-Oct-06 7:59
MrPieGuy12-Oct-06 7:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.