扫描仪对接(C#)

2022-09-19 15:07:51 浏览数 (1)

前言

对接扫描仪的几种方式:

  1. TWAIN。此为大多数扫描仪基础协议。是C 语言写的底层dll,对.NET来说通过DLLImport来扩展使用。 此协议是很底层的协议,并没有经过.NET封装。所以要了解其机制才能更好的来开发。 .NET 例子: http://www.codeproject.com/Articles/1376/NET-TWAIN-image-scanner http://www.codeproject.com/Articles/171666/Twain-for-WPF-Applications-Look-Ma-No-Handles
  2. WIA。假如您的机器支持这个协议。那就可以好好开心一下了。.NET已经封装了支持该协议的dll。 在COM里导入Windows.Image.Acquire.dll 使用其方法就可以了。一般最近年头的扫描仪都支持这个协议。 相关文章: https://blog.csdn.net/younghaiqing/article/details/78431454 https://www.likecs.com/show-503057.html
  3. 假如都不支持! 经过研究发现所有的扫描仪自带的驱动程序及其扩展程序都会有扫描完成后触发一个程序的功能(比如扫描完成后打开image程序来进行浏览)此处就要找到其配置文件。一般都在appdata,rooming中等。可以关键字来进行搜索。 找到后更改配置用compare(推荐TortoiseMerge) 来进行前后对比,发现相应修改的地方。之后就是启动自己程序的时候把该文件修改了,换成触发自己程序的路径,这样每次扫描到图片都会触发自己写的程序并传入图片的args。 关闭时候再修复就好了。

对接Twain协议

添加引用

Nuget 添加依赖 NTwain

https://github.com/soukoku/ntwain

初始化

代码语言:javascript复制
private TwainSession session = null;

//所有的扫描仪
private List<DataSource> sourceList = new List<DataSource>();

//当前使用的扫描仪
private DataSource source_current = null;

private void initScanner()
{
  //Allow old Device DSM drives
  PlatformInfo.Current.PreferNewDSM = false;
  //开始扫描
  var appId = TWIdentity.CreateFromAssembly(DataGroups.Image, Assembly.GetExecutingAssembly());
  session = new TwainSession(appId);
  session.TransferReady  = Session_TransferReady;
  session.DataTransferred  = Session_DataTransferred;
  if (session.Open() != ReturnCode.Success)
  {
    Console.WriteLine("扫描启动失败");
  }

  var source_list = session.GetSources();
  foreach (DataSource source in source_list)
  {
    sourceList.Add(source);
  }
}

private void Session_TransferReady(object sender, TransferReadyEventArgs e)
{
  Console.WriteLine("Session_TransferReady");
}

private void Session_DataTransferred(object sender, DataTransferredEventArgs e)
{
  //ImageSource img = GenerateThumbnail(e);
  Console.WriteLine(GenerateFile(e));
}

DataSource可用以下的属性

  • int Id 如:349 每次重启应用的时候Id会变。
  • string Name 如:Canon DR-M160 TWAIN
  • string Manufacturer 如:Copyright CANON ELECTRONICS INC.
  • string ProductFamily 如:Canon DR-M160 Driver
  • bool IsOpen 如:False

对于双面扫描我们可以通过下面的方式来判断

每一页扫描都会先触发一次Session_TransferReady,之后触发两次Session_DataTransferred,从而来判定一个页面的两面。

扫描

代码语言:javascript复制
private void Scanner_btn_Click(object sender, System.Windows.RoutedEventArgs e)
{
  if (source_current == null)
  {
    source_current = sourceList[0];
  }
  if (!source_current.IsOpen)
  {
    source_current.Open();
  }
  try
  {
    session.CurrentSource.Enable(SourceEnableMode.ShowUI, false, new WindowInteropHelper(this).Handle);
  }
  catch (Exception err)
  {
    System.Windows.MessageBox.Show(err.ToString());
  }
}

其中第一个参数可以传如下的值

  • SourceEnableMode.ShowUI 显示扫描仪配置页面,并且能进行扫描。
  • SourceEnableMode.NoUI 直接扫描不显示配置页面。
  • SourceEnableMode.ShowUIOnly 只显示配置页面,不能扫描,一般和上面的配合使用。

图片处理

Session_DataTransferred回调中我们可以处理图片

获取扫描图片的地址

代码语言:javascript复制
private string GenerateFile(DataTransferredEventArgs e)
{
  string imagepath = "";

  switch (e.TransferType)
  {
    case XferMech.Native:
      using (System.IO.Stream stream = e.GetNativeImageStream())
      {
        if (stream != null)
        {
          string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
          var dirPath = Path.Combine(docPath, "scanner_pic");
          DirectoryInfo directoryInfo = new DirectoryInfo(dirPath);
          if (!directoryInfo.Exists)
          {
            System.IO.Directory.CreateDirectory(dirPath);
          }
          string timestr = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-ffff");
          imagepath = Path.Combine(dirPath, timestr   ".jpg");
          using (var fileStream = File.Create(imagepath))
          {
            stream.CopyTo(fileStream);
          }
        }
      }
      break;

    case XferMech.File:
      imagepath = e.FileDataPath;
      break;
  }
  return imagepath;
}

获取扫描的缩略图

代码语言:javascript复制
private ImageSource GenerateThumbnail(DataTransferredEventArgs e)
{
  BitmapSource img = null;

  switch (e.TransferType)
  {
    case XferMech.Native:
      using (System.IO.Stream stream = e.GetNativeImageStream())
      {
        if (stream != null)
        {
          img = stream.ConvertToWpfBitmap(300, 0);
        }
      }
      break;

    case XferMech.File:
      img = new BitmapImage(new Uri(e.FileDataPath));
      if (img.CanFreeze)
      {
        img.Freeze();
      }

      break;
  }
  return img;
}

Twain方式2

上面的方式在部分扫描以上 扫描弹出设置的时候会报错,所以有找的开源的代码:

源代码地址:http://www.codeproject.com/Articles/171666/Twain-for-WPF-Applications-Look-Ma-No-Handles

TwainDefs.cs

代码语言:javascript复制
using System;
using System.Runtime.InteropServices;

namespace ztwain
{
    public class TwProtocol
    {                                   // TWON_PROTOCOL...
        public const short Major = 1;
        public const short Minor = 9;
    }

    [Flags]
    internal enum TwDG : short
    {                                   // DG_.....
        Control = 0x0001,
        Image = 0x0002,
        Audio = 0x0004
    }

    internal enum TwDAT : short
    {                                   // DAT_....
        Null = 0x0000,
        Capability = 0x0001,
        Event = 0x0002,
        Identity = 0x0003,
        Parent = 0x0004,
        PendingXfers = 0x0005,
        SetupMemXfer = 0x0006,
        SetupFileXfer = 0x0007,
        Status = 0x0008,
        UserInterface = 0x0009,
        XferGroup = 0x000a,
        TwunkIdentity = 0x000b,
        CustomDSData = 0x000c,
        DeviceEvent = 0x000d,
        FileSystem = 0x000e,
        PassThru = 0x000f,

        ImageInfo = 0x0101,
        ImageLayout = 0x0102,
        ImageMemXfer = 0x0103,
        ImageNativeXfer = 0x0104,
        ImageFileXfer = 0x0105,
        CieColor = 0x0106,
        GrayResponse = 0x0107,
        RGBResponse = 0x0108,
        JpegCompression = 0x0109,
        Palette8 = 0x010a,
        ExtImageInfo = 0x010b,

        SetupFileXfer2 = 0x0301
    }

    internal enum TwMSG : short
    {                                   // MSG_.....
        Null = 0x0000,
        Get = 0x0001,
        GetCurrent = 0x0002,
        GetDefault = 0x0003,
        GetFirst = 0x0004,
        GetNext = 0x0005,
        Set = 0x0006,
        Reset = 0x0007,
        QuerySupport = 0x0008,

        XFerReady = 0x0101,
        CloseDSReq = 0x0102,
        CloseDSOK = 0x0103,
        DeviceEvent = 0x0104,

        CheckStatus = 0x0201,

        OpenDSM = 0x0301,
        CloseDSM = 0x0302,

        OpenDS = 0x0401,
        CloseDS = 0x0402,
        UserSelect = 0x0403,

        DisableDS = 0x0501,
        EnableDS = 0x0502,
        EnableDSUIOnly = 0x0503,

        ProcessEvent = 0x0601,

        EndXfer = 0x0701,
        StopFeeder = 0x0702,

        ChangeDirectory = 0x0801,
        CreateDirectory = 0x0802,
        Delete = 0x0803,
        FormatMedia = 0x0804,
        GetClose = 0x0805,
        GetFirstFile = 0x0806,
        GetInfo = 0x0807,
        GetNextFile = 0x0808,
        Rename = 0x0809,
        Copy = 0x080A,
        AutoCaptureDir = 0x080B,

        PassThru = 0x0901
    }

    internal enum TwRC : short
    {                                   // TWRC_....
        Success = 0x0000,
        Failure = 0x0001,
        CheckStatus = 0x0002,
        Cancel = 0x0003,
        DSEvent = 0x0004,
        NotDSEvent = 0x0005,
        XferDone = 0x0006,
        EndOfList = 0x0007,
        InfoNotSupported = 0x0008,
        DataNotAvailable = 0x0009
    }

    internal enum TwCC : short
    {                                   // TWCC_....
        Success = 0x0000,
        Bummer = 0x0001,
        LowMemory = 0x0002,
        NoDS = 0x0003,
        MaxConnections = 0x0004,
        OperationError = 0x0005,
        BadCap = 0x0006,
        BadProtocol = 0x0009,
        BadValue = 0x000a,
        SeqError = 0x000b,
        BadDest = 0x000c,
        CapUnsupported = 0x000d,
        CapBadOperation = 0x000e,
        CapSeqError = 0x000f,
        Denied = 0x0010,
        FileExists = 0x0011,
        FileNotFound = 0x0012,
        NotEmpty = 0x0013,
        PaperJam = 0x0014,
        PaperDoubleFeed = 0x0015,
        FileWriteError = 0x0016,
        CheckDeviceOnline = 0x0017
    }

    internal enum TwOn : short
    {                                   // TWON_....
        Array = 0x0003,
        Enum = 0x0004,
        One = 0x0005,
        Range = 0x0006,
        DontCare = -1
    }

    internal enum TwType : short
    {                                   // TWTY_....
        Int8 = 0x0000,
        Int16 = 0x0001,
        Int32 = 0x0002,
        UInt8 = 0x0003,
        UInt16 = 0x0004,
        UInt32 = 0x0005,
        Bool = 0x0006,
        Fix32 = 0x0007,
        Frame = 0x0008,
        Str32 = 0x0009,
        Str64 = 0x000a,
        Str128 = 0x000b,
        Str255 = 0x000c,
        Str1024 = 0x000d,
        Str512 = 0x000e
    }

    internal enum TwCap : int
    {
        XferCount = 0x0001,         // CAP_XFERCOUNT
        ICompression = 0x0100,          // ICAP_...
        IPixelType = 0x0101,
        IUnits = 0x0102,
        IXferMech = 0x0103,
        CAP_DUPLEXENABLED = 0x1013,         //单双面		  类型TwType.Bool
        ICAP_AUTOMATICDESKEW = 0x1151,     //歪斜校正		  类型TwType.Bool
        CCAP_CEI_BLANKSKIP = 0x8001,      //跳过空白页	  类型TwType.Int32
        CCAP_CEI_CROPDETECT = 0x8000   37 //消除黑框
    }

    // ------------------- STRUCTS --------------------------------------------

    [StructLayout(LayoutKind.Sequential, Pack = 2, CharSet = CharSet.Ansi)]
    internal class TwIdentity
    {                                   // TW_IDENTITY
        public IntPtr Id;
        public TwVersion Version;
        public short ProtocolMajor;
        public short ProtocolMinor;
        public int SupportedGroups;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)]
        public string Manufacturer;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)]
        public string ProductFamily;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)]
        public string ProductName;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2, CharSet = CharSet.Ansi)]
    internal struct TwVersion
    {                                   // TW_VERSION
        public short MajorNum;
        public short MinorNum;
        public short Language;
        public short Country;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)]
        public string Info;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    internal class TwUserInterface
    {                                   // TW_USERINTERFACE
        public short ShowUI;                // bool is strictly 32 bit, so use short
        public short ModalUI;
        public IntPtr ParentHand;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    internal class TwStatus
    {                                   // TW_STATUS
        public short ConditionCode;     // TwCC
        public short Reserved;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    internal struct TwEvent
    {                                   // TW_EVENT
        public IntPtr EventPtr;
        public short Message;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    internal class TwImageInfo
    {                                   // TW_IMAGEINFO
        public int XResolution;
        public int YResolution;
        public int ImageWidth;
        public int ImageLength;
        public short SamplesPerPixel;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public short[] BitsPerSample;

        public short BitsPerPixel;
        public short Planar;
        public short PixelType;
        public short Compression;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    internal class TwPendingXfers
    {                                   // TW_PENDINGXFERS
        public short Count;
        public int EOJ;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    internal struct TwFix32
    {                                               // TW_FIX32
        public short Whole;
        public ushort Frac;

        public float ToFloat()
        {
            return (float)Whole   ((float)Frac / 65536.0f);
        }

        public void FromFloat(float f)
        {
            int i = (int)((f * 65536.0f)   0.5f);
            Whole = (short)(i >> 16);
            Frac = (ushort)(i & 0x0000ffff);
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    internal class TwCapability
    {                                   // TW_CAPABILITY
        public TwCapability(TwCap cap)
        {
            Cap = (short)cap;
            ConType = -1;
        }

        public TwCapability(TwCap cap, short sval)
        {
            Cap = (short)cap;
            ConType = (short)TwOn.One;
            Handle = TwainWin32.GlobalAlloc(0x42, 6);
            IntPtr pv = TwainWin32.GlobalLock(Handle);
            Marshal.WriteInt16(pv, 0, (short)TwType.Int16);
            Marshal.WriteInt32(pv, 2, (int)sval);
            TwainWin32.GlobalUnlock(Handle);
        }

        ~TwCapability()
        {
            if (Handle != IntPtr.Zero)
                TwainWin32.GlobalFree(Handle);
        }

        public short Cap;
        public short ConType;
        public IntPtr Handle;
    }
} // namespace TwainLib

TwainLib.cs

代码语言:javascript复制
using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ztwain
{
  public enum TwainCommand
  {
    Not = -1,
    Null = 0,
    TransferReady = 1,
    CloseRequest = 2,
    CloseOk = 3,
    DeviceEvent = 4
  }

  public delegate void ProductNameHandler(string ProductName);

  public class Twain
  {
    private const short CountryUSA = 1;
    private const short LanguageUSA = 13;
    private ProductNameHandler productNameHandler;

    public Twain(ProductNameHandler handler)
    {
      appid = new TwIdentity();
      appid.Id = IntPtr.Zero;
      appid.Version.MajorNum = 1;
      appid.Version.MinorNum = 1;
      appid.Version.Language = LanguageUSA;
      appid.Version.Country = CountryUSA;
      appid.Version.Info = "Hack 1";
      appid.ProtocolMajor = TwProtocol.Major;
      appid.ProtocolMinor = TwProtocol.Minor;
      appid.SupportedGroups = (int)(TwDG.Image | TwDG.Control);
      appid.Manufacturer = "NETMaster";
      appid.ProductFamily = "Freeware";
      appid.ProductName = "Hack";

      srcds = new TwIdentity();
      srcds.Id = IntPtr.Zero;

      evtmsg.EventPtr = Marshal.AllocHGlobal(Marshal.SizeOf(winmsg));
      productNameHandler = handler;
    }

    ~Twain()
    {
      Marshal.FreeHGlobal(evtmsg.EventPtr);
    }

    public void Init(IntPtr hwndp)
    {
      Finish();
      TwRC rc = DSMparent(appid, IntPtr.Zero, TwDG.Control, TwDAT.Parent, TwMSG.OpenDSM, ref hwndp);
      if (rc == TwRC.Success)
      {
        rc = DSMident(appid, IntPtr.Zero, TwDG.Control, TwDAT.Identity, TwMSG.GetDefault, srcds);
        if (rc == TwRC.Success)
          hwnd = hwndp;
        else
          DSMparent(appid, IntPtr.Zero, TwDG.Control, TwDAT.Parent, TwMSG.CloseDSM, ref hwndp);
      }
      if (productNameHandler != null)
      {
        productNameHandler(srcds.ProductName);
      }
    }

    public void Select()
    {
      CloseSrc();
      if (appid.Id == IntPtr.Zero)
      {
        Init(hwnd);
        if (appid.Id == IntPtr.Zero)
          return;
      }
      DSMident(appid, IntPtr.Zero, TwDG.Control, TwDAT.Identity, TwMSG.UserSelect, srcds);
      if (productNameHandler != null)
      {
        productNameHandler(srcds.ProductName);
      }
    }

    public void Acquire()
    {
      Acquire(true, -1);
    }

    public void Acquire(bool showUI, short num)
    {
      TwRC rc;
      CloseSrc();
      if (appid.Id == IntPtr.Zero)
      {
        Init(hwnd);
        if (appid.Id == IntPtr.Zero)
          return;
      }
      rc = DSMident(appid, IntPtr.Zero, TwDG.Control, TwDAT.Identity, TwMSG.OpenDS, srcds);
      if (rc != TwRC.Success)
        return;

      TwCapability cap = new TwCapability(TwCap.XferCount, num);
      rc = DScap(appid, srcds, TwDG.Control, TwDAT.Capability, TwMSG.Set, cap);

      //跳过空白页
      TwCapability cap1 = new TwCapability(TwCap.CCAP_CEI_BLANKSKIP, 0);  //1为跳过空白页
      rc = DScap(appid, srcds, TwDG.Control, TwDAT.Capability, TwMSG.Set, cap1);

      //单面,双面
      TwCapability cap2 = new TwCapability(TwCap.CAP_DUPLEXENABLED, 1);   //0单面,1双面
      rc = DScap(appid, srcds, TwDG.Control, TwDAT.Capability, TwMSG.Set, cap2);

      //歪斜校正
      TwCapability cap3 = new TwCapability(TwCap.ICAP_AUTOMATICDESKEW, 1);
      rc = DScap(appid, srcds, TwDG.Control, TwDAT.Capability, TwMSG.Set, cap3);

      //歪斜校正
      TwCapability cap4 = new TwCapability(TwCap.CCAP_CEI_CROPDETECT, 1);
      rc = DScap(appid, srcds, TwDG.Control, TwDAT.Capability, TwMSG.Set, cap4);

      if (rc != TwRC.Success)
      {
        CloseSrc();
        return;
      }

      TwUserInterface guif = new TwUserInterface();
      guif.ShowUI = (short)(showUI ? 1 : 0);
      guif.ModalUI = 1;
      guif.ParentHand = hwnd;
      rc = DSuserif(appid, srcds, TwDG.Control, TwDAT.UserInterface, TwMSG.EnableDS, guif);
      if (rc != TwRC.Success)
      {
        CloseSrc();
        return;
      }
    }

    public void TransferPictures(Action<IntPtr> picAction)
    {
      if (srcds.Id == IntPtr.Zero)
        return;

      TwRC rc;
      IntPtr hbitmap = IntPtr.Zero;
      TwPendingXfers pxfr = new TwPendingXfers();

      do
      {
        pxfr.Count = 0;
        hbitmap = IntPtr.Zero;

        TwImageInfo iinf = new TwImageInfo();
        rc = DSiinf(appid, srcds, TwDG.Image, TwDAT.ImageInfo, TwMSG.Get, iinf);
        if (rc != TwRC.Success)
        {
          CloseSrc();
          return;
        }

        rc = DSixfer(appid, srcds, TwDG.Image, TwDAT.ImageNativeXfer, TwMSG.Get, ref hbitmap);
        if (rc != TwRC.XferDone)
        {
          CloseSrc();
          return;
        }

        rc = DSpxfer(appid, srcds, TwDG.Control, TwDAT.PendingXfers, TwMSG.EndXfer, pxfr);
        if (rc != TwRC.Success)
        {
          CloseSrc();
          return;
        }

        picAction(hbitmap);
      }
      while (pxfr.Count != 0);

      rc = DSpxfer(appid, srcds, TwDG.Control, TwDAT.PendingXfers, TwMSG.Reset, pxfr);
      return;
    }

    public TwainCommand PassMessage(ref Message m)
    {
      if (srcds.Id == IntPtr.Zero)
        return TwainCommand.Not;

      int pos = TwainWin32.GetMessagePos();

      winmsg.hwnd = m.HWnd;
      winmsg.message = m.Msg;
      winmsg.wParam = m.WParam;
      winmsg.lParam = m.LParam;
      winmsg.time = TwainWin32.GetMessageTime();
      winmsg.x = (short)pos;
      winmsg.y = (short)(pos >> 16);

      Marshal.StructureToPtr(winmsg, evtmsg.EventPtr, false);
      evtmsg.Message = 0;
      TwRC rc = DSevent(appid, srcds, TwDG.Control, TwDAT.Event, TwMSG.ProcessEvent, ref evtmsg);
      if (rc == TwRC.NotDSEvent)
        return TwainCommand.Not;
      if (evtmsg.Message == (short)TwMSG.XFerReady)
        return TwainCommand.TransferReady;
      if (evtmsg.Message == (short)TwMSG.CloseDSReq)
        return TwainCommand.CloseRequest;
      if (evtmsg.Message == (short)TwMSG.CloseDSOK)
        return TwainCommand.CloseOk;
      if (evtmsg.Message == (short)TwMSG.DeviceEvent)
        return TwainCommand.DeviceEvent;

      return TwainCommand.Null;
    }

    public void CloseSrc()
    {
      if (srcds.Id != IntPtr.Zero)
      {
        TwUserInterface guif = new TwUserInterface();
        DSuserif(appid, srcds, TwDG.Control, TwDAT.UserInterface, TwMSG.DisableDS, guif);
        DSMident(appid, IntPtr.Zero, TwDG.Control, TwDAT.Identity, TwMSG.CloseDS, srcds);
      }
    }

    public void Finish()
    {
      TwRC rc;
      CloseSrc();
      if (appid.Id != IntPtr.Zero)
        rc = DSMparent(appid, IntPtr.Zero, TwDG.Control, TwDAT.Parent, TwMSG.CloseDSM, ref hwnd);
      appid.Id = IntPtr.Zero;
    }

    private IntPtr hwnd;
    private TwIdentity appid;
    private TwIdentity srcds;
    private TwEvent evtmsg;
    private WINMSG winmsg;

    // ------ DSM entry point DAT_ variants:
    [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);

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

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

    // ------ DSM entry point DAT_ variants to DS:
    [DllImport("twain_32.dll", EntryPoint = "#1")]
    private static extern TwRC DSuserif([In, Out] TwIdentity origin, [In, Out] TwIdentity dest, TwDG dg, TwDAT dat, TwMSG msg, TwUserInterface guif);

    [DllImport("twain_32.dll", EntryPoint = "#1")]
    private static extern TwRC DSevent([In, Out] TwIdentity origin, [In, Out] TwIdentity dest, TwDG dg, TwDAT dat, TwMSG msg, ref TwEvent evt);

    [DllImport("twain_32.dll", EntryPoint = "#1")]
    private static extern TwRC DSstatus([In, Out] TwIdentity origin, [In] TwIdentity dest, TwDG dg, TwDAT dat, TwMSG msg, [In, Out] TwStatus dsmstat);

    [DllImport("twain_32.dll", EntryPoint = "#1")]
    private static extern TwRC DScap([In, Out] TwIdentity origin, [In] TwIdentity dest, TwDG dg, TwDAT dat, TwMSG msg, [In, Out] TwCapability capa);

    [DllImport("twain_32.dll", EntryPoint = "#1")]
    private static extern TwRC DSiinf([In, Out] TwIdentity origin, [In] TwIdentity dest, TwDG dg, TwDAT dat, TwMSG msg, [In, Out] TwImageInfo imginf);

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

    [DllImport("twain_32.dll", EntryPoint = "#1")]
    private static extern TwRC DSpxfer([In, Out] TwIdentity origin, [In] TwIdentity dest, TwDG dg, TwDAT dat, TwMSG msg, [In, Out] TwPendingXfers pxfr);

    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    internal struct WINMSG
    {
      public IntPtr hwnd;
      public int message;
      public IntPtr wParam;
      public IntPtr lParam;
      public int time;
      public int x;
      public int y;
    }
  } // class Twain
}

TwainWin32.cs

代码语言:javascript复制
using System;
using System.Runtime.InteropServices;

namespace ztwain
{
  /// <summary>
  /// Imports from Windows32
  /// </summary>
  public static class TwainWin32
  {
    [DllImport("kernel32.dll", ExactSpelling = true)]
    public static extern IntPtr GlobalLock(IntPtr handle);

    [DllImport("kernel32.dll", ExactSpelling = true)]
    public static extern IntPtr GlobalUnlock(IntPtr handle);

    [DllImport("kernel32.dll", ExactSpelling = true)]
    public static extern IntPtr GlobalAlloc(int flags, int size);

    [DllImport("kernel32.dll", ExactSpelling = true)]
    public static extern IntPtr GlobalFree(IntPtr handle);

    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam);

    [DllImport("user32")]
    public static extern uint RegisterWindowMessage(string lpString);

    [DllImport("user32")]
    public static extern bool SendMessage(IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam);

    [DllImport("user32.dll")]
    public static extern int FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", ExactSpelling = true)]
    public static extern int GetMessagePos();

    [DllImport("user32.dll", ExactSpelling = true)]
    public static extern int GetMessageTime();

    [DllImport("gdi32.dll", ExactSpelling = true)]
    public static extern int GetDeviceCaps(IntPtr hDC, int nIndex);

    [DllImport("gdi32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr CreateDC(string szdriver, string szdevice, string szoutput, IntPtr devmode);

    [DllImport("gdi32.dll", ExactSpelling = true)]
    public static extern bool DeleteDC(IntPtr hdc);

    [StructLayout(LayoutKind.Sequential, Pack = 2)]
    public class BITMAPINFOHEADER
    {
      public uint biSize;
      public int biWidth;
      public int biHeight;
      public ushort biPlanes;
      public ushort biBitCount;
      public uint biCompression;
      public uint biSizeImage;
      public int biXPelsPerMeter;
      public int biYPelsPerMeter;
      public uint biClrUsed;
      public uint biClrImportant;
    }
  }
  }

TwainDibToBitmap.cs

代码语言:javascript复制
using System;
using System.Runtime.InteropServices;
using System.Windows.Media.Imaging;
using System.Windows.Media;

namespace ztwain
{
  internal static class TwainDibToBitmap
  {
    /// <summary>
    /// Get managed BitmapSource from a DIB provided as a low level windows hadle
    ///
    /// Notes:
    /// Data is copied from the source so the windows handle can be saftely discarded
    /// even when the BitmapSource is in use.
    ///
    /// Only a subset of possible DIB forrmats is supported.
    ///
    /// </summary>
    /// <param name="dibHandle"></param>
    /// <returns>A copy of the image in a managed BitmapSource </returns>
    ///
    public static BitmapSource FormHDib(IntPtr dibHandle)
    {
      BitmapSource bs = null;
      IntPtr bmpPtr = IntPtr.Zero;
      bool flip = true; // vertivcally flip the image

      try
      {
        bmpPtr = TwainWin32.GlobalLock(dibHandle);
        TwainWin32.BITMAPINFOHEADER bmi = new TwainWin32.BITMAPINFOHEADER();
        Marshal.PtrToStructure(bmpPtr, bmi);

        if (bmi.biSizeImage == 0)
          bmi.biSizeImage = (uint)(((((bmi.biWidth * bmi.biBitCount)   31) & ~31) >> 3) * bmi.biHeight);

        int palettSize = 0;

        if (bmi.biClrUsed != 0)
          throw new NotSupportedException("DibToBitmap: DIB with pallet is not supported");

        // pointer to the beginning of the bitmap bits
        IntPtr pixptr = (IntPtr)((int)bmpPtr   bmi.biSize   palettSize);

        // Define parameters used to create the BitmapSource.
        PixelFormat pf = PixelFormats.Default;
        switch (bmi.biBitCount)
        {
          case 32:
            pf = PixelFormats.Bgr32;
            break;

          case 24:
            pf = PixelFormats.Bgr24;
            break;

          case 8:
            pf = PixelFormats.Gray8;
            break;

          case 1:
            pf = PixelFormats.BlackWhite;
            break;

          default:   // not supported
            throw new NotSupportedException("DibToBitmap: Can't determine picture format (biBitCount="   bmi.biBitCount   ")");
            // break;
        }
        int width = bmi.biWidth;
        int height = bmi.biHeight;
        int stride = (int)(bmi.biSizeImage / height);
        byte[] imageBytes = new byte[stride * height];

        //Debug: Initialize the image with random data.
        //Random value = new Random();
        //value.NextBytes(rawImage);
        if (flip)
        {
          for (int i = 0, j = 0, k = (height - 1) * stride; i < height; i  , j  = stride, k -= stride)
            Marshal.Copy(((IntPtr)((int)pixptr   j)), imageBytes, k, stride);
        }
        else
        {
          Marshal.Copy(pixptr, imageBytes, 0, imageBytes.Length);
        }

        int xDpi = (int)Math.Round(bmi.biXPelsPerMeter * 2.54 / 100); // pels per meter to dots per inch
        int yDpi = (int)Math.Round(bmi.biYPelsPerMeter * 2.54 / 100);

        // Create a BitmapSource.
        bs = BitmapSource.Create(width, height, xDpi, yDpi, pf, null, imageBytes, stride);
      }
      catch (Exception)
      {
      }
      finally
      {
        // cleanup
        if (bmpPtr != IntPtr.Zero)
        { // locked sucsessfully
          TwainWin32.GlobalUnlock(dibHandle);
        }
      }
      return bs;
    }
  }
}

TwainWPF.cs

代码语言:javascript复制
using System;
using System.Windows;

using System.Windows.Interop;
using System.Windows.Media.Imaging;

namespace ztwain
{
  // A delegate type for hooking up change notifications.
  public delegate void TwainEventHandler(TwainWPF sender);

  public delegate void TwainProductNameHandler(TwainWPF sender, string ProductName);

  public delegate void TwainTransferReadyHandler(TwainWPF sender, BitmapSource imageSource);

  public delegate void TwainTransferFinishHandler(TwainWPF sender);

  public class TwainWPF : DependencyObject
  {
    // events
    public event TwainEventHandler TwainCloseRequest;

    public event TwainEventHandler TwainCloseOk;

    public event TwainEventHandler TwainDeviceEvent;

    public event TwainTransferReadyHandler TwainTransferReady;

    public event TwainTransferFinishHandler TwainTransferFinish;

    public event TwainProductNameHandler TwainProductNameEvent;

    public bool IsScanning
    { get { return TwainMessageProcessing; } }

    private bool TwainMessageProcessing = false;
    private Twain tw = null;

    private System.IntPtr _handle = IntPtr.Zero;

    public Window TheMainWindow = null;

    public uint WM_App_Acquire = TwainWin32.RegisterWindowMessage("IBN_WfpTwain_Acquire");
    //If you do not want so register a message, a simple const will do (just make sure it is unique)
    //public const uint WM_App_Aquire =  0x8123; // WM_App   0x123 - should be uniqe within the application

    public System.IntPtr WindowHandle
    {
      get
      {
        if (_handle == IntPtr.Zero)
          _handle = (new WindowInteropHelper(TheMainWindow)).Handle;
        return _handle;
      }
    }

    public TwainWPF(Window win)
    {
      TheMainWindow = win;
      // hook to events of the main window
      if (WindowHandle != IntPtr.Zero)
      {
        // main windows is initialized and we can hook events and start woking with it
        HostWindow_Loaded(this, null);
      }
      else
      {
        // hook events etc later, when the main window is loaded.
        TheMainWindow.Loaded  = HostWindow_Loaded;
      }
      TheMainWindow.Closing  = HostWindow_Closing;
    }

    ~TwainWPF()
    {
      // by now the interface should already be closed. we call terminate just in case.
      TerminateTw();
    }

    /// <summary>
    /// Open the Twain source selection dialog
    /// </summary>
    /// <returns></returns>
    public bool Select()
    {
      if (tw != null)
      {
        tw.Select();
        return true;
      }
      else
        return false;
    }

    /// <summary>
    /// Activate Twain aquire
    ///
    /// notes:
    /// Activation is done using post message to reduce friction between WPF and Windows events.
    /// </summary>
    /// <param name="showUI"></param>
    /// <returns></returns>
    public bool Acquire(bool showUI)
    {
      if (tw != null)
      {
        TwainMessageProcessing = true;
        bool posted = TwainWin32.PostMessage(WindowHandle, WM_App_Acquire, (IntPtr)(showUI ? 1 : 0), IntPtr.Zero);
        return posted;
      }
      else
        return false;
    }

    private void HostWindow_Loaded(object sender, RoutedEventArgs e)
    {
      AddMessageHook();
      tw = new Twain((string name) =>
                     {
                       if (TwainProductNameEvent != null)
                       {
                         TwainProductNameEvent(this, name);
                       }
                     });
      tw.Init(WindowHandle);
    }

    private void HostWindow_Closing(object sender, EventArgs e)
    {
      RemoveMessageHook();
    }

    private void AddMessageHook()
    {
      HwndSource src = HwndSource.FromHwnd(WindowHandle);
      src.AddHook(new HwndSourceHook(this.WndProc));
    }

    private void RemoveMessageHook()
    {
      HwndSource src = HwndSource.FromHwnd(WindowHandle);
      src.AddHook(new HwndSourceHook(this.WndProc));
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
      System.Windows.Forms.Message m = new System.Windows.Forms.Message();
      m.HWnd = hwnd;
      m.Msg = msg;
      m.WParam = wParam;
      m.LParam = lParam;

      if (handled)
        return IntPtr.Zero;

      if (msg == WM_App_Acquire)
      {
        tw.Acquire(wParam != IntPtr.Zero, -1);
      }

      //registered with "DSMAPPMESSAGE32"
      if (tw != null)
      {
        PreFilterMessage(m, ref handled);
      }
      return IntPtr.Zero;
    }

    private void TerminateTw()
    {
      if (tw != null)
      {
        tw.Finish();
        tw = null;
      }
      TwainMessageProcessing = false;
    }

    // Twain event callbacks
    protected void OnTwainCloseRequest()
    {
      if (TwainCloseRequest != null)
        TwainCloseRequest(this);
      tw.CloseSrc();
    }

    protected void OnTwainCloseOk()
    {
      if (TwainCloseOk != null)
        TwainCloseOk(this);

      //EndingScan();
      tw.CloseSrc();
    }

    protected void OnTwainDeviceEvent()
    {
      if (TwainDeviceEvent != null)
        TwainDeviceEvent(this);
    }

    protected void OnTwainTransferReady()
    {
      if (TwainTransferReady == null)
        return;
      tw.TransferPictures(imgHandle =>
                          {
                            BitmapSource image = TwainDibToBitmap.FormHDib(imgHandle);
                            TwainTransferReady(this, image);
                            TwainWin32.GlobalFree(imgHandle);
                          });
      tw.CloseSrc();
      EndingScan();
      if (TwainTransferFinish != null)
      {
        TwainTransferFinish(this);
      }
    }

    private void EndingScan()
    {
      // stop sending messged to the Twain processon
      if (TwainMessageProcessing)
      {
        TwainMessageProcessing = false;
      }
    }

    protected void PreFilterMessage(System.Windows.Forms.Message m, ref bool handled)
    {
      TwainCommand cmd = tw.PassMessage(ref m);
      if (cmd == TwainCommand.Not || cmd == TwainCommand.Null)
        return; // do not change handled

      switch (cmd)
      {
        case TwainCommand.CloseRequest:
          {
            OnTwainCloseRequest();
            break;
          }
        case TwainCommand.CloseOk:
          {
            OnTwainCloseOk();
            break;
          }
        case TwainCommand.DeviceEvent:
          {
            OnTwainDeviceEvent();
            break;
          }
        case TwainCommand.TransferReady:
          {
            OnTwainTransferReady();
            break;
          }
      }

      handled = true;
    }
  }
}

调用方式

代码语言:javascript复制
protected TwainWPF TwainInterface = null;

public Window1()
{
  InitializeComponent();

  TwainInterface = new TwainWPF(this);
  TwainInterface.TwainTransferReady  = new TwainTransferReadyHandler(TwainTransferReady);
  TwainInterface.TwainTransferFinish  = TwainInterface_TwainTransferFinish;
  TwainInterface.TwainCloseRequest  = new TwainEventHandler(TwainUIClose);
  TwainInterface.TwainProductNameEvent  = TwainInterface_TwainProductNameEvent;

  select_btn.Click  = Select_btn_Click;
  scanner_btn.Click  = Scanner_btn_Click;
}

private void TwainUIClose(TwainWPF sender)
{
}

//单页扫描结果
private void TwainTransferReady(TwainWPF sender, BitmapSource imageSource)
{
}

//单词扫描结束
private void TwainInterface_TwainTransferFinish(TwainWPF sender)
{
}

private void Select_btn_Click(object sender, RoutedEventArgs e)
{
  TwainInterface.Select();
}

private void Scanner_btn_Click(object sender, RoutedEventArgs e)
{
  TwainInterface.Acquire(true);
}

private void TwainInterface_TwainProductNameEvent(TwainWPF sender, string ProductName)
{
  scannerName.Text = ProductName;
}

康佳扫描仪对接

使用厂家提供的开发COM组件,必须要安装两个东西

  • 扫描仪驱动
  • 开发包(附带COM组件)

添加COM引用

安装完驱动后

项目中添加啊COM引用TechHeroScanProj1

初始化

代码语言:javascript复制
private TechHeroScanProj1.TechHeroScan axTechHeroScan1 = null;        

axTechHeroScan1 = new TechHeroScanProj1.TechHeroScan();
//设置参数
axTechHeroScan1.Pixel_Type = 2;         // 扫描类型:0黑白 1灰度 2彩色
axTechHeroScan1.Resolution = 200;       // 分辨率
axTechHeroScan1.Duplex = true;          // 双面扫描:true双面 false单面
axTechHeroScan1.SetAutoDeskew(true);
axTechHeroScan1.PaperSize = -1;          // 纸张大小:-1时为自动判断纸张大小  1 A4  2 B5 具体请看help.doc
axTechHeroScan1.Rotation = 0;           //旋转角度: 0 90 180 270 为旋转角度 360为根据文字方向自动旋转
axTechHeroScan1.ShowSetupBeforeScan = false;   //扫描前显示驱动界面
axTechHeroScan1.ShowIndicators = false;  //显示扫描进度条
axTechHeroScan1.SetDetectDoubleFeed(2);  //双张检测 0不检测  1长度检测  2超声波检测

选择扫描仪

代码语言:javascript复制
private void Select_btn_Click(object sender, System.Windows.RoutedEventArgs e)
{
  axTechHeroScan1.SelectScanner();
}

开始和错误回调

代码语言:javascript复制
axTechHeroScan1.OnBeforeScan  = AxTechHeroScan1_OnBeforeScan;
axTechHeroScan1.OnScanError  = AxTechHeroScan1_OnScanError;

private void AxTechHeroScan1_OnBeforeScan()
{
  Console.WriteLine("扫描前");
}

private void AxTechHeroScan1_OnScanError(int ErrorCode, int Additional)
{
  Console.WriteLine("扫描失败", ErrorCode);
}

扫描

两种扫描方式使用一种即可,如果没有文件命名的需求建议直接使用内部重命名的方式。

内部重命名

代码语言:javascript复制
axTechHeroScan1.OnPageDone  = AxTechHeroScan1_OnPageDone;
axTechHeroScan1.OnScanDone  = AxTechHeroScan1_OnScanDone;

private void AxTechHeroScan1_OnPageDone(string strFileName)
{
  Console.WriteLine(strFileName);
}

private void AxTechHeroScan1_OnScanDone()
{
  Console.WriteLine("扫描结束");
}

// 按钮事件
private void Scanner_btn_Click(object sender, System.Windows.RoutedEventArgs e)
{
  //开始扫描
  axTechHeroScan1.ScanImage(0, "D:\scanner_pic\", "img_", "jpg");
}

手动重命名

代码语言:javascript复制
axTechHeroScan1.OnPageDoneDib  = AxTechHeroScan1_OnPageDoneDib;
axTechHeroScan1.OnScanDone  = AxTechHeroScan1_OnScanDone;

private void AxTechHeroScan1_OnPageDoneDib(int dib, int Resolution)
{
  string timestr = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-ffff");
  axTechHeroScan1.SaveJpgFile("D:\scanner_pic\"   timestr   ".jpg", dib, Resolution, 75);
  axTechHeroScan1.FreeDib(dib);
}

private void AxTechHeroScan1_OnScanDone()
{
  Console.WriteLine("扫描结束");
}

// 按钮事件
private void Scanner2_btn_Click(object sender, System.Windows.RoutedEventArgs e)
{
  //开始扫描
  axTechHeroScan1.ScanToDib(0);
}

WIA对接

在COM里导入

一般最近年头的扫描仪都支持这个协议。

错误代码

https://docs.microsoft.com/zh-cn/windows/win32/wia/-wia-error-codes

弹窗设置

代码语言:javascript复制
ImageFile imageFile = null;
CommonDialog cdc = new WIA.CommonDialog();

try
{
  imageFile = cdc.ShowAcquireImage(
    WIA.WiaDeviceType.ScannerDeviceType,
    WIA.WiaImageIntent.TextIntent,
    WIA.WiaImageBias.MaximizeQuality,
    "{00000000-0000-0000-0000-000000000000}",
    true,
    true,
    false
  );
  string fileName = Path.GetTempFileName()   ".png";
  Console.WriteLine(fileName);
  File.Delete(fileName);
  imageFile.SaveFile(fileName);//保存temp文件;
}
catch (System.Runtime.InteropServices.COMException)
{
  imageFile = null;
}

0 人点赞