示例#1
0
        /// <summary>
        /// Instantiate TWAIN and open the DSM...
        /// </summary>
        /// <param name="a_intptrHwnd">Parent window (needed for Windows)</param>
        /// <param name="a_writeoutputdelegate">Optional text output callback</param>
        /// <param name="a_reportimagedelegate">Optional report image callback</param>
        /// <param name="m_setmessagefilterdelegate">Optional message filter callback</param>
        /// <param name="a_szManufacturer">Application manufacturer</param>
        /// <param name="a_szProductFamily">Application family</param>
        /// <param name="a_szProductName">Name of the application</param>
        /// <param name="a_u16ProtocolMajor">TWAIN protocol major (doesn't have to match TWAINH.CS)</param>
        /// <param name="a_u16ProtocolMinor">TWAIN protocol minor (doesn't have to match TWAINH.CS)</param>
        /// <param name="a_aszSupportedGroups">Bitmask of DG_ flags</param>
        /// <param name="a_szTwcy">Application's country code</param>
        /// <param name="a_szInfo">Info about the application</param>
        /// <param name="a_szTwlg">Application's language</param>
        /// <param name="a_u16MajorNum">Application's major version</param>
        /// <param name="a_u16MinorNum">Application's minor version</param>
        /// <param name="a_blUseLegacyDSM">The the legacy DSM (like TWAIN_32.DLL)</param>
        /// <param name="a_blUseCallbacks">Use callbacks (preferred)</param>
        public TWAINCSToolkit(
            IntPtr a_intptrHwnd,
            WriteOutputDelegate a_writeoutputdelegate,
            ReportImageDelegate a_reportimagedelegate,
            SetMessageFilterDelegate m_setmessagefilterdelegate,
            string a_szManufacturer,
            string a_szProductFamily,
            string a_szProductName,
            ushort a_u16ProtocolMajor,
            ushort a_u16ProtocolMinor,
            string[] a_aszSupportedGroups,
            string a_szTwcy,
            string a_szInfo,
            string a_szTwlg,
            ushort a_u16MajorNum,
            ushort a_u16MinorNum,
            bool a_blUseLegacyDSM,
            bool a_blUseCallbacks
        )
        {
            TWAIN.STS sts;
            uint u32SupportedGroups;

            // Init stuff...
            m_intptrHwnd = a_intptrHwnd;
            if (a_writeoutputdelegate == null)
            {
                WriteOutput = WriteOutputStub;
            }
            else
            {
                WriteOutput = a_writeoutputdelegate;
            }
            ReportImage = a_reportimagedelegate;
            SetMessageFilter = m_setmessagefilterdelegate;
            m_szImagePath = null;
            m_iImageCount = 0;

            // Convert the supported groups from strings to flags...
            u32SupportedGroups = 0;
            foreach (string szSupportedGroup in a_aszSupportedGroups)
            {
                TWAIN.DG dg = (TWAIN.DG)Enum.Parse(typeof(TWAIN.DG), szSupportedGroup.Remove(0, 3));
                if (Enum.IsDefined(typeof(TWAIN.DG), dg))
                {
                    u32SupportedGroups |= (uint)dg;
                }
            }

            // Instantiate TWAIN, and register ourselves...
            m_twain = new TWAIN
            (
                a_szManufacturer,
                a_szProductFamily,
                a_szProductName,
                a_u16ProtocolMajor,
                a_u16ProtocolMinor,
                u32SupportedGroups,
                (TWAIN.TWCY)Enum.Parse(typeof(TWAIN.TWCY), a_szTwcy),
                a_szInfo,
                (TWAIN.TWLG)Enum.Parse(typeof(TWAIN.TWLG), a_szTwlg),
                a_u16MajorNum,
                a_u16MinorNum,
                a_blUseLegacyDSM,
                a_blUseCallbacks,
                DeviceEventCallback,
                ScanCallback
            );

            // Store some values...
            m_blUseCallbacks = a_blUseCallbacks;

            // Our default transfer mechanism...
            m_twsxXferMech = TWAIN.TWSX.NATIVE;

            // Our default file transfer info...
            m_twsetupfilexfer = default(TWAIN.TW_SETUPFILEXFER);
            m_twsetupfilexfer.Format = TWAIN.TWFF.TIFF;
            if (TWAIN.GetPlatform() == TWAIN.Platform.WINDOWS)
            {
                m_twsetupfilexfer.FileName.Set(Path.GetTempPath() + "img");
            }
            else if (TWAIN.GetPlatform() == TWAIN.Platform.LINUX)
            {
                m_twsetupfilexfer.FileName.Set(Path.GetTempPath() + "img");
            }
            else if (TWAIN.GetPlatform() == TWAIN.Platform.MACOSX)
            {
                m_twsetupfilexfer.FileName.Set("/var/tmp/img");
            }
            else
            {
                Log.Msg(Log.Severity.Throw, "Unsupported platform..." + TWAIN.GetPlatform());
            }

            // We've not been in the scan callback yet...
            m_blScanStart = true;

            // Open the DSM...
            try
            {
                sts = m_twain.DatParent(TWAIN.DG.CONTROL, TWAIN.MSG.OPENDSM, ref m_intptrHwnd);
            }
            catch (Exception exception)
            {
                Log.Msg(Log.Severity.Error, "OpenDSM exception: " + exception.Message);
                sts = TWAIN.STS.FAILURE;
            }
            if (sts != TWAIN.STS.SUCCESS)
            {
                Log.Msg(Log.Severity.Error, "OpenDSM failed...");
                Cleanup();
                throw new Exception("OpenDSM failed...");
            }
        }
示例#2
0
        /// <summary>
        /// Our scanning callback function.  We appeal directly to the supporting
        /// TWAIN object.  This way we don't have to maintain some kind of a loop
        /// inside of the application, which is the source of most problems that
        /// developers run into.
        /// 
        /// While it looks scary at first, there's really not a lot going on in
        /// here.  We do some sanity checks, we watch for certain kinds of events,
        /// we support the four methods of transferring images, and we dump out
        /// some meta-data about the transferred image.  However, because it does
        /// look scary I dropped in some region pragmas to break things up...
        /// </summary>
        /// <param name="a_blClosing">We're shutting down</param>
        /// <returns>TWAIN status</returns>
        private TWAIN.STS ScanCallback(bool a_blClosing)
        {
            bool blXferDone;
            TWAINCSToolkit.MSG msgPendingxfers = TWAINCSToolkit.MSG.ENDXFER;
            TWAIN.STS sts;
            string szFilename = "";
            MemoryStream memorystream;
            TWAIN.TW_IMAGEINFO twimageinfo = default(TWAIN.TW_IMAGEINFO);

            // Validate...
            if (m_twain == null)
            {
                Log.Msg(Log.Severity.Error, "m_twain is null...");
                if (ReportImage != null) ReportImage("ScanCallback: 001", "", "", "", CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                return (TWAIN.STS.FAILURE);
            }

            // We're leaving...
            if (a_blClosing)
            {
                if (ReportImage != null) ReportImage("ScanCallback: 002", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.IDENTITY.ToString(), TWAIN.MSG.CLOSEDS.ToString(), CvtSts(TWAIN.STS.SUCCESS), null, null, null, null, 0);
                return (TWAIN.STS.SUCCESS);
            }

            // Somebody pushed the Cancel or the OK button...
            if (m_twain.IsMsgCloseDsReq())
            {
                m_twain.Rollback(TWAIN.STATE.S4);
                ReportImage("ScanCallback: 003", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.NULL.ToString(), TWAIN.MSG.CLOSEDSREQ.ToString(), CvtSts(TWAIN.STS.SUCCESS), null, null, null, null, 0);
                return (TWAIN.STS.SUCCESS);
            }
            else if (m_twain.IsMsgCloseDsOk())
            {
                m_twain.Rollback(TWAIN.STATE.S4);
                ReportImage("ScanCallback: 004", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.NULL.ToString(), TWAIN.MSG.CLOSEDSOK.ToString(), CvtSts(TWAIN.STS.SUCCESS), null, null, null, null, 0);
                return (TWAIN.STS.SUCCESS);
            }

            // Init ourselves...
            if (m_blScanStart)
            {
                TWAIN.TW_CAPABILITY twcapability;

                // Don't come in here again until the start of the next scan batch...
                m_blScanStart = false;

                // Make a note of it...
                WriteOutput(Environment.NewLine + "Entered state 5..." + Environment.NewLine);

                // Get the current setting for the image transfer...
                twcapability = default(TWAIN.TW_CAPABILITY);
                twcapability.Cap = TWAIN.CAP.ICAP_XFERMECH;
                sts = m_twain.DatCapability(TWAIN.DG.CONTROL, TWAIN.MSG.GETCURRENT, ref twcapability);
                if (sts == TWAIN.STS.SUCCESS)
                {
                    try
                    {
                        string[] asz = m_twain.CapabilityToCsv(twcapability).Split(new char[] { ',' });
                        m_twain.DsmMemFree(ref twcapability.hContainer);
                        m_twsxXferMech = (TWAIN.TWSX)ushort.Parse(asz[3]);
                    }
                    catch
                    {
                        m_twsxXferMech = TWAIN.TWSX.NATIVE;
                    }
                }
                else
                {
                    m_twsxXferMech = TWAIN.TWSX.NATIVE;
                }
            }

            // We're waiting for that first image to show up, if we don't
            // see it, then return...
            if (!m_twain.IsMsgXferReady())
            {
                // If we're on Windows we need to send event requests to the driver...
                if (TWAIN.GetPlatform() == TWAIN.Platform.WINDOWS)
                {
                    TWAIN.TW_EVENT twevent = default(TWAIN.TW_EVENT);
                    twevent.pEvent = Marshal.AllocHGlobal(256); // over allocate for MSG structure
                    if (twevent.pEvent != IntPtr.Zero)
                    {
                        m_twain.DatEvent(TWAIN.DG.CONTROL, TWAIN.MSG.PROCESSEVENT, ref twevent);
                        Marshal.FreeHGlobal(twevent.pEvent);
                    }
                }

                // Scoot...
                return (TWAIN.STS.SUCCESS);
            }

            // Transfer the image, at this point we're showing it on the form.  The
            // application should queue this to some other thread or process as fast
            // as possible, so that we can get to the next image.
            //
            // This is the point where TWAIN tells us about things like jams and
            // multifeeds.

            // Init some more stuff...
            blXferDone = false;

            ///////////////////////////////////////////////////////////////////////////////
            //
            // Beginning of the image transfer section...
            //

            // A native transfer gives us a handle to a DIB, which is not
            // quite what we need to fill in a Bitmap in C#, so there's
            // some work going on under the hood that requires both processing
            // power and memory that may not make this the best choice.
            // However, all drivers support native, and the format is reasonably
            // easy to process...
            #region TWAIN.TWSX.NATIVE
            if (m_twsxXferMech == TWAIN.TWSX.NATIVE)
            {
                Bitmap bitmap = null;
                sts = m_twain.DatImagenativexfer(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref bitmap);
                if (sts != TWAIN.STS.XFERDONE)
                {
                    WriteOutput("Scanning error: " + sts + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 005", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGENATIVEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }
                else
                {
                    msgPendingxfers = ReportImage("ScanCallback: 006", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGENATIVEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), bitmap, null, null, null, 0);
                    bitmap = null;
                    blXferDone = true;
                }
            }
            #endregion

            // File transfers are not supported by all TWAIN drivers (it's an
            // optional transfer format in the TWAIN Specification)...
            #region TWAIN.TWSX.FILE
            else if (m_twsxXferMech == TWAIN.TWSX.FILE)
            {
                Bitmap bitmap;
                TWAIN.TW_SETUPFILEXFER twsetupfilexfer = default(TWAIN.TW_SETUPFILEXFER);

                // Get a copy of the current setup file transfer info...
                twsetupfilexfer = m_twsetupfilexfer;

                // ***WARNING***
                // Override the current setting with one that supports the pixeltype and
                // compression of the current image.  The choices are JPEG or TIFF.  Note
                // that this only works for drivers that report final value in state 6...
                if (m_blAutomaticJpegOrTiff)
                {
                    // We need the image info for this one...
                    if (twimageinfo.BitsPerPixel == 0)
                    {
                        sts = m_twain.DatImageinfo(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimageinfo);
                        if (sts != TWAIN.STS.SUCCESS)
                        {
                            WriteOutput("ImageInfo failed: " + sts + Environment.NewLine);
                            m_twain.Rollback(m_stateAfterScan);
                            ReportImage("ScanCallback: 007", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEINFO.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                            return (TWAIN.STS.SUCCESS);
                        }
                    }

                    // Assume TIFF, unless we detect JPEG (JFIF)...
                    twsetupfilexfer.Format = TWAIN.TWFF.TIFF;
                    if (twimageinfo.Compression == (ushort)TWAIN.TWCP.JPEG)
                    {
                        twsetupfilexfer.Format = TWAIN.TWFF.JFIF;
                    }
                }

                // If specified, m_szImagePath wins over DAT_SETUPFILEXFER...
                string szFile = m_twsetupfilexfer.FileName.Get();
                if ((m_szImagePath != null) && (m_szImagePath != ""))
                {
                    szFile = m_szImagePath;
                }

                // Build the base...
                szFile = System.IO.Path.Combine(szFile, m_iImageXferCount.ToString("D6"));

                // Add the image transfer count and the extension...
                switch (twsetupfilexfer.Format)
                {
                    default: twsetupfilexfer.FileName.Set(szFile + ".xxx"); break;
                    case TWAIN.TWFF.BMP: twsetupfilexfer.FileName.Set(szFile + ".bmp"); break;
                    case TWAIN.TWFF.DEJAVU: twsetupfilexfer.FileName.Set(szFile + ".dejavu"); break;
                    case TWAIN.TWFF.EXIF: twsetupfilexfer.FileName.Set(szFile + ".exif"); break;
                    case TWAIN.TWFF.FPX: twsetupfilexfer.FileName.Set(szFile + ".fpx"); break;
                    case TWAIN.TWFF.JFIF: twsetupfilexfer.FileName.Set(szFile + ".jpg"); break;
                    case TWAIN.TWFF.JP2: twsetupfilexfer.FileName.Set(szFile + ".jp2"); break;
                    case TWAIN.TWFF.JPX: twsetupfilexfer.FileName.Set(szFile + ".jpx"); break;
                    case TWAIN.TWFF.PDF: twsetupfilexfer.FileName.Set(szFile + ".pdf"); break;
                    case TWAIN.TWFF.PDFA: twsetupfilexfer.FileName.Set(szFile + ".pdf"); break;
                    case TWAIN.TWFF.PICT: twsetupfilexfer.FileName.Set(szFile + ".pict"); break;
                    case TWAIN.TWFF.PNG: twsetupfilexfer.FileName.Set(szFile + ".png"); break;
                    case TWAIN.TWFF.SPIFF: twsetupfilexfer.FileName.Set(szFile + ".spiff"); break;
                    case TWAIN.TWFF.TIFF: twsetupfilexfer.FileName.Set(szFile + ".tif"); break;
                    case TWAIN.TWFF.TIFFMULTI: twsetupfilexfer.FileName.Set(szFile + ".tif"); break;
                    case TWAIN.TWFF.XBM: twsetupfilexfer.FileName.Set(szFile + ".xbm"); break;
                }

                // Setup the file transfer...
                sts = m_twain.DatSetupfilexfer(TWAIN.DG.CONTROL, TWAIN.MSG.SET, ref twsetupfilexfer);
                if (sts != TWAIN.STS.SUCCESS)
                {
                    WriteOutput("Scanning error: " + sts + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 008", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.SETUPFILEXFER.ToString(), TWAIN.MSG.SET.ToString(), CvtSts(sts), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }

                // Transfer the image...
                sts = m_twain.DatImagefilexfer(TWAIN.DG.IMAGE, TWAIN.MSG.GET);
                if (sts != TWAIN.STS.XFERDONE)
                {
                    WriteOutput("Scanning error: " + sts + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 009", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEFILEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }
                else
                {
                    try
                    {
                        szFilename = twsetupfilexfer.FileName.Get();
                        Image image = Image.FromFile(szFilename);
                        bitmap = new Bitmap(image);
                        image.Dispose();
                        image = null;
                        msgPendingxfers = ReportImage("ScanCallback: 010", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEFILEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), bitmap, szFilename, m_twain.ImageinfoToCsv(twimageinfo), null, 0);
                        bitmap = null;
                        blXferDone = true;
                    }
                    catch
                    {
                        WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 011", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEFILEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, szFilename, m_twain.ImageinfoToCsv(twimageinfo), null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }
                }
            }

            #endregion

            // Memory transfers are supported by all TWAIN drivers, and offer
            // the fastest and most efficient way of moving image data from
            // the scanner into the application.  However, the data format may
            // be nothing more than a stream of compressed raster data, which
            // is impossible to interpret until the meta-data is collected
            // using DAT_IMAGEINFO or DAT_EXTIMAGEINFO.  And under TWAIN the
            // meta-data cannot be collected until after the image data is
            // fully transferred, so under C# we have some challenges...
            //
            // And because there's a lot going on in here I've tossed in some
            // more region pragmas to break it up...
            #region TWAIN.TWSX.MEMORY
            else if (m_twsxXferMech == TWAIN.TWSX.MEMORY)
            {
                Bitmap bitmap;
                byte[] abImage = null;
                IntPtr intptrTotalAllocated;
                IntPtr intptrTotalXfer;
                IntPtr intptrOffset;
                TWAIN.TW_SETUPMEMXFER twsetupmemxfer = default(TWAIN.TW_SETUPMEMXFER);
                TWAIN.TW_IMAGEMEMXFER twimagememxfer = default(TWAIN.TW_IMAGEMEMXFER);
                TWAIN.TW_MEMORY twmemory = default(TWAIN.TW_MEMORY);
                const int iSpaceForHeader = 512;

                // Get the preferred transfer size from the driver...
                sts = m_twain.DatSetupmemxfer(TWAIN.DG.CONTROL, TWAIN.MSG.GET, ref twsetupmemxfer);
                if (sts != TWAIN.STS.SUCCESS)
                {
                    WriteOutput("Scanning error: " + sts + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 012", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.SETUPMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }

                // Allocate our unmanaged memory...
                twmemory.Flags = (uint)TWAIN.TWMF.APPOWNS | (uint)TWAIN.TWMF.POINTER;
                twmemory.Length = twsetupmemxfer.Preferred;
                twmemory.TheMem = Marshal.AllocHGlobal((int)twsetupmemxfer.Preferred);
                if (twmemory.TheMem == IntPtr.Zero)
                {
                    sts = TWAIN.STS.LOWMEMORY;
                    WriteOutput("Scanning error: " + sts + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 013", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }

                // Loop through all the strips of data, at the end of this the byte
                // array abImage has all of the data that we got from the scanner...
                #region Tranfer the image from the driver...
                intptrTotalAllocated = (IntPtr)iSpaceForHeader;
                intptrOffset = (IntPtr)iSpaceForHeader;
                intptrTotalXfer = IntPtr.Zero;
                sts = TWAIN.STS.SUCCESS;
                while (sts == TWAIN.STS.SUCCESS)
                {
                    byte[] abTmp;

                    // Append the new data to the end of the data we've transferred so far...
                    intptrOffset = (IntPtr)((int)iSpaceForHeader + (int)intptrTotalXfer);

                    // Get a strip of image data...
                    twimagememxfer.Memory.Flags = twmemory.Flags;
                    twimagememxfer.Memory.Length = twmemory.Length;
                    twimagememxfer.Memory.TheMem = twmemory.TheMem;
                    sts = m_twain.DatImagememxfer(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimagememxfer);
                    if (sts == TWAIN.STS.XFERDONE)
                    {
                        intptrTotalXfer = (IntPtr)((UInt64)intptrTotalXfer + (UInt64)twimagememxfer.BytesWritten);
                    }
                    else if (sts == TWAIN.STS.SUCCESS)
                    {
                        intptrTotalXfer = (IntPtr)((UInt64)intptrTotalXfer + (UInt64)twimagememxfer.BytesWritten);
                    }
                    else
                    {
                        WriteOutput("Scanning error: " + sts + Environment.NewLine);
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 014", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }

                    // Allocate memory for our new strip...
                    intptrTotalAllocated = (IntPtr)((UInt64)intptrTotalAllocated + (UInt64)twimagememxfer.BytesWritten);
                    abTmp = new byte[(int)intptrTotalAllocated];
                    if (abTmp == null)
                    {
                        sts = TWAIN.STS.LOWMEMORY;
                        WriteOutput("Scanning error: " + sts + Environment.NewLine);
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 015", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }

                    // Copy the existing data, if we have it...
                    if (abImage != null)
                    {
                        Buffer.BlockCopy(abImage, 0, abTmp, 0, (int)intptrTotalAllocated - (int)twimagememxfer.BytesWritten);
                    }

                    // Switch pointers...
                    abImage = abTmp;
                    abTmp = null;

                    // Copy the new strip into place...
                    Marshal.Copy(twimagememxfer.Memory.TheMem, abImage, (int)intptrOffset, (int)twimagememxfer.BytesWritten);
                }
                #endregion

                // Increment our counter...
                m_iImageCount += 1;

                // Do this if the data is JPEG compressed...
                #region JPEG Images (grayscale and color)...
                if ((TWAIN.TWCP)twimagememxfer.Compression == TWAIN.TWCP.JPEG)
                {
                    string szFile = "";

                    try
                    {
                        // Write the data to disk...
                        if ((m_szImagePath != null) && Directory.Exists(m_szImagePath))
                        {
                            // Save the image...
                            szFile = Path.Combine(m_szImagePath, "img" + m_iImageCount.ToString("D6") + ".jpg");
                            using (FileStream filestream = new FileStream(szFile, FileMode.Create, FileAccess.Write))
                            {
                                filestream.Write(abImage, iSpaceForHeader, abImage.Length - iSpaceForHeader);
                            }

                            // Show the image from disk...
                            Image image = Image.FromFile(szFile);
                            Bitmap bitmapFile = new Bitmap(image);
                            image.Dispose();
                            image = null;
                            msgPendingxfers = ReportImage("ScanCallback: 016", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.XFERDONE), bitmapFile, szFile, null, abImage, iSpaceForHeader);
                            blXferDone = true;
                        }

                        // Display the image from memory...
                        else
                        {
                            // Turn the data into a bitmap...
                            memorystream = new MemoryStream(abImage, iSpaceForHeader, abImage.Length - iSpaceForHeader);
                            Image image = Image.FromStream(memorystream);
                            bitmap = new Bitmap(image);
                            image.Dispose();
                            image = null;
                            msgPendingxfers = ReportImage("ScanCallback: 017", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), bitmap, null, null, abImage, iSpaceForHeader);
                            bitmap = null;
                            memorystream = null;
                            blXferDone = true;
                        }
                    }
                    catch
                    {
                        WriteOutput("Unable to save image to disk <" + szFile + ">" + Environment.NewLine);
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 018", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }
                }
                #endregion

                // Do this if the data is GROUP4 compressed, the way to add support for
                // this is to create a bitonal TIFF header, place it in a memory stream
                // along with the data, and then let .NET decode it...
                #region Group4 Images (black and white images)...
                else if ((TWAIN.TWCP)twimagememxfer.Compression == TWAIN.TWCP.GROUP4)
                {
                    IntPtr intptrTiff;
                    TiffBitonalG4 tiffbitonalg4;
                    string szFile = "";

                    // We need the image info for this one...
                    if (twimageinfo.BitsPerPixel == 0)
                    {
                        sts = m_twain.DatImageinfo(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimageinfo);
                        if (sts != TWAIN.STS.SUCCESS)
                        {
                            WriteOutput("ImageInfo failed: " + sts + Environment.NewLine);
                            m_twain.Rollback(m_stateAfterScan);
                            ReportImage("ScanCallback: 019", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEINFO.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                            return (TWAIN.STS.SUCCESS);
                        }
                    }

                    // Create a TIFF header...
                    tiffbitonalg4 = new TiffBitonalG4((uint)twimageinfo.ImageWidth, (uint)twimageinfo.ImageLength, (uint)twimageinfo.XResolution.Whole, (uint)intptrTotalXfer);

                    // Create memory for the TIFF header...
                    intptrTiff = Marshal.AllocHGlobal(Marshal.SizeOf(tiffbitonalg4));

                    // Copy the header into the memory...
                    Marshal.StructureToPtr(tiffbitonalg4, intptrTiff, true);

                    // Copy the memory into the byte array (we left room for it), giving us a
                    // TIFF image starting at (iSpaceForHeader - Marshal.SizeOf(tiffbitonal))
                    // in the byte array...
                    Marshal.Copy(intptrTiff, abImage, iSpaceForHeader - Marshal.SizeOf(tiffbitonalg4), Marshal.SizeOf(tiffbitonalg4));

                    // Create a TIFF image and load it...
                    try
                    {
                        bool blDeleteWhenDone = false;
                        string szImagePath = m_szImagePath;

                        // We don't have a valid user supplied folder, so use the temp
                        // directory...
                        if ((m_szImagePath == null) || !Directory.Exists(m_szImagePath))
                        {
                            blDeleteWhenDone = true;
                            szImagePath = Path.GetTempPath();
                        }

                        // Write the image to disk...
                        try
                        {
                            szFile = Path.Combine(szImagePath, "img" + m_iImageCount.ToString("D6") + ".tif");
                            using (FileStream filestream = new FileStream(szFile, FileMode.Create, FileAccess.Write))
                            {
                                filestream.Write
                                (
                                    abImage,
                                    iSpaceForHeader - Marshal.SizeOf(tiffbitonalg4),
                                    abImage.Length - (iSpaceForHeader - Marshal.SizeOf(tiffbitonalg4))
                                );
                            }
                        }
                        catch
                        {
                            WriteOutput("Unable to save image to disk <" + szFile + ">" + Environment.NewLine);
                            m_twain.Rollback(m_stateAfterScan);
                            ReportImage("ScanCallback: 020", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FILEWRITEERROR), null, szFile, null, null, 0);
                            return (TWAIN.STS.SUCCESS);
                        }

                        // Free the memory...
                        Marshal.FreeHGlobal(intptrTiff);
                        intptrTiff = IntPtr.Zero;

                        // Build the bitmap from disk (to reduce our memory footprint)...
                        Image image = Image.FromFile(szFile);
                        bitmap = new Bitmap(image);
                        image.Dispose();
                        image = null;

                        // Send it off to the application...
                        msgPendingxfers = ReportImage("ScanCallback: 021", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), bitmap, szFile, m_twain.ImageinfoToCsv(twimageinfo), abImage, iSpaceForHeader);

                        // Delete it if it's temp...
                        if (blDeleteWhenDone)
                        {
                            try
                            {
                                File.Delete(szFile);
                            }
                            catch
                            {
                                WriteOutput("Failed to delete temporary image file <" + szFile + ">" + Environment.NewLine);
                                m_twain.Rollback(m_stateAfterScan);
                                ReportImage("ScanCallback: 022", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FILEWRITEERROR), null, szFile, null, null, 0);
                                return (TWAIN.STS.SUCCESS);
                            }
                        }
                    }
                    catch
                    {
                        WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 023", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }
                }
                #endregion

                // We have no compression, we need to handle the data one raster or
                // one pixel at a time to make sure we get the alignment right, and that
                // means doing different stuff for black and white vs gray vs color...
                #region Uncompressed images (all pixel types)...
                else if ((TWAIN.TWCP)twimagememxfer.Compression == TWAIN.TWCP.NONE)
                {
                    // We need the image info for this one...
                    if (twimageinfo.BitsPerPixel == 0)
                    {
                        sts = m_twain.DatImageinfo(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimageinfo);
                        if (sts != TWAIN.STS.SUCCESS)
                        {
                            WriteOutput("ImageInfo failed: " + sts + Environment.NewLine);
                            m_twain.Rollback(m_stateAfterScan);
                            ReportImage("ScanCallback: 024", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEINFO.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                            return (TWAIN.STS.SUCCESS);
                        }
                    }

                    // Handle uncompressed bitonal images...
                    #region Handle uncompressed bitonal images...
                    if (twimageinfo.BitsPerPixel == 1)
                    {
                        try
                        {
                            IntPtr intptrTiff;
                            TiffBitonalUncompressed tiffbitonaluncompressed;
                            string szFile = "";

                            // We need the image info for this one...
                            if (twimageinfo.BitsPerPixel == 0)
                            {
                                sts = m_twain.DatImageinfo(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimageinfo);
                                if (sts != TWAIN.STS.SUCCESS)
                                {
                                    WriteOutput("ImageInfo failed: " + sts + Environment.NewLine);
                                    m_twain.Rollback(m_stateAfterScan);
                                    ReportImage("ScanCallback: 025", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                                    return (TWAIN.STS.SUCCESS);
                                }
                            }

                            // Create a TIFF header...
                            tiffbitonaluncompressed = new TiffBitonalUncompressed((uint)twimageinfo.ImageWidth, (uint)twimageinfo.ImageLength, (uint)twimageinfo.XResolution.Whole, (uint)intptrTotalXfer);

                            // Create memory for the TIFF header...
                            intptrTiff = Marshal.AllocHGlobal(Marshal.SizeOf(tiffbitonaluncompressed));

                            // Copy the header into the memory...
                            Marshal.StructureToPtr(tiffbitonaluncompressed, intptrTiff, true);

                            // Copy the memory into the byte array (we left room for it), giving us a
                            // TIFF image starting at (iSpaceForHeader - Marshal.SizeOf(tiffbitonal))
                            // in the byte array...
                            Marshal.Copy(intptrTiff, abImage, iSpaceForHeader - Marshal.SizeOf(tiffbitonaluncompressed), Marshal.SizeOf(tiffbitonaluncompressed));

                            // Create a TIFF image and load it...
                            try
                            {
                                bool blDeleteWhenDone = false;
                                string szImagePath = m_szImagePath;

                                // We don't have a valid user supplied folder, so use the temp
                                // directory...
                                if ((m_szImagePath == null) || !Directory.Exists(m_szImagePath))
                                {
                                    blDeleteWhenDone = true;
                                    szImagePath = Path.GetTempPath();
                                }

                                // Write the image to disk...
                                try
                                {
                                    szFile = Path.Combine(szImagePath, "img" + m_iImageCount.ToString("D6") + ".tif");
                                    using (FileStream filestream = new FileStream(szFile, FileMode.Create, FileAccess.Write))
                                    {
                                        filestream.Write
                                        (
                                            abImage,
                                            iSpaceForHeader - Marshal.SizeOf(tiffbitonaluncompressed),
                                            abImage.Length - (iSpaceForHeader - Marshal.SizeOf(tiffbitonaluncompressed))
                                        );
                                    }
                                }
                                catch
                                {
                                    WriteOutput("Unable to save image to disk <" + szFile + ">" + Environment.NewLine);
                                    m_twain.Rollback(m_stateAfterScan);
                                    ReportImage("ScanCallback: 026", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FILEWRITEERROR), null, szFile, null, null, 0);
                                    return (TWAIN.STS.SUCCESS);
                                }

                                // Free the memory...
                                Marshal.FreeHGlobal(intptrTiff);
                                intptrTiff = IntPtr.Zero;

                                // Build the bitmap from the file, make sure we discard the image so the file isn't locked...
                                Image image = Image.FromFile(szFile);
                                bitmap = new Bitmap(image);
                                image.Dispose();
                                image = null;

                                // Send the stuff to the application...
                                ReportImage("ScanCallback: 027", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), bitmap, szFile, m_twain.ImageinfoToCsv(twimageinfo), abImage, iSpaceForHeader);

                                // Delete it if it's temp...
                                if (blDeleteWhenDone)
                                {
                                    try
                                    {
                                        File.Delete(szFile);
                                    }
                                    catch
                                    {
                                        WriteOutput("Failed to delete temporary image file <" + szFile + ">" + Environment.NewLine);
                                        m_twain.Rollback(m_stateAfterScan);
                                        ReportImage("ScanCallback: 028", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FILEWRITEERROR), null, szFile, null, null, 0);
                                        return (TWAIN.STS.SUCCESS);
                                    }
                                }

                                // Cleanup...
                                bitmap = null;
                                memorystream = null;
                                blXferDone = true;
                            }
                            catch
                            {
                                WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                                m_twain.Rollback(m_stateAfterScan);
                                ReportImage("ScanCallback: 029", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                                return (TWAIN.STS.SUCCESS);
                            }
                        }
                        catch
                        {
                            WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                            m_twain.Rollback(m_stateAfterScan);
                            ReportImage("ScanCallback: 030", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                            return (TWAIN.STS.SUCCESS);
                        }
                    }
                    #endregion

                    // Handle uncompressed color images...
                    #region Handle uncompressed color images...
                    else if (twimageinfo.BitsPerPixel == 24)
                    {
                        try
                        {
                            IntPtr intptrTiff;
                            TiffColorUncompressed tiffcoloruncompressed;
                            string szFile = "";

                            // We need the image info for this one...
                            if (twimageinfo.BitsPerPixel == 0)
                            {
                                sts = m_twain.DatImageinfo(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimageinfo);
                                if (sts != TWAIN.STS.SUCCESS)
                                {
                                    WriteOutput("ImageInfo failed: " + sts + Environment.NewLine);
                                    m_twain.Rollback(m_stateAfterScan);
                                    ReportImage("ScanCallback: 031", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                                    return (TWAIN.STS.SUCCESS);
                                }
                            }

                            // Create a TIFF header...
                            tiffcoloruncompressed = new TiffColorUncompressed((uint)twimageinfo.ImageWidth, (uint)twimageinfo.ImageLength, (uint)twimageinfo.XResolution.Whole, (uint)intptrTotalXfer);

                            // Create memory for the TIFF header...
                            intptrTiff = Marshal.AllocHGlobal(Marshal.SizeOf(tiffcoloruncompressed));

                            // Copy the header into the memory...
                            Marshal.StructureToPtr(tiffcoloruncompressed, intptrTiff, true);

                            // Copy the memory into the byte array (we left room for it), giving us a
                            // TIFF image starting at (iSpaceForHeader - Marshal.SizeOf(tiffbitonal))
                            // in the byte array...
                            Marshal.Copy(intptrTiff, abImage, iSpaceForHeader - Marshal.SizeOf(tiffcoloruncompressed), Marshal.SizeOf(tiffcoloruncompressed));

                            // Create a TIFF image and load it...
                            try
                            {
                                bool blDeleteWhenDone = false;
                                string szImagePath = m_szImagePath;

                                // We don't have a valid user supplied folder, so use the temp
                                // directory...
                                if ((m_szImagePath == null) || !Directory.Exists(m_szImagePath))
                                {
                                    blDeleteWhenDone = true;
                                    szImagePath = Path.GetTempPath();
                                }

                                // Write the image to disk...
                                try
                                {
                                    szFile = Path.Combine(szImagePath, "img" + m_iImageCount.ToString("D6") + ".tif");
                                    using (FileStream filestream = new FileStream(szFile, FileMode.Create, FileAccess.Write))
                                    {
                                        filestream.Write
                                        (
                                            abImage,
                                            iSpaceForHeader - Marshal.SizeOf(tiffcoloruncompressed),
                                            abImage.Length - (iSpaceForHeader - Marshal.SizeOf(tiffcoloruncompressed))
                                        );
                                    }
                                }
                                catch
                                {
                                    WriteOutput("Unable to save image to disk <" + szFile + ">" + Environment.NewLine);
                                    m_twain.Rollback(m_stateAfterScan);
                                    ReportImage("ScanCallback: 032", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FILEWRITEERROR), null, szFile, null, null, 0);
                                    return (TWAIN.STS.SUCCESS);
                                }

                                // Free the memory...
                                Marshal.FreeHGlobal(intptrTiff);
                                intptrTiff = IntPtr.Zero;

                                // Build the bitmap from the file, make sure we discard the image so the file isn't locked...
                                Image image = Image.FromFile(szFile);
                                bitmap = new Bitmap(image);
                                image.Dispose();
                                image = null;

                                // Send the stuff to the application...
                                ReportImage("ScanCallback: 033", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), bitmap, szFile, m_twain.ImageinfoToCsv(twimageinfo), abImage, iSpaceForHeader);

                                // Delete it if it's temp...
                                if (blDeleteWhenDone)
                                {
                                    try
                                    {
                                        File.Delete(szFile);
                                    }
                                    catch
                                    {
                                        WriteOutput("Failed to delete temporary image file <" + szFile + ">" + Environment.NewLine);
                                        m_twain.Rollback(m_stateAfterScan);
                                        ReportImage("ScanCallback: 034", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FILEWRITEERROR), null, szFile, null, null, 0);
                                        return (TWAIN.STS.SUCCESS);
                                    }
                                }

                                // Cleanup...
                                bitmap = null;
                                memorystream = null;
                                blXferDone = true;
                            }
                            catch
                            {
                                WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                                m_twain.Rollback(m_stateAfterScan);
                                ReportImage("ScanCallback: 035", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                                return (TWAIN.STS.SUCCESS);
                            }
                        }
                        catch
                        {
                            WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                            m_twain.Rollback(m_stateAfterScan);
                            ReportImage("ScanCallback: 036", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                            return (TWAIN.STS.SUCCESS);
                        }
                    }
                    #endregion

                    // Handle uncompressed grayscale images...
                    #region Handle uncompressed grayscale images...
                    else if (twimageinfo.BitsPerPixel == 8)
                    {
                        try
                        {
                            IntPtr intptrTiff;
                            TiffGrayscaleUncompressed tiffgrayscaleuncompressed;
                            string szFile = "";

                            // We need the image info for this one...
                            if (twimageinfo.BitsPerPixel == 0)
                            {
                                sts = m_twain.DatImageinfo(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimageinfo);
                                if (sts != TWAIN.STS.SUCCESS)
                                {
                                    WriteOutput("ImageInfo failed: " + sts + Environment.NewLine);
                                    m_twain.Rollback(m_stateAfterScan);
                                    ReportImage("ScanCallback: 037", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                                    return (TWAIN.STS.SUCCESS);
                                }
                            }

                            // Create a TIFF header...
                            tiffgrayscaleuncompressed = new TiffGrayscaleUncompressed((uint)twimageinfo.ImageWidth, (uint)twimageinfo.ImageLength, (uint)twimageinfo.XResolution.Whole, (uint)intptrTotalXfer);

                            // Create memory for the TIFF header...
                            intptrTiff = Marshal.AllocHGlobal(Marshal.SizeOf(tiffgrayscaleuncompressed));

                            // Copy the header into the memory...
                            Marshal.StructureToPtr(tiffgrayscaleuncompressed, intptrTiff, true);

                            // Copy the memory into the byte array (we left room for it), giving us a
                            // TIFF image starting at (iSpaceForHeader - Marshal.SizeOf(tiffbitonal))
                            // in the byte array...
                            Marshal.Copy(intptrTiff, abImage, iSpaceForHeader - Marshal.SizeOf(tiffgrayscaleuncompressed), Marshal.SizeOf(tiffgrayscaleuncompressed));

                            // Create a TIFF image and load it...
                            try
                            {
                                bool blDeleteWhenDone = false;
                                string szImagePath = m_szImagePath;

                                // We don't have a valid user supplied folder, so use the temp
                                // directory...
                                if ((m_szImagePath == null) || !Directory.Exists(m_szImagePath))
                                {
                                    blDeleteWhenDone = true;
                                    szImagePath = Path.GetTempPath();
                                }

                                // Write the image to disk...
                                try
                                {
                                    szFile = Path.Combine(szImagePath, "img" + m_iImageCount.ToString("D6") + ".tif");
                                    using (FileStream filestream = new FileStream(szFile, FileMode.Create, FileAccess.Write))
                                    {
                                        filestream.Write
                                        (
                                            abImage,
                                            iSpaceForHeader - Marshal.SizeOf(tiffgrayscaleuncompressed),
                                            abImage.Length - (iSpaceForHeader - Marshal.SizeOf(tiffgrayscaleuncompressed))
                                        );
                                    }
                                }
                                catch
                                {
                                    WriteOutput("Unable to save image to disk <" + szFile + ">" + Environment.NewLine);
                                    m_twain.Rollback(m_stateAfterScan);
                                    ReportImage("ScanCallback: 038", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FILEWRITEERROR), null, szFile, null, null, 0);
                                    return (TWAIN.STS.SUCCESS);
                                }

                                // Free the memory...
                                Marshal.FreeHGlobal(intptrTiff);
                                intptrTiff = IntPtr.Zero;

                                // Build the bitmap from the file, make sure we discard the image so the file isn't locked...
                                Image image = Image.FromFile(szFile);
                                bitmap = new Bitmap(image);
                                image.Dispose();
                                image = null;

                                // Send the stuff to the application...
                                ReportImage("ScanCallback: 039", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), bitmap, szFile, m_twain.ImageinfoToCsv(twimageinfo), abImage, iSpaceForHeader);

                                // Delete it if it's temp...
                                if (blDeleteWhenDone)
                                {
                                    try
                                    {
                                        File.Delete(szFile);
                                    }
                                    catch
                                    {
                                        WriteOutput("Failed to delete temporary image file <" + szFile + ">" + Environment.NewLine);
                                        m_twain.Rollback(m_stateAfterScan);
                                        ReportImage("ScanCallback: 040", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FILEWRITEERROR), null, szFile, null, null, 0);
                                        return (TWAIN.STS.SUCCESS);
                                    }
                                }

                                // Cleanup...
                                bitmap = null;
                                memorystream = null;
                                blXferDone = true;
                            }
                            catch
                            {
                                WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                                m_twain.Rollback(m_stateAfterScan);
                                ReportImage("ScanCallback: 041", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                                return (TWAIN.STS.SUCCESS);
                            }
                        }
                        catch
                        {
                            WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                            m_twain.Rollback(m_stateAfterScan);
                            ReportImage("ScanCallback: 042", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                            return (TWAIN.STS.SUCCESS);
                        }
                    }
                    #endregion

                    // Uh-oh...
                    #region Uh-oh...
                    else
                    {
                        WriteOutput("Scanning error: unsupported pixeltype..." + Environment.NewLine);
                        m_iImageCount -= 1;
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 043", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }
                    #endregion
                }
                #endregion

                // Uh-oh...
                #region Uh-oh
                else
                {
                    WriteOutput("Scanning error: unsupported compression..." + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 044", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }
                #endregion
            }
            #endregion

            // Memory file transfers combine the best of file transfers and
            // memory transgers, however at this time it's not clear that any
            // TWAIN drivers have ever supported it...
            #region TWAIN.TWSX.MEMFILE
            else if (m_twsxXferMech == TWAIN.TWSX.MEMFILE)
            {
                Bitmap bitmap;
                byte[] abImage = null;
                IntPtr intptrTotalAllocated;
                IntPtr intptrTotalXfer;
                IntPtr intptrOffset;
                TWAIN.TW_SETUPMEMXFER twsetupmemxfer = default(TWAIN.TW_SETUPMEMXFER);
                TWAIN.TW_IMAGEMEMXFER twimagememxfer = default(TWAIN.TW_IMAGEMEMXFER);

                // Get the preferred transfer size from the driver...
                sts = m_twain.DatSetupmemxfer(TWAIN.DG.CONTROL, TWAIN.MSG.GET, ref twsetupmemxfer);
                if (sts != TWAIN.STS.SUCCESS)
                {
                    WriteOutput("Scanning error: " + sts + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 045", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.SETUPMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }

                // Allocate our unmanaged memory...
                twimagememxfer.Memory.Flags = (uint)TWAIN.TWMF.APPOWNS | (uint)TWAIN.TWMF.POINTER;
                twimagememxfer.Memory.Length = twsetupmemxfer.Preferred;
                twimagememxfer.Memory.TheMem = Marshal.AllocHGlobal((int)twsetupmemxfer.Preferred);
                if (twimagememxfer.Memory.TheMem == IntPtr.Zero)
                {
                    sts = TWAIN.STS.LOWMEMORY;
                    WriteOutput("Scanning error: " + sts + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 046", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMFILEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }

                // Loop through all the strips of data...
                intptrTotalAllocated = IntPtr.Zero;
                intptrTotalXfer = IntPtr.Zero;
                sts = TWAIN.STS.SUCCESS;
                while (sts == TWAIN.STS.SUCCESS)
                {
                    byte[] abTmp;

                    // Append the new data to the end of the data we've transferred so far...
                    intptrOffset = (IntPtr)intptrTotalXfer;

                    // Get a strip of image data...
                    sts = m_twain.DatImagememfilexfer(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimagememxfer);
                    if (sts == TWAIN.STS.XFERDONE)
                    {
                        intptrTotalXfer = (IntPtr)((UInt64)intptrTotalXfer + (UInt64)twimagememxfer.BytesWritten);
                    }
                    else if (sts == TWAIN.STS.SUCCESS)
                    {
                        intptrTotalXfer = (IntPtr)((UInt64)intptrTotalXfer + (UInt64)twimagememxfer.BytesWritten);
                    }
                    else
                    {
                        WriteOutput("Scanning error: " + sts + Environment.NewLine);
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 047", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }

                    // Allocate memory for our new strip...
                    intptrTotalAllocated = (IntPtr)((UInt64)intptrTotalAllocated + (UInt64)twimagememxfer.BytesWritten);
                    abTmp = new byte[(int)intptrTotalAllocated];
                    if (abTmp == null)
                    {
                        sts = TWAIN.STS.LOWMEMORY;
                        WriteOutput("Scanning error: " + sts + Environment.NewLine);
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 048", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }

                    // Copy the existing data, if we have it...
                    if (abImage != null)
                    {
                        Buffer.BlockCopy(abImage, 0, abTmp, 0, (int)intptrTotalAllocated - (int)twimagememxfer.BytesWritten);
                    }

                    // Switch pointers...
                    abImage = abTmp;
                    abTmp = null;

                    // Copy the new strip into place...
                    Marshal.Copy(twimagememxfer.Memory.TheMem, abImage, (int)intptrOffset, (int)twimagememxfer.BytesWritten);
                }

                // Okay, turn the data into a bitmap...
                memorystream = new MemoryStream(abImage);
                try
                {
                    Image image = Image.FromStream(memorystream);
                    bitmap = new Bitmap(image);
                    image.Dispose();
                    image = null;
                    msgPendingxfers = ReportImage("ScanCallback: 049", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMFILEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), bitmap, null, null, null, 0);
                    bitmap = null;
                    blXferDone = true;
                }
                catch
                {
                    WriteOutput("Scanning error: unable to load image..." + Environment.NewLine);
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 050", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMFILEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);
                }
                memorystream = null;
            }
            #endregion

            // Uh-oh...
            #region Uh-oh...
            else
            {
                WriteOutput("Scan: unrecognized ICAP_XFERMECH value..." + m_twsxXferMech + Environment.NewLine);
                m_twain.Rollback(m_stateAfterScan);
                ReportImage("ScanCallback: 051", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEMEMFILEXFER.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(TWAIN.STS.FAILURE), null, null, null, null, 0);
                return (TWAIN.STS.SUCCESS);
            }
            #endregion

            //
            // End of the image transfer section...
            //
            ///////////////////////////////////////////////////////////////////////////////

            // Give us a blank line...
            WriteOutput(Environment.NewLine);

            // If we're doing a file tranfer, then output the file we just did...
            if (szFilename != "")
            {
                WriteOutput("File: " + szFilename + Environment.NewLine);
            }

            // Let's get some meta data.  TWAIN only guarantees that this data
            // is accurate in state 7 after TWRC_XFERDONE has been received...
            if (blXferDone)
            {
                if (twimageinfo.BitsPerPixel == 0)
                {
                    twimageinfo = default(TWAIN.TW_IMAGEINFO);
                    sts = m_twain.DatImageinfo(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twimageinfo);
                    if (sts != TWAIN.STS.SUCCESS)
                    {
                        WriteOutput("ImageInfo failed: " + sts + Environment.NewLine);
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 052", TWAIN.DG.IMAGE.ToString(), TWAIN.DAT.IMAGEINFO.ToString(), TWAIN.MSG.GET.ToString(), CvtSts(sts), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }
                }
                WriteOutput("ImageInfo: " + m_twain.ImageinfoToCsv(twimageinfo) + Environment.NewLine);
            }

            // And let's get some more meta data, this time using extended image
            // info, which is a little more complex.  This is just being done to
            // show how to make the call, as a general rule an applications should
            // use one or the other, not both...
            if (blXferDone)
            {
                TWAIN.TW_EXTIMAGEINFO twextimageinfo = default(TWAIN.TW_EXTIMAGEINFO);
                TWAIN.TW_INFO twinfo = default(TWAIN.TW_INFO);
                twextimageinfo.NumInfos = 0;
                twinfo.InfoId = (ushort)TWAIN.TWEI.DOCUMENTNUMBER; twextimageinfo.Set(twextimageinfo.NumInfos++, ref twinfo);
                twinfo.InfoId = (ushort)TWAIN.TWEI.PAGESIDE; twextimageinfo.Set(twextimageinfo.NumInfos++, ref twinfo);
                sts = m_twain.DatExtimageinfo(TWAIN.DG.IMAGE, TWAIN.MSG.GET, ref twextimageinfo);
                if (sts == TWAIN.STS.SUCCESS)
                {
                    string szResult = "ExtImageInfo: ";
                    twextimageinfo.Get(0, ref twinfo);
                    if (twinfo.ReturnCode == (ushort)TWAIN.STS.SUCCESS)
                    {
                        szResult += "DocumentNumber=" + twinfo.Item + " ";
                    }
                    twextimageinfo.Get(1, ref twinfo);
                    if (twinfo.ReturnCode == (ushort)TWAIN.STS.SUCCESS)
                    {
                        szResult += "PageSide=" + "TWCS_" + (TWAIN.TWCS)twinfo.Item + " ";
                    }
                    WriteOutput(szResult + Environment.NewLine);
                }
            }

            // Increment the image transfer count...
            if (blXferDone)
            {
                m_iImageXferCount += 1;
            }

            // Tell TWAIN that we're done with this image, this is the one place
            // that we go downstate without using the Rollback function, so that
            // we can examine the TW_PENDINGXFERS structure...
            TWAIN.TW_PENDINGXFERS twpendingxfers = default(TWAIN.TW_PENDINGXFERS);
            sts = m_twain.DatPendingxfers(TWAIN.DG.CONTROL, TWAIN.MSG.ENDXFER, ref twpendingxfers);
            if (sts != TWAIN.STS.SUCCESS)
            {
                WriteOutput("Scanning error: " + sts + Environment.NewLine);
                m_twain.Rollback(m_stateAfterScan);
                ReportImage("ScanCallback: 053", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.PENDINGXFERS.ToString(), TWAIN.MSG.ENDXFER.ToString(), CvtSts(sts), null, null, null, null, 0);
                return (TWAIN.STS.SUCCESS);
            }

            // We've been asked to do extra work...
            switch (msgPendingxfers)
            {
                // No work needed here...
                default:
                case TWAINCSToolkit.MSG.ENDXFER:
                    break;

                // Reset, we're exiting from scanning...
                case TWAINCSToolkit.MSG.RESET:
                    m_twain.Rollback(m_stateAfterScan);
                    ReportImage("ScanCallback: 054", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.PENDINGXFERS.ToString(), TWAIN.MSG.RESET.ToString(), CvtSts(sts), null, null, null, null, 0);
                    return (TWAIN.STS.SUCCESS);

                // Stop the feeder...
                case TWAINCSToolkit.MSG.STOPFEEDER:
                    twpendingxfers = default(TWAIN.TW_PENDINGXFERS);
                    sts = m_twain.DatPendingxfers(TWAIN.DG.CONTROL, TWAIN.MSG.STOPFEEDER, ref twpendingxfers);
                    if (sts != TWAIN.STS.SUCCESS)
                    {
                        // If we can't stop gracefully, then just abort...
                        m_twain.Rollback(m_stateAfterScan);
                        ReportImage("ScanCallback: 055", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.PENDINGXFERS.ToString(), TWAIN.MSG.RESET.ToString(), CvtSts(sts), null, null, null, null, 0);
                        return (TWAIN.STS.SUCCESS);
                    }
                    break;
            }

            // If count goes to zero, then the session is complete, and the
            // driver goes to state 5, otherwise it goes to state 6 in
            // preperation for the next image.  We'll also return a value of
            // zero if the transfer hits an error, like a paper jam.  And then,
            // just to keep it interesting, we also need to pay attention to
            // whether or not we have a UI running.  If we don't, then state 5
            // is our target, otherwise we want to go to state 4 (programmatic
            // mode)...
            if (twpendingxfers.Count == 0)
            {
                WriteOutput(Environment.NewLine + "Scanning done: " + TWAIN.STS.SUCCESS + Environment.NewLine);

                // Any attempt to scan will look like a new session to us...
                m_blScanStart = true;

                // We saved this value for you when MSG_ENABLEDS was called, if the
                // UI is up, then goto state 5...
                m_twain.Rollback(m_stateAfterScan);
                ReportImage("ScanCallback: 056", TWAIN.DG.CONTROL.ToString(), TWAIN.DAT.PENDINGXFERS.ToString(), TWAIN.MSG.RESET.ToString(), CvtSts(sts), null, null, null, null, 0);
            }

            // All done...
            return (TWAIN.STS.SUCCESS);
        }