Tizen 2.1 base
[platform/upstream/hplip.git] / scan / sane.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # (c) Copyright 2003-2008 Hewlett-Packard Development Company, L.P.
5 #
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.
10 #
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.
15 #
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
19 #
20 # Based on:
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/ .#
27 #
28 # Modified to work without PIL by Don Welch
29 #
30 # (C) Copyright 2003 A.M. Kuchling.  All Rights Reserved
31 # (C) Copyright 2004 A.M. Kuchling, Ralph Heinkel  All Rights Reserved
32 #
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.
40 #
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/ .
52 #
53 # Original authors: Andrew Kuchling, Ralph Heinkel
54 # Modified by: Don Welch
55 #
56
57 # Std Lib
58 import scanext
59 import threading
60 import time
61 import os
62 import Queue
63
64 # Local
65 from base.g import *
66 from base import utils
67
68 EVENT_SCAN_CANCELED = 1
69
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" }
73
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" }
81
82
83
84
85 class Option:
86     """Class representing a SANE option.
87     Attributes:
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,
95             UNIT_PIXEL, etc.
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:
99         None : No constraint
100         (min,max,step)  Integer values, from min to max, stepping by
101         list of integers or strings: only the listed values are allowed
102     """
103
104     def __init__(self, args, cur_device):
105         import string
106         self.cur_device = cur_device
107
108         self.index, self.name, self.title, self.desc, self.type, \
109             self.unit, self.size, self.cap, self.constraint = args
110
111         if type(self.name) != type(''):
112             self.name = str(self.name)
113
114     def isActive(self):
115         return scanext.isOptionActive(self.cap)
116
117     def isSettable(self):
118         return scanext.isOptionSettable(self.cap)
119
120     def __repr__(self):
121         if self.isSettable():
122             settable = 'yes'
123         else:
124             settable = 'no'
125
126         if self.isActive():
127             active = 'yes'
128             curValue = self.cur_device.getOption(self.name)
129         else:
130             active = 'no'
131             curValue = '<not available, inactive option>'
132
133
134         return """\nName:      %s
135 Cur value: %s
136 Index:     %d
137 Title:     %s
138 Desc:      %s
139 Type:      %s
140 Unit:      %s
141 Constr:    %s
142 isActive:    %s
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)
147         return s
148
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))
156
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))
161
162                 self.cur_device.setOption(self.name, value)
163
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)
171                             v = x
172
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))
175
176                     self.cur_device.setOption(self.name, v)
177
178         else:
179             value = self.cur_device.getOption(self.name)
180
181         return value
182
183
184 ##class _SaneIterator:
185 ##    """ intended for ADF scans.
186 ##    """
187 ##
188 ##    def __init__(self, cur_device):
189 ##        self.cur_device = cur_device
190 ##
191 ##    def __iter__(self):
192 ##        return self
193 ##
194 ##    def __del__(self):
195 ##        self.cur_device.cancelScan()
196 ##
197 ##    def next(self):
198 ##        try:
199 ##            self.cur_device.startScan()
200 ##        except error, v:
201 ##            if v == 'Document feeder out of documents':
202 ##                raise StopIteration
203 ##            else:
204 ##                raise
205 ##        return self.cur_device.performScan(1)
206
207
208
209
210 class ScanDevice:
211     """Class representing a SANE device.
212     Methods:
213     startScan()    -- initiate a scan, using the current settings
214     cancelScan()   -- cancel an in-progress scanning operation
215
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.
219
220     Attributes:
221     optlist -- list of option names
222
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.
229     """
230
231     def __init__(self, dev):
232         self.scan_thread = None
233         self.dev = scanext.openDevice(dev)
234         self.options = {}
235         self.__load_options_dict()
236
237
238     def __load_options_dict(self):
239         opts = self.options
240         opt_list = self.dev.getOptions()
241
242         for t in opt_list:
243             o = Option(t, self)
244
245             if o.type != scanext.TYPE_GROUP:
246                 opts[o.name] = o
247
248
249     def setOption(self, key, value):
250         opts = self.options
251
252         if key not in opts:
253             opts[key] = value
254             return
255
256         opt = opts[key]
257
258         if opt.type == scanext.TYPE_GROUP:
259             log.error("Groups can't be set: %s" % key)
260
261         if not scanext.isOptionActive(opt.cap):
262             log.error("Inactive option: %s" % key)
263
264         if not scanext.isOptionSettable(opt.cap):
265             log.error("Option can't be set by software: %s" % key)
266
267         if type(value) == int and opt.type == scanext.TYPE_FIXED:
268             # avoid annoying errors of backend if int is given instead float:
269             value = float(value)
270
271         try:
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))
275             return
276
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()
280
281
282     def getOption(self, key):
283         opts = self.options
284
285         if key == 'optlist':
286             return opts.keys()
287
288         if key == 'area':
289             return (opts["tl-x"], opts["tl-y"]), (opts["br-x"], opts["br-y"])
290
291         if key not in opts:
292             raise AttributeError, 'No such attribute: %s' % key
293
294         opt = opts[key]
295
296         if opt.type == scanext.TYPE_BUTTON:
297             raise AttributeError, "Buttons don't have values: %s" % key
298
299         if opt.type == scanext.TYPE_GROUP:
300             raise AttributeError, "Groups don't have values: %s " % key
301
302         if not scanext.isOptionActive(opt.cap):
303             raise AttributeError, 'Inactive option: %s' % key
304
305         return self.dev.getOption(opt.index)
306
307
308     def getOptionObj(self, key):
309         opts = self.options
310         if key in opts:
311             return opts[key]
312
313
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)
317
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
324         """
325         return self.dev.getParameters()
326
327
328     def getOptions(self):
329         "Return a list of tuples describing all the available options"
330         return self.dev.getOptions()
331
332
333     def startScan(self, byte_format='BGRA', update_queue=None, event_queue=None):
334         """
335             Perform a scan with the current device.
336             Calls sane_start().
337         """
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()
342
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
347         else:
348             # Already active
349             return False, 0, scanext.SANE_STATUS_DEVICE_BUSY
350
351
352     def cancelScan(self):
353         "Cancel an in-progress scanning operation."
354         return self.dev.cancelScan()
355
356
357     def getScan(self):
358         "Get the output buffer and info about a completed scan."
359         if not self.isScanActive():
360             s = self.scan_thread
361
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
364
365
366     def freeScan(self):
367         "Cleanup the scan file after a completed scan."
368         if not self.isScanActive():
369             s = self.scan_thread
370
371             try:
372                 s.buffer.close()
373                 os.remove(s.buffer_path)
374             except (IOError, AttributeError):
375                 pass
376
377
378     def isScanActive(self):
379         if self.scan_thread is not None:
380             return self.scan_thread.isAlive() and self.scan_thread.scan_active
381         else:
382             return False
383
384
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:
389
390             try:
391                 self.scan_thread.join()
392             except KeyboardInterrupt:
393                 pass
394
395
396     def waitForScanActive(self):
397         time.sleep(0.5)
398         if self.scan_thread is not None:
399             while True:
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:
404                     return
405
406                 time.sleep(0.5)
407                 #print "Waiting..."
408
409
410 ##    def scanMulti(self):
411 ##        return _SaneIterator(self)
412
413
414     def closeScan(self):
415         "Close the SANE device after a scan."
416         self.dev.closeScan()
417
418
419
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
424         self.dev = device
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")
429         self.format = -1
430         self.format_name = ''
431         self.last_frame = -1
432         self.pixels_per_line = -1
433         self.lines = -1
434         self.depth = -1
435         self.bytes_per_line = -1
436         self.pad_bytes = -1
437         self.total_read = 0
438         self.byte_format = byte_format
439
440
441     def updateQueue(self, status, bytes_read):
442         if self.update_queue is not None:
443             try:
444                 status = int(status)
445             except (ValueError, TypeError):
446                 status = -1 #scanext.SANE_STATUS_GOOD
447
448             self.update_queue.put((status, bytes_read))
449             time.sleep(0)
450
451
452     def run(self):
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()
456
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)
465
466         w = self.buffer.write
467
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
471
472                 log.debug("pad_bytes=%d" % self.pad_bytes)
473
474                 dir = -1
475                 if self.byte_format == 'RGBA':
476                     dir = 1
477
478                 try:
479                     st, t = self.dev.readScan(self.bytes_per_line)
480                 except scanext.error, st:
481                     self.updateQueue(st, 0)
482
483                 #print st
484                 while st == scanext.SANE_STATUS_GOOD:
485
486                     if t:
487                         index = 0
488                         while index < len(t) - self.pad_bytes:
489                             w(t[index:index+3:dir])
490                             w('\xff')
491                             index += 3
492
493                         self.total_read += len(t)
494                         self.updateQueue(st, self.total_read)
495                         log.debug("Read %d bytes" % self.total_read)
496
497                     else:
498                         time.sleep(0.1)
499
500                     try:
501                         st, t = self.dev.readScan(self.bytes_per_line)
502                     except scanext.error, st:
503                         self.updateQueue(st, self.total_read)
504                         break
505
506                     if self.checkCancel():
507                         break
508
509         elif self.format == scanext.FRAME_GRAY:
510
511             if self.depth == 1: # 1 bpp lineart
512                 self.pad_bytes = self.bytes_per_line - (self.pixels_per_line + 7) // 8;
513
514                 log.debug("pad_bytes=%d" % self.pad_bytes)
515
516                 try:
517                     st, t = self.dev.readScan(self.bytes_per_line)
518                 except scanext.error, st:
519                     self.updateQueue(st, 0)
520
521                 while st == scanext.SANE_STATUS_GOOD:
522
523                     if t:
524                         index = 0
525                         while index < len(t) - self.pad_bytes:
526                             k = 0x80
527                             j = ord(t[index])
528
529                             for b in range(8):
530                                 if k & j:
531                                     w("\x00\x00\x00\xff")
532                                 else:
533                                     w("\xff\xff\xff\xff")
534
535                                 k = k >> 1
536
537                             index += 1
538
539                         self.total_read += len(t)
540                         self.updateQueue(st, self.total_read)
541                         log.debug("Read %d bytes" % self.total_read)
542                     else:
543                         time.sleep(0.1)
544
545                     try:
546                         st, t = self.dev.readScan(self.bytes_per_line)
547                     except scanext.error, st:
548                         self.updateQueue(st, self.total_read)
549                         break
550
551                     if self.checkCancel():
552                         break
553
554             elif self.depth == 8: # 8 bpp grayscale
555                 self.pad_bytes = self.bytes_per_line - self.pixels_per_line
556
557                 log.debug("pad_bytes=%d" % self.pad_bytes)
558
559                 try:
560                     st, t = self.dev.readScan(self.bytes_per_line)
561                 except scanext.error, st:
562                     self.updateQueue(st, 0)
563
564                 while st == scanext.SANE_STATUS_GOOD:
565
566                     if t:
567                         index = 0
568                         while index < len(t) - self.pad_bytes:
569                             j = t[index]
570                             w(j)
571                             w(j)
572                             w(j)
573                             w("\xff")
574
575                             index += 1
576
577                         self.total_read += len(t)
578                         self.updateQueue(st, self.total_read)
579                         log.debug("Read %d bytes" % self.total_read)
580                     else:
581                         time.sleep(0.1)
582
583                     try:
584                         st, t = self.dev.readScan(self.bytes_per_line)
585                     except scanext.error, st:
586                         self.updateQueue(st, self.total_read)
587                         break
588
589                     if self.checkCancel():
590                         break
591
592         #self.dev.cancelScan()
593         self.buffer.seek(0)
594         self.scan_active = False
595         log.debug("Scan thread exiting...")
596
597
598     def checkCancel(self):
599         canceled = False
600         while self.event_queue.qsize():
601             try:
602                 event = self.event_queue.get(0)
603                 if event == EVENT_SCAN_CANCELED:
604                     canceled = True
605                     log.debug("Cancel pressed!")
606                     self.dev.cancelScan()
607
608             except Queue.Empty:
609                 break
610
611         return canceled
612
613
614
615 def init():
616     return scanext.init()
617
618
619 def deInit():
620     return scanext.deInit()
621
622
623 def openDevice(dev):
624     "Open a device for scanning"
625     return ScanDevice(dev)
626
627
628 def getDevices(local_only=0):
629     return scanext.getDevices(local_only)
630
631
632 def reportError(code):
633     log.error("SANE: %s (code=%d)" % (scanext.getErrorMessage(code), code))
634
635