mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 01:47:47 +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
 |