注意,直接pip install twain是没有的,必须要通过whl离线安装
首先下载twain的whl文件,网址:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twainmodule
下载自己Python对应的版本,例如我是python3.6.7 32位,就选cp36的
然后通过pip install twain-1.0.4-cp36-cp36m-win32.whl安装
在自己的py代码中,输入下面的代码,然后执行
import twain sm = twain.SourceManager(0) ss = sm.OpenSource() ss.RequestAcquire(0,0) rv = ss.XferImageNatively() if rv: (handle, count) = rv twain.DIBToBMFile(handle, 'image.bmp')
会弹出扫描仪选择窗口
选中自己的扫描仪名称,扫描仪就会启动,图像文件名image.bmp
我们可以用GetSourceName()来获取扫描仪的名称,然后下次把扫描仪名称传入OpenSource(扫描仪名称)中,就不会弹出选择来源的对话框了
import twain sm = twain.SourceManager(0) ss = sm.OpenSource() name = ss.GetSourceName() print(name) # FBH6315+ ss.RequestAcquire(0,0) rv = ss.XferImageNatively() if rv: (handle, count) = rv twain.DIBToBMFile(handle, 'image.bmp')
打开安装包site-packages,里面有个twain.py的文件,打开twain.py,在代码最下方,有个acquire()的方法,调用这个方法就可以自定义扫描了,其中的参数分别表示:
path:必传参数,图像保存的位置,其他的参数都可以不传
ds_name:扫描仪名称
dpi:dpi
pixel_type:颜色模式,有“bw”、“gray”、“color”三个选项,第二个和第三个是灰度和彩色,第一个不知道啥
frame:扫描范围,传入一个元组,(left, top, right, bottom)表示扫描的左上右下的范围,单位是英寸,例如要设置A4大小,A4是210mmX297mm,分别换算成英寸(除以25.4),
frame=(0.0, 0.0 ,8.27 ,11.7)传入这个参数,如果要旋转90度扫描(注意不是图像旋转90度),就把8.27和11.7换一下
show_ui:显示扫描仪设置界面,默认是False不显示,设置为True就是显示
def acquire(path, ds_name=None, dpi=None, pixel_type=None, bpp=None, frame=None, parent_window=None, show_ui=False, dsm_name=None): '''Acquires single image into file Required argument: path -- path where to save image Keyword arguments: ds_name -- name of twain data source, if not provided user will be presented with selection dialog dpi -- resolution in dots per inch pixel_type -- can be 'bw', 'gray', 'color' bpp -- bits per pixel frame -- tuple (left, top, right, bottom) scan area in inches parent_window -- can be Tk, Wx, Gtk window object or Win32 window handle show_ui -- if True source's UI dialog will be presented to user Returns a dictionary describing image, or None if scanning was cancelled by user '''
我们在程序中直接调用acquire()方法,就能自定义扫描,代码如下
import twain twain.acquire(r'./test.bmp')
这样执行可能会弹出错误AttributeError: module 'twain' has no attribute 'acquire',就是twain.py中没有acquire模块,但是在twain.py中确实是存在acquire方法的,这是因为在Python环境中还存在其他twain模块,导致找不到acquire模块
解决方法,直接把twain.py复制一份,改一下名字,例如twain_module.py,把这个作为模块导入,一切就ok了
import twain_module twain_module.acquire(r'./test.bmp') #默认参数扫描
import twain_module twain_module.acquire(r'./test.bmp',dpi=300,pixel_type="color") # 设置dpi300,彩色模式
扫描设置也就生效了
.。。。。
还有两个问题
第一,如果我们设置的图像后缀名不是bmp,例如jpg,那么会报错,
ModuleNotFoundError: No module named 'Image' ,并且图像也没保存,我们打开twian_module.py,发现twian_module.py中导入了PIL包,它其实是通过PIL转换了格式
但是导入的方式是旧版的import Image,python3.x的导入方式是 from PIL import Image,我们将twain_module.py中的全部改一下(一共两处),就ok了
第二,如果我们设置了不是bmp格式的图像,会发现我们设置的dpi不生效,一直是96,在查看twian_module.py代码后,我们发现,如果是bmp格式的图形,twain_module会直接设置图像dpi,当时如果不是bmp,会通过PIL转换后保存,但是在PIL保存时并没有设置dpi
我们自己手动修改一下,给PIL保存的时候加上dpi
1.先在sd.acquire_file中传入dpi=dpi
sd.acquire_file(before=before, after=after, show_ui=show_ui, dpi=dpi)
2.再修改acquire_file方法
def acquire_file(self, before, after=lambda more: None, show_ui=True, modal=False,dpi=None): if dpi is None: dpi = 96 .......................... if ext != '.bmp': # import Image Image.open(bmppath).save(filepath,dpi=(dpi,dpi)) os.remove(bmppath)
这两步其实也简单,大家自己读一下twain.py的源码后明白了
最后将我的twain_module.py上传上来,将文件放在你程序的同级,或者site-packages中,都可以(前提是你安装过twain)
''' Created on Sep 4, 2011 @author: misha ''' from ctypes import * import weakref import sys from PIL import Image TWON_PROTOCOLMAJOR = 2 TWON_PROTOCOLMINOR = 1 DF_DSM2 = 0x10000000 DF_APP2 = 0x20000000 DF_DS2 = 0x40000000 ACAP_AUDIOFILEFORMAT = 4609 ACAP_XFERMECH = 4610 CAP_ALARMS = 4120 CAP_ALARMVOLUME = 4121 CAP_AUTHOR = 4096 CAP_AUTOFEED = 4103 CAP_AUTOMATICCAPTURE = 4122 CAP_AUTOMATICSENSEMEDIUM = 4155 CAP_AUTOSCAN = 4112 CAP_BATTERYMINUTES = 4146 CAP_BATTERYPERCENTAGE = 4147 CAP_CAMERAENABLED = 4150 CAP_CAMERAORDER = 4151 CAP_CAMERAPREVIEWUI = 4129 CAP_CAMERASIDE = 4148 CAP_CAPTION = 4097 CAP_CLEARBUFFERS = 4125 CAP_CLEARPAGE = 4104 CAP_CUSTOMBASE = 32768 CAP_CUSTOMDSDATA = 4117 CAP_CUSTOMINTERFACEGUID = 4156 CAP_DEVICEEVENT = 4130 CAP_DEVICEONLINE = 4111 CAP_DEVICETIMEDATE = 4127 CAP_DUPLEX = 4114 CAP_DUPLEXENABLED = 4115 CAP_ENABLEDSUIONLY = 4116 CAP_ENDORSER = 4118 CAP_EXTENDEDCAPS = 4102 CAP_FEEDERALIGNMENT = 4141 CAP_FEEDERENABLED = 4098 CAP_FEEDERLOADED = 4099 CAP_FEEDERORDER = 4142 CAP_FEEDERPOCKET = 4154 CAP_FEEDERPREP = 4153 CAP_FEEDPAGE = 4105 CAP_INDICATORS = 4107 CAP_JOBCONTROL = 4119 CAP_LANGUAGE = 4140 CAP_MAXBATCHBUFFERS = 4126 CAP_MICRENABLED = 4152 CAP_PAGEMULTIPLEACQUIRE = 4131 CAP_PAPERBINDING = 4143 CAP_PAPERDETECTABLE = 4109 CAP_PASSTHRU = 4145 CAP_POWERDOWNTIME = 4148 CAP_POWERSUPPLY = 4128 CAP_PRINTER = 4134 CAP_PRINTERENABLED = 4135 CAP_PRINTERINDEX = 4136 CAP_PRINTERMODE = 4137 CAP_PRINTERSTRING = 4138 CAP_PRINTERSUFFIX = 4139 CAP_REACQUIREALLOWED = 4144 CAP_REWINDPAGE = 4106 CAP_SEGMENTED = 4149 CAP_SERIALNUMBER = 4132 CAP_SUPPORTEDCAPS = 4101 CAP_SUPPORTEDCAPSEXT = 4108 CAP_THUMBNAILSENABLED = 4113 CAP_TIMEBEFOREFIRSTCAPTURE = 4123 CAP_TIMEBETWEENCAPTURES = 4124 CAP_TIMEDATE = 4100 CAP_UICONTROLLABLE = 4110 CAP_XFERCOUNT = 1 DAT_AUDIOFILEXFER = 513 DAT_AUDIOINFO = 514 DAT_AUDIONATIVEXFER = 515 DAT_CAPABILITY = 1 DAT_CIECOLOR = 262 DAT_CUSTOMBASE = 32768 DAT_CUSTOMDSDATA = 12 DAT_DEVICEEVENT = 13 DAT_ENTRYPOINT = 0x0403 DAT_EVENT = 2 DAT_EXTIMAGEINFO = 267 DAT_FILESYSTEM = 14 DAT_GRAYRESPONSE = 263 DAT_IDENTITY = 3 DAT_IMAGEFILEXFER = 261 DAT_IMAGEINFO = 257 DAT_IMAGELAYOUT = 258 DAT_IMAGEMEMXFER = 259 DAT_IMAGENATIVEXFER = 260 DAT_JPEGCOMPRESSION = 265 DAT_NULL = 0 DAT_PALETTE8 = 266 DAT_PARENT = 4 DAT_PASSTHRU = 15 DAT_PENDINGXFERS = 5 DAT_RGBRESPONSE = 264 DAT_SETUPFILEXFER = 7 DAT_SETUPMEMXFER = 6 DAT_STATUS = 8 DAT_TWUNKIDENTITY = 11 DAT_USERINTERFACE = 9 DAT_XFERGROUP = 10 DG_AUDIO = 4 DG_CONTROL = 1 DG_IMAGE = 2 ICAP_AUTOBRIGHT = 4352 ICAP_AUTODISCARDBLANKPAGES = 4404 ICAP_AUTOMATICBORDERDETECTION = 4432 ICAP_AUTOMATICCOLORENABLED = 4441 ICAP_AUTOMATICCOLORNONCOLORPIXELTYPE = 4442 ICAP_AUTOMATICCROPUSESFRAME = 4439 ICAP_AUTOMATICDESKEW = 4433 ICAP_AUTOMATICLENGTHDETECTION = 4440 ICAP_AUTOMATICROTATE = 4434 ICAP_AUTOSIZE = 4438 ICAP_BARCODEDETECTIONENABLED = 4407 ICAP_BARCODEMAXRETRIES = 4412 ICAP_BARCODEMAXSEARCHPRIORITIES = 4409 ICAP_BARCODESEARCHMODE = 4411 ICAP_BARCODESEARCHPRIORITIES = 4410 ICAP_BARCODETIMEOUT = 4413 ICAP_BITDEPTH = 4395 ICAP_BITDEPTHREDUCTION = 4396 ICAP_BITORDER = 4380 ICAP_BITORDERCODES = 4390 ICAP_BRIGHTNESS = 4353 ICAP_CCITTKFACTOR = 4381 ICAP_COLORMANAGEMENTENABLED = 4443 ICAP_COMPRESSION = 256 ICAP_CONTRAST = 4355 ICAP_CUSTHALFTONE = 4356 ICAP_EXPOSURETIME = 4357 ICAP_EXTIMAGEINFO = 4399 ICAP_FEEDERTYPE = 4436 ICAP_FILTER = 4358 ICAP_FLASHUSED = 4359 ICAP_FLASHUSED2 = 4422 ICAP_FLIPROTATION = 4406 ICAP_FRAMES = 4372 ICAP_GAMMA = 4360 ICAP_HALFTONES = 4361 ICAP_HIGHLIGHT = 4362 ICAP_ICCPROFILE = 4437 ICAP_IMAGEDATASET = 4398 ICAP_IMAGEFILEFORMAT = 4364 ICAP_IMAGEFILTER = 4423 ICAP_IMAGEMERGE = 4444 ICAP_IMAGEMERGEHEIGHTTHRESHOLD = 4445 ICAP_JPEGPIXELTYPE = 4392 ICAP_JPEGQUALITY = 4435 ICAP_LAMPSTATE = 4365 ICAP_LIGHTPATH = 4382 ICAP_LIGHTSOURCE = 4366 ICAP_MAXFRAMES = 4378 ICAP_MINIMUMHEIGHT = 4400 ICAP_MINIMUMWIDTH = 4401 ICAP_NOISEFILTER = 4424 ICAP_ORIENTATION = 4368 ICAP_OVERSCAN = 4425 ICAP_PATCHCODEDETECTIONENABLED = 4415 ICAP_PATCHCODEMAXRETRIES = 4420 ICAP_PATCHCODEMAXSEARCHPRIORITIES = 4417 ICAP_PATCHCODESEARCHMODE = 4419 ICAP_PATCHCODESEARCHPRIORITIES = 4418 ICAP_PATCHCODETIMEOUT = 4421 ICAP_PHYSICALHEIGHT = 4370 ICAP_PHYSICALWIDTH = 4369 ICAP_PIXELFLAVOR = 4383 ICAP_PIXELFLAVORCODES = 4391 ICAP_PIXELTYPE = 257 ICAP_PLANARCHUNKY = 4384 ICAP_ROTATION = 4385 ICAP_SHADOW = 4371 ICAP_SUPPORTEDBARCODETYPES = 4408 ICAP_SUPPORTEDEXTIMAGEINFO = 4446 ICAP_SUPPORTEDPATCHCODETYPES = 4416 ICAP_SUPPORTEDSIZES = 4386 ICAP_THRESHOLD = 4387 ICAP_TILES = 4379 ICAP_TIMEFILL = 4394 ICAP_UNDEFINEDIMAGESIZE = 4397 ICAP_UNITS = 258 ICAP_XFERMECH = 259 ICAP_XNATIVERESOLUTION = 4374 ICAP_XRESOLUTION = 4376 ICAP_XSCALING = 4388 ICAP_YNATIVERESOLUTION = 4375 ICAP_YRESOLUTION = 4377 ICAP_YSCALING = 4389 ICAP_ZOOMFACTOR = 4414 MSG_CHANGEDIRECTORY = 2049 MSG_CHECKSTATUS = 513 MSG_CLOSEDS = 1026 MSG_CLOSEDSM = 770 MSG_CLOSEDSOK = 259 MSG_CLOSEDSREQ = 258 MSG_CREATEDIRECTORY = 2050 MSG_CUSTOMBASE = 32768 MSG_DELETE = 2051 MSG_DEVICEEVENT = 260 MSG_DISABLEDS = 1281 MSG_ENABLEDS = 1282 MSG_ENABLEDSUIONLY = 1283 MSG_ENDXFER = 1793 MSG_FORMATMEDIA = 2052 MSG_GET = 1 MSG_GETCLOSE = 2053 MSG_GETCURRENT = 2 MSG_GETDEFAULT = 3 MSG_GETFIRST = 4 MSG_GETFIRSTFILE = 2054 MSG_GETINFO = 2055 MSG_GETNEXT = 5 MSG_GETNEXTFILE = 2056 MSG_NULL = 0 MSG_OPENDS = 1025 MSG_OPENDSM = 769 MSG_PASSTHRU = 2305 MSG_PROCESSEVENT = 1537 MSG_QUERYSUPPORT = 8 MSG_RENAME = 2057 MSG_RESET = 7 MSG_SET = 6 MSG_USERSELECT = 1027 MSG_XFERREADY = 257 TWAF_AIFF = 1 TWAF_AU = 3 TWAF_SND = 4 TWAF_WAV = 0 TWAL_ALARM = 0 TWAL_BARCODE = 3 TWAL_DOUBLEFEED = 4 TWAL_FEEDERERROR = 1 TWAL_FEEDERWARNING = 2 TWAL_JAM = 5 TWAL_PATCHCODE = 6 TWAL_POWER = 7 TWAL_SKEW = 8 TWAS_AUTO = 1 TWAS_CURRENT = 2 TWAS_NONE = 0 TWBCOR_ROT0 = 0 TWBCOR_ROT180 = 2 TWBCOR_ROT270 = 3 TWBCOR_ROT90 = 1 TWBCOR_ROTX = 4 TWBD_HORZ = 0 TWBD_HORZVERT = 2 TWBD_VERT = 1 TWBD_VERTHORZ = 3 TWBO_LSBFIRST = 0 TWBO_MSBFIRST = 1 TWBP_AUTO = -1 TWBP_DISABLE = -2 TWBR_CUSTHALFTONE = 2 TWBR_DIFFUSION = 3 TWBR_HALFTONE = 1 TWBR_THRESHOLD = 0 TWBT_2OF5DATALOGIC = 15 TWBT_2OF5IATA = 16 TWBT_2OF5INDUSTRIAL = 13 TWBT_2OF5INTERLEAVED = 1 TWBT_2OF5MATRIX = 14 TWBT_2OF5NONINTERLEAVED = 2 TWBT_3OF9 = 0 TWBT_3OF9FULLASCII = 17 TWBT_CODABAR = 6 TWBT_CODABARWITHSTARTSTOP = 18 TWBT_CODE128 = 4 TWBT_CODE93 = 3 TWBT_EAN13 = 10 TWBT_EAN8 = 9 TWBT_MAXICODE = 19 TWBT_PDF417 = 12 TWBT_POSTNET = 11 TWBT_UCC128 = 5 TWBT_UPCA = 7 TWBT_UPCE = 8 TWCB_AUTO = 0 TWCB_CLEAR = 1 TWCB_NOCLEAR = 2 TWCC_BADCAP = 6 TWCC_BADDEST = 12 TWCC_BADPROTOCOL = 9 TWCC_BADVALUE = 10 TWCC_BUMMER = 1 TWCC_CAPBADOPERATION = 14 TWCC_CAPSEQERROR = 15 TWCC_CAPUNSUPPORTED = 13 TWCC_CHECKDEVICEONLINE = 23 TWCC_CUSTOMBASE = 32768 TWCC_DAMAGEDCORNER = 25 TWCC_DENIED = 16 TWCC_DOCTOODARK = 28 TWCC_DOCTOOLIGHT = 27 TWCC_FILEEXISTS = 17 TWCC_FILENOTFOUND = 18 TWCC_FILEWRITEERROR = 22 TWCC_FOCUSERROR = 26 TWCC_LOWMEMORY = 2 TWCC_MAXCONNECTIONS = 4 TWCC_NODS = 3 TWCC_NOMEDIA = 29 TWCC_NOTEMPTY = 19 TWCC_OPERATIONERROR = 5 TWCC_PAPERDOUBLEFEED = 21 TWCC_PAPERJAM = 20 TWCC_SEQERROR = 11 TWCC_SUCCESS = 0 TWCP_BITFIELDS = 12 TWCP_GROUP31D = 2 TWCP_GROUP31DEOL = 3 TWCP_GROUP32D = 4 TWCP_GROUP4 = 5 TWCP_JBIG = 8 TWCP_JPEG = 6 TWCP_LZW = 7 TWCP_NONE = 0 TWCP_PACKBITS = 1 TWCP_PNG = 9 TWCP_RLE4 = 10 TWCP_RLE8 = 11 TWCS_BOTH = 0 TWCS_BOTTOM = 2 TWCS_TOP = 1 TWCY_AFGHANISTAN = 1001 TWCY_ALBANIA = 355 TWCY_ALGERIA = 213 TWCY_AMERICANSAMOA = 684 TWCY_ANDORRA = 27 TWCY_ANGOLA = 1002 TWCY_ANGUILLA = 8090 TWCY_ANTIGUA = 8091 TWCY_ARGENTINA = 54 TWCY_ARMENIA = 374 TWCY_ARUBA = 297 TWCY_ASCENSIONI = 247 TWCY_AUSTRALIA = 61 TWCY_AUSTRIA = 43 TWCY_AZERBAIJAN = 994 TWCY_BAHAMAS = 8092 TWCY_BAHRAIN = 973 TWCY_BANGLADESH = 880 TWCY_BARBADOS = 8093 TWCY_BELARUS = 375 TWCY_BELGIUM = 32 TWCY_BELIZE = 501 TWCY_BENIN = 229 TWCY_BERMUDA = 8094 TWCY_BHUTAN = 1003 TWCY_BOLIVIA = 591 TWCY_BOSNIAHERZGO = 387 TWCY_BOTSWANA = 267 TWCY_BRAZIL = 55 TWCY_BRITAIN = 6 TWCY_BRITVIRGINIS = 8095 TWCY_BRUNEI = 673 TWCY_BULGARIA = 359 TWCY_BURKINAFASO = 1004 TWCY_BURMA = 1005 TWCY_BURUNDI = 1006 TWCY_CAMAROON = 237 TWCY_CAMBODIA = 855 TWCY_CANADA = 2 TWCY_CAPEVERDEIS = 238 TWCY_CAYMANIS = 8096 TWCY_CENTRALAFREP = 1007 TWCY_CHAD = 1008 TWCY_CHILE = 56 TWCY_CHINA = 86 TWCY_CHRISTMASIS = 1009 TWCY_COCOSIS = 1009 TWCY_COLOMBIA = 57 TWCY_COMOROS = 1010 TWCY_CONGO = 1011 TWCY_COOKIS = 1012 TWCY_COSTARICA = 506 TWCY_CROATIA = 385 TWCY_CUBA = 5 TWCY_CYPRUS = 357 TWCY_CZECHOSLOVAKIA = 42 TWCY_CZECHREPUBLIC = 420 TWCY_DENMARK = 45 TWCY_DIEGOGARCIA = 246 TWCY_DJIBOUTI = 1013 TWCY_DOMINCANREP = 8098 TWCY_DOMINICA = 8097 TWCY_EASTERIS = 1014 TWCY_ECUADOR = 593 TWCY_EGYPT = 20 TWCY_ELSALVADOR = 503 TWCY_EQGUINEA = 1015 TWCY_ERITREA = 291 TWCY_ESTONIA = 372 TWCY_ETHIOPIA = 251 TWCY_FAEROEIS = 298 TWCY_FALKLANDIS = 1016 TWCY_FIJIISLANDS = 679 TWCY_FINLAND = 358 TWCY_FRANCE = 33 TWCY_FRANTILLES = 596 TWCY_FRGUIANA = 594 TWCY_FRPOLYNEISA = 689 TWCY_FUTANAIS = 1043 TWCY_GABON = 241 TWCY_GAMBIA = 220 TWCY_GEORGIA = 995 TWCY_GERMANY = 49 TWCY_GHANA = 233 TWCY_GIBRALTER = 350 TWCY_GREECE = 30 TWCY_GREENLAND = 299 TWCY_GRENADA = 8099 TWCY_GRENEDINES = 8015 TWCY_GUADELOUPE = 590 TWCY_GUAM = 671 TWCY_GUANTANAMOBAY = 5399 TWCY_GUATEMALA = 502 TWCY_GUINEA = 224 TWCY_GUINEABISSAU = 1017 TWCY_GUYANA = 592 TWCY_HAITI = 509 TWCY_HONDURAS = 504 TWCY_HONGKONG = 852 TWCY_HUNGARY = 36 TWCY_ICELAND = 354 TWCY_INDIA = 91 TWCY_INDONESIA = 62 TWCY_IRAN = 98 TWCY_IRAQ = 964 TWCY_IRELAND = 353 TWCY_ISRAEL = 972 TWCY_ITALY = 39 TWCY_IVORYCOAST = 225 TWCY_JAMAICA = 8010 TWCY_JAPAN = 81 TWCY_JORDAN = 962 TWCY_KENYA = 254 TWCY_KIRIBATI = 1018 TWCY_KOREA = 82 TWCY_KUWAIT = 965 TWCY_LAOS = 1019 TWCY_LATVIA = 371 TWCY_LEBANON = 1020 TWCY_LESOTHO = 266 TWCY_LIBERIA = 231 TWCY_LIBYA = 218 TWCY_LIECHTENSTEIN = 41 TWCY_LITHUANIA = 370 TWCY_LUXENBOURG = 352 TWCY_MACAO = 853 TWCY_MACEDONIA = 389 TWCY_MADAGASCAR = 1021 TWCY_MALAWI = 265 TWCY_MALAYSIA = 60 TWCY_MALDIVES = 960 TWCY_MALI = 1022 TWCY_MALTA = 356 TWCY_MARSHALLIS = 692 TWCY_MAURITANIA = 1023 TWCY_MAURITIUS = 230 TWCY_MAYOTTEIS = 269 TWCY_MEXICO = 3 TWCY_MICRONESIA = 691 TWCY_MIQUELON = 508 TWCY_MOLDOVA = 373 TWCY_MONACO = 33 TWCY_MONGOLIA = 1024 TWCY_MONTSERRAT = 8011 TWCY_MOROCCO = 212 TWCY_MOZAMBIQUE = 1025 TWCY_MYANMAR = 95 TWCY_NAMIBIA = 264 TWCY_NAURU = 1026 TWCY_NEPAL = 977 TWCY_NETHANTILLES = 599 TWCY_NETHERLANDS = 31 TWCY_NEVIS = 8012 TWCY_NEWCALEDONIA = 687 TWCY_NEWZEALAND = 64 TWCY_NICARAGUA = 505 TWCY_NIGER = 227 TWCY_NIGERIA = 234 TWCY_NIUE = 1027 TWCY_NORFOLKI = 1028 TWCY_NORTHKOREA = 850 TWCY_NORWAY = 47 TWCY_OMAN = 968 TWCY_PAKISTAN = 92 TWCY_PALAU = 1029 TWCY_PANAMA = 507 TWCY_PARAGUAY = 595 TWCY_PERU = 51 TWCY_PHILLIPPINES = 63 TWCY_PITCAIRNIS = 1030 TWCY_PNEWGUINEA = 675 TWCY_POLAND = 48 TWCY_PORTUGAL = 351 TWCY_PUERTORICO = 787 TWCY_QATAR = 974 TWCY_REUNIONI = 1031 TWCY_ROMANIA = 40 TWCY_RUSSIA = 7 TWCY_RWANDA = 250 TWCY_SAIPAN = 670 TWCY_SANMARINO = 39 TWCY_SAOTOME = 1033 TWCY_SAUDIARABIA = 966 TWCY_SENEGAL = 221 TWCY_SERBIA = 381 TWCY_SEYCHELLESIS = 1034 TWCY_SIERRALEONE = 1035 TWCY_SINGAPORE = 65 TWCY_SLOVAKIA = 421 TWCY_SLOVENIA = 386 TWCY_SOLOMONIS = 1036 TWCY_SOMALI = 1037 TWCY_SOUTHAFRICA = 27 TWCY_SOUTHKOREA = 82 TWCY_SPAIN = 34 TWCY_SRILANKA = 94 TWCY_STHELENA = 1032 TWCY_STKITTS = 8013 TWCY_STLUCIA = 8014 TWCY_STPIERRE = 508 TWCY_STVINCENT = 8015 TWCY_SUDAN = 1038 TWCY_SURINAME = 597 TWCY_SWAZILAND = 268 TWCY_SWEDEN = 46 TWCY_SWITZERLAND = 41 TWCY_SYRIA = 1039 TWCY_TAIWAN = 886 TWCY_TANZANIA = 255 TWCY_THAILAND = 66 TWCY_TOBAGO = 8016 TWCY_TOGO = 228 TWCY_TONGAIS = 676 TWCY_TRINIDAD = 8016 TWCY_TUNISIA = 216 TWCY_TURKEY = 90 TWCY_TURKSCAICOS = 8017 TWCY_TUVALU = 1040 TWCY_UAEMIRATES = 971 TWCY_UGANDA = 256 TWCY_UKRAINE = 380 TWCY_UNITEDKINGDOM = 44 TWCY_URUGUAY = 598 TWCY_USA = 1 TWCY_USSR = 7 TWCY_USVIRGINIS = 340 TWCY_VANUATU = 1041 TWCY_VATICANCITY = 39 TWCY_VENEZUELA = 58 TWCY_VIETNAM = 84 TWCY_WAKE = 1042 TWCY_WALLISIS = 1043 TWCY_WESTERNSAHARA = 1044 TWCY_WESTERNSAMOA = 1045 TWCY_YEMEN = 1046 TWCY_YUGOSLAVIA = 38 TWCY_ZAIRE = 243 TWCY_ZAMBIA = 260 TWCY_ZIMBABWE = 263 TWDE_CHECKAUTOMATICCAPTURE = 0 TWDE_CHECKBATTERY = 1 TWDE_CHECKDEVICEONLINE = 2 TWDE_CHECKFLASH = 3 TWDE_CHECKPOWERSUPPLY = 4 TWDE_CHECKRESOLUTION = 5 TWDE_CUSTOMEVENTS = 32768 TWDE_DEVICEADDED = 6 TWDE_DEVICEOFFLINE = 7 TWDE_DEVICEREADY = 8 TWDE_DEVICEREMOVED = 9 TWDE_IMAGECAPTURED = 10 TWDE_IMAGEDELETED = 11 TWDE_LAMPFAILURE = 14 TWDE_PAPERDOUBLEFEED = 12 TWDE_PAPERJAM = 13 TWDE_POWERSAVE = 15 TWDE_POWERSAVENOTIFY = 16 TWDR_GET = 1 TWDR_SET = 2 TWDSK_DISABLED = 3 TWDSK_FAIL = 2 TWDSK_REPORTONLY = 1 TWDSK_SUCCESS = 0 TWDX_1PASSDUPLEX = 1 TWDX_2PASSDUPLEX = 2 TWDX_NONE = 0 TWEI_BARCODECONFIDENCE = 4634 TWEI_BARCODECOUNT = 4633 TWEI_BARCODEROTATION = 4635 TWEI_BARCODETEXT = 4610 TWEI_BARCODETEXTLENGTH = 4636 TWEI_BARCODETYPE = 4611 TWEI_BARCODEX = 4608 TWEI_BARCODEY = 4609 TWEI_BLACKSPECKLESREMOVED = 4647 TWEI_BOOKNAME = 4664 TWEI_CAMERA = 4668 TWEI_CHAPTERNUMBER = 4665 TWEI_DESHADEBLACKCOUNTNEW = 4639 TWEI_DESHADEBLACKCOUNTOLD = 4638 TWEI_DESHADEBLACKRLMAX = 4641 TWEI_DESHADEBLACKRLMIN = 4640 TWEI_DESHADECOUNT = 4637 TWEI_DESHADEHEIGHT = 4614 TWEI_DESHADELEFT = 4613 TWEI_DESHADESIZE = 4616 TWEI_DESHADETOP = 4612 TWEI_DESHADEWHITECOUNTNEW = 4643 TWEI_DESHADEWHITECOUNTOLD = 4642 TWEI_DESHADEWHITERLAVE = 4645 TWEI_DESHADEWHITERLMAX = 4646 TWEI_DESHADEWHITERLMIN = 4644 TWEI_DESHADEWIDTH = 4615 TWEI_DESKEWSTATUS = 4651 TWEI_DOCUMENTNUMBER = 4666 TWEI_ENDORSEDTEXT = 4627 TWEI_FILESYSTEMSOURCE = 4678 TWEI_FORMCONFIDENCE = 4628 TWEI_FORMHORZDOCOFFSET = 4631 TWEI_FORMTEMPLATEMATCH = 4629 TWEI_FORMTEMPLATEPAGEMATCH = 4630 TWEI_FORMVERTDOCOFFSET = 4632 TWEI_FRAME = 4670 TWEI_FRAMENUMBER = 4669 TWEI_HORZLINECOUNT = 4649 TWEI_HORZLINELENGTH = 4620 TWEI_HORZLINETHICKNESS = 4621 TWEI_HORZLINEXCOORD = 4618 TWEI_HORZLINEYCOORD = 4619 TWEI_ICCPROFILE = 4672 TWEI_IMAGEMERGED = 4679 TWEI_LASTSEGMENT = 4673 TWEI_MAGDATA = 4675 TWEI_MAGDATALENGTH = 4680 TWEI_MAGTYPE = 4676 TWEI_PAGENUMBER = 4667 TWEI_PAGESIDE = 4677 TWEI_PATCHCODE = 4626 TWEI_PIXELFLAVOR = 4671 TWEI_SEGMENTNUMBER = 4674 TWEI_SKEWCONFIDENCE = 4654 TWEI_SKEWFINALANGLE = 4653 TWEI_SKEWORIGINALANGLE = 4652 TWEI_SKEWWINDOWX1 = 4655 TWEI_SKEWWINDOWX2 = 4657 TWEI_SKEWWINDOWX3 = 4659 TWEI_SKEWWINDOWX4 = 4661 TWEI_SKEWWINDOWY1 = 4656 TWEI_SKEWWINDOWY2 = 4658 TWEI_SKEWWINDOWY3 = 4660 TWEI_SKEWWINDOWY4 = 4662 TWEI_SPECKLESREMOVED = 4617 TWEI_VERTLINECOUNT = 4650 TWEI_VERTLINELENGTH = 4624 TWEI_VERTLINETHICKNESS = 4625 TWEI_VERTLINEXCOORD = 4622 TWEI_VERTLINEYCOORD = 4623 TWEI_WHITESPECKLESREMOVED = 4648 TWEJ_MIDSEPARATOR = 1 TWEJ_NONE = 0 TWEJ_PATCH1 = 2 TWEJ_PATCH2 = 3 TWEJ_PATCH3 = 4 TWEJ_PATCH4 = 5 TWEJ_PATCH6 = 6 TWEJ_PATCHT = 7 TWFA_CENTER = 2 TWFA_LEFT = 1 TWFA_NONE = 0 TWFA_RIGHT = 3 TWFE_GENERAL = 0 TWFE_PHOTO = 1 TWFF_BMP = 2 TWFF_DEJAVU = 14 TWFF_EXIF = 9 TWFF_FPX = 5 TWFF_JFIF = 4 TWFF_JP2 = 11 TWFF_JPX = 13 TWFF_PDF = 10 TWFF_PDFA = 15 TWFF_PDFA2 = 16 TWFF_PICT = 1 TWFF_PNG = 7 TWFF_SPIFF = 8 TWFF_TIFF = 0 TWFF_TIFFMULTI = 6 TWFF_XBM = 3 TWFL_AUTO = 3 TWFL_NONE = 0 TWFL_OFF = 1 TWFL_ON = 2 TWFL_REDEYE = 4 TWFO_FIRSTPAGEFIRST = 0 TWFO_LASTPAGEFIRST = 1 TWFR_BOOK = 0 TWFR_FANFOLD = 1 TWFS_FILESYSTEM = 0 TWFS_RECURSIVEDELETE = 1 TWFT_BLACK = 8 TWFT_BLUE = 2 TWFT_CYAN = 5 TWFT_GREEN = 1 TWFT_MAGENTA = 6 TWFT_NONE = 3 TWFT_RED = 0 TWFT_WHITE = 4 TWFT_YELLOW = 7 TWFY_CAMERA = 0 TWFY_CAMERABOTTOM = 2 TWFY_CAMERAPREVIEW = 3 TWFY_CAMERATOP = 1 TWFY_DIRECTORY = 6 TWFY_DOMAIN = 4 TWFY_HOST = 5 TWFY_IMAGE = 7 TWFY_UNKNOWN = 8 TWIF_AUTO = 1 TWIF_BANDPASS = 3 TWIF_FINELINE = 4 TWIF_HIGHPASS = 4 TWIF_LOWPASS = 2 TWIF_NONE = 0 TWIF_TEXT = 3 TWJC_JSIC = 1 TWJC_JSIS = 2 TWJC_JSXC = 3 TWJC_JSXS = 4 TWJC_NONE = 0 TWLG_AFRIKAANS = 14 TWLG_ALBANIA = 15 TWLG_ARABIC = 16 TWLG_ARABIC_ALGERIA = 17 TWLG_ARABIC_BAHRAIN = 18 TWLG_ARABIC_EGYPT = 19 TWLG_ARABIC_IRAQ = 20 TWLG_ARABIC_JORDAN = 21 TWLG_ARABIC_KUWAIT = 22 TWLG_ARABIC_LEBANON = 23 TWLG_ARABIC_LIBYA = 24 TWLG_ARABIC_MOROCCO = 25 TWLG_ARABIC_OMAN = 26 TWLG_ARABIC_QATAR = 27 TWLG_ARABIC_SAUDIARABIA = 28 TWLG_ARABIC_SYRIA = 29 TWLG_ARABIC_TUNISIA = 30 TWLG_ARABIC_UAE = 31 TWLG_ARABIC_YEMEN = 32 TWLG_ASSAMESE = 87 TWLG_BASQUE = 33 TWLG_BENGALI = 88 TWLG_BIHARI = 89 TWLG_BODO = 90 TWLG_BULGARIAN = 35 TWLG_BYELORUSSIAN = 34 TWLG_CATALAN = 36 TWLG_CHINESE = 37 TWLG_CHINESE_HONGKONG = 38 TWLG_CHINESE_PRC = 39 TWLG_CHINESE_SIMPLIFIED = 41 TWLG_CHINESE_SINGAPORE = 40 TWLG_CHINESE_TAIWAN = 42 TWLG_CHINESE_TRADITIONAL = 43 TWLG_CROATIA = 44 TWLG_CZECH = 45 TWLG_DAN = 0 TWLG_DANISH = 0 TWLG_DOGRI = 91 TWLG_DUT = 1 TWLG_DUTCH = 1 TWLG_DUTCH_BELGIAN = 46 TWLG_ENG = 2 TWLG_ENGLISH = 2 TWLG_ENGLISH_AUSTRALIAN = 47 TWLG_ENGLISH_CANADIAN = 48 TWLG_ENGLISH_IRELAND = 49 TWLG_ENGLISH_NEWZEALAND = 50 TWLG_ENGLISH_SOUTHAFRICA = 51 TWLG_ENGLISH_UK = 52 TWLG_ENGLISH_USA = 13 TWLG_ESTONIAN = 53 TWLG_FAEROESE = 54 TWLG_FARSI = 55 TWLG_FCF = 3 TWLG_FIN = 4 TWLG_FINNISH = 4 TWLG_FRENCH = 5 TWLG_FRENCH_BELGIAN = 56 TWLG_FRENCH_CANADIAN = 3 TWLG_FRENCH_LUXEMBOURG = 57 TWLG_FRENCH_SWISS = 58 TWLG_FRN = 5 TWLG_GER = 6 TWLG_GERMAN = 6 TWLG_GERMAN_AUSTRIAN = 59 TWLG_GERMAN_LIECHTENSTEIN = 61 TWLG_GERMAN_LUXEMBOURG = 60 TWLG_GERMAN_SWISS = 62 TWLG_GREEK = 63 TWLG_GUJARATI = 92 TWLG_HARYANVI = 93 TWLG_HEBREW = 64 TWLG_HINDI = 94 TWLG_HUNGARIAN = 65 TWLG_ICE = 7 TWLG_ICELANDIC = 7 TWLG_INDONESIAN = 66 TWLG_ITALIAN = 8 TWLG_ITALIAN_SWISS = 67 TWLG_ITN = 8 TWLG_JAPANESE = 68 TWLG_KANNADA = 95 TWLG_KASHMIRI = 96 TWLG_KOREAN = 69 TWLG_KOREAN_JOHAB = 70 TWLG_LATVIAN = 71 TWLG_LITHUANIAN = 72 TWLG_MALAYALAM = 97 TWLG_MARATHI = 98 TWLG_MARWARI = 99 TWLG_MEGHALAYAN = 100 TWLG_MIZO = 101 TWLG_NAGA = 102 TWLG_NOR = 9 TWLG_NORWEGIAN = 9 TWLG_NORWEGIAN_BOKMAL = 73 TWLG_NORWEGIAN_NYNORSK = 74 TWLG_ORISSI = 103 TWLG_POLISH = 75 TWLG_POR = 10 TWLG_PORTUGUESE = 10 TWLG_PORTUGUESE_BRAZIL = 76 TWLG_PUNJABI = 104 TWLG_PUSHTU = 105 TWLG_ROMANIAN = 77 TWLG_RUSSIAN = 78 TWLG_SERBIAN_CYRILLIC = 106 TWLG_SERBIAN_LATIN = 79 TWLG_SIKKIMI = 107 TWLG_SLOVAK = 80 TWLG_SLOVENIAN = 81 TWLG_SPA = 11 TWLG_SPANISH = 11 TWLG_SPANISH_MEXICAN = 82 TWLG_SPANISH_MODERN = 83 TWLG_SWE = 12 TWLG_SWEDISH = 12 TWLG_SWEDISH_FINLAND = 108 TWLG_TAMIL = 109 TWLG_TELUGU = 110 TWLG_THAI = 84 TWLG_TRIPURI = 111 TWLG_TURKISH = 85 TWLG_UKRANIAN = 86 TWLG_URDU = 112 TWLG_USA = 13 TWLG_USERLOCALE = -1 TWLG_VIETNAMESE = 113 TWLP_REFLECTIVE = 0 TWLP_TRANSMISSIVE = 1 TWLS_BLUE = 2 TWLS_GREEN = 1 TWLS_IR = 6 TWLS_NONE = 3 TWLS_RED = 0 TWLS_UV = 5 TWLS_WHITE = 4 TWMF_APPOWNS = 1 TWMF_DSMOWNS = 2 TWMF_DSOWNS = 4 TWMF_HANDLE = 16 TWMF_POINTER = 8 TWNF_AUTO = 1 TWNF_LONEPIXEL = 2 TWNF_MAJORITYRULE = 3 TWNF_NONE = 0 TWON_ARRAY = 3 TWON_DONTCARE16 = 65535 TWON_DONTCARE32 = -1 TWON_DONTCARE8 = 255 TWON_DSMCODEID = 63 TWON_DSMID = 461 TWON_ENUMERATION = 4 TWON_ICONID = 962 TWON_ONEVALUE = 5 TWON_RANGE = 6 TWOR_LANDSCAPE = 3 TWOR_PORTRAIT = 0 TWOR_ROT0 = 0 TWOR_ROT180 = 2 TWOR_ROT270 = 3 TWOR_ROT90 = 1 TWOV_ALL = 4 TWOV_AUTO = 1 TWOV_LEFTRIGHT = 3 TWOV_NONE = 0 TWOV_TOPBOTTOM = 2 TWPA_CMY = 2 TWPA_GRAY = 1 TWPA_RGB = 0 TWPCH_PATCH1 = 0 TWPCH_PATCH2 = 1 TWPCH_PATCH3 = 2 TWPCH_PATCH4 = 3 TWPCH_PATCH6 = 4 TWPCH_PATCHT = 5 TWPC_CHUNKY = 0 TWPC_PLANAR = 1 TWPF_CHOCOLATE = 0 TWPF_VANILLA = 1 TWPM_COMPOUNDSTRING = 2 TWPM_MULTISTRING = 1 TWPM_SINGLESTRING = 0 TWPR_ENDORSERBOTTOMAFTER = 7 TWPR_ENDORSERBOTTOMBEFORE = 6 TWPR_ENDORSERTOPAFTER = 5 TWPR_ENDORSERTOPBEFORE = 4 TWPR_IMPRINTERBOTTOMAFTER = 3 TWPR_IMPRINTERBOTTOMBEFORE = 2 TWPR_IMPRINTERTOPAFTER = 1 TWPR_IMPRINTERTOPBEFORE = 0 TWPS_BATTERY = 1 TWPS_EXTERNAL = 0 TWPT_BW = 0 TWPT_CIEXYZ = 8 TWPT_CMY = 4 TWPT_CMYK = 5 TWPT_GRAY = 1 TWPT_PALETTE = 3 TWPT_RGB = 2 TWPT_YUV = 6 TWPT_YUVK = 7 TWQC_GET = 1 TWQC_GETCURRENT = 8 TWQC_GETDEFAULT = 4 TWQC_RESET = 16 TWQC_SET = 2 TWRC_CANCEL = 3 TWRC_CHECKSTATUS = 2 TWRC_CUSTOMBASE = 32768 TWRC_DATANOTAVAILABLE = 9 TWRC_DSEVENT = 4 TWRC_ENDOFLIST = 7 TWRC_FAILURE = 1 TWRC_INFONOTSUPPORTED = 8 TWRC_NOTDSEVENT = 5 TWRC_SUCCESS = 0 TWRC_XFERDONE = 6 TWSS_2A0 = 18 TWSS_4A0 = 17 TWSS_A0 = 19 TWSS_A1 = 20 TWSS_A10 = 25 TWSS_A2 = 21 TWSS_A3 = 11 TWSS_A4 = 1 TWSS_A4LETTER = 1 TWSS_A5 = 5 TWSS_A6 = 13 TWSS_A7 = 22 TWSS_A8 = 23 TWSS_A9 = 24 TWSS_B3 = 12 TWSS_B4 = 6 TWSS_B5LETTER = 2 TWSS_B6 = 7 TWSS_BUSINESSCARD = 53 TWSS_C0 = 44 TWSS_C1 = 45 TWSS_C10 = 51 TWSS_C2 = 46 TWSS_C3 = 47 TWSS_C4 = 14 TWSS_C5 = 15 TWSS_C6 = 16 TWSS_C7 = 48 TWSS_C8 = 49 TWSS_C9 = 50 TWSS_ISOB0 = 26 TWSS_ISOB1 = 27 TWSS_ISOB10 = 33 TWSS_ISOB2 = 28 TWSS_ISOB3 = 12 TWSS_ISOB4 = 6 TWSS_ISOB5 = 29 TWSS_ISOB6 = 7 TWSS_ISOB7 = 30 TWSS_ISOB8 = 31 TWSS_ISOB9 = 32 TWSS_JISB0 = 34 TWSS_JISB1 = 35 TWSS_JISB10 = 43 TWSS_JISB2 = 36 TWSS_JISB3 = 37 TWSS_JISB4 = 38 TWSS_JISB5 = 2 TWSS_JISB6 = 39 TWSS_JISB7 = 40 TWSS_JISB8 = 41 TWSS_JISB9 = 42 TWSS_NONE = 0 TWSS_USEXECUTIVE = 10 TWSS_USLEDGER = 9 TWSS_USLEGAL = 4 TWSS_USLETTER = 3 TWSS_USSTATEMENT = 52 TWSX_FILE = 1 TWSX_MEMORY = 2 TWSX_NATIVE = 0 TWTY_BOOL = 6 TWTY_FIX32 = 7 TWTY_FRAME = 8 TWTY_INT16 = 1 TWTY_INT32 = 2 TWTY_INT8 = 0 TWTY_STR1024 = 13 TWTY_STR128 = 11 TWTY_STR255 = 12 TWTY_STR32 = 9 TWTY_STR64 = 10 TWTY_UINT16 = 4 TWTY_UINT32 = 5 TWTY_UINT8 = 3 TWTY_UNI512 = 14 TWUN_CENTIMETERS = 1 TWUN_INCHES = 0 TWUN_PICAS = 2 TWUN_PIXELS = 5 TWUN_POINTS = 3 TWUN_TWIPS = 4 class TwainError(Exception): pass class excCapabilityFormatNotSupported(TwainError): pass class excDSTransferCancelled(TwainError): pass class excSMGetProcAddressFailed(TwainError): pass class excSMLoadFileFailed(TwainError): pass class excSMOpenFailed(TwainError): pass class excImageFormat(Exception): pass class excTWCC_BADCAP(TwainError): pass class excTWCC_BADDEST(Exception): pass class excTWCC_BADPROTOCOL(Exception): pass class excTWCC_BUMMER(Exception): '''General failure. Unload Source immediately.''' pass class excTWCC_CAPBADOPERATION(Exception): '''Operation (i.e., Get or Set) not supported on capability.''' pass class excTWCC_CAPSEQERROR(Exception): '''Capability has dependencies on other capabilities and cannot be operated upon at this time. ''' pass class excTWCC_CAPUNSUPPORTED(Exception): '''Capability not supported by Source.''' pass class excTWCC_CHECKDEVICEONLINE(Exception): '''Check the device status using CAP_DEVICEONLINE, this condition code can be returned by any TWAIN operation in state 4 or higher, or from the state 3 DG_CONTROL / DAT_IDENTITY / MSG_OPENDS. The state remains unchanged. If in state 4 the Application can poll with CAP_DEVICELINE until the value returns TRUE. ''' pass class excTWCC_DENIED(Exception): pass class excTWCC_FILEEXISTS(Exception): pass class excTWCC_FILENOTFOUND(Exception): pass class excTWCC_FILEWRITEERROR(Exception): pass class excTWCC_MAXCONNECTIONS(Exception): pass class excTWCC_NODS(Exception): pass class excTWCC_NOTEMPTY(Exception): pass class excTWCC_OPERATIONERROR(Exception): pass class excTWCC_PAPERDOUBLEFEED(Exception): pass class excTWCC_PAPERJAM(Exception): pass class excTWCC_SEQERROR(Exception): pass class excTWCC_SUCCESS(Exception): pass class excTWCC_UNKNOWN(Exception): pass _ext_to_type = {'.bmp': TWFF_BMP, '.jpg': TWFF_JFIF, '.jpeg': TWFF_JFIF, '.png': TWFF_PNG, '.tiff': TWFF_TIFF, '.tif': TWFF_TIFF, } class CancelAll(Exception): '''Exception used by callbacks to cancel remaining image transfers''' pass class CheckStatus(Exception): '''This exception means that operation succeeded but user value was truncated to fit valid range ''' pass class _Image(object): def __init__(self, handle): self._handle = handle def __del__(self): self.close() def close(self): '''Releases memory of image''' self._free(self._handle) self._handle = None def save(self, filepath): '''Saves in-memory image to BMP file''' _dib_write(self._handle, filepath, self._lock, self._unlock) def _float2fix(x): if x <= -2**15 - 1 and 2**15 + 1 <= x: raise Exception('Float value is out of range') x = int(x * 2**16 + 0.5) whole = x >> 16 frac = x & 0xffff return TW_FIX32(whole, frac) def _fix2float(x): return x.Whole + float(x.Frac) / 2**16 def _frame2tuple(frame): return (_fix2float(frame.Left), _fix2float(frame.Top), _fix2float(frame.Right), _fix2float(frame.Bottom)) def _tuple2frame(tup): return TW_FRAME(_float2fix(tup[0]), _float2fix(tup[1]), _float2fix(tup[2]), _float2fix(tup[3])) class TW_CAPABILITY(Structure): _pack_ = 2 _fields_ = [("Cap", c_uint16), ('ConType', c_uint16), ('hContainer', c_void_p)] class TW_ONEVALUE(Structure): _pack_ = 2 _fields_ = [('ItemType', c_uint16), ('Item', c_uint32)] class TW_FIX32(Structure): _pack_ = 2 _fields_ = [('Whole', c_int16), ('Frac', c_uint16)] class TW_FRAME(Structure): _pack_ = 2 _fields_ = [('Left', TW_FIX32), ('Top', TW_FIX32), ('Right', TW_FIX32), ('Bottom', TW_FIX32)] class TW_STATUS(Structure): _pack_ = 2 _fields_ = [('ConditionCode', c_uint16), # Any TWCC_ constant ('Data', c_uint16)] class TW_IMAGELAYOUT(Structure): _pack_ = 2 _fields_ = [('Frame', TW_FRAME), # Any TWCC_ constant ('DocumentNumber', c_uint32), ('PageNumber', c_uint32), ('FrameNumber', c_uint32)] class TW_USERINTERFACE(Structure): _pack_ = 2 _fields_ = [('ShowUI', c_uint16), ('ModalUI', c_uint16), ('hParent', c_void_p)] class MSG(Structure): _pack_ = 8 _fields_ = [('hwnd', c_void_p), ('message', c_uint), ('wParam', c_void_p), ('lParam', c_void_p), ('time', c_uint32), ('pt_x', c_long), ('pt_y', c_long)] class TW_EVENT(Structure): _pack_ = 2 _fields_ = [('pEvent', c_void_p), ('TWMessage', c_uint16)] class TW_VERSION(Structure): _pack_ = 2 _fields_ = [('MajorNum', c_uint16), ('MinorNum', c_uint16), ('Language', c_uint16), ('Country', c_uint16), ('Info', c_char * 34)] class TW_IDENTITY(Structure): _pack_ = 2 _fields_ = [('Id', c_uint32), ('Version', TW_VERSION), ('ProtocolMajor', c_uint16), ('ProtocolMinor', c_uint16), ('SupportedGroups', c_uint32), ('Manufacturer', c_char * 34), ('ProductFamily', c_char * 34), ('ProductName', c_char * 34)] class TW_IMAGEINFO(Structure): _pack_ = 2 _fields_ = [('XResolution', TW_FIX32), ('YResolution', TW_FIX32), ('ImageWidth', c_int32), ('ImageLength', c_int32), ('SamplesPerPixel', c_int16), ('BitsPerSample', c_int16 * 8), ('BitsPerPixel', c_int16), ('Planar', c_uint16), ('PixelType', c_int16), ('Compression', c_uint16)] class TW_PENDINGXFERS(Structure): _pack_ = 2 _fields_ = [('Count', c_uint16), ('EOJ', c_uint32)] class TW_RANGE(Structure): _pack_ = 2 _fields_ = [('ItemType', c_uint16), ('MinValue', c_uint32), ('MaxValue', c_uint32), ('StepSize', c_uint32), ('DefaultValue', c_uint32), ('CurrentValue', c_uint32)] class TW_ENUMERATION(Structure): _pack_ = 2 _fields_ = [('ItemType', c_uint16), ('NumItems', c_uint32), ('CurrentIndex', c_uint32), ('DefaultIndex', c_uint32)] class TW_ARRAY(Structure): _pack_ = 2 _fields_ = [('ItemType', c_uint16), ('NumItems', c_uint32)] class TW_SETUPFILEXFER(Structure): _pack_ = 2 _fields_ = [('FileName', c_char * 256), ('Format', c_uint16), ('VRefNum', c_int16)] class TW_ENTRYPOINT(Structure): _pack_ = 2 _fields_ = [('Size', c_uint32), ('DSM_Entry', WINFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))), ('DSM_MemAllocate', WINFUNCTYPE(c_void_p, c_uint32)), ('DSM_MemFree', WINFUNCTYPE(None, c_void_p)), ('DSM_MemLock', WINFUNCTYPE(c_void_p, c_void_p)), ('DSM_MemUnlock', WINFUNCTYPE(None, c_void_p))] _mapping = {TWTY_INT8: c_int8, TWTY_UINT8: c_uint8, TWTY_INT16: c_int16, TWTY_UINT16: c_uint16, TWTY_UINT32: c_uint32, TWTY_INT32: c_int32, TWTY_BOOL: c_uint16, TWTY_FIX32: TW_FIX32, TWTY_FRAME: TW_FRAME, TWTY_STR32: c_char*34, TWTY_STR64: c_char*66, TWTY_STR128: c_char*130, TWTY_STR255: c_char*255} def _is_good_type(type_id): return type_id in list(_mapping.keys()) def _struct2dict(struct, decode): result = {} for field, _ in struct._fields_: value = getattr(struct, field) if hasattr(value, "_length_") and hasattr(value, "_type_"): # Probably an array value = list(value) elif hasattr(value, "_fields_"): # Probably another struct value = _struct2dict(value, decode) if isinstance(value, bytes): value = decode(value) result[field] = value return result _exc_mapping = {TWCC_SUCCESS: excTWCC_SUCCESS, TWCC_BUMMER: excTWCC_BUMMER, TWCC_LOWMEMORY: MemoryError, TWCC_NODS: excTWCC_NODS, TWCC_OPERATIONERROR: excTWCC_OPERATIONERROR, TWCC_BADCAP: excTWCC_BADCAP, TWCC_BADPROTOCOL: excTWCC_BADPROTOCOL, TWCC_BADVALUE: ValueError, TWCC_SEQERROR: excTWCC_SEQERROR, TWCC_BADDEST: excTWCC_BADDEST, TWCC_CAPUNSUPPORTED: excTWCC_CAPUNSUPPORTED, TWCC_CAPBADOPERATION: excTWCC_CAPBADOPERATION, TWCC_CAPSEQERROR: excTWCC_CAPSEQERROR, TWCC_DENIED: excTWCC_DENIED, TWCC_FILEEXISTS: excTWCC_FILEEXISTS, TWCC_FILENOTFOUND: excTWCC_FILENOTFOUND, TWCC_NOTEMPTY: excTWCC_NOTEMPTY, TWCC_PAPERJAM: excTWCC_PAPERJAM, TWCC_PAPERDOUBLEFEED: excTWCC_PAPERDOUBLEFEED, TWCC_FILEWRITEERROR: excTWCC_FILEWRITEERROR, TWCC_CHECKDEVICEONLINE: excTWCC_CHECKDEVICEONLINE} def _win_check(result, func, args): if func is _GlobalFree: if result: raise WinError() return None elif func is _GlobalUnlock: if not result and GetLastError() != 0: raise WinError() return result elif func is _GetMessage: if result == -1: raise WinError() return result elif func is _TranslateMessage or func is _DispatchMessage: return result else: if not result: raise WinError() return result _GlobalLock = windll.kernel32.GlobalLock _GlobalLock.errcheck = _win_check _GlobalUnlock = windll.kernel32.GlobalUnlock _GlobalUnlock.errcheck = _win_check _GlobalAlloc = windll.kernel32.GlobalAlloc _GlobalAlloc.errcheck = _win_check _GlobalFree = windll.kernel32.GlobalFree _GlobalFree.errcheck = _win_check _GlobalSize = windll.kernel32.GlobalSize _GlobalSize.errcheck = _win_check _GetMessage = windll.user32.GetMessageW _TranslateMessage = windll.user32.TranslateMessage _TranslateMessage.errcheck = _win_check _DispatchMessage = windll.user32.DispatchMessageW _DispatchMessage.errcheck = _win_check GMEM_ZEROINIT = 0x0040 def _twain1_alloc(size): return _GlobalAlloc(GMEM_ZEROINIT, size) _twain1_free = _GlobalFree _twain1_lock = _GlobalLock _twain1_unlock = _GlobalUnlock class _Source(object): def __init__(self, sm, ds_id): self._sm = sm self._id = ds_id self._state = 'open' self._version2 = bool(ds_id.SupportedGroups & DF_DS2) if self._version2: self._alloc = sm._alloc self._free = sm._free self._lock = sm._lock self._unlock = sm._unlock self._encode = sm._encode self._decode = sm._decode else: self._alloc = _twain1_alloc self._free = _twain1_free self._lock = _twain1_lock self._unlock = _twain1_unlock self._encoding = sys.getfilesystemencoding() self._encode = lambda s: s.encode(self._encoding) self._decode = lambda s: s.decode(self._encoding) def __del__(self): self.close() def close(self): '''This method is used to close the data source object. It gives finer control over this connects than relying on garbage collection. ''' if self._state == 'ready': self._end_all_xfers() if self._state == 'enabled': self._disable() if self._state == 'open': self._sm._close_ds(self._id) self._state = 'closed' self._sm = None destroy = close def _call(self, dg, dat, msg, buf, expected_returns=[TWRC_SUCCESS]): return self._sm._call(self._id, dg, dat, msg, buf, expected_returns) def _get_capability(self, cap, current): twCapability = TW_CAPABILITY(cap, TWON_DONTCARE16, 0) self._call(DG_CONTROL, DAT_CAPABILITY, current, byref(twCapability)) try: ptr = self._lock(twCapability.hContainer) try: if twCapability.ConType == TWON_ONEVALUE: type_id = cast(ptr, POINTER(c_uint16))[0] if not _is_good_type(type_id): msg = "Capability Code = %d, Format Code = %d, Item Type = %d" % (cap, twCapability.ConType, type_id) raise excCapabilityFormatNotSupported(msg) ctype = _mapping.get(type_id) val = cast(ptr + 2, POINTER(ctype))[0] if type_id in (TWTY_INT8, TWTY_UINT8, TWTY_INT16, TWTY_UINT16, TWTY_UINT32, TWTY_INT32): pass elif type_id == TWTY_BOOL: val = bool(val) elif type_id == TWTY_FIX32: val = _fix2float(val) elif type_id == TWTY_FRAME: val = _frame2tuple(val) return type_id, val elif twCapability.ConType == TWON_RANGE: rng = cast(ptr, POINTER(TW_RANGE)).contents return {'MinValue': rng.MinValue, 'MaxValue': rng.MaxValue, 'StepSize': rng.StepSize, 'DefaultValue': rng.DefaultValue, 'CurrentValue': rng.CurrentValue} elif twCapability.ConType == TWON_ENUMERATION: enum = cast(ptr, POINTER(TW_ENUMERATION)).contents if not _is_good_type(enum.ItemType): msg = "Capability Code = %d, Format Code = %d, Item Type = %d" % (cap, twCapability.ConType, enum.ItemType) raise excCapabilityFormatNotSupported(msg); ctype = _mapping[enum.ItemType] item_p = cast(ptr + sizeof(TW_ENUMERATION), POINTER(ctype)) values = [el for el in item_p[0:enum.NumItems]] return enum.ItemType, (enum.CurrentIndex, enum.DefaultIndex, values) elif twCapability.ConType == TWON_ARRAY: arr = cast(ptr, POINTER(TW_ARRAY)).contents if not _is_good_type(arr.ItemType): msg = "Capability Code = %d, Format Code = %d, Item Type = %d" % (cap, twCapability.ConType, arr.ItemType) raise excCapabilityFormatNotSupported(msg); ctype = _mapping[arr.ItemType] item_p = cast(ptr + sizeof(TW_ARRAY), POINTER(ctype)) return arr.ItemType, [el for el in item_p[0:arr.NumItems]] else: msg = "Capability Code = %d, Format Code = %d" % (cap, twCapability.ConType) raise excCapabilityFormatNotSupported(msg) finally: self._unlock(twCapability.hContainer) finally: self._free(twCapability.hContainer) def GetCapability(self, cap): '''This function is used to return the capability information from the source. If the capability is not supported, an exception should be returned. Capabilities are returned as a tuple of a type (TWTY_*) and a value. The format of values depends on their container type. Capabilities can be in any of the following containers: singleton, range, enumerator or array. singletons are returned as a single value (integer or string) ranges are returned as a tuple dictionary containing MinValue, MaxValue, StepSize, DefaultValue and CurrentValue enumerators and arrays are returned as tuples, each containing a list which has the actual values ''' return self._get_capability(cap, MSG_GET) def get_capability_current(self, cap): '''This function is used to return the current value of a capability from the source. If the capability is not supported, an exception should be returned. Capabilities are returned as a tuple of a type (TWTY_*) and a value. The format of values depends on their container type. Capabilities can be in any of the following containers: singleton, range, enumerator or array. singletons are returned as a single value (integer or string) ranges are returned as a tuple dictionary containing MinValue, MaxValue, StepSize, DefaultValue and CurrentValue enumerators and arrays are returned as tuples, each containing a list which has the actual values ''' return self._get_capability(cap, MSG_GETCURRENT) def get_capability_default(self, cap): '''This function is used to return the default value of a capability from the source. If the capability is not supported, an exception should be returned. Capabilities are returned as a tuple of a type (TWTY_*) and a value. The format of values depends on their container type. Capabilities can be in any of the following containers: singleton, range, enumerator or array. singletons are returned as a single value (integer or string) ranges are returned as a tuple dictionary containing MinValue, MaxValue, StepSize, DefaultValue and CurrentValue enumerators and arrays are returned as tuples, each containing a list which has the actual values ''' return self._get_capability(cap, MSG_GETDEFAULT) @property def name(self): '''Get the name of the source. This can be used later for connecting to the same source. ''' return self._decode(self._id.ProductName) @property def identity(self): '''This function is used to retrieve information about the source. driver. The information is returned in a dictionary. ''' res = _struct2dict(self._id, self._decode) res.update(_struct2dict(self._id.Version, self._decode)) return res def set_capability(self, cap, type_id, value): '''This function is used to set the value of a capability in the source. Three parameters are required, a Capability Identifier (twain.CAP_* or twain.ICAP_*) a value type (twain.TWTY_*) and a value If the capability is not supported, an exception should be returned. This function is used to set a value using a TW_ONEVALUE. ''' if not _is_good_type(type_id): raise excCapabilityFormatNotSupported("Capability Code = %d, Format Code = %d" % (cap, type_id)) ctype = _mapping[type_id] if type_id in (TWTY_INT8, TWTY_INT16, TWTY_INT32, TWTY_UINT8, TWTY_UINT16, TWTY_UINT32, TWTY_BOOL): cval = ctype(value) elif type_id in (TWTY_STR32, TWTY_STR64, TWTY_STR128, TWTY_STR255): cval = ctype(self._encode(value)) elif type_id == TWTY_FIX32: cval = _float2fix(value) elif type_id == TWTY_FRAME: cval = _tuple2frame(value) else: assert 0, 'invalid case' handle = self._alloc(sizeof(TW_ONEVALUE) + sizeof(ctype)) try: ptr = self._lock(handle) try: cast(ptr, POINTER(c_uint16))[0] = type_id cast(ptr + 2, POINTER(ctype))[0] = cval finally: self._unlock(handle) capability = TW_CAPABILITY(cap, TWON_ONEVALUE, handle) rv = self._call(DG_CONTROL, DAT_CAPABILITY, MSG_SET, byref(capability), [TWRC_CHECKSTATUS]) finally: self._free(handle) if rv == TWRC_CHECKSTATUS: raise CheckStatus def reset_capability(self, cap): '''This function is used to reset the value of a capability to the source default. One parameter is required, a Capability Identifier (twain.CAP_* or twain.ICAP_*).''' cap = TW_CAPABILITY(Cap=cap) self._call(DG_CONTROL, DAT_CAPABILITY, MSG_RESET, byref(cap)) def set_image_layout(self, frame, document_number=1, page_number=1, frame_number=1): '''This function is used to inform the source of the Image Layout. It uses a tuple containing frame coordinates, document number, page number, frame number. ''' il = TW_IMAGELAYOUT(Frame=_tuple2frame(frame), DocumentNumber=document_number, PageNumber=page_number, FrameNumber=frame_number) rv = self._call(DG_IMAGE, DAT_IMAGELAYOUT, MSG_SET, byref(il), (TWRC_SUCCESS, TWRC_CHECKSTATUS)) if rv == TWRC_CHECKSTATUS: raise CheckStatus def get_image_layout(self): '''This function is used to ask the source for Image Layout. It returns a tuple containing frame coordinates, document number, page number, frame number. Valid states 4 through 6 ''' il = TW_IMAGELAYOUT() self._call(DG_IMAGE, DAT_IMAGELAYOUT, MSG_GET, byref(il)) return _frame2tuple(il.Frame), il.DocumentNumber, il.PageNumber, il.FrameNumber def get_image_layout_default(self): '''This function is used to ask the source for default Image Layout. It returns a tuple containing frame coordinates, document number, page number, frame number. Valid states 4 through 6 ''' il = TW_IMAGELAYOUT() self._call(DG_IMAGE, DAT_IMAGELAYOUT, MSG_GETDEFAULT, byref(il)) return _frame2tuple(il.Frame), il.DocumentNumber, il.PageNumber, il.FrameNumber def reset_image_layout(self): '''This function is used to reset Image Layout to its default settings''' il = TW_IMAGELAYOUT() self._call(DG_IMAGE, DAT_IMAGELAYOUT, MSG_RESET, byref(il)) def _enable(self, show_ui, modal_ui, hparent): '''This function is used to ask the source to begin aquistion. Parameters: show_ui - bool modal_ui - bool ''' ui = TW_USERINTERFACE(ShowUI=show_ui, ModalUI=modal_ui, hParent=hparent) self._call(DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, byref(ui)) self._state = 'enabled' def _disable(self): '''This function is used to ask the source to hide the user interface.''' ui = TW_USERINTERFACE() self._call(DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, byref(ui)) self._state = 'open' def _process_event(self, msg_ref): '''The TWAIN interface requires that the windows events are available to both the application and the twain source (which operates in the same process). This method is called in the event loop to pass on those events. ''' event = TW_EVENT(cast(msg_ref, c_void_p), 0) rv = self._call(DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, byref(event), (TWRC_DSEVENT, TWRC_NOTDSEVENT)) if event.TWMessage == MSG_XFERREADY: self._state = 'ready' return rv, event.TWMessage def _modal_loop(self, callback): done = False msg = MSG() while not done: if not _GetMessage(byref(msg), 0, 0, 0): break rc, event = self._process_event(byref(msg)) if callback: callback(event) if event in (MSG_XFERREADY, MSG_CLOSEDSREQ): done = True if rc == TWRC_NOTDSEVENT: _TranslateMessage(byref(msg)) _DispatchMessage(byref(msg)) def _acquire(self, callback, show_ui=True, modal=False): self._enable(show_ui, modal, self._sm._hwnd) try: def callback_lolevel(event): if event == MSG_XFERREADY: more = True while more: try: more = callback() except CancelAll: self._end_all_xfers() break self._modal_loop(callback_lolevel) finally: self._disable() @property def file_xfer_params(self): '''Retrieve the configured transfer file name / format''' sfx = TW_SETUPFILEXFER() self._call(DG_CONTROL, DAT_SETUPFILEXFER, MSG_GET, byref(sfx)) return self._decode(sfx.FileName), sfx.Format @file_xfer_params.setter def file_xfer_params(self, params): '''Where the application is transferring the data via a file, configure the file name. Parameters: params -- tuple (path, format), path should absolute file path format should be one of TWFF_* constants ''' (path, fmt) = params sfx = TW_SETUPFILEXFER(self._encode(path), fmt, 0) self._call(DG_CONTROL, DAT_SETUPFILEXFER, MSG_SET, byref(sfx)) @property def image_info(self): '''This function is used to ask the source for Image Info. Normally, the application is notified that the image is ready for transfer using the message loop. However, it is hard to get at the message loop in toolkits such as wxPython. As an alternative, I poll the source looking for image information. When the image information is available, the image is ready for transfer ''' ii = TW_IMAGEINFO() self._call(DG_IMAGE, DAT_IMAGEINFO, MSG_GET, byref(ii)); return {"XResolution": _fix2float(ii.XResolution), "YResolution": _fix2float(ii.YResolution), "ImageWidth": ii.ImageWidth, "ImageLength": ii.ImageLength, "SamplesPerPixel": ii.SamplesPerPixel, "BitsPerSample": list(ii.BitsPerSample), "BitsPerPixel": ii.BitsPerPixel, "Planar": ii.Planar, "PixelType": ii.PixelType, "Compression": ii.Compression} def _get_native_image(self): hbitmap = c_void_p() rv = self._call(DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, byref(hbitmap), (TWRC_XFERDONE, TWRC_CANCEL)) return rv, hbitmap def _get_file_image(self): return self._call(DG_IMAGE, DAT_IMAGEFILEXFER, MSG_GET, None, (TWRC_XFERDONE, TWRC_CANCEL)) def _get_file_audio(self): return self._call(DG_AUDIO, DAT_AUDIOFILEXFER, MSG_GET, None, (TWRC_XFERDONE, TWRC_CANCEL)) def _end_xfer(self): px = TW_PENDINGXFERS() self._call(DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, byref(px)) if px.Count == 0: self._state = 'enabled' return px.Count def _end_all_xfers(self): '''Cancel all outstanding transfers on the data source.''' px = TW_PENDINGXFERS() self._call(DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, byref(px)) self._state = 'enabled' def RequestAcquire(self, ShowUI, ModalUI): '''This function is used to ask the source to begin acquisition. Parameters: ShowUI - bool (default 1) ModalUI - bool (default 1) ''' self._enable(ShowUI, ModalUI, self._sm._hwnd) def ModalLoop(self): '''This function should be called after call to RequiestAcquire it will return after acquisition complete. ''' self._modal_loop(self._sm._cb) def HideUI(self): '''This function is used to ask the source to hide the user interface.''' self._disable() def XferImageNatively(self): '''Perform a 'Native' form transfer of the image. When successful, this routine returns two values, an image handle and a count of the number of images remaining in the source. ''' rv, handle = self._get_native_image() more = self._end_xfer() if rv == TWRC_CANCEL: raise excDSTransferCancelled return handle.value, more def XferImageByFile(self): '''Perform a file based transfer of the image. When successful, the file is saved to the image file, defined in a previous call to SetXferFileName. Returns the number of pending transfers ''' rv = self._get_file_image() more = self._end_xfer() if rv == TWRC_CANCEL: raise excDSTransferCancelled return more def acquire_file(self, before, after=lambda more: None, show_ui=True, modal=False,dpi=None): '''Acquires one ore more images as files. Call returns when acquisition complete Parameters: before -- callback called before each acquired file, it should return full file path. It can also throw CancelAll to cancel acquisition after -- callback called after each acquired file, it receives number of images remaining. It can throw CancelAll to cancel remaining acquisition show_ui -- if True source's UI will be presented to user modal -- if True source's UI will be modal ''' if dpi is None: dpi = 96 _, (_, _, mechs) = self.GetCapability(ICAP_XFERMECH) if TWSX_FILE not in mechs: raise Exception('File transfer is not supported') def callback(): filepath = before(self.image_info) import os _, ext = os.path.splitext(filepath) ext = ext.lower() if ext != '.bmp': import tempfile handle, bmppath = tempfile.mkstemp('.bmp') os.close(handle) else: bmppath = filepath self.file_xfer_params = bmppath, TWFF_BMP rv = self._get_file_image() more = self._end_xfer() if rv == TWRC_CANCEL: raise excDSTransferCancelled if ext != '.bmp': # import Image Image.open(bmppath).save(filepath,dpi=(dpi,dpi)) os.remove(bmppath) after(more) return more self.set_capability(ICAP_XFERMECH, TWTY_UINT16, TWSX_FILE) self._acquire(callback, show_ui, modal) def acquire_natively(self, after, before=lambda img_info: None, show_ui=True, modal=False): '''Acquires one ore more images in memory. Call returns when acquisition complete Parameters: before -- callback called before each acquired file. It can throw CancelAll to cancel acquisition after -- callback called after each acquired file, it receives an image object and number of images remaining. It can throw CancelAll to cancel remaining acquisition show_ui -- if True source's UI will be presented to user modal -- if True source's UI will be modal ''' def callback(): before(self.image_info) rv, handle = self._get_native_image() more = self._end_xfer() if rv == TWRC_CANCEL: raise excDSTransferCancelled after(_Image(handle), more) return more self.set_capability(ICAP_XFERMECH, TWTY_UINT16, TWSX_NATIVE) self._acquire(callback, show_ui, modal) def is_twain2(self): return self._version2 GetCapabilityCurrent = get_capability_current GetCapabilityDefault = get_capability_default def GetSourceName(self): return self.name def GetIdentity(self): return self.identity SetCapability = set_capability ResetCapability = reset_capability SetImageLayout = set_image_layout GetImageLayout = get_image_layout GetDefaultImageLayout = get_image_layout_default ResetImageLayout = reset_image_layout def SetXferFileName(self, path, format): self.file_xfer_params = path, format def GetXferFileName(self): return self.file_xfer_params def GetImageInfo(self): return self.image_info class SourceManager(object): '''Represents a Data Source Manager connection''' def __init__(self, parent_window, MajorNum = 1, MinorNum = 0, Language = TWLG_USA, Country = TWCY_USA, Info = "", ProductName = "TWAIN Python Interface", ProtocolMajor = TWON_PROTOCOLMAJOR, ProtocolMinor = TWON_PROTOCOLMINOR, SupportedGroups = DG_IMAGE | DG_CONTROL, Manufacturer = "Kevin Gill", ProductFamily = "TWAIN Python Interface", dsm_name = None): '''Constructor for a TWAIN Source Manager Object. This constructor has one position argument, parent_window, which should contain Tk, Wx or Gtk window object or the windows handle of the main window. The following are the named parameters parent_window mandatory MajorNum default = 1 MinorNum default = 0 Language default = TWLG_USA Country default = TWCY_USA Info default = 'TWAIN Python Interface 1.0.0.0 10/02/2002' ProductName default = 'TWAIN Python Interface' ProtocolMajor default = TWON_PROTOCOLMAJOR ProtocolMinor default = TWON_PROTOCOLMINOR SupportedGroups default = DG_IMAGE | DG_CONTROL Manufacturer default = 'Kevin Gill' ProductFamily default = 'TWAIN Python Interface' ''' self._sources = weakref.WeakSet() self._cb = None self._state = 'closed' self._parent_window = parent_window if hasattr(parent_window, 'winfo_id'): # tk window self._hwnd = parent_window.winfo_id() elif hasattr(parent_window, 'GetHandle'): # wx window self._hwnd = parent_window.GetHandle() elif hasattr(parent_window, 'window') and hasattr(parent_window.window, 'handle'): # gtk window self._hwnd = parent_window.window.handle else: self._hwnd = int(parent_window) try: if dsm_name: twain_dll = WinDLL(dsm_name) else: dsm_name = 'twaindsm.dll' try: twain_dll = WinDLL(dsm_name) except WindowsError: dsm_name = 'twain_32.dll' twain_dll = WinDLL(dsm_name) except WindowsError as e: raise excSMLoadFileFailed(e) try: self._entry = twain_dll[1] except AttributeError as e: raise excSMGetProcAddressFailed(e) self._entry.restype = c_uint16 self._entry.argtypes = (POINTER(TW_IDENTITY), POINTER(TW_IDENTITY), c_uint32, c_uint16, c_uint16, c_void_p) self._app_id = TW_IDENTITY(Version=TW_VERSION(MajorNum=MajorNum, MinorNum=MinorNum, Language=Language, Country=Country, Info=Info.encode('utf8')), ProtocolMajor=ProtocolMajor, ProtocolMinor=ProtocolMinor, SupportedGroups=SupportedGroups | DF_APP2, Manufacturer=Manufacturer.encode('utf8'), ProductFamily=ProductFamily.encode('utf8'), ProductName=ProductName.encode('utf8')) rv = self._entry(self._app_id, None, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, byref(c_void_p(self._hwnd))) if rv != TWRC_SUCCESS: raise excSMOpenFailed("[%s], return code %d" % (dsm_name, rv)) self._version2 = bool(self._app_id.SupportedGroups & DF_DSM2) if self._version2: entrypoint = TW_ENTRYPOINT(Size=sizeof(TW_ENTRYPOINT)) rv = self._entry(self._app_id, None, DG_CONTROL, DAT_ENTRYPOINT, MSG_GET, byref(entrypoint)) if rv != TWRC_SUCCESS: raise excSMOpenFailed("[%s], return code %d from DG_CONTROL DAT_ENTRYPOINT MSG_GET" % (dsm_name, rv)) self._alloc = entrypoint.DSM_MemAllocate self._free = entrypoint.DSM_MemFree self._lock = entrypoint.DSM_MemLock self._unlock = entrypoint.DSM_MemUnlock self._encode = lambda s: s.encode('utf8') self._decode = lambda s: s.decode('utf8') else: self._alloc = _twain1_alloc self._free = _twain1_free self._lock = _twain1_lock self._unlock = _twain1_unlock self._encoding = sys.getfilesystemencoding() self._encode = lambda s: s.encode(self._encoding) self._decode = lambda s: s.decode(self._encoding) self._state = 'open' def __del__(self): self.close() def close(self): '''This method is used to force the SourceManager to close down. It is provided for finer control than letting garbage collection drop the connections. ''' while self._sources: self._sources.pop().close() if self._state == 'open': self._call(None, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, byref(c_void_p(self._hwnd))) self._state = 'closed' destroy = close def _call(self, dest_id, dg, dat, msg, buf, expected_returns=[]): rv = self._entry(self._app_id, dest_id, dg, dat, msg, buf) if rv == TWRC_SUCCESS or rv in expected_returns: return rv elif rv == TWRC_FAILURE: status = TW_STATUS() rv = self._entry(self._app_id, dest_id, DG_CONTROL, DAT_STATUS, MSG_GET, byref(status)); if rv != TWRC_SUCCESS: raise Exception('DG_CONTROL DAT_STATUS MSG_GET returned non success code, rv = %d' % rv) code = status.ConditionCode exc = _exc_mapping.get(code, excTWCC_UNKNOWN("ConditionCode = %d" % code)) raise exc else: raise Exception('Unexpected result: %d' % rv) def _user_select(self): ds_id = TW_IDENTITY() rv = self._call(None, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT, byref(ds_id), (TWRC_SUCCESS, TWRC_CANCEL)) if rv == TWRC_SUCCESS: return ds_id elif rv == TWRC_CANCEL: return None def _open_ds(self, ds_id): self._call(None, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, byref(ds_id)) def _close_ds(self, ds_id): self._call(None, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, byref(ds_id)) def open_source(self, product_name=None): '''Open a TWAIN Source. Returns a Source Object, which can be used to communicate with the source There is one optional string parameter, which allows the application to name the source to be opened, i.e. to open from application configuration ''' if not product_name: ds_id = self._user_select() if not ds_id: return None else: ds_id = TW_IDENTITY(ProductName=product_name) self._open_ds(ds_id) source = _Source(self, ds_id) self._sources.add(source) return source @property def identity(self): '''This function is used to retrieve the identity of our application. The information is returned in a dictionary. ''' res = _struct2dict(self._app_id, self._decode) res.update(_struct2dict(self._app_id.Version, self._decode)) return res @property def source_list(self): '''Returns a list containing the names of the available source.''' names = [] ds_id = TW_IDENTITY() rv = self._call(None, DG_CONTROL, DAT_IDENTITY, MSG_GETFIRST, byref(ds_id), (TWRC_SUCCESS, TWRC_ENDOFLIST)) while (rv != TWRC_ENDOFLIST): names.append(self._decode(ds_id.ProductName)) rv = self._call(None, DG_CONTROL, DAT_IDENTITY, MSG_GETNEXT, byref(ds_id), (TWRC_SUCCESS, TWRC_ENDOFLIST)) return names def SetCallback(self, cb): '''Register a python function to be used for notification that the transfer is ready, etc. ''' self._cb = cb def is_twain2(self): return self._version2 OpenSource = open_source def GetIdentity(self): return self.identity def GetSourceList(self): return self.source_list def version(): '''Retrieve the version of the Python Twain Interface''' return '2.0' Version = version class BITMAPINFOHEADER(Structure): _pack_ = 4 _fields_ = [('biSize', c_uint32), ('biWidth', c_long), ('biHeight', c_long), ('biPlanes', c_uint16), ('biBitCount', c_uint16), ('biCompression', c_uint32), ('biSizeImage', c_uint32), ('biXPelsPerMeter', c_long), ('biYPelsPerMeter', c_long), ('biClrUsed', c_uint32), ('biClrImportant', c_uint32)] def _dib_write(handle, path, lock, unlock): file_header_size = 14 ptr = lock(handle) try: char_ptr = cast(ptr, POINTER(c_char)) bih = cast(ptr, POINTER(BITMAPINFOHEADER)).contents if bih.biCompression != 0: msg = 'Cannot handle compressed image. Compression Format %d' % bih.biCompression raise excImageFormat(msg) bits_offset = file_header_size + bih.biSize + bih.biClrUsed * 4 if bih.biSizeImage == 0: row_bytes = (((bih.biWidth * bih.biBitCount) + 31) & ~31) / 8; bih.biSizeImage = row_bytes * bih.biHeight dib_size = bih.biSize + bih.biClrUsed * 4 + bih.biSizeImage file_size = dib_size + file_header_size def _write_bmp(f): import struct f.write(b'BM') f.write(struct.pack('LHHL', file_size, 0, 0, bits_offset)) for i in range(dib_size): f.write(char_ptr[i]) if path: f = open(path, 'wb') try: _write_bmp(f) finally: f.close() else: import io f = io.StringIO(file_size) try: _write_bmp(f) return f.getvalue() finally: f.close() finally: unlock(handle) def DIBToBMFile(handle, path=None): '''Convert a DIB (Device Independent Bitmap) to a windows bitmap file format. The BitMap file is either returned as a string, or written to a file with the name given in the second argument. Can only be used with twain 1.x sources ''' return _dib_write(handle, path, _GlobalLock, _GlobalUnlock) def DIBToXBMFile(handle, path=None): '''Convert a DIB (Device Independent Bitmap) to an X-Windows bitmap file (XBM format). The XBM file is either returned as a string, or written to a file with the name given in the third argument. Parameters: a handle to a global area containing a DIB, a prefix to be used for the name and an optional filename for file only output. Can only be used with twain 1.x sources ''' import tempfile import os handle, bmppath = tempfile.mkstemp('.bmp') os.close(handle) DIBToBMFile(handle, bmppath) # import Image Image.open(bmppath).save(path, 'xbm') os.remove(bmppath) def GlobalHandleGetBytes(handle, offset, count): '''Read a specified number of bytes from a global handle. The following parameters are required Handle - a global handle Offset - an index into the handle data Count - The number of bytes to be returned Can only be used with twain 1.x sources ''' size = _GlobalSize(handle) ptr = _GlobalLock(handle) try: char_ptr = cast(ptr, POINTER(c_char)) return char_ptr[min(offset, size) : min(offset + count, size)] finally: _GlobalUnlock(handle) def GlobalHandlePutBytes(handle, offset, count, data): '''Write a specified number of bytes to a global handle. The following parameters are required Handle - a global handle Offset - an index into the handle data Count - The number of bytes to be returned Data - String of data to be written Can only be used with twain 1.x sources ''' size = _GlobalSize(handle) ptr = _GlobalLock(handle) try: char_ptr = cast(ptr, POINTER(c_char)) offset = min(offset, size) end = min(offset + count, size) count = end - offset count = min(count, len(data)) for i in range(count): char_ptr[i + offset] = data[i] finally: _GlobalUnlock(handle) '''Allocate a specified number of bytes via a global handle. The following parameters are required Size - The number of bytes to be allocated Can only be used with twain 1.x sources ''' GlobalHandleAllocate = _GlobalAlloc '''Free an allocated heap section via the global handle. The following parameters are required handle - The number of bytes to be allocated Can only be used with twain 1.x sources ''' GlobalHandleFree = _GlobalFree def acquire(path, ds_name=None, dpi=None, pixel_type=None, bpp=None, frame=None, parent_window=None, show_ui=False, dsm_name=None): '''Acquires single image into file Required argument: path -- path where to save image Keyword arguments: ds_name -- name of twain data source, if not provided user will be presented with selection dialog dpi -- resolution in dots per inch pixel_type -- can be 'bw', 'gray', 'color' bpp -- bits per pixel frame -- tuple (left, top, right, bottom) scan area in inches parent_window -- can be Tk, Wx, Gtk window object or Win32 window handle show_ui -- if True source's UI dialog will be presented to user Returns a dictionary describing image, or None if scanning was cancelled by user ''' if pixel_type: pixel_type_map = {'bw': TWPT_BW, 'gray': TWPT_GRAY, 'color': TWPT_RGB} twain_pixel_type = pixel_type_map[pixel_type] if not parent_window: from tkinter import Tk parent_window = Tk() sm = SourceManager(parent_window, dsm_name=dsm_name) try: sd = sm.open_source(ds_name) if not sd: return None try: if pixel_type: sd.set_capability(ICAP_PIXELTYPE, TWTY_UINT16, twain_pixel_type) sd.set_capability(ICAP_UNITS, TWTY_UINT16, TWUN_INCHES) if bpp: sd.set_capability(ICAP_BITDEPTH, TWTY_UINT16, bpp) if dpi: sd.set_capability(ICAP_XRESOLUTION, TWTY_FIX32, dpi) sd.set_capability(ICAP_YRESOLUTION, TWTY_FIX32, dpi) if frame: try: sd.set_image_layout(frame) except CheckStatus: pass res = [] def before(img_info): res.append(img_info) return path def after(more): if more: raise CancelAll try: sd.acquire_file(before=before, after=after, show_ui=show_ui, dpi=dpi) except excDSTransferCancelled: return None finally: sd.close() finally: sm.close() return res[0]