mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	This is, I guess, a few things the Python devs were just fed up with.
* "while 1" is now "while True"
* Types are compared with isinstance instead of ==
* Sort a list in one go with sorted()
My own twist is to also replace type('') with str, type(()) with tuple,
type([]) with list, type(1) with int, and type(5000.0) with float.
		
	
			
		
			
				
	
	
		
			289 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # sane.py
 | |
| #
 | |
| # Python wrapper on top of the _sane module, which is in turn a very
 | |
| # thin wrapper on top of the SANE library.  For a complete understanding
 | |
| # of SANE, consult the documentation at the SANE home page:
 | |
| # http://www.mostang.com/sane/ .
 | |
| 
 | |
| __version__ = '2.0'
 | |
| __author__  = ['Andrew Kuchling', 'Ralph Heinkel']
 | |
| 
 | |
| from PIL import Image
 | |
| 
 | |
| import _sane
 | |
| from _sane import *
 | |
| 
 | |
| TYPE_STR = { TYPE_BOOL:   "TYPE_BOOL",   TYPE_INT:    "TYPE_INT",
 | |
|              TYPE_FIXED:  "TYPE_FIXED",  TYPE_STRING: "TYPE_STRING",
 | |
|              TYPE_BUTTON: "TYPE_BUTTON", TYPE_GROUP:  "TYPE_GROUP" }
 | |
| 
 | |
| UNIT_STR = { UNIT_NONE:        "UNIT_NONE",
 | |
|              UNIT_PIXEL:       "UNIT_PIXEL",
 | |
|              UNIT_BIT:         "UNIT_BIT",
 | |
|              UNIT_MM:          "UNIT_MM",
 | |
|              UNIT_DPI:         "UNIT_DPI",
 | |
|              UNIT_PERCENT:     "UNIT_PERCENT",
 | |
|              UNIT_MICROSECOND: "UNIT_MICROSECOND" }
 | |
| 
 | |
| 
 | |
| class Option:
 | |
|     """Class representing a SANE option.
 | |
|     Attributes:
 | |
|     index -- number from 0 to n, giving the option number
 | |
|     name -- a string uniquely identifying the option
 | |
|     title -- single-line string containing a title for the option
 | |
|     desc -- a long string describing the option; useful as a help message
 | |
|     type -- type of this option.  Possible values: TYPE_BOOL,
 | |
|             TYPE_INT, TYPE_STRING, and so forth.
 | |
|     unit -- units of this option.  Possible values: UNIT_NONE,
 | |
|             UNIT_PIXEL, etc.
 | |
|     size -- size of the value in bytes
 | |
|     cap -- capabilities available; CAP_EMULATED, CAP_SOFT_SELECT, etc.
 | |
|     constraint -- constraint on values.  Possible values:
 | |
|         None : No constraint
 | |
|         (min,max,step)  Integer values, from min to max, stepping by
 | |
|         list of integers or strings: only the listed values are allowed
 | |
|     """
 | |
| 
 | |
|     def __init__(self, args, scanDev):
 | |
|         self.scanDev = scanDev # needed to get current value of this option
 | |
|         self.index, self.name = args[0], args[1]
 | |
|         self.title, self.desc = args[2], args[3]
 | |
|         self.type, self.unit  = args[4], args[5]
 | |
|         self.size, self.cap   = args[6], args[7]
 | |
|         self.constraint = args[8]
 | |
|         def f(x):
 | |
|             if x=='-': return '_'
 | |
|             else: return x
 | |
|         if not isinstance(self.name, str): self.py_name=str(self.name)
 | |
|         else: self.py_name=''.join(map(f, self.name))
 | |
| 
 | |
|     def is_active(self):
 | |
|         return _sane.OPTION_IS_ACTIVE(self.cap)
 | |
|     def is_settable(self):
 | |
|         return _sane.OPTION_IS_SETTABLE(self.cap)
 | |
|     def __repr__(self):
 | |
|         if self.is_settable():
 | |
|             settable = 'yes'
 | |
|         else:
 | |
|             settable = 'no'
 | |
|         if self.is_active():
 | |
|             active = 'yes'
 | |
|             curValue = repr(getattr(self.scanDev, self.py_name))
 | |
|         else:
 | |
|             active = 'no'
 | |
|             curValue = '<not available, inactive option>'
 | |
|         s = """\nName:      %s
 | |
| Cur value: %s
 | |
| Index:     %d
 | |
| Title:     %s
 | |
| Desc:      %s
 | |
| Type:      %s
 | |
| Unit:      %s
 | |
| Constr:    %s
 | |
| active:    %s
 | |
| settable:  %s\n""" % (self.py_name, curValue,
 | |
|                       self.index, self.title, self.desc,
 | |
|                       TYPE_STR[self.type], UNIT_STR[self.unit],
 | |
|                       repr(self.constraint), active, settable)
 | |
|         return s
 | |
| 
 | |
| 
 | |
| class _SaneIterator:
 | |
|     """ intended for ADF scans.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, device):
 | |
|         self.device = device
 | |
| 
 | |
|     def __iter__(self):
 | |
|         return self
 | |
| 
 | |
|     def __del__(self):
 | |
|         self.device.cancel()
 | |
| 
 | |
|     def next(self):
 | |
|         try:
 | |
|             self.device.start()
 | |
|         except error as v:
 | |
|             if v == 'Document feeder out of documents':
 | |
|                 raise StopIteration
 | |
|             else:
 | |
|                 raise
 | |
|         return self.device.snap(1)
 | |
| 
 | |
| 
 | |
| 
 | |
| class SaneDev:
 | |
|     """Class representing a SANE device.
 | |
|     Methods:
 | |
|     start()    -- initiate a scan, using the current settings
 | |
|     snap()     -- snap a picture, returning an Image object
 | |
|     arr_snap() -- snap a picture, returning a numarray object
 | |
|     cancel()   -- cancel an in-progress scanning operation
 | |
|     fileno()   -- return the file descriptor for the scanner (handy for select)
 | |
| 
 | |
|     Also available, but rather low-level:
 | |
|     get_parameters() -- get the current parameter settings of the device
 | |
|     get_options()    -- return a list of tuples describing all the options.
 | |
| 
 | |
|     Attributes:
 | |
|     optlist -- list of option names
 | |
| 
 | |
|     You can also access an option name to retrieve its value, and to
 | |
|     set it.  For example, if one option has a .name attribute of
 | |
|     imagemode, and scanner is a SaneDev object, you can do:
 | |
|          print scanner.imagemode
 | |
|          scanner.imagemode = 'Full frame'
 | |
|          scanner.['imagemode'] returns the corresponding Option object.
 | |
|     """
 | |
|     def __init__(self, devname):
 | |
|         d=self.__dict__
 | |
|         d['sane_signature'] = self._getSaneSignature(devname)
 | |
|         d['scanner_model']  = d['sane_signature'][1:3]
 | |
|         d['dev'] = _sane._open(devname)
 | |
|         self.__load_option_dict()
 | |
| 
 | |
|     def _getSaneSignature(self, devname):
 | |
|         devices = get_devices()
 | |
|         if not devices:
 | |
|             raise RuntimeError('no scanner available')
 | |
|         for dev in devices:
 | |
|             if devname == dev[0]:
 | |
|                 return dev
 | |
|         raise RuntimeError('no such scan device "%s"' % devname)
 | |
| 
 | |
|     def __load_option_dict(self):
 | |
|         d=self.__dict__
 | |
|         d['opt']={}
 | |
|         optlist=d['dev'].get_options()
 | |
|         for t in optlist:
 | |
|             o=Option(t, self)
 | |
|             if o.type!=TYPE_GROUP:
 | |
|                 d['opt'][o.py_name]=o
 | |
| 
 | |
|     def __setattr__(self, key, value):
 | |
|         dev=self.__dict__['dev']
 | |
|         optdict=self.__dict__['opt']
 | |
|         if key not in optdict:
 | |
|             self.__dict__[key]=value ; return
 | |
|         opt=optdict[key]
 | |
|         if opt.type==TYPE_GROUP:
 | |
|             raise AttributeError("Groups can't be set: "+key)
 | |
|         if not _sane.OPTION_IS_ACTIVE(opt.cap):
 | |
|             raise AttributeError('Inactive option: '+key)
 | |
|         if not _sane.OPTION_IS_SETTABLE(opt.cap):
 | |
|             raise AttributeError("Option can't be set by software: "+key)
 | |
|         if isinstance(value, int) and opt.type == TYPE_FIXED:
 | |
|             # avoid annoying errors of backend if int is given instead float:
 | |
|             value = float(value)
 | |
|         self.last_opt = dev.set_option(opt.index, value)
 | |
|         # do binary AND to find if we have to reload options:
 | |
|         if self.last_opt & INFO_RELOAD_OPTIONS:
 | |
|             self.__load_option_dict()
 | |
| 
 | |
|     def __getattr__(self, key):
 | |
|         dev=self.__dict__['dev']
 | |
|         optdict=self.__dict__['opt']
 | |
|         if key=='optlist':
 | |
|             return list(self.opt.keys())
 | |
|         if key=='area':
 | |
|             return (self.tl_x, self.tl_y),(self.br_x, self.br_y)
 | |
|         if key not in optdict:
 | |
|             raise AttributeError('No such attribute: '+key)
 | |
|         opt=optdict[key]
 | |
|         if opt.type==TYPE_BUTTON:
 | |
|             raise AttributeError("Buttons don't have values: "+key)
 | |
|         if opt.type==TYPE_GROUP:
 | |
|             raise AttributeError("Groups don't have values: "+key)
 | |
|         if not _sane.OPTION_IS_ACTIVE(opt.cap):
 | |
|             raise AttributeError('Inactive option: '+key)
 | |
|         value = dev.get_option(opt.index)
 | |
|         return value
 | |
| 
 | |
|     def __getitem__(self, key):
 | |
|         return self.opt[key]
 | |
| 
 | |
|     def get_parameters(self):
 | |
|         """Return a 5-tuple holding all the current device settings:
 | |
|    (format, last_frame, (pixels_per_line, lines), depth, bytes_per_line)
 | |
| 
 | |
| - format is one of 'L' (grey), 'RGB', 'R' (red), 'G' (green), 'B' (blue).
 | |
| - last_frame [bool] indicates if this is the last frame of a multi frame image
 | |
| - (pixels_per_line, lines) specifies the size of the scanned image (x,y)
 | |
| - lines denotes the number of scanlines per frame
 | |
| - depth gives number of pixels per sample
 | |
| """
 | |
|         return self.dev.get_parameters()
 | |
| 
 | |
|     def get_options(self):
 | |
|         "Return a list of tuples describing all the available options"
 | |
|         return self.dev.get_options()
 | |
| 
 | |
|     def start(self):
 | |
|         "Initiate a scanning operation"
 | |
|         return self.dev.start()
 | |
| 
 | |
|     def cancel(self):
 | |
|         "Cancel an in-progress scanning operation"
 | |
|         return self.dev.cancel()
 | |
| 
 | |
|     def snap(self, no_cancel=0):
 | |
|         "Snap a picture, returning a PIL image object with the results"
 | |
|         (mode, last_frame,
 | |
|          (xsize, ysize), depth, bytes_per_line) = self.get_parameters()
 | |
|         if mode in ['gray', 'red', 'green', 'blue']:
 | |
|             format = 'L'
 | |
|         elif mode == 'color':
 | |
|             format = 'RGB'
 | |
|         else:
 | |
|             raise ValueError('got unknown "mode" from self.get_parameters()')
 | |
|         im=Image.new(format, (xsize,ysize))
 | |
|         self.dev.snap( im.im.id, no_cancel )
 | |
|         return im
 | |
| 
 | |
|     def scan(self):
 | |
|         self.start()
 | |
|         return self.snap()
 | |
| 
 | |
|     def multi_scan(self):
 | |
|         return _SaneIterator(self)
 | |
| 
 | |
|     def arr_snap(self, multipleOf=1):
 | |
|         """Snap a picture, returning a numarray object with the results.
 | |
|         By default the resulting array has the same number of pixels per
 | |
|         line as specified in self.get_parameters()[2][0]
 | |
|         However sometimes it is necessary to obtain arrays where
 | |
|         the number of pixels per line is e.g. a multiple of 4. This can then
 | |
|         be achieved with the option 'multipleOf=4'. So if the scanner
 | |
|         scanned 34 pixels per line, you will obtain an array with 32 pixels
 | |
|         per line.
 | |
|         """
 | |
|         (mode, last_frame, (xsize, ysize), depth, bpl) = self.get_parameters()
 | |
|         if not mode in ['gray', 'red', 'green', 'blue']:
 | |
|             raise RuntimeError('arr_snap() only works with monochrome images')
 | |
|         if multipleOf < 1:
 | |
|             raise ValueError('option "multipleOf" must be a positive number')
 | |
|         elif multipleOf > 1:
 | |
|             pixels_per_line = xsize - divmod(xsize, 4)[1]
 | |
|         else:
 | |
|             pixels_per_line = xsize
 | |
|         return self.dev.arr_snap(pixels_per_line)
 | |
| 
 | |
|     def arr_scan(self, multipleOf=1):
 | |
|         self.start()
 | |
|         return self.arr_snap(multipleOf=multipleOf)
 | |
| 
 | |
|     def fileno(self):
 | |
|         "Return the file descriptor for the scanning device"
 | |
|         return self.dev.fileno()
 | |
| 
 | |
|     def close(self):
 | |
|         self.dev.close()
 | |
| 
 | |
| 
 | |
| def open(devname):
 | |
|     "Open a device for scanning"
 | |
|     new=SaneDev(devname)
 | |
|     return new
 |