Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canon TWAIN scanner stuck on 'Warming up' for minutes

Tags:

c#

twain

scanning

I'm trying to interface with a TWAIN-compliant multifunction printer and scanner, a Canon Pixma MG5750, from C# using the NTwain library. I'm writing a program to scan an image into an Image object.

The scanner has to warm up before scanning the image; it displays the following popup while doing so:

enter image description here

Once this process has finished, it begins scanning the document.

While the program does work, the problem is that this warming up process sometimes takes far too long for no apparent reason, up to a few minutes. This problem never occurs when using Canon's own app, IJ Scan Utility, which uses TWAIN and displays the same dialog, but only for a few seconds.

Is there a TWAIN capability I can use to increase the speed of this warm up process? I've tried changing the ICapXResolution and ICapYResolution, but these only increase the speed of the actual scan after the warm up, not affecting the warm up itself.

My program is shown below. Note that it's a Console app, hence the use of ThreadPool.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using NTwain;
using NTwain.Data;
using System.Drawing;
using System.Threading;

namespace TwainExample
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(o => TwainWork());
            Console.ReadLine();
        }

        static void TwainWork()
        {
            var identity = TWIdentity.CreateFromAssembly(DataGroups.Image, Assembly.GetEntryAssembly());

            var twain = new TwainSession(identity);
            twain.Open();

            twain.DataTransferred += (s, e) =>
            {
                var stream = e.GetNativeImageStream();
                var image = Image.FromStream(stream);
                // Do things with the image...
            };

            var source = twain.First();
            Console.WriteLine($"Scanning from {source.Name}...");
            var openCode = source.Open();
            Console.WriteLine($"Open: {openCode}");
            source.Enable(SourceEnableMode.NoUI, false, IntPtr.Zero);
        }
    }
}

It outputs:

Scanning from Canon MG5700 series Network...
Open: Success
like image 208
Aaron Christiansen Avatar asked Mar 30 '18 14:03

Aaron Christiansen


2 Answers

I built a web service for a scanner a while ago (also with TWAIN) hosted in a console app with OWIN. I suppose by coincidence it never occurred to me to take the same approach as you, because I just built it as a regular web app and when I noticed a similar problem, I found some examples that locked the scanning process itself to a single thread.

Basically, I don't think you need the [STAThread] attribute or the ThreadPool for that matter. Well, you can keep the ThreadPool if you want your console app to remain responsive.

You also need the device ID to fetch the correct data source if I recall correctly. I've adapted some of my code (there were a lot more parts involved related to setting the scan profile, this is now very naively inlined), try this (with or without ThreadPool):

class Program
{
    static void Main(string[] args)
    {
        var scanner = new TwainScanner();
        scanner.Scan("your device id");
        Console.ReadLine();
    }
}

public sealed class CustomTwainSession : TwainSession
{
    private Exception _error;
    private bool _cancel;
    private ReturnCode _returnCode;
    private DataSource _dataSource;
    private Bitmap _image;

    public Exception Error => _error;
    public bool IsSuccess => _error == null && _returnCode == ReturnCode.Success;
    public Bitmap Bitmap => _image;

    static CustomTwainSession()
    {
        PlatformInfo.Current.PreferNewDSM = false;
    }

    public CustomTwainSession(): base(TwainScanner.TwainAppId)
    {
        _cancel = false;

        TransferReady += OnTransferReady;
        DataTransferred += OnDataTransferred;
        TransferError += OnTransferError;
    }

    public void Start(string deviceId)
    {
        try
        {
            _returnCode = Open();
            if (_returnCode == ReturnCode.Success)
            {

                _dataSource = this.FirstOrDefault(x => x.Name == deviceId);
                if (_dataSource != null)
                {
                    _returnCode = _dataSource.Open();
                    if (_returnCode == ReturnCode.Success)
                    {
                        _returnCode = _dataSource.Enable(SourceEnableMode.NoUI, false, IntPtr.Zero);
                    }
                }
                else
                {
                    throw new Exception($"Device {deviceId} not found.");
                }
            }

        }
        catch (Exception ex)
        {
            _error = ex;
        }

        if (_dataSource != null && IsSourceOpen)
        {
            _dataSource.Close();
        }
        if (IsDsmOpen)
        {
            Close();
        }
    }

    private void OnTransferReady(object sender, TransferReadyEventArgs e)
    {
        _dataSource.Capabilities.CapFeederEnabled.SetValue(BoolType.False);
        _dataSource.Capabilities.CapDuplexEnabled.SetValue(BoolType.False);
        _dataSource.Capabilities.ICapPixelType.SetValue(PixelType.RGB);
        _dataSource.Capabilities.ICapUnits.SetValue(Unit.Inches);
        TWImageLayout imageLayout;
        _dataSource.DGImage.ImageLayout.Get(out imageLayout);
        imageLayout.Frame = new TWFrame
        {
            Left = 0,
            Right = 210 * 0.393701f,
            Top = 0,
            Bottom = 297 * 0.393701f
        };
        _dataSource.DGImage.ImageLayout.Set(imageLayout);
        _dataSource.Capabilities.ICapXResolution.SetValue(150);
        _dataSource.Capabilities.ICapYResolution.SetValue(150);

        if (_cancel)
        {
            e.CancelAll = true;
        }
    }

    private void OnDataTransferred(object sender, DataTransferredEventArgs e)
    {
        using (var output = Image.FromStream(e.GetNativeImageStream()))
        {
            _image = new Bitmap(output);
        }
    }

    private void OnTransferError(object sender, TransferErrorEventArgs e)
    {
        _error = e.Exception;
        _cancel = true;
    }

    public void Dispose()
    {
        _image.Dispose();
    }
}

public sealed class TwainScanner
{
    public static TWIdentity TwainAppId { get; }
    private static CustomTwainSession Session { get; set; }
    static volatile object _locker = new object();

    static TwainScanner()
    {
        TwainAppId = TWIdentity.CreateFromAssembly(DataGroups.Image | DataGroups.Control, Assembly.GetEntryAssembly());
    }

    public Bitmap Scan(string deviceId)
    {
        bool lockWasTaken = false;
        try
        {
            if (Monitor.TryEnter(_locker))
            {
                lockWasTaken = true;

                if (Session != null)
                {
                    Session.Dispose();
                    Session = null;
                }

                Session = new CustomTwainSession();

                Session.Start(deviceId);

                return Session.Bitmap;
            }
            else
            {
                return null;
            }
        }
        finally
        {
            if (lockWasTaken)
            {
                Monitor.Exit(_locker);
            }
        }
    }

}

As I may have eluded, this was some years ago and I did not know all that much about threading at the time. I would probably do it differently but right now I cannot test it with an actual scanner. This is just the locking mechanism I had around my scanning logic and I know it solved the unresponsiveness issue.

Others feel free to shoot holes in this particular approach; I know it's not ideal :)

like image 143
Fred Kleuver Avatar answered Oct 08 '22 03:10

Fred Kleuver


This was much simpler to fix than I thought it would be!

My Canon multifunction exposes two devices to Windows. On my machine, they are:

  • Canon MG5700 series Network
  • WIA-MG5700 series _C6CA27000000

Use the WIA device, not the Canon one. The WIA device warms up almost instantly!

The code posted by Fred Kleuver works beautifully for scanning, so long as you use the WIA device; it appears to crash when using the Canon one.

like image 21
Aaron Christiansen Avatar answered Oct 08 '22 01:10

Aaron Christiansen