2 # -*- coding: utf-8 -*-
4 # (c) Copyright 2003-2008 Hewlett-Packard Development Company, L.P.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # "sane.py", part of the Python Imaging Library (PIL)
22 # http://www.pythonware.com/products/pil/
23 # Python wrapper on top of the _sane module, which is in turn a very
24 # thin wrapper on top of the SANE library. For a complete understanding
25 # of SANE, consult the documentation at the SANE home page:
26 # http://www.mostang.com/sane/ .#
28 # Modified to work without PIL by Don Welch
30 # (C) Copyright 2003 A.M. Kuchling. All Rights Reserved
31 # (C) Copyright 2004 A.M. Kuchling, Ralph Heinkel All Rights Reserved
33 # Permission to use, copy, modify, and distribute this software and its
34 # documentation for any purpose and without fee is hereby granted,
35 # provided that the above copyright notice appear in all copies and that
36 # both that copyright notice and this permission notice appear in
37 # supporting documentation, and that the name of A.M. Kuchling and
38 # Ralph Heinkel not be used in advertising or publicity pertaining to
39 # distribution of the software without specific, written prior permission.
41 # A.M. KUCHLING, R.H. HEINKEL DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
42 # SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
43 # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
44 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
45 # USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
46 # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
47 # PERFORMANCE OF THIS SOFTWARE.
48 # Python wrapper on top of the scanext module, which is in turn a very
49 # thin wrapper on top of the SANE library. For a complete understanding
50 # of SANE, consult the documentation at the SANE home page:
51 # http://www.mostang.com/sane/ .
53 # Original authors: Andrew Kuchling, Ralph Heinkel
54 # Modified by: Don Welch
66 from base import utils
68 EVENT_SCAN_CANCELED = 1
70 TYPE_STR = { scanext.TYPE_BOOL: "TYPE_BOOL", scanext.TYPE_INT: "TYPE_INT",
71 scanext.TYPE_FIXED: "TYPE_FIXED", scanext.TYPE_STRING: "TYPE_STRING",
72 scanext.TYPE_BUTTON: "TYPE_BUTTON", scanext.TYPE_GROUP: "TYPE_GROUP" }
74 UNIT_STR = { scanext.UNIT_NONE: "UNIT_NONE",
75 scanext.UNIT_PIXEL: "UNIT_PIXEL",
76 scanext.UNIT_BIT: "UNIT_BIT",
77 scanext.UNIT_MM: "UNIT_MM",
78 scanext.UNIT_DPI: "UNIT_DPI",
79 scanext.UNIT_PERCENT: "UNIT_PERCENT",
80 scanext.UNIT_MICROSECOND: "UNIT_MICROSECOND" }
86 """Class representing a SANE option.
88 index -- number from 0 to n, giving the option number
89 name -- a string uniquely identifying the option
90 title -- single-line string containing a title for the option
91 desc -- a long string describing the option; useful as a help message
92 type -- type of this option. Possible values: TYPE_BOOL,
93 TYPE_INT, TYPE_STRING, and so forth.
94 unit -- units of this option. Possible values: UNIT_NONE,
96 size -- size of the value in bytes
97 cap -- capabilities available; CAP_EMULATED, CAP_SOFT_SELECT, etc.
98 constraint -- constraint on values. Possible values:
100 (min,max,step) Integer values, from min to max, stepping by
101 list of integers or strings: only the listed values are allowed
104 def __init__(self, args, cur_device):
106 self.cur_device = cur_device
108 self.index, self.name, self.title, self.desc, self.type, \
109 self.unit, self.size, self.cap, self.constraint = args
111 if type(self.name) != type(''):
112 self.name = str(self.name)
115 return scanext.isOptionActive(self.cap)
117 def isSettable(self):
118 return scanext.isOptionSettable(self.cap)
121 if self.isSettable():
128 curValue = self.cur_device.getOption(self.name)
131 curValue = '<not available, inactive option>'
143 isSettable: %s\n""" % (self.name, curValue,
144 self.index, self.title, self.desc,
145 TYPE_STR[self.type], UNIT_STR[self.unit],
146 self.constraint, active, settable)
149 def limitAndSet(self, value):
150 if value is not None and self.constraint is not None:
151 if type(self.constraint) == type(()):
152 if value < self.constraint[0]:
153 value = self.constraint[0]
154 log.warn("Invalid value for %s (%s < min value of %d). Using %d." %
155 (self.name, self.name, value, value))
157 elif value > self.constraint[1]:
158 value = self.constraint[1]
159 log.warn("Invalid value for %s (%s > max value of %d). Using %d." %
160 (self.name, self.name, value, value))
162 self.cur_device.setOption(self.name, value)
164 elif type(self.constraint) == type([]):
165 if value not in self.constraint:
166 v = self.constraint[0]
167 min_dist = sys.maxint
168 for x in self.constraint:
169 if abs(value-x) < min_dist:
170 min_dist = abs(value-x)
173 log.warn("Invalid value for %s (%s not in constraint list: %s). Using %d." %
174 (self.name, self.name, value, ', '.join(self.constraint), v))
176 self.cur_device.setOption(self.name, v)
179 value = self.cur_device.getOption(self.name)
184 ##class _SaneIterator:
185 ## """ intended for ADF scans.
188 ## def __init__(self, cur_device):
189 ## self.cur_device = cur_device
191 ## def __iter__(self):
194 ## def __del__(self):
195 ## self.cur_device.cancelScan()
199 ## self.cur_device.startScan()
201 ## if v == 'Document feeder out of documents':
202 ## raise StopIteration
205 ## return self.cur_device.performScan(1)
211 """Class representing a SANE device.
213 startScan() -- initiate a scan, using the current settings
214 cancelScan() -- cancel an in-progress scanning operation
216 Also available, but rather low-level:
217 getParameters() -- get the current parameter settings of the device
218 getOptions() -- return a list of tuples describing all the options.
221 optlist -- list of option names
223 You can also access an option name to retrieve its value, and to
224 set it. For example, if one option has a .name attribute of
225 imagemode, and scanner is a ScanDevice object, you can do:
226 print scanner.imagemode
227 scanner.imagemode = 'Full frame'
228 scanner.['imagemode'] returns the corresponding Option object.
231 def __init__(self, dev):
232 self.scan_thread = None
233 self.dev = scanext.openDevice(dev)
235 self.__load_options_dict()
238 def __load_options_dict(self):
240 opt_list = self.dev.getOptions()
245 if o.type != scanext.TYPE_GROUP:
249 def setOption(self, key, value):
258 if opt.type == scanext.TYPE_GROUP:
259 log.error("Groups can't be set: %s" % key)
261 if not scanext.isOptionActive(opt.cap):
262 log.error("Inactive option: %s" % key)
264 if not scanext.isOptionSettable(opt.cap):
265 log.error("Option can't be set by software: %s" % key)
267 if type(value) == int and opt.type == scanext.TYPE_FIXED:
268 # avoid annoying errors of backend if int is given instead float:
272 self.last_opt = self.dev.setOption(opt.index, value)
273 except scanext.error:
274 log.error("Unable to set option %s to value %s" % (key, value))
277 # do binary AND to find if we have to reload options:
278 if self.last_opt & scanext.INFO_RELOAD_OPTIONS:
279 self.__load_options_dict()
282 def getOption(self, key):
289 return (opts["tl-x"], opts["tl-y"]), (opts["br-x"], opts["br-y"])
292 raise AttributeError, 'No such attribute: %s' % key
296 if opt.type == scanext.TYPE_BUTTON:
297 raise AttributeError, "Buttons don't have values: %s" % key
299 if opt.type == scanext.TYPE_GROUP:
300 raise AttributeError, "Groups don't have values: %s " % key
302 if not scanext.isOptionActive(opt.cap):
303 raise AttributeError, 'Inactive option: %s' % key
305 return self.dev.getOption(opt.index)
308 def getOptionObj(self, key):
314 def getParameters(self):
315 """Return a 6-tuple holding all the current device settings:
316 (format, format_name, last_frame, (pixels_per_line, lines), depth, bytes_per_line)
318 - format is the SANE frame type
319 - format is one of 'grey', 'color' (RGB), 'red', 'green', 'blue'.
320 - last_frame [bool] indicates if this is the last frame of a multi frame image
321 - (pixels_per_line, lines) specifies the size of the scanned image (x,y)
322 - lines denotes the number of scanlines per frame
323 - depth gives number of pixels per sample
325 return self.dev.getParameters()
328 def getOptions(self):
329 "Return a list of tuples describing all the available options"
330 return self.dev.getOptions()
333 def startScan(self, byte_format='BGRA', update_queue=None, event_queue=None):
335 Perform a scan with the current device.
338 if not self.isScanActive():
339 status = self.dev.startScan()
340 self.format, self.format_name, self.last_frame, self.pixels_per_line, \
341 self.lines, self.depth, self.bytes_per_line = self.dev.getParameters()
343 self.scan_thread = ScanThread(self.dev, byte_format, update_queue, event_queue)
344 self.scan_thread.scan_active = True
345 self.scan_thread.start()
346 return True, self.lines * self.bytes_per_line, status
349 return False, 0, scanext.SANE_STATUS_DEVICE_BUSY
352 def cancelScan(self):
353 "Cancel an in-progress scanning operation."
354 return self.dev.cancelScan()
358 "Get the output buffer and info about a completed scan."
359 if not self.isScanActive():
362 return s.buffer, s.format, s.format_name, s.pixels_per_line, \
363 s.lines, s.depth, s.bytes_per_line, s.pad_bytes, s.total_read
367 "Cleanup the scan file after a completed scan."
368 if not self.isScanActive():
373 os.remove(s.buffer_path)
374 except (IOError, AttributeError):
378 def isScanActive(self):
379 if self.scan_thread is not None:
380 return self.scan_thread.isAlive() and self.scan_thread.scan_active
385 def waitForScanDone(self):
386 if self.scan_thread is not None and \
387 self.scan_thread.isAlive() and \
388 self.scan_thread.scan_active:
391 self.scan_thread.join()
392 except KeyboardInterrupt:
396 def waitForScanActive(self):
398 if self.scan_thread is not None:
400 #print self.scan_thread.isAlive()
401 #print self.scan_thread.scan_active
402 if self.scan_thread.isAlive() and \
403 self.scan_thread.scan_active:
410 ## def scanMulti(self):
411 ## return _SaneIterator(self)
415 "Close the SANE device after a scan."
420 class ScanThread(threading.Thread):
421 def __init__(self, device, byte_format='BGRA', update_queue=None, event_queue=None):
422 threading.Thread.__init__(self)
423 self.scan_active = True
425 self.update_queue = update_queue
426 self.event_queue = event_queue
427 self.buffer_fd, self.buffer_path = utils.make_temp_file(prefix='hpscan')
428 self.buffer = os.fdopen(self.buffer_fd, "w+b")
430 self.format_name = ''
432 self.pixels_per_line = -1
435 self.bytes_per_line = -1
438 self.byte_format = byte_format
441 def updateQueue(self, status, bytes_read):
442 if self.update_queue is not None:
445 except (ValueError, TypeError):
446 status = -1 #scanext.SANE_STATUS_GOOD
448 self.update_queue.put((status, bytes_read))
453 #self.scan_active = True
454 self.format, self.format_name, self.last_frame, self.pixels_per_line, \
455 self.lines, self.depth, self.bytes_per_line = self.dev.getParameters()
457 log.debug("format=%d" % self.format)
458 log.debug("format_name=%s" % self.format_name)
459 log.debug("last_frame=%d" % self.last_frame)
460 log.debug("ppl=%d" % self.pixels_per_line)
461 log.debug("lines=%d" % self.lines)
462 log.debug("depth=%d" % self.depth)
463 log.debug("bpl=%d" % self.bytes_per_line)
464 log.debug("byte_format=%s" % self.byte_format)
466 w = self.buffer.write
468 if self.format == scanext.FRAME_RGB: # "Color"
469 if self.depth == 8: # 8 bpp (32bit)
470 self.pad_bytes = self.bytes_per_line - 3 * self.pixels_per_line
472 log.debug("pad_bytes=%d" % self.pad_bytes)
475 if self.byte_format == 'RGBA':
479 st, t = self.dev.readScan(self.bytes_per_line)
480 except scanext.error, st:
481 self.updateQueue(st, 0)
484 while st == scanext.SANE_STATUS_GOOD:
488 while index < len(t) - self.pad_bytes:
489 w(t[index:index+3:dir])
493 self.total_read += len(t)
494 self.updateQueue(st, self.total_read)
495 log.debug("Read %d bytes" % self.total_read)
501 st, t = self.dev.readScan(self.bytes_per_line)
502 except scanext.error, st:
503 self.updateQueue(st, self.total_read)
506 if self.checkCancel():
509 elif self.format == scanext.FRAME_GRAY:
511 if self.depth == 1: # 1 bpp lineart
512 self.pad_bytes = self.bytes_per_line - (self.pixels_per_line + 7) // 8;
514 log.debug("pad_bytes=%d" % self.pad_bytes)
517 st, t = self.dev.readScan(self.bytes_per_line)
518 except scanext.error, st:
519 self.updateQueue(st, 0)
521 while st == scanext.SANE_STATUS_GOOD:
525 while index < len(t) - self.pad_bytes:
531 w("\x00\x00\x00\xff")
533 w("\xff\xff\xff\xff")
539 self.total_read += len(t)
540 self.updateQueue(st, self.total_read)
541 log.debug("Read %d bytes" % self.total_read)
546 st, t = self.dev.readScan(self.bytes_per_line)
547 except scanext.error, st:
548 self.updateQueue(st, self.total_read)
551 if self.checkCancel():
554 elif self.depth == 8: # 8 bpp grayscale
555 self.pad_bytes = self.bytes_per_line - self.pixels_per_line
557 log.debug("pad_bytes=%d" % self.pad_bytes)
560 st, t = self.dev.readScan(self.bytes_per_line)
561 except scanext.error, st:
562 self.updateQueue(st, 0)
564 while st == scanext.SANE_STATUS_GOOD:
568 while index < len(t) - self.pad_bytes:
577 self.total_read += len(t)
578 self.updateQueue(st, self.total_read)
579 log.debug("Read %d bytes" % self.total_read)
584 st, t = self.dev.readScan(self.bytes_per_line)
585 except scanext.error, st:
586 self.updateQueue(st, self.total_read)
589 if self.checkCancel():
592 #self.dev.cancelScan()
594 self.scan_active = False
595 log.debug("Scan thread exiting...")
598 def checkCancel(self):
600 while self.event_queue.qsize():
602 event = self.event_queue.get(0)
603 if event == EVENT_SCAN_CANCELED:
605 log.debug("Cancel pressed!")
606 self.dev.cancelScan()
616 return scanext.init()
620 return scanext.deInit()
624 "Open a device for scanning"
625 return ScanDevice(dev)
628 def getDevices(local_only=0):
629 return scanext.getDevices(local_only)
632 def reportError(code):
633 log.error("SANE: %s (code=%d)" % (scanext.getErrorMessage(code), code))