Patches to sane.py and _sanemodule.c (was: Re: [Image-SIG] Sane:
Problems in setting color mode)
abel deuring
a.deuring@satzbau-gmbh.de
Sun, 22 Sep 2002 02:15:42 +0200
This is a multi-part message in MIME format.
--------------030403010402060201000401
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
Hi all,
abel deuring wrote:
> But I'm afraid that you won't get very far anyway with the Sane module
> from PIL 1.1.3. This is the source code of the read loop in SaneDev_Snap
> from this version:
>
> SaneDev_snap(self, args)
[...]
> Three other bugs in this code:
>
> 1. 'SANE_Parameters p' is not initialized (a call to sane_get_parameters
> is missing), hence 'switch (p.format)' uses an arbtrary value;
> 2. the scan finishes, sane_cancel must be called. (try for example the -
> relatively new - test backend on this code. Without a call to
> sane_cancel after sane_start / sane_read; calls of sane_control_option
> will return errors. The requierement to call sane_cancel is also
> mentioned in the Sane API documentation);
> 3. If the image aqusistion requires more than one call of sane_read,
> SaneDev_snap will return an error:
Sorry, here I wrote nonsense :( I didn't propoerly read the 'if'
expression which detects errors.
Anyway, attached are patches to sane.py and _sanemodule.c which fix some
problems of the current Sane interface:
- RGB scans (1 pass and 3 pass) are now working again
- a backend may now return padding bytes at the end of a scan line
- somewhat better Sane error handling
- sane.py now reloads the option paramters, if a call to
sane_control_option returns SANE_INFO_RELOAD_OPTIONS.
These issues are still unresolved;
- support for 1 bit and 16 bit scan data
- handscanner support. I am not sure, if it is worth the effort to
support handscanners. But sane.snap and/or sane.start should at least
return an error, if "hand scanner behaviour" (i.e.,
sane_get_parameters sets the number of scan lines to -1) can be
detected.
- array options (e.g. gamma tables) are not supported.
Abel
--------------030403010402060201000401
Content-Type: text/plain;
name="_sanemodule.c.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="_sanemodule.c.diff"
--- _sanemodule.c.orig Sun Sep 22 01:48:18 2002
+++ _sanemodule.c Sun Sep 22 01:48:33 2002
@@ -79,7 +79,7 @@
{
if (self->h) sane_close(self->h);
self->h=NULL;
- PyMem_DEL(self);
+ PyObject_DEL(self);
}
static PyObject *
@@ -103,7 +103,7 @@
case(SANE_FRAME_GREEN): format="G"; break;
case(SANE_FRAME_BLUE): format="B"; break;
}
-
+
return Py_BuildValue("si(ii)ii", format, p.last_frame, p.pixels_per_line, p.lines,
p.depth, p.bytes_per_line);
}
@@ -295,6 +295,7 @@
return NULL;
}
memcpy(v, PyString_AsString(value), PyString_Size(value));
+ ((char*)v)[PyString_Size(value)] = 0;
break;
case(SANE_TYPE_BUTTON):
case(SANE_TYPE_GROUP):
@@ -329,18 +330,23 @@
return Py_BuildValue("i", i);
}
+#define READSIZE 32768
+#define RED_FRAME 1
+#define GREEN_FRAME 2
+#define BLUE_FRAME 4
+#define ALL_FRAMES 7
+
static PyObject *
SaneDev_snap(self, args)
SaneDevObject *self;
PyObject *args;
{
SANE_Status st;
- SANE_Byte buffer[8192]; /* XXX how big should the buffer be? */
+ SANE_Byte buffer[READSIZE]; /* XXX how big should the buffer be? */
SANE_Int len;
Imaging im;
SANE_Parameters p;
- char *format="unknown format";
- int px, py;
+ int px, py, remain, cplen, bufpos, padbytes, frames = 0;
long L;
if (!PyArg_ParseTuple(args, "i", &L))
@@ -348,30 +354,242 @@
im=(Imaging)L;
st=SANE_STATUS_GOOD; px=py=0;
- while (st!=SANE_STATUS_EOF)
+ /* xxx not yet implemented
+ - lineart scans
+ - scans with more than 8 bit
+ ?? simply return 8 bit and discard the remaining data??
+ - handscanner support (i.e., unknown image length during start)
+ - generally: move the functionality from method snap in sane.py
+ down here -- I don't like this cross-dependency.
+ we need to call sane_get_parameters here, and we can create
+ the result Image object here.
+ */
+ sane_get_parameters(self->h, &p);
+ if (p.format == SANE_FRAME_GRAY)
{
- st=sane_read(self->h, buffer, 8192, &len);
- if (st && (st!=SANE_STATUS_EOF)) return PySane_Error(st);
- if (st==SANE_STATUS_GOOD)
- {
- if (p.format==SANE_FRAME_RGB)
- {
- }
- else
- {
- /* Handle some sort of 8-bit code */
- /* XXX Optimize */
- int i;
- for (i=0; i<len && py <im->ysize; i++)
- {
- im->image8[py][px]=buffer[i];
- if (++px >= (int) im->xsize)
- px = 0, py++;
- }
- }
- }
+ remain = p.bytes_per_line * im->ysize;
+ padbytes = p.bytes_per_line - im->xsize;
+ bufpos = 0;
+ while (st!=SANE_STATUS_EOF && remain > 0 && py < im->ysize)
+ {
+ st=sane_read(self->h, buffer,
+ remain<READSIZE ? remain : READSIZE, &len);
+ if (st && (st!=SANE_STATUS_EOF))
+ {
+ sane_cancel(self->h);
+ return PySane_Error(st);
+ }
+ remain -= len;
+ /* skip possible pad bytes at the start of the buffer */
+ len -= bufpos;
+ while (len > 0)
+ {
+ cplen = len;
+ if (px + cplen >= im->xsize)
+ cplen = im->xsize - px;
+ memcpy(&im->image8[py][px], &buffer[bufpos], cplen);
+ len -= cplen;
+ bufpos += cplen;
+ px += cplen;
+ if (px >= im->xsize)
+ {
+ px = 0;
+ py++;
+ bufpos += padbytes;
+ len -= padbytes;
+ }
+ }
+ bufpos = -len;
+ }
}
-
+ else if (p.format == SANE_FRAME_RGB)
+ {
+ int lastlen;
+ remain = p.bytes_per_line * im->ysize;
+ padbytes = p.bytes_per_line - 3 * im->xsize;
+ bufpos = 0;
+ len = 0;
+ lastlen = 0;
+ /* probably not very efficient. But we have to deal with these
+ possible conditions:
+ - we may have padding bytes at the end of a scan line
+ - the number of bytes read with sane_read may be smaller
+ than the number of pad bytes
+ - the buffer may become empty after setting any of the
+ red/green/blue pixel values
+
+ */
+ while (st != SANE_STATUS_EOF && py < im->ysize)
+ {
+ while (len <= 0)
+ {
+ bufpos -= lastlen;
+ if (remain == 0)
+ {
+ PyErr_SetString(ErrorObject, "internal _sane error: premature end of scan");
+ sane_cancel(self->h);
+ return NULL;
+ }
+ st = sane_read(self->h, buffer,
+ remain<(READSIZE) ? remain : (READSIZE), &len);
+ if (st && (st!=SANE_STATUS_EOF))
+ {
+ sane_cancel(self->h);
+ return PySane_Error(st);
+ }
+ lastlen = len;
+ remain -= len;
+ if (bufpos >= len)
+ /* skip pad bytes */
+ len = 0;
+ else
+ len -= bufpos;
+ }
+ ((UINT8**)(im->image32))[py][px++] = buffer[bufpos++]; len--;
+ if (!len)
+ {
+ bufpos -= lastlen;
+ st = sane_read(self->h, buffer,
+ remain<(READSIZE) ? remain : (READSIZE), &len);
+ if (st && (st!=SANE_STATUS_EOF))
+ {
+ sane_cancel(self->h);
+ return PySane_Error(st);
+ }
+ remain -= len;
+ lastlen = len;
+ }
+ ((UINT8**)(im->image32))[py][px++] = buffer[bufpos++]; len--;
+ if (!len)
+ {
+ bufpos -= lastlen;
+ st = sane_read(self->h, buffer,
+ remain<(READSIZE) ? remain : (READSIZE), &len);
+ if (st && (st!=SANE_STATUS_EOF))
+ {
+ sane_cancel(self->h);
+ return PySane_Error(st);
+ }
+ remain -= len;
+ lastlen = len;
+ }
+ ((UINT8**)(im->image32))[py][px++] = buffer[bufpos++]; len--;
+ ((UINT8**)(im->image32))[py][px++] = 0;
+
+ if (px >= im->xsize * 4)
+ {
+ px = 0;
+ py++;
+ bufpos += padbytes;
+ len -= padbytes;
+ }
+ }
+ }
+ else /* should be SANE_FRAME_RED, GREEN or BLUE */
+ {
+ int lastlen, pxa, offset;
+ char errmsg[80];
+
+ while (frames != ALL_FRAMES)
+ {
+ remain = p.bytes_per_line * im->ysize;
+ padbytes = p.bytes_per_line - im->xsize;
+ bufpos = 0;
+ len = 0;
+ lastlen = 0;
+ py = 0;
+ switch (p.format)
+ {
+ case SANE_FRAME_RED:
+ offset = 0;
+ frames |= RED_FRAME;
+ break;
+ case SANE_FRAME_GREEN:
+ offset = 1;
+ frames |= GREEN_FRAME;
+ break;
+ case SANE_FRAME_BLUE:
+ offset = 2;
+ frames |= BLUE_FRAME;
+ break;
+ default:
+ sane_cancel(self->h);
+ snprintf(errmsg, 80, "unknown/invalid frame format: %i", p.format);
+ PyErr_SetString(ErrorObject, errmsg);
+ return NULL;
+ }
+ px = offset;
+ pxa = 3;
+ st = SANE_STATUS_GOOD;
+ while (st != SANE_STATUS_EOF && py < im->ysize)
+ {
+ while (len <= 0)
+ {
+ bufpos -= lastlen;
+ if (remain == 0)
+ {
+ PyErr_SetString(ErrorObject, "internal _sane error: premature end of scan");
+ sane_cancel(self->h);
+ return NULL;
+ }
+ st = sane_read(self->h, buffer,
+ remain<(READSIZE) ? remain : (READSIZE), &len);
+ if (st && (st!=SANE_STATUS_EOF))
+ {
+ sane_cancel(self->h);
+ return PySane_Error(st);
+ }
+ lastlen = len;
+ remain -= len;
+ if (bufpos >= len)
+ /* skip pad bytes */
+ len = 0;
+ else
+ len -= bufpos;
+ }
+ ((UINT8**)(im->image32))[py][px] = buffer[bufpos++]; len--;
+ ((UINT8**)(im->image32))[py][pxa] = 0;
+ px += 4;
+ pxa += 4;
+
+ if (px >= im->xsize * 4)
+ {
+ px = offset;
+ pxa = 3;
+ py++;
+ bufpos += padbytes;
+ len -= padbytes;
+ }
+ }
+ if (!p.last_frame)
+ {
+ /* the sane_read calls in the above loop should return
+ SANE_STATUS_GOOD, but backend may need another sane_read
+ call which returns SANE_STATUS_EOF in order to start
+ a new frame.
+ */
+ do {
+ st = sane_read(self->h, buffer, READSIZE, &len);
+ }
+ while (st == SANE_STATUS_GOOD);
+ if (st != SANE_STATUS_EOF)
+ {
+ sane_cancel(self->h);
+ return PySane_Error(st);
+ }
+
+ st = sane_start(self->h);
+ if (st) return PySane_Error(st);
+ st = sane_get_parameters(self->h, &p);
+ if (st)
+ {
+ sane_cancel(self->h);
+ return PySane_Error(st);
+ }
+ }
+ }
+ }
+ sane_cancel(self->h);
Py_INCREF(Py_None);
return Py_None;
}
@@ -634,6 +852,7 @@
insint(d, "UNIT_MM", SANE_UNIT_MM);
insint(d, "UNIT_DPI", SANE_UNIT_DPI);
insint(d, "UNIT_PERCENT", SANE_UNIT_PERCENT);
+ insint(d, "UNIT_MICROSECOND", SANE_UNIT_MICROSECOND);
insint(d, "CAP_SOFT_SELECT", SANE_CAP_SOFT_SELECT);
insint(d, "CAP_HARD_SELECT", SANE_CAP_HARD_SELECT);
@@ -642,6 +861,9 @@
insint(d, "CAP_AUTOMATIC", SANE_CAP_AUTOMATIC);
insint(d, "CAP_INACTIVE", SANE_CAP_INACTIVE);
insint(d, "CAP_ADVANCED", SANE_CAP_ADVANCED);
+
+ /* handy for checking array lengths: */
+ insint(d, "SANE_WORD_SIZE", sizeof(SANE_Word));
/* Check for errors */
if (PyErr_Occurred())
--------------030403010402060201000401
Content-Type: text/plain;
name="sane.py.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="sane.py.diff"
--- sane.py.orig Sun Sep 22 01:48:02 2002
+++ sane.py Sun Sep 22 01:53:43 2002
@@ -1,34 +1,47 @@
+import string
import Image
import _sane
from _sane import *
+def _f(x):
+ if x=='-': return '_'
+ else: return x
+
+def _py_name(s):
+ return string.join(map(_f, s), '')
+
class Option:
def __init__(self, args):
- import string
+ self.set_param(args)
+
+ def set_param(self, args):
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 type(self.name)!=type(''): self.py_name=str(self.name)
- else: self.py_name=string.join(map(f, self.name), '')
+ else: self.py_name=string.join(map(_f, self.name), '')
class SaneDev:
def __init__(self, devname):
d=self.__dict__
d['dev']=_sane._open(devname)
d['opt']={}
-
- optlist=d['dev'].get_options()
+ optlist = self.dev.get_options()
for t in optlist:
- o=Option(t)
+ o = Option(t)
if o.type!=TYPE_GROUP:
- d['opt'][o.py_name]=o
+ # xxx would be better to check if an option name is present!
+ self.opt[o.py_name]=o
+
+ def reload_opts(self):
+ optlist = self.dev.get_options()
+ for t in optlist:
+ if t[4] != TYPE_GROUP and t[1]:
+ self.opt[_py_name(t[1])].set_param(t)
def __setattr__(self, key, value):
dev=self.__dict__['dev']
@@ -44,6 +57,8 @@
raise AttributeError, "Option can't be set by software: "+key
self.last_opt = dev.set_option(opt.index, value)
+ if self.last_opt & INFO_RELOAD_OPTIONS:
+ self.reload_opts()
def __getattr__(self, key):
dev=self.__dict__['dev']
@@ -58,6 +73,8 @@
if not _sane.OPTION_IS_ACTIVE(opt.cap):
raise AttributeError, 'Inactive option: '+key
self.last_opt, value = dev.get_option(opt.index)
+ if self.last_opt & INFO_RELOAD_OPTIONS:
+ self.reload_opts()
return value
def get_parameters(self): return self.dev.get_parameters()
@@ -67,6 +84,8 @@
def fileno(self): return self.dev.fileno()
def snap(self):
format, last_frame, (xsize, ysize), depth, bytes_per_line = self.get_parameters()
+ if format in ('R', 'G', 'B'):
+ format = 'RGB'
im=Image.new(format, (xsize,ysize))
self.dev.snap( im.im.id )
return im
--------------030403010402060201000401--