pax_global_header00006660000000000000000000000064125612357400014517gustar00rootroot0000000000000052 comment=91e336c280a9ff0dfec30d18d761c2f1064dfa79 Sane-2.8.2/000077500000000000000000000000001256123574000124165ustar00rootroot00000000000000Sane-2.8.2/.gitignore000066400000000000000000000013111256123574000144020ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ Sane-2.8.2/.travis.yml000066400000000000000000000005561256123574000145350ustar00rootroot00000000000000language: python python: - pypy - pypy3 - 2.6 - 2.7 - 3.2 - 3.3 - 3.4 install: - sudo apt-get install libsane-dev # No tests: just check it can build and install script: - python setup.py build - python setup.py install after_script: - pip install pep8 pyflakes - pep8 --statistics --count . - pyflakes . | tee >(wc -l) matrix: fast_finish: true Sane-2.8.2/CHANGES.rst000066400000000000000000000061101256123574000142160ustar00rootroot00000000000000Version 2.8.2 ------------- - _sane.c: - Fix reading of 1bit scan data - sane.py: - Fix document feeder out of documents exception checking Version 2.8.1 ------------- - sane.py: - Fix array shape returned by sane.arr_snap() Version 2.8.0 ------------- - _sane.c: - Rewritten snap method, it now also works with backends which do not report the number of lines in advance. Also, it now reads the data into a generic memory buffer, removing the dependency on Pillow and numpy/numarray in the C code. A PIL image is created via via Image.frombuffer in sane.py now. - General cleanup, fixing some Py_INCREF and Py_DECREF issues along the way. - sane.py: - Make arr_snap return a 3D numpy array, shaped (samples, width, heigth) - Remove multipleOf support in arr_snap, this should be done by the caller - Overall cleanup - Expose the localOnly option to sane.get_devices() - Documentation: - New python-sphinx generated documentation, reworked example Version 2.7.0 ------------- - Split from Pillow to its own repo - _sane.c : - support for numpy (numarray still possible), define ARRAY_SUPPORT - arr_scan allows colors array using numpy - checkArray_Support : answer as tuple the array_support at compilation and if the libraries are found at execution - sane.py: - arr_scan allows colors array using numpy - setup.py: - define WITH_NUMPY if available - demo_numpy: - various call of arr_snap except 16bit colors not supported by Epkowa or libsane from V1.0 to V2.0 ----------------- - _sane.c: - Values for option constraints are correctly translated to floats if value type is TYPE_FIXED for SANE_CONSTRAINT_RANGE and SANE_CONSTRAINT_WORD_LIST - added constants INFO_INEXACT, INFO_RELOAD_OPTIONS, INFO_RELOAD_PARAMS (possible return values of set_option()) to module dictionary. - removed additional return variable 'i' from SaneDev_get_option(), because it is only set when SANE_ACTION_SET_VALUE is used. - scanDev.get_parameters() now returns the scanner mode as 'format', no more the typical PIL codes. So 'L' became 'gray', 'RGB' is now 'color', 'R' is 'red', 'G' is 'green', 'B' is 'red'. This matches the way scanDev.mode is set. This should be the only incompatibility vs. version 1.0. - sane.py - ScanDev got new method __load_option_dict() called from __init__() and from __setattr__() if backend reported that the frontend should reload the options. - Nice human-readable __repr__() method added for class Option - if __setattr__ (i.e. set_option) reports that all other options have to be reloaded due to a change in the backend then they are reloaded. - due to the change in SaneDev_get_option() only the 'value' is returned from get_option(). - in __setattr__ integer values are automatically converted to floats if SANE backend expects SANE_FIXED (i.e. fix-point float) - The scanner options can now directly be accessed via scanDev[optionName] instead scanDev.opt[optionName]. (The old way still works). V1.0 ---- - A.M. Kuchling's original pysane package. Sane-2.8.2/COPYING000066400000000000000000000020441256123574000134510ustar00rootroot00000000000000(C) Copyright 2003 A.M. Kuchling. All Rights Reserved (C) Copyright 2004 A.M. Kuchling, Ralph Heinkel All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of A.M. Kuchling and Ralph Heinkel not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. A.M. KUCHLING, R.H. HEINKEL DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Sane-2.8.2/README.rst000066400000000000000000000014611256123574000141070ustar00rootroot00000000000000Python SANE module 2.8.2 ======================== .. image:: https://travis-ci.org/python-pillow/Sane.svg :target: https://travis-ci.org/python-pillow/Sane Python SANE has been split from Python-Pillow as of version 2.7.0. The SANE module provides an interface to the SANE scanner and frame grabber interface for Linux. This module was contributed by Andrew Kuchling formerly maintained by Ralph Heinkel. It is currently maintained by Sandro Mani. Build ----- To build this module, type:: python setup.py build In order to install the module type:: python setup.py install For some basic documentation please look at the file sanedoc.txt The example.py script gives a basic example on how to use the software. Documentation ------------- Available at http://python-sane.readthedocs.org/en/latest/ Sane-2.8.2/_sane.c000066400000000000000000000624341256123574000136600ustar00rootroot00000000000000/*********************************************************** (C) Copyright 2003 A.M. Kuchling. All Rights Reserved (C) Copyright 2004 A.M. Kuchling, Ralph Heinkel All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of A.M. Kuchling and Ralph Heinkel not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. A.M. KUCHLING, R.H. HEINKEL DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ******************************************************************/ /* * added support for arrays using numpy : * dec. 13th, 2014, J.F. Berar */ #include #include #include #if PY_MAJOR_VERSION >= 3 #define PyInt_AsLong PyLong_AsLong #define PyInt_FromLong PyLong_FromLong #define PyInt_Check PyLong_Check #endif #define RAISE_IF(test, message) \ if(test) \ { \ PyErr_SetString(ErrorObject, message); \ return NULL; \ } static PyObject *ErrorObject; typedef struct { PyObject_HEAD SANE_Handle h; } SaneDevObject; static PyTypeObject SaneDev_Type; /* Raise a SANE exception */ static PyObject * PySane_Error(SANE_Status st) { PyErr_SetString(ErrorObject, sane_strstatus(st)); return NULL; } /* SaneDev methods */ static void SaneDev_dealloc(SaneDevObject *self) { if(self->h) sane_close(self->h); self->h = NULL; PyObject_DEL(self); } static PyObject * SaneDev_close(SaneDevObject *self, PyObject *args) { if(!PyArg_ParseTuple(args, "")) return NULL; if(self->h) sane_close(self->h); self->h = NULL; Py_INCREF(Py_None); return Py_None; } static PyObject * SaneDev_get_parameters(SaneDevObject *self, PyObject *args) { SANE_Status st; SANE_Parameters p; char *format; if(!PyArg_ParseTuple(args, "")) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); Py_BEGIN_ALLOW_THREADS st = sane_get_parameters(self->h, &p); Py_END_ALLOW_THREADS if(st != SANE_STATUS_GOOD) return PySane_Error(st); switch(p.format) { case(SANE_FRAME_GRAY): format="gray"; break; case(SANE_FRAME_RGB): format="color"; break; case(SANE_FRAME_RED): format="red"; break; case(SANE_FRAME_GREEN): format="green"; break; case(SANE_FRAME_BLUE): format="blue"; break; default: format="unknown format"; break; } return Py_BuildValue("si(ii)ii", format, p.last_frame, p.pixels_per_line, p.lines, p.depth, p.bytes_per_line); } static PyObject * SaneDev_fileno(SaneDevObject *self, PyObject *args) { if(!PyArg_ParseTuple(args, "")) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); SANE_Int fd; SANE_Status st = sane_get_select_fd(self->h, &fd); if(st != SANE_STATUS_GOOD) return PySane_Error(st); return PyInt_FromLong(fd); } static PyObject * SaneDev_start(SaneDevObject *self, PyObject *args) { SANE_Status st; if(!PyArg_ParseTuple(args, "")) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); Py_BEGIN_ALLOW_THREADS st = sane_start(self->h); Py_END_ALLOW_THREADS if(st != SANE_STATUS_GOOD) return PySane_Error(st); Py_INCREF(Py_None); return Py_None; } static PyObject * SaneDev_cancel(SaneDevObject *self, PyObject *args) { if(!PyArg_ParseTuple(args, "")) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); sane_cancel(self->h); Py_INCREF(Py_None); return Py_None; } static PyObject * SaneDev_get_options(SaneDevObject *self, PyObject *args) { int i = 0, j = 0; if(!PyArg_ParseTuple(args, "")) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); PyObject *list = PyList_New(0); if(!list) return NULL; while(1) { const SANE_Option_Descriptor *d = sane_get_option_descriptor(self->h, i); if(!d) break; PyObject *constraint = NULL; switch(d->constraint_type) { case SANE_CONSTRAINT_NONE: Py_INCREF(Py_None); constraint = Py_None; break; case SANE_CONSTRAINT_RANGE: if(d->type == SANE_TYPE_INT) constraint = Py_BuildValue("iii", d->constraint.range->min, d->constraint.range->max, d->constraint.range->quant); else if(d->type == SANE_TYPE_FIXED) constraint = Py_BuildValue("ddd", SANE_UNFIX(d->constraint.range->min), SANE_UNFIX(d->constraint.range->max), SANE_UNFIX(d->constraint.range->quant) ); break; case SANE_CONSTRAINT_WORD_LIST: constraint = PyList_New(d->constraint.word_list[0]); if(!constraint) break; if(d->type == SANE_TYPE_INT) for(j = 1; j <= d->constraint.word_list[0]; ++j) PyList_SetItem(constraint, j - 1, PyInt_FromLong(d->constraint.word_list[j])); else if(d->type == SANE_TYPE_FIXED) for(j = 1; j <= d->constraint.word_list[0]; ++j) PyList_SetItem(constraint, j - 1, PyFloat_FromDouble(SANE_UNFIX(d->constraint.word_list[j]))); break; case SANE_CONSTRAINT_STRING_LIST: constraint = PyList_New(0); if(!constraint) break; PyObject *item = NULL; for(j = 0; d->constraint.string_list[j] != NULL; j++) { #if PY_MAJOR_VERSION >= 3 item = PyUnicode_DecodeLatin1(d->constraint.string_list[j], strlen(d->constraint.string_list[j]), NULL); #else item = PyString_FromString(d->constraint.string_list[j]); #endif PyList_Append(constraint, item); Py_XDECREF(item); } break; } if(constraint) { PyObject *value = Py_BuildValue("isssiiiiO", i, d->name, d->title, d->desc, d->type, d->unit, d->size, d->cap, constraint); PyList_Append(list, value); Py_XDECREF(value); Py_DECREF(constraint); } i++; } return list; } static PyObject * SaneDev_get_option(SaneDevObject *self, PyObject *args) { int n = 0; if(!PyArg_ParseTuple(args, "i", &n)) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); const SANE_Option_Descriptor *d = sane_get_option_descriptor(self->h, n); RAISE_IF(d == NULL, "Invalid option specified"); void *v = malloc(d->size + 1); SANE_Status st = sane_control_option(self->h, n, SANE_ACTION_GET_VALUE, v, NULL); if(st != SANE_STATUS_GOOD) { free(v); return PySane_Error(st); } PyObject *value = NULL; switch(d->type) { case SANE_TYPE_BOOL: case SANE_TYPE_INT: value = Py_BuildValue("i", *( (SANE_Int*)v) ); break; case SANE_TYPE_FIXED: value = Py_BuildValue("d", SANE_UNFIX((*((SANE_Fixed*)v))) ); break; case SANE_TYPE_STRING: #if PY_MAJOR_VERSION >= 3 value = PyUnicode_DecodeLatin1((const char *) v, strlen((const char *) v), NULL); #else value = Py_BuildValue("s", v); #endif break; case SANE_TYPE_BUTTON: case SANE_TYPE_GROUP: value = Py_BuildValue("O", Py_None); break; default: PyErr_SetString(ErrorObject, "Unknown option type"); } free(v); return value; } static PyObject * SaneDev_set_option(SaneDevObject *self, PyObject *args) { PyObject *value = NULL; int n = 0; if(!PyArg_ParseTuple(args, "iO", &n, &value)) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); const SANE_Option_Descriptor *d = sane_get_option_descriptor(self->h, n); RAISE_IF(d == NULL, "Invalid option specified"); void *v = malloc(d->size + 1); SANE_Word wordval; switch(d->type) { case SANE_TYPE_BOOL: case SANE_TYPE_INT: if(!PyInt_Check(value)) { PyErr_SetString(PyExc_TypeError, "SANE_INT and SANE_BOOL require an integer"); free(v); return NULL; } wordval = PyInt_AsLong(value); memcpy(v, &wordval, sizeof(SANE_Word)); break; case SANE_TYPE_FIXED: if(!PyFloat_Check(value)) { PyErr_SetString(PyExc_TypeError, "SANE_FIXED requires a floating point number"); free(v); return NULL; } wordval = SANE_FIX(PyFloat_AsDouble(value)); memcpy(v, &wordval, sizeof(SANE_Word)); break; case SANE_TYPE_STRING: #if PY_MAJOR_VERSION >= 3 if(!PyUnicode_Check(value)) { PyErr_SetString(PyExc_TypeError, "SANE_STRING requires a string"); free(v); return NULL; } PyObject *strobj = PyUnicode_AsLatin1String(value); if(!strobj) { PyErr_SetString(PyExc_TypeError, "SANE_STRING requires a latin1 string"); free(v); return NULL; } strncpy(v, PyBytes_AsString(strobj), d->size - 1); ((char*)v)[d->size - 1] = 0; Py_DECREF(strobj); #else if(!PyString_Check(value)) { PyErr_SetString(PyExc_TypeError, "SANE_STRING requires a string"); free(v); return NULL; } strncpy(v, PyString_AsString(value), d->size - 1); ((char*)v)[d->size - 1] = 0; #endif break; case SANE_TYPE_BUTTON: case SANE_TYPE_GROUP: PyErr_SetString(ErrorObject, "SANE_TYPE_BUTTON and SANE_TYPE_GROUP can't be set"); free(v); return NULL; } SANE_Int info = 0; SANE_Status st = sane_control_option(self->h, n, SANE_ACTION_SET_VALUE, v, &info); free(v); if(st != SANE_STATUS_GOOD) return PySane_Error(st); return Py_BuildValue("i", info); } static PyObject * SaneDev_set_auto_option(SaneDevObject *self, PyObject *args) { SANE_Int i = 0; int n = 0; if(!PyArg_ParseTuple(args, "i", &n)) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); SANE_Status st = sane_control_option(self->h, n, SANE_ACTION_SET_AUTO, NULL, &i); if(st != SANE_STATUS_GOOD) return PySane_Error(st); return Py_BuildValue("i", i); } static PyObject * SaneDev_snap(SaneDevObject *self, PyObject *args) { SANE_Status st; int noCancel = 0; int allow16bitsamples = 0; if(!PyArg_ParseTuple(args, "|ii", &noCancel, &allow16bitsamples)) return NULL; RAISE_IF(self->h == NULL, "SaneDev object is closed"); /* Get parameters, prepare buffers */ SANE_Parameters p = {}; st = sane_get_parameters(self->h, &p); if(st != SANE_STATUS_GOOD) return PySane_Error(st); RAISE_IF(p.depth != 1 && p.depth != 8 && p.depth != 16, "Bad pixel depth"); int imgSamplesPerPixel = (p.format == SANE_FRAME_GRAY ? 1 : 3); int imgPixelsPerLine = p.pixels_per_line; int imgSampleSize = (p.depth == 16 && allow16bitsamples ? 2 : 1); int imgBytesPerLine = imgPixelsPerLine * imgSamplesPerPixel * imgSampleSize; int imgBytesPerScanLine = imgBytesPerLine; if(p.depth == 1) { /* See Sane spec chapter 4.3.8 */ imgBytesPerScanLine = imgSamplesPerPixel * ((imgPixelsPerLine + 7) / 8); } int imgBufCurLine = 0; int imgBufLines = p.lines < 1 ? 1 : p.lines; const unsigned char bitMasks[8] = {128, 64, 32, 16, 8, 4, 2, 1}; SANE_Byte* imgBuf = (SANE_Byte*)malloc(imgBufLines * imgBytesPerLine); SANE_Int lineBufUsed = 0; SANE_Byte* lineBuf = (SANE_Byte*)malloc(imgBytesPerScanLine); int i, j; /* Read data */ Py_BEGIN_ALLOW_THREADS st = SANE_STATUS_GOOD; while(st == SANE_STATUS_GOOD) { /* Read one line */ lineBufUsed = 0; while(lineBufUsed < imgBytesPerScanLine) { SANE_Int nRead = 0; st = sane_read(self->h, lineBuf + lineBufUsed, imgBytesPerScanLine - lineBufUsed, &nRead); if(st != SANE_STATUS_GOOD) break; lineBufUsed += nRead; } /* Check status, in particular if need to restart for the next frame */ if(st != SANE_STATUS_GOOD) { if(st == SANE_STATUS_EOF && p.last_frame != SANE_TRUE) { /* If this was not the last frame, setup to read the next one */ st = sane_start(self->h); if(st != SANE_STATUS_GOOD) break; st = sane_get_parameters(self->h, &p); if(st != SANE_STATUS_GOOD) break; /* Continue reading */ continue; } break; } /* Resize image buffer if necessary */ if(imgBufCurLine >= imgBufLines) { imgBufLines *= 2; imgBuf = (SANE_Byte*)realloc(imgBuf, imgBufLines * imgBytesPerLine); } int imgBufOffset = imgBufCurLine * imgBytesPerLine; /* Copy data to image buffer */ if(p.format == SANE_FRAME_GRAY || p.format == SANE_FRAME_RGB) { if(p.depth == 1) { /* See Sane spec chapter 3.2.1 */ for(j = 0; j < imgSamplesPerPixel; ++j) { for(i = 0; i < imgPixelsPerLine; ++i) { int iImgBuf = imgBufOffset + imgSamplesPerPixel * i + j; int lineByte = imgSamplesPerPixel * (i / 8) + j; imgBuf[iImgBuf] = (lineBuf[lineByte] & bitMasks[i % 8]) ? 0 : 255; } } } else if(p.depth == 8) { memcpy(imgBuf + imgBufOffset, lineBuf, imgBytesPerLine); } else if(p.depth == 16) { if(imgSampleSize == 2) memcpy(imgBuf + imgBufOffset, lineBuf, imgBytesPerLine); else for(i = 0; i < imgBytesPerLine; ++i) { int16_t value = *((int16_t*)(&lineBuf[2 * i])); /* x >> 8 == x / 256 => rescale from uint16 to uint8 */ imgBuf[imgBufOffset + i] = value >> 8; } } } else if(p.format == SANE_FRAME_RED || p.format == SANE_FRAME_GREEN || p.format == SANE_FRAME_BLUE) { int channel = p.format - SANE_FRAME_RED; if(p.depth == 1) { /* See Sane spec chapter 3.2.1 */ for(i = 0; i < imgPixelsPerLine; ++i) { int iImgBuf = imgBufOffset + 3 * i + channel; imgBuf[iImgBuf] = (lineBuf[i / 8] & bitMasks[i % 8]) ? 0 : 255; } } else if(p.depth == 8) { for(i = 0; i < p.pixels_per_line; ++i) imgBuf[imgBufOffset + 3 * i + channel] = lineBuf[i]; } else if(p.depth == 16) { for(i = 0; i < p.pixels_per_line; ++i) { int16_t value = *(int16_t*)(lineBuf + 2 * i); if(imgSampleSize == 2) { *(int16_t*)(imgBuf+imgBufOffset + 2 * (3 * i + channel)) = value; } else { /* x >> 8 == x / 256 => rescale from uint16 to uint8 */ imgBuf[imgBufOffset + 3 * i + channel] = value >> 8; } } } } else { free(lineBuf); free(imgBuf); PyErr_SetString(ErrorObject, "Invalid frame format"); return NULL; } ++imgBufCurLine; } /* noCancel is true for ADF scans, see _SaneIterator class in sane.py */ if(noCancel != 1) sane_cancel(self->h); free(lineBuf); Py_END_ALLOW_THREADS if(st != SANE_STATUS_EOF) { free(imgBuf); return PySane_Error(st); } /* Create byte array with image data (PyByteArray_FromStringAndSize makes a copy) */ imgBufLines = imgBufCurLine; imgBuf = (SANE_Byte*)realloc(imgBuf, imgBufLines * imgBytesPerLine); PyObject* pyByteArray = PyByteArray_FromStringAndSize((const char*)imgBuf, imgBufLines * imgBytesPerLine); free(imgBuf); if(!pyByteArray) return NULL; PyObject* ret = Py_BuildValue("Oiiii", pyByteArray, imgPixelsPerLine, imgBufLines, imgSamplesPerPixel, imgSampleSize); Py_DECREF(pyByteArray); return ret; } static PyMethodDef SaneDev_methods[] = { {"get_parameters", (PyCFunction)SaneDev_get_parameters, 1}, {"get_options", (PyCFunction)SaneDev_get_options, 1}, {"get_option", (PyCFunction)SaneDev_get_option, 1}, {"set_option", (PyCFunction)SaneDev_set_option, 1}, {"set_auto_option", (PyCFunction)SaneDev_set_auto_option, 1}, {"start", (PyCFunction)SaneDev_start, 1}, {"cancel", (PyCFunction)SaneDev_cancel, 1}, {"snap", (PyCFunction)SaneDev_snap, 1}, {"fileno", (PyCFunction)SaneDev_fileno, 1}, {"close", (PyCFunction)SaneDev_close, 1}, {NULL, NULL} /* sentinel */ }; static PyTypeObject SaneDev_Type = { PyVarObject_HEAD_INIT(NULL, 0) "SaneDev", /*tp_name*/ sizeof(SaneDevObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)SaneDev_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number */ 0, /*tp_as_sequence */ 0, /*tp_as_mapping */ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ SaneDev_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ }; /* ------------------------------------------------------------------------- */ static PyObject * PySane_init(PyObject *self, PyObject *args) { if(!PyArg_ParseTuple(args, "")) return NULL; /* XXX Authorization is not yet supported */ SANE_Int version; SANE_Status st = sane_init(&version, NULL); if(st != SANE_STATUS_GOOD) return PySane_Error(st); return Py_BuildValue("iiii", version, SANE_VERSION_MAJOR(version), SANE_VERSION_MINOR(version), SANE_VERSION_BUILD(version)); } static PyObject * PySane_exit(PyObject *self, PyObject *args) { if(!PyArg_ParseTuple(args, "")) return NULL; sane_exit(); Py_INCREF(Py_None); return Py_None; } static PyObject * PySane_get_devices(PyObject *self, PyObject *args) { int local_only = 0; if(!PyArg_ParseTuple(args, "|i", &local_only)) return NULL; const SANE_Device **devices; SANE_Status st; Py_BEGIN_ALLOW_THREADS st = sane_get_devices(&devices, local_only); Py_END_ALLOW_THREADS if(st != SANE_STATUS_GOOD) return PySane_Error(st); PyObject *list = NULL; if(!(list = PyList_New(0))) return NULL; int i; for(i = 0; devices[i] != NULL; ++i) { const SANE_Device *dev = devices[i]; PyObject *tuple = Py_BuildValue("ssss", dev->name, dev->vendor, dev->model, dev->type); PyList_Append(list, tuple); Py_XDECREF(tuple); } return list; } static PyObject * PySane_open(PyObject *self, PyObject *args) { char *name; if(!PyArg_ParseTuple(args, "s", &name)) return NULL; if(PyType_Ready(&SaneDev_Type) < 0) return NULL; SaneDevObject *dev = PyObject_NEW(SaneDevObject, &SaneDev_Type); RAISE_IF(dev == NULL, "Failed to create SaneDev object"); dev->h = NULL; SANE_Status st; Py_BEGIN_ALLOW_THREADS st = sane_open(name, &(dev->h)); Py_END_ALLOW_THREADS if(st != SANE_STATUS_GOOD) { Py_DECREF(dev); return PySane_Error(st); } return(PyObject *)dev; } static PyObject * PySane_OPTION_IS_ACTIVE(PyObject *self, PyObject *args) { long lg; if(!PyArg_ParseTuple(args, "l", &lg)) return NULL; SANE_Int cap = lg; return PyInt_FromLong( SANE_OPTION_IS_ACTIVE(cap)); } static PyObject * PySane_OPTION_IS_SETTABLE(PyObject *self, PyObject *args) { long lg; if(!PyArg_ParseTuple(args, "l", &lg)) return NULL; SANE_Int cap = lg; return PyInt_FromLong( SANE_OPTION_IS_SETTABLE(cap)); } /* List of functions defined in the module */ static PyMethodDef PySane_methods[] = { {"init", PySane_init, 1}, {"exit", PySane_exit, 1}, {"get_devices", PySane_get_devices, 1}, {"_open", PySane_open, 1}, {"OPTION_IS_ACTIVE", PySane_OPTION_IS_ACTIVE, 1}, {"OPTION_IS_SETTABLE",PySane_OPTION_IS_SETTABLE, 1}, {NULL, NULL} /* sentinel */ }; static void insint(PyObject *d, char *name, int value) { PyObject *v = PyInt_FromLong((long) value); if(!v || PyDict_SetItemString(d, name, v) == -1) PyErr_SetString(ErrorObject, "Can't initialize sane module"); Py_XDECREF(v); } #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef PySane_moduledef = { PyModuleDef_HEAD_INIT, "_sane", NULL, 0, PySane_methods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit__sane(void) { /* Create the module and add the functions */ PyObject *m = PyModule_Create(&PySane_moduledef); if(!m) return NULL; #else /* if PY_MAJOR_VERSION < 3 */ PyMODINIT_FUNC init_sane(void) { /* Create the module and add the functions */ PyObject *m = Py_InitModule("_sane", PySane_methods); if(!m) return; #endif /* Add some symbolic constants to the module */ PyObject *d = PyModule_GetDict(m); ErrorObject = PyErr_NewException("_sane.error", NULL, NULL); PyDict_SetItemString(d, "error", ErrorObject); insint(d, "INFO_INEXACT", SANE_INFO_INEXACT); insint(d, "INFO_RELOAD_OPTIONS", SANE_INFO_RELOAD_OPTIONS); insint(d, "RELOAD_PARAMS", SANE_INFO_RELOAD_PARAMS); insint(d, "FRAME_GRAY", SANE_FRAME_GRAY); insint(d, "FRAME_RGB", SANE_FRAME_RGB); insint(d, "FRAME_RED", SANE_FRAME_RED); insint(d, "FRAME_GREEN", SANE_FRAME_GREEN); insint(d, "FRAME_BLUE", SANE_FRAME_BLUE); insint(d, "CONSTRAINT_NONE", SANE_CONSTRAINT_NONE); insint(d, "CONSTRAINT_RANGE", SANE_CONSTRAINT_RANGE); insint(d, "CONSTRAINT_WORD_LIST", SANE_CONSTRAINT_WORD_LIST); insint(d, "CONSTRAINT_STRING_LIST", SANE_CONSTRAINT_STRING_LIST); insint(d, "TYPE_BOOL", SANE_TYPE_BOOL); insint(d, "TYPE_INT", SANE_TYPE_INT); insint(d, "TYPE_FIXED", SANE_TYPE_FIXED); insint(d, "TYPE_STRING", SANE_TYPE_STRING); insint(d, "TYPE_BUTTON", SANE_TYPE_BUTTON); insint(d, "TYPE_GROUP", SANE_TYPE_GROUP); insint(d, "UNIT_NONE", SANE_UNIT_NONE); insint(d, "UNIT_PIXEL", SANE_UNIT_PIXEL); insint(d, "UNIT_BIT", SANE_UNIT_BIT); 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); insint(d, "CAP_SOFT_DETECT", SANE_CAP_SOFT_DETECT); insint(d, "CAP_EMULATED", SANE_CAP_EMULATED); 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)); /* possible return values of set_option() */ insint(d, "INFO_INEXACT", SANE_INFO_INEXACT); insint(d, "INFO_RELOAD_OPTIONS", SANE_INFO_RELOAD_OPTIONS); insint(d, "INFO_RELOAD_PARAMS", SANE_INFO_RELOAD_PARAMS); /* Check for errors */ if(PyErr_Occurred()) { Py_DECREF(m); m = NULL; } #if PY_MAJOR_VERSION >= 3 return m; #endif } Sane-2.8.2/doc/000077500000000000000000000000001256123574000131635ustar00rootroot00000000000000Sane-2.8.2/doc/Makefile000066400000000000000000000151761256123574000146350ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-sane.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-sane.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/python-sane" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-sane" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." Sane-2.8.2/doc/conf.py000066400000000000000000000214201256123574000144610ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # python-sane documentation build configuration file, created by # sphinx-quickstart on Sun Feb 15 18:59:52 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sysconfig import sys import os # -- Mock module for _sane try: try: # Python >= 3.3 from unittest.mock import MagicMock except: try: # Python < 3.3 from mock import Mock as MagicMock except: raise ImportError class Mock(MagicMock): @classmethod def __getattr__(cls, name): return Mock() sys.modules.update([('_sane', Mock())]) except: pass # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. libbuilddir = "lib.{p}-{v[0]}.{v[1]}".format(p=sysconfig.get_platform(), v=sys.version_info) sys.path.insert(0, os.path.join(os.path.abspath('.'), '..', 'build', libbuilddir)) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.coverage', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'python-sane' copyright = u'2003-2015, Andrew Kuchling, Ralph Heinkel, Sandro Mani' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # import sane # The short X.Y version. version = sane.__version__ # The full version, including alpha/beta/rc tags. release = sane.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'python-sanedoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'python-sane.tex', u'python-sane Documentation', u'Andrew Kuchling, Ralph Heinkel, Sandro Mani', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'python-sane', u'python-sane Documentation', [u'Andrew Kuchling, Ralph Heinkel, Sandro Mani'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'python-sane', u'python-sane Documentation', u'Andrew Kuchling, Ralph Heinkel, Sandro Mani', 'python-sane', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False Sane-2.8.2/doc/index.rst000066400000000000000000000021141256123574000150220ustar00rootroot00000000000000************************* python-sane documentation ************************* The sane module is an Python interface to the SANE (Scanning is Now Easy) library, which provides access to various raster scanning devices such as flatbed scanners and digital cameras. For more information about SANE, consult the SANE website at `www.sane-project.org `_. Note that this documentation doesn't duplicate all the information in the SANE documentation, which you must also consult to get a complete understanding. This module has been originally developed by `A.M. Kuchling `_, it is currently maintained by `Sandro Mani `_. .. contents:: :local: :depth: 1 Indices ======= * :ref:`genindex` * :ref:`search` Reference ========= .. automodule:: sane :members: init, get_devices, open, exit .. autoclass:: sane.SaneDev :members: .. autoclass:: sane.Option :members: .. autoclass:: sane._SaneIterator :members: Example ======= .. literalinclude:: ../example.py :language: python :linenos: Sane-2.8.2/example.py000066400000000000000000000030641256123574000144260ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import sane import numpy from PIL import Image # # Change these for 16bit / grayscale scans # depth = 8 mode = 'color' # # Initialize sane # ver = sane.init() print('SANE version:', ver) # # Get devices # devices = sane.get_devices() print('Available devices:', devices) # # Open first device # dev = sane.open(devices[0][0]) # # Set some options # params = dev.get_parameters() try: dev.depth = depth except: print('Cannot set depth, defaulting to %d' % params[3]) try: dev.mode = mode except: print('Cannot set mode, defaulting to %s' % params[0]) try: dev.br_x = 320. dev.br_y = 240. except: print('Cannot set scan area, using default') params = dev.get_parameters() print('Device parameters:', params) # # Start a scan and get and PIL.Image object # dev.start() im = dev.snap() im.save('test_pil.png') # # Start another scan and get a numpy array object # # Initiate the scan and get and numpy array dev.start() arr = dev.arr_snap() print("Array shape: %s, size: %d, type: %s, range: %d-%d, mean: %.1f, stddev: " "%.1f" % (repr(arr.shape), arr.size, arr.dtype, arr.min(), arr.max(), arr.mean(), arr.std())) if arr.dtype == numpy.uint16: arr = (arr / 255).astype(numpy.uint8) if params[0] == 'color': im = Image.frombytes('RGB', arr.shape[1:], arr.tostring(), 'raw', 'RGB', 0, 1) else: im = Image.frombytes('L', arr.shape[1:], arr.tostring(), 'raw', 'L', 0, 1) im.save('test_numpy.png') # # Close the device # dev.close() Sane-2.8.2/sane.py000066400000000000000000000324321256123574000137220ustar00rootroot00000000000000# 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.sane-project.org/docs.html __version__ = '2.8.2' __author__ = ['Andrew Kuchling', 'Ralph Heinkel', 'Sandro Mani'] import _sane TYPE_STR = {_sane.TYPE_BOOL: "TYPE_BOOL", _sane.TYPE_INT: "TYPE_INT", _sane.TYPE_FIXED: "TYPE_FIXED", _sane.TYPE_STRING: "TYPE_STRING", _sane.TYPE_BUTTON: "TYPE_BUTTON", _sane.TYPE_GROUP: "TYPE_GROUP"} UNIT_STR = {_sane.UNIT_NONE: "UNIT_NONE", _sane.UNIT_PIXEL: "UNIT_PIXEL", _sane.UNIT_BIT: "UNIT_BIT", _sane.UNIT_MM: "UNIT_MM", _sane.UNIT_DPI: "UNIT_DPI", _sane.UNIT_PERCENT: "UNIT_PERCENT", _sane.UNIT_MICROSECOND: "UNIT_MICROSECOND"} class Option: """ Class representing a SANE option. These are returned by a :func:`SaneDev.__getitem__` lookup of an option on the device, i.e.:: option = scanner["mode"] The :class:`Option` class has the following 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: ``TYPE_BOOL``, ``TYPE_INT``, ``TYPE_STRING``, etc. * `unit` -- Units of this option. ``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)`` : Range - list of integers or strings: listed of permitted values """ 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] if not isinstance(self.name, str): self.py_name = str(self.name) else: self.py_name = self.name.replace("-", "_") def is_active(self): """ :returns: Whether the option is active. """ return _sane.OPTION_IS_ACTIVE(self.cap) def is_settable(self): """ :returns: Whether the option is settable. """ 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 = '' s = ("\n" "Name: %s\n" "Cur value: %s\n" "Index: %d\n" "Title: %s\n" "Desc: %s\n" "Type: %s\n" "Unit: %s\n" "Constr: %s\n" "active: %s\n" "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: """ Iterator 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 Exception as e: if str(e) == 'Document feeder out of documents': raise StopIteration else: raise return self.device.snap(True) class SaneDev: """ Class representing a SANE device. Besides the functions documented below, the class has some special attributes which can be read: * `devname` -- The scanner device name (as passed to :func:`sane.open`). * `sane_signature` -- The tuple ``(devname, brand, name, type)``. * `scanner_model` -- The tuple ``(brand, name)``. * `opt` -- Dictionary of options. * `optlist` -- List of option names. * `area` -- Scan area. Furthermore, the scanner options are also exposed as attributes, which can be read and set:: print scanner.mode scanner.mode = 'Color' An :class:`Option` object for a scanner option can be retrieved via :func:`__getitem__`, i.e.:: option = scanner['mode'] """ def __init__(self, devname): d = self.__dict__ d['devname'] = devname d['dev'] = _sane._open(devname) self.__load_option_dict() def __get_sane_signature(self): d = self.__dict__ if 'sane_signature' not in d: devices = _sane.get_devices() if devices: for dev in devices: if d['devname'] == dev[0]: d['sane_signature'] = dev break if 'sane_signature' not in d: raise RuntimeError("No such scan device '%s'" % d['devname']) return d['sane_signature'] def __load_option_dict(self): d = self.__dict__ d['opt'] = {} for t in d['dev'].get_options(): o = Option(t, self) if o.type != _sane.TYPE_GROUP: d['opt'][o.py_name] = o def __setattr__(self, key, value): d = self.__dict__ if key in ('dev', 'optlist', 'area', 'sane_signature', 'scanner_model'): raise AttributeError("Read-only attribute: " + key) if key not in self.opt: d[key] = value return opt = d['opt'][key] if opt.type == _sane.TYPE_BUTTON: raise AttributeError("Buttons don't have values: " + key) if opt.type == _sane.TYPE_GROUP: raise AttributeError("Groups don't have values: " + 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 == _sane.TYPE_FIXED: # avoid annoying errors of backend if int is given instead float: value = float(value) result = d['dev'].set_option(opt.index, value) # do binary AND to find if we have to reload options: if result & _sane.INFO_RELOAD_OPTIONS: self.__load_option_dict() def __getattr__(self, key): d = self.__dict__ 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 == 'sane_signature': return self.__get_sane_signature() if key == 'scanner_model': return self.__get_sane_signature()[1:3] if key in d: return d[key] if key not in d['opt']: raise AttributeError("No such attribute: " + key) opt = d['opt'][key] if opt.type == _sane.TYPE_BUTTON: raise AttributeError("Buttons don't have values: " + key) if opt.type == _sane.TYPE_GROUP: raise AttributeError("Groups don't have values: " + key) if not _sane.OPTION_IS_ACTIVE(opt.cap): raise AttributeError("Inactive option: " + key) return d['dev'].get_option(opt.index) def __getitem__(self, key): return self.opt[key] def get_parameters(self): """ Returns a 5-tuple holding all the current device settings: ``(format, last_frame, (pixels_per_line, lines), depth, bytes_per_line)`` * `format` -- One of ``"grey"``, ``"color"``, ``"red"``, ``"green"``, ``"blue"`` or ``"unknown format"``. * `last_frame` -- Whether this is the last frame of a multi frame image. * `pixels_per_line` -- Width of the scanned image. * `lines` -- Height of the scanned image. * `depth` -- The number of bits per sample. * `bytes_per_line` -- The number of bytes per line. :returns: A tuple containing the device settings. :raises _sane.error: If an error occurs. Note: Some backends may return different parameters depending on whether :func:`SaneDev.start` was called or not. """ return self.__dict__['dev'].get_parameters() def get_options(self): """ :returns: A list of tuples describing all the available options. """ return self.dev.get_options() def start(self): """ Initiate a scanning operation. :throws _sane.error: If an error occurs, for instance if a option is set to an invalid value. """ self.dev.start() def cancel(self): """ Cancel an in-progress scanning operation. """ self.dev.cancel() def snap(self, no_cancel=False): """ Read image data and return a ``PIL.Image`` object. An RGB image is returned for multi-band images, a L image for single-band images. ``no_cancel`` is used for ADF scans by :class:`_SaneIterator`. :returns: A ``PIL.Image`` object. :raises _sane.error: If an error occurs. :raises RuntimeError: If `PIL.Image` cannot be imported. """ try: from PIL import Image except: raise RuntimeError("Cannot import PIL.Image") (data, width, height, samples, sampleSize) = self.dev.snap(no_cancel) if not data: raise RuntimeError("Scanner returned no data") mode = 'RGB' if samples == 3 else 'L' return Image.frombuffer(mode, (width, height), bytes(data), "raw", mode, 0, 1) def scan(self): """ Convenience method which calls :func:`SaneDev.start` followed by :func:`SaneDev.snap`. """ self.start() return self.snap() def arr_snap(self): """ Read image data and return a 3d numpy array of the shape ``(nbands, width, heigth)``. :returns: A ``numpy.array`` object. :raises _sane.error: If an error occurs. :raises RuntimeError: If `numpy` cannot be imported. """ try: import numpy except: raise RuntimeError("Cannot import numpy") (data, width, height, samples, sampleSize) = self.dev.snap(False, True) if not data: raise RuntimeError("Scanner returned no data") if sampleSize == 1: np = numpy.frombuffer(data, numpy.uint8) elif sampleSize == 2: np = numpy.frombuffer(data, numpy.uint16) else: raise RuntimeError("Unexpected sample size: %d" % sampleSize) return numpy.reshape(np, (height, width, samples)) def arr_scan(self): """ Convenience method which calls :func:`SaneDev.start` followed by :func:`SaneDev.arr_snap`. """ self.start() return self.arr_snap() def multi_scan(self): """ :returns: A :class:`_SaneIterator` for ADF scans. """ return _SaneIterator(self) def fileno(self): """ :returns: The file descriptor for the scanning device, if any. :raises _sane.error: If an error occurs. """ return self.dev.fileno() def close(self): """ Close the scanning device. """ self.dev.close() def init(): """ Initialize sane. :returns: A tuple ``(sane_ver, ver_maj, ver_min, ver_patch)``. :raises _sane.error: If an error occurs. """ return _sane.init() def get_devices(localOnly=False): """ Return a list of 4-tuples containing the available scanning devices. If `localOnly` is `True`, only local devices will be returned. Each tuple is of the format ``(device_name, vendor, model, type)``, with: * `device_name` -- The device name, suitable for passing to :func:`sane.open`. * `vendor` -- The device vendor. * `mode` -- The device model vendor. * `type` -- the device type, such as ``"virtual device"`` or ``"video camera"``. :returns: A list of scanning devices. :raises _sane.error: If an error occurs. """ return _sane.get_devices(localOnly) def open(devname): """ Open a device for scanning. Suitable values for devname are returned in the first item of the tuples returned by :func:`sane.get_devices`. :returns: A :class:`SaneDev` object on success. :raises _sane.error: If an error occurs. """ return SaneDev(devname) def exit(): """ Exit sane. """ _sane.exit() Sane-2.8.2/sanedoc.txt000066400000000000000000000175501256123574000146030ustar00rootroot00000000000000 python-sane documentation ************************* The sane module is an Python interface to the SANE (Scanning is Now Easy) library, which provides access to various raster scanning devices such as flatbed scanners and digital cameras. For more information about SANE, consult the SANE website at www.sane- project.org. Note that this documentation doesn't duplicate all the information in the SANE documentation, which you must also consult to get a complete understanding. This module has been originally developed by A.M. Kuchling, it is currently maintained by Sandro Mani. * Indices * Reference * Example Indices ======= * *Index* * *Search Page* Reference ========= sane.init() Initialize sane. Returns: A tuple "(sane_ver, ver_maj, ver_min, ver_patch)". Raises _sane.error: If an error occurs. sane.get_devices(localOnly=False) Return a list of 4-tuples containing the available scanning devices. If *localOnly* is *True*, only local devices will be returned. Each tuple is of the format "(device_name, vendor, model, type)", with: * *device_name* -- The device name, suitable for passing to "sane.open()". * *vendor* -- The device vendor. * *mode* -- The device model vendor. * *type* -- the device type, such as ""virtual device"" or ""video camera"". Returns: A list of scanning devices. Raises _sane.error: If an error occurs. sane.open(devname) Open a device for scanning. Suitable values for devname are returned in the first item of the tuples returned by "sane.get_devices()". Returns: A "SaneDev" object on success. Raises _sane.error: If an error occurs. sane.exit() Exit sane. class class sane.SaneDev(devname) Class representing a SANE device. Besides the functions documented below, the class has some special attributes which can be read: * *devname* -- The scanner device name (as passed to "sane.open()"). * *sane_signature* -- The tuple "(devname, brand, name, type)". * *scanner_model* -- The tuple "(brand, name)". * *opt* -- Dictionary of options. * *optlist* -- List of option names. * *area* -- Scan area. Furthermore, the scanner options are also exposed as attributes, which can be read and set: print scanner.mode scanner.mode = 'Color' An "Option" object for a scanner option can be retrieved via "__getitem__()", i.e.: option = scanner['mode'] arr_scan() Convenience method which calls "SaneDev.start()" followed by "SaneDev.arr_snap()". arr_snap() Read image data and return a 3d numpy array of the shape "(nbands, width, heigth)". Returns: A "numpy.array" object. Raises: * **_sane.error** -- If an error occurs. * **RuntimeError** -- If *numpy* cannot be imported. cancel() Cancel an in-progress scanning operation. close() Close the scanning device. fileno() Returns: The file descriptor for the scanning device, if any. Raises _sane.error: If an error occurs. get_options() Returns: A list of tuples describing all the available options. get_parameters() Returns a 5-tuple holding all the current device settings: "(format, last_frame, (pixels_per_line, lines), depth, bytes_per_line)" * *format* -- One of ""grey"", ""color"", ""red"", ""green"", ""blue"" or ""unknown format"". * *last_frame* -- Whether this is the last frame of a multi frame image. * *pixels_per_line* -- Width of the scanned image. * *lines* -- Height of the scanned image. * *depth* -- The number of bits per sample. * *bytes_per_line* -- The number of bytes per line. Returns: A tuple containing the device settings. Raises _sane.error: If an error occurs. Note: Some backends may return different parameters depending on whether "SaneDev.start()" was called or not. multi_scan() Returns: A "_SaneIterator" for ADF scans. scan() Convenience method which calls "SaneDev.start()" followed by "SaneDev.snap()". snap(no_cancel=False) Read image data and return a "PIL.Image" object. An RGB image is returned for multi-band images, a L image for single-band images. "no_cancel" is used for ADF scans by "_SaneIterator". Returns: A "PIL.Image" object. Raises: * **_sane.error** -- If an error occurs. * **RuntimeError** -- If *PIL.Image* cannot be imported. start() Initiate a scanning operation. Throws _sane.error: If an error occurs, for instance if a option is set to an invalid value. class class sane.Option(args, scanDev) Class representing a SANE option. These are returned by a "SaneDev.__getitem__()" lookup of an option on the device, i.e.: option = scanner["mode"] The "Option" class has the following 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: "TYPE_BOOL", "TYPE_INT", "TYPE_STRING", etc. * *unit* -- Units of this option. "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)" : Range * list of integers or strings: listed of permitted values is_active() Returns: Whether the option is active. is_settable() Returns: Whether the option is settable. class class sane._SaneIterator(device) Iterator for ADF scans. Example ======= #!/usr/bin/env python from __future__ import print_function import sane import numpy from PIL import Image # # Change these for 16bit / grayscale scans # depth = 8 mode = 'color' # # Initialize sane # ver = sane.init() print('SANE version:', ver) # # Get devices # devices = sane.get_devices() print('Available devices:', devices) # # Open first device # dev = sane.open(devices[0][0]) # # Set some options # params = dev.get_parameters() try: dev.depth = depth except: print('Cannot set depth, defaulting to %d' % params[3]) try: dev.mode = mode except: print('Cannot set mode, defaulting to %s' % params[0]) try: dev.br_x = 320. dev.br_y = 240. except: print('Cannot set scan area, using default') params = dev.get_parameters() print('Device parameters:', params) # # Start a scan and get and PIL.Image object # dev.start() im = dev.snap() im.save('test_pil.png') # # Start another scan and get a numpy array object # # Initiate the scan and get and numpy array dev.start() arr = dev.arr_snap() print("Array shape: %s, size: %d, type: %s, range: %d-%d, mean: %.1f, stddev: " "%.1f" % (repr(arr.shape), arr.size, arr.dtype, arr.min(), arr.max(), arr.mean(), arr.std())) if arr.dtype == numpy.uint16: arr = (arr / 255).astype(numpy.uint8) if params[0] == 'color': im = Image.frombytes('RGB', arr.shape[1:], arr.tostring(), 'raw', 'RGB', 0, 1) else: im = Image.frombytes('L', arr.shape[1:], arr.tostring(), 'raw', 'L', 0, 1) im.save('test_numpy.png') # # Close the device # dev.close() Sane-2.8.2/setup.py000066400000000000000000000006331256123574000141320ustar00rootroot00000000000000from distutils.core import setup, Extension sane = Extension('_sane', include_dirs=[], libraries=['sane'], define_macros=[], extra_compile_args=[], sources=['_sane.c']) setup(name='python-sane', version='2.8.2', description='This is the python-sane package', py_modules=['sane'], ext_modules=[sane])