diff options
Diffstat (limited to 'backend/pixma/pixma_common.c')
-rw-r--r-- | backend/pixma/pixma_common.c | 1187 |
1 files changed, 1187 insertions, 0 deletions
diff --git a/backend/pixma/pixma_common.c b/backend/pixma/pixma_common.c new file mode 100644 index 0000000..7b7ecec --- /dev/null +++ b/backend/pixma/pixma_common.c @@ -0,0 +1,1187 @@ +/* SANE - Scanner Access Now Easy. + + Copyright (C) 2011-2019 Rolf Bensch <rolf at bensch hyphen online dot de> + Copyright (C) 2007-2008 Nicolas Martin, <nicols-guest at alioth dot debian dot org> + Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.de> + + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. + */ +#include "../include/sane/config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <math.h> /* pow(C90) */ + +#include <sys/time.h> /* gettimeofday(4.3BSD) */ +#include <unistd.h> /* usleep */ + +#include "pixma_rename.h" +#include "pixma_common.h" +#include "pixma_io.h" + +#include "../include/sane/sanei_usb.h" +#include "../include/sane/sane.h" + +#ifdef __GNUC__ +# define UNUSED(v) (void) v +#else +# define UNUSED(v) +#endif + +extern const pixma_config_t pixma_mp150_devices[]; +extern const pixma_config_t pixma_mp750_devices[]; +extern const pixma_config_t pixma_mp730_devices[]; +extern const pixma_config_t pixma_mp800_devices[]; +extern const pixma_config_t pixma_iclass_devices[]; + +static const pixma_config_t *const pixma_devices[] = { + pixma_mp150_devices, + pixma_mp750_devices, + pixma_mp730_devices, + pixma_mp800_devices, + pixma_iclass_devices, + NULL +}; + +static pixma_t *first_pixma = NULL; +static time_t tstart_sec = 0; +static uint32_t tstart_usec = 0; +static int debug_level = 1; + + +#ifndef NDEBUG + +static void +u8tohex (uint8_t x, char *str) +{ + static const char hdigit[16] = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f' + }; + str[0] = hdigit[(x >> 4) & 0xf]; + str[1] = hdigit[x & 0xf]; + str[2] = '\0'; +} + +static void +u32tohex (uint32_t x, char *str) +{ + u8tohex (x >> 24, str); + u8tohex (x >> 16, str + 2); + u8tohex (x >> 8, str + 4); + u8tohex (x, str + 6); +} + +void +pixma_hexdump (int level, const void *d_, unsigned len) +{ + const uint8_t *d = (const uint8_t *) (d_); + unsigned ofs, c, plen; + char line[100]; /* actually only 1+8+1+8*3+1+8*3+1 = 61 bytes needed */ + + if (level > debug_level) + return; + if (level == debug_level) + /* if debuglevel == exact match and buffer contains more than 3 lines, print 2 lines + .... */ + plen = (len > 64) ? 32: len; + else + plen = len; + ofs = 0; + while (ofs < plen) + { + char *p; + line[0] = ' '; + u32tohex (ofs, line + 1); + line[9] = ':'; + p = line + 10; + for (c = 0; c != 16 && (ofs + c) < plen; c++) + { + u8tohex (d[ofs + c], p); + p[2] = ' '; + p += 3; + if (c == 7) + { + p[0] = ' '; + p++; + } + } + p[0] = '\0'; + pixma_dbg (level, "%s\n", line); + ofs += c; + } + if (len > plen) + pixma_dbg(level, "......\n"); +} + +static void +time2str (char *buf, unsigned size) +{ + time_t sec; + uint32_t usec; + + pixma_get_time (&sec, &usec); + sec -= tstart_sec; + if (usec >= tstart_usec) + { + usec -= tstart_usec; + } + else + { + usec = 1000000 + usec - tstart_usec; + sec--; + } + snprintf (buf, size, "%lu.%03u", (unsigned long) sec, + (unsigned) (usec / 1000)); +} + +void +pixma_dump (int level, const char *type, const void *data, int len, + int size, int max) +{ + int actual_len, print_len; + char buf[20]; + + if (level > debug_level) + return; + if (debug_level >= 20) + max = -1; /* dump every bytes */ + + time2str (buf, sizeof (buf)); + pixma_dbg (level, "%s T=%s len=%d\n", type, buf, len); + + actual_len = (size >= 0) ? size : len; + print_len = (max >= 0 && max < actual_len) ? max : actual_len; + if (print_len >= 0) + { + pixma_hexdump (level, data, print_len); + if (print_len < actual_len) + pixma_dbg (level, " ...\n"); + } + if (len < 0) + pixma_dbg (level, " ERROR: %s\n", pixma_strerror (len)); + pixma_dbg (level, "\n"); +} + + +#endif /* NDEBUG */ + +/* NOTE: non-reentrant */ +const char * +pixma_strerror (int error) +{ + static char buf[50]; + + /* TODO: more human friendly messages */ + switch (error) + { + case PIXMA_EIO: + return "EIO"; + case PIXMA_ENODEV: + return "ENODEV"; + case PIXMA_EACCES: + return "EACCES"; + case PIXMA_ENOMEM: + return "ENOMEM"; + case PIXMA_EINVAL: + return "EINVAL"; + case PIXMA_EBUSY: + return "EBUSY"; + case PIXMA_ECANCELED: + return "ECANCELED"; + case PIXMA_ENOTSUP: + return "ENOTSUP"; + case PIXMA_ETIMEDOUT: + return "ETIMEDOUT"; + case PIXMA_EPROTO: + return "EPROTO"; + case PIXMA_EPAPER_JAMMED: + return "EPAPER_JAMMED"; + case PIXMA_ECOVER_OPEN: + return "ECOVER_OPEN"; + case PIXMA_ENO_PAPER: + return "ENO_PAPER"; + case PIXMA_EOF: + return "EEOF"; + } + snprintf (buf, sizeof (buf), "EUNKNOWN:%d", error); + return buf; +} + +void +pixma_set_debug_level (int level) +{ + debug_level = level; +} + +void +pixma_set_be16 (uint16_t x, uint8_t * buf) +{ + buf[0] = x >> 8; + buf[1] = x; +} + +void +pixma_set_be32 (uint32_t x, uint8_t * buf) +{ + buf[0] = x >> 24; + buf[1] = x >> 16; + buf[2] = x >> 8; + buf[3] = x; +} + +uint16_t +pixma_get_be16 (const uint8_t * buf) +{ + return ((uint16_t) buf[0] << 8) | buf[1]; +} + +uint32_t +pixma_get_be32 (const uint8_t * buf) +{ + return ((uint32_t) buf[0] << 24) + ((uint32_t) buf[1] << 16) + + ((uint32_t) buf[2] << 8) + buf[3]; +} + +uint8_t +pixma_sum_bytes (const void *data, unsigned len) +{ + const uint8_t *d = (const uint8_t *) data; + unsigned i, sum = 0; + for (i = 0; i != len; i++) + sum += d[i]; + return sum; +} + +void +pixma_sleep (unsigned long usec) +{ + usleep (usec); +} + +void +pixma_get_time (time_t * sec, uint32_t * usec) +{ + struct timeval tv; + gettimeofday (&tv, NULL); + if (sec) + *sec = tv.tv_sec; + if (usec) + *usec = tv.tv_usec; +} + +/* convert 24/48 bit RGB to 8/16 bit ir + * + * Formular: g = R + * drop G + B + * + * sptr: source color scale buffer + * gptr: destination gray scale buffer + * c == 3: 24 bit RGB -> 8 bit ir + * c == 6: 48 bit RGB -> 16 bit ir + */ +uint8_t * +pixma_r_to_ir (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c) +{ + unsigned i; + + /* PDBG (pixma_dbg (4, "*pixma_rgb_to_ir*****\n")); */ + + for (i = 0; i < w; i++) + { + *gptr++ = *sptr++; + if (c == 6) *gptr++ = *sptr++; /* 48 bit RGB: high byte */ + sptr += (c == 6) ? 4 : 2; /* drop G + B */ + } + return gptr; +} + +/* convert 24/48 bit RGB to 8/16 bit grayscale + * + * Formular: g = (R + G + B) / 3 + * + * sptr: source color scale buffer + * gptr: destination gray scale buffer + * c == 3: 24 bit RGB -> 8 bit gray + * c == 6: 48 bit RGB -> 16 bit gray + */ +uint8_t * +pixma_rgb_to_gray (uint8_t * gptr, uint8_t * sptr, unsigned w, unsigned c) +{ + unsigned i, j, g; + + /* PDBG (pixma_dbg (4, "*pixma_rgb_to_gray*****\n")); */ + + for (i = 0; i < w; i++) + { + for (j = 0, g = 0; j < 3; j++) + { + g += *sptr++; + if (c == 6) g += (*sptr++ << 8); /* 48 bit RGB: high byte */ + } + + g /= 3; /* 8 or 16 bit gray */ + *gptr++ = g; + if (c == 6) *gptr++ = (g >> 8); /* 16 bit gray: high byte */ + } + return gptr; +} + +/** + * This code was taken from the genesys backend + * uses threshold and threshold_curve to control software binarization + * @param sp device set up for the scan + * @param dst pointer where to store result + * @param src pointer to raw data + * @param width width of the processed line + * @param c 1 for 1-channel single-byte data, + * 3 for 3-channel single-byte data, + * 6 for double-byte data + * */ +uint8_t * +pixma_binarize_line(pixma_scan_param_t * sp, uint8_t * dst, uint8_t * src, unsigned width, unsigned c) +{ + unsigned j, x, windowX, sum = 0; + unsigned threshold; + unsigned offset, addCol; + int dropCol, offsetX; + unsigned char mask; + uint8_t min, max; + + /* PDBG (pixma_dbg (4, "*pixma_binarize_line***** src = %u, dst = %u, width = %u, c = %u, threshold = %u, threshold_curve = %u *****\n", + src, dst, width, c, sp->threshold, sp->threshold_curve)); */ + + /* 16 bit grayscale not supported */ + if (c == 6) + { + PDBG (pixma_dbg (1, "*pixma_binarize_line***** Error: 16 bit grayscale not supported\n")); + return dst; + } + + /* first, color convert to grayscale */ + if (c != 1) + pixma_rgb_to_gray(dst, src, width, c); + + /* second, normalize line */ + min = 255; + max = 0; + for (x = 0; x < width; x++) + { + if (src[x] > max) + { + max = src[x]; + } + if (src[x] < min) + { + min = src[x]; + } + } + + /* safeguard against dark or white areas */ + if(min>80) + min=0; + if(max<80) + max=255; + for (x = 0; x < width; x++) + { + src[x] = ((src[x] - min) * 255) / (max - min); + } + + /* third, create sliding window, prefill the sliding sum */ + /* ~1mm works best, but the window needs to have odd # of pixels */ + windowX = (6 * sp->xdpi) / 150; + if (!(windowX % 2)) + windowX++; + + /* to avoid conflicts with *dst start with offset */ + offsetX = 1 + (windowX / 2) / 8; + for (j = offsetX; j <= windowX; j++) + sum += src[j]; + /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** windowX = %u, startX = %u, sum = %u\n", + windowX, startX, sum)); */ + + /* fourth, walk the input buffer, output bits */ + for (j = 0; j < width; j++) + { + /* output image location */ + offset = j % 8; + mask = 0x80 >> offset; + threshold = sp->threshold; + + /* move sum/update threshold only if there is a curve */ + if (sp->threshold_curve) + { + addCol = j + windowX / 2; + dropCol = addCol - windowX; + + if (dropCol >= offsetX && addCol < width) + { + sum += src[addCol]; + sum -= (sum < src[dropCol] ? sum : src[dropCol]); /* no negative sum */ + } + threshold = sp->lineart_lut[sum / windowX]; + /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** addCol = %u, dropCol = %d, sum = %u, windowX = %u, lut-element = %d, threshold = %u\n", + addCol, dropCol, sum, windowX, sum/windowX, threshold)); */ + } + + /* lookup threshold */ + if (src[j] > threshold) + *dst &= ~mask; /* white */ + else + *dst |= mask; /* black */ + + if (offset == 7) + dst++; + } + + /* PDBG (pixma_dbg (4, " *pixma_binarize_line***** ready: src = %u, dst = %u *****\n", src, dst)); */ + + return dst; +} + +/** + This code was taken from the genesys backend + Function to build a lookup table (LUT), often + used by scanners to implement brightness/contrast/gamma + or by backends to speed binarization/thresholding + + offset and slope inputs are -127 to +127 + + slope rotates line around central input/output val, + 0 makes horizontal line + + pos zero neg + . x . . x + . x . . x + out . x .xxxxxxxxxxx . x + . x . . x + ....x....... ............ .......x.... + in in in + + offset moves line vertically, and clamps to output range + 0 keeps the line crossing the center of the table + + high low + . xxxxxxxx . + . x . + out x . x + . . x + ............ xxxxxxxx.... + in in + + out_min/max provide bounds on output values, + useful when building thresholding lut. + 0 and 255 are good defaults otherwise. + * */ +static SANE_Status +load_lut (unsigned char * lut, + int in_bits, int out_bits, + int out_min, int out_max, + int slope, int offset) +{ + int i, j; + double shift, rise; + int max_in_val = (1 << in_bits) - 1; + int max_out_val = (1 << out_bits) - 1; + unsigned char * lut_p = lut; + + /* PDBG (pixma_dbg (4, "*load_lut***** start %d %d *****\n", slope, offset)); */ + + /* slope is converted to rise per unit run: + * first [-127,127] to [-1,1] + * then multiply by PI/2 to convert to radians + * then take the tangent (T.O.A) + * then multiply by the normal linear slope + * because the table may not be square, i.e. 1024x256*/ + rise = tan((double)slope/127 * M_PI/2) * max_out_val / max_in_val; + + /* line must stay vertically centered, so figure + * out vertical offset at central input value */ + shift = (double)max_out_val/2 - (rise*max_in_val/2); + + /* convert the user offset setting to scale of output + * first [-127,127] to [-1,1] + * then to [-max_out_val/2,max_out_val/2]*/ + shift += (double)offset / 127 * max_out_val / 2; + + for(i=0;i<=max_in_val;i++){ + j = rise*i + shift; + + if(j<out_min){ + j=out_min; + } + else if(j>out_max){ + j=out_max; + } + + *lut_p=j; + lut_p++; + } + + /* PDBG (pixma_dbg (4, "*load_lut***** finish *****\n")); */ + /* PDBG (pixma_hexdump (4, lut, max_in_val+1)); */ + + return SANE_STATUS_GOOD; +} + +int +pixma_map_status_errno (unsigned status) +{ + switch (status) + { + case PIXMA_STATUS_OK: + return 0; + case PIXMA_STATUS_FAILED: + return PIXMA_ECANCELED; + case PIXMA_STATUS_BUSY: + return PIXMA_EBUSY; + default: + return PIXMA_EPROTO; + } +} + +int +pixma_check_result (pixma_cmdbuf_t * cb) +{ + const uint8_t *r = cb->buf; + unsigned header_len = cb->res_header_len; + unsigned expected_reslen = cb->expected_reslen; + int error; + unsigned len; + + if (cb->reslen < 0) + return cb->reslen; + + len = (unsigned) cb->reslen; + if (len >= header_len) + { + error = pixma_map_status_errno (pixma_get_be16 (r)); + if (expected_reslen != 0) + { + if (len == expected_reslen) + { + if (pixma_sum_bytes (r + header_len, len - header_len) != 0) + error = PIXMA_EPROTO; + } + else + { + /* This case will happen when a command cannot be completely + executed, e.g. because you press the cancel button. The + device will return only a header with PIXMA_STATUS_FAILED. */ + if (len != header_len) + error = PIXMA_EPROTO; + } + } + } + else + error = PIXMA_EPROTO; + +#ifndef NDEBUG + if (error == PIXMA_EPROTO) + { + pixma_dbg (1, "WARNING: result len=%d expected %d\n", + len, cb->expected_reslen); + pixma_hexdump (1, r, MIN (len, 64)); + } +#endif + return error; +} + +int +pixma_cmd_transaction (pixma_t * s, const void *cmd, unsigned cmdlen, + void *data, unsigned expected_len) +{ + int error, tmo; + + error = pixma_write (s->io, cmd, cmdlen); + if (error != (int) cmdlen) + { + if (error >= 0) + { + /* Write timeout is too low? */ + PDBG (pixma_dbg + (1, "ERROR: incomplete write, %u out of %u written\n", + (unsigned) error, cmdlen)); + error = PIXMA_ETIMEDOUT; + } + return error; + } + + /* When you send the start_session command while the scanner optic is + going back to the home position after the last scan session has been + cancelled, you won't get the response before it arrives home. This takes + about 5 seconds. If the last session was succeeded, the scanner will + immediatly answer with PIXMA_STATUS_BUSY. + + Is 8 seconds timeout enough? This affects ALL commands that use + pixma_cmd_transaction(). Default value set in pixma_open(). */ + tmo = s->rec_tmo; + do + { + error = pixma_read (s->io, data, expected_len); + if (error == PIXMA_ETIMEDOUT) + { + PDBG (pixma_dbg (2, "No response yet. Timed out in %d sec.\n", tmo)); + +#ifndef HAVE_SANEI_USB_SET_TIMEOUT + /* 1s timeout + Only needed, if sanei_usb_set_timeout() isn't available. + pixma_read() has an internal timeout of 1 sec. */ + pixma_sleep (1000000); +#endif + } + } + while (error == PIXMA_ETIMEDOUT && --tmo != 0); + if (error < 0) + { + PDBG (pixma_dbg (1, "WARNING: Error in response phase. cmd:%02x%02x\n", + ((const uint8_t *) cmd)[0], + ((const uint8_t *) cmd)[1])); + PDBG (pixma_dbg (1," If the scanner hangs, reset it and/or unplug the " + "USB cable.\n")); + } + return error; /* length of the result packet or error */ +} + +uint8_t * +pixma_newcmd (pixma_cmdbuf_t * cb, unsigned cmd, + unsigned dataout, unsigned datain) +{ + unsigned cmdlen = cb->cmd_header_len + dataout; + unsigned reslen = cb->res_header_len + datain; + + if (cmdlen > cb->size || reslen > cb->size) + return NULL; + memset (cb->buf, 0, cmdlen); + cb->cmdlen = cmdlen; + cb->expected_reslen = reslen; + pixma_set_be16 (cmd, cb->buf); + pixma_set_be16 (dataout + datain, cb->buf + cb->cmd_len_field_ofs); + if (dataout != 0) + return cb->buf + cb->cmd_header_len; + else + return cb->buf + cb->res_header_len; +} + +int +pixma_exec (pixma_t * s, pixma_cmdbuf_t * cb) +{ + if (cb->cmdlen > cb->cmd_header_len) + pixma_fill_checksum (cb->buf + cb->cmd_header_len, + cb->buf + cb->cmdlen - 1); + cb->reslen = + pixma_cmd_transaction (s, cb->buf, cb->cmdlen, cb->buf, + cb->expected_reslen); + return pixma_check_result (cb); +} + +int +pixma_exec_short_cmd (pixma_t * s, pixma_cmdbuf_t * cb, unsigned cmd) +{ + pixma_newcmd (cb, cmd, 0, 0); + return pixma_exec (s, cb); +} + +int +pixma_check_dpi (unsigned dpi, unsigned max) +{ + /* valid dpi = 75 * 2^n */ + unsigned temp = dpi / 75; + if (dpi > max || dpi < 75 || 75 * temp != dpi || (temp & (temp - 1)) != 0) + return PIXMA_EINVAL; + return 0; +} + + +int +pixma_init (void) +{ + PDBG (pixma_dbg (2, "pixma version %d.%d.%d\n", PIXMA_VERSION_MAJOR, + PIXMA_VERSION_MINOR, PIXMA_VERSION_BUILD)); + PASSERT (first_pixma == NULL); + if (tstart_sec == 0) + pixma_get_time (&tstart_sec, &tstart_usec); + return pixma_io_init (); +} + +void +pixma_cleanup (void) +{ + while (first_pixma) + pixma_close (first_pixma); + pixma_io_cleanup (); +} + +int +pixma_open (unsigned devnr, pixma_t ** handle) +{ + int error; + pixma_t *s; + const pixma_config_t *cfg; + + *handle = NULL; + cfg = pixma_get_device_config (devnr); + if (!cfg) + return PIXMA_EINVAL; /* invalid devnr */ + PDBG (pixma_dbg (2, "pixma_open(): %s\n", cfg->name)); + + s = (pixma_t *) calloc (1, sizeof (s[0])); + if (!s) + return PIXMA_ENOMEM; + s->next = first_pixma; + first_pixma = s; + + s->cfg = cfg; + s->rec_tmo = 8; /* set receive timeout to 8 seconds */ + error = pixma_connect (devnr, &s->io); + if (error < 0) + { + PDBG (pixma_dbg + (2, "pixma_connect() failed %s\n", pixma_strerror (error))); + goto rollback; + } + strncpy (s->id, pixma_get_device_id (devnr), sizeof (s->id) - 1); + s->ops = s->cfg->ops; + s->scanning = 0; + error = s->ops->open (s); + if (error < 0) + goto rollback; + error = pixma_deactivate (s->io); + if (error < 0) + goto rollback; + *handle = s; + return 0; + +rollback: + PDBG (pixma_dbg (2, "pixma_open() failed %s\n", pixma_strerror (error))); + pixma_close (s); + return error; +} + +void +pixma_close (pixma_t * s) +{ + pixma_t **p; + + if (!s) + return; + for (p = &first_pixma; *p && *p != s; p = &((*p)->next)) + { + } + PASSERT (*p); + if (!(*p)) + return; + PDBG (pixma_dbg (2, "pixma_close(): %s\n", s->cfg->name)); + if (s->io) + { + if (s->scanning) + { + PDBG (pixma_dbg (3, "pixma_close(): scanning in progress, call" + " finish_scan()\n")); + s->ops->finish_scan (s); + } + s->ops->close (s); + pixma_disconnect (s->io); + } + *p = s->next; + free (s); +} + +int +pixma_scan (pixma_t * s, pixma_scan_param_t * sp) +{ + int error; + + error = pixma_check_scan_param (s, sp); + if (error < 0) + return error; + + if (sp->mode == PIXMA_SCAN_MODE_LINEART) + { + load_lut(sp->lineart_lut, 8, 8, 50, 205, + sp->threshold_curve, sp->threshold-127); + } + +#ifndef NDEBUG + pixma_dbg (3, "\n"); + pixma_dbg (3, "pixma_scan(): start\n"); + pixma_dbg (3, " line_size=%"PRIu64" image_size=%"PRIu64" channels=%u depth=%u\n", + sp->line_size, sp->image_size, sp->channels, sp->depth); + pixma_dbg (3, " dpi=%ux%u offset=(%u,%u) dimension=%ux%u\n", + sp->xdpi, sp->ydpi, sp->x, sp->y, sp->w, sp->h); + pixma_dbg (3, " gamma_table=%p source=%d\n", sp->gamma_table, sp->source); + pixma_dbg (3, " threshold=%d threshold_curve=%d\n", sp->threshold, sp->threshold_curve); + pixma_dbg (3, " adf-wait=%d\n", sp->adf_wait); + pixma_dbg (3, " ADF page count: %d\n", sp->adf_pageid); +#endif + + s->param = sp; + s->cancel = 0; + s->cur_image_size = 0; + s->imagebuf.wptr = NULL; + s->imagebuf.wend = NULL; + s->imagebuf.rptr = NULL; + s->imagebuf.rend = NULL; + s->underrun = 0; + error = s->ops->scan (s); + if (error >= 0) + { + s->scanning = 1; + } + else + { + PDBG (pixma_dbg + (3, "pixma_scan() failed %s\n", pixma_strerror (error))); + } + + return error; +} + +static uint8_t * +fill_pixels (pixma_t * s, uint8_t * ptr, uint8_t * end, uint8_t value) +{ + if (s->cur_image_size < s->param->image_size) + { + long n = s->param->image_size - s->cur_image_size; + if (n > (end - ptr)) + n = end - ptr; + memset (ptr, value, n); + s->cur_image_size += n; + ptr += n; + } + return ptr; +} + +int +pixma_read_image (pixma_t * s, void *buf, unsigned len) +{ + int result; + pixma_imagebuf_t ib; + + if (!s->scanning) + return 0; + if (s->cancel) + { + result = PIXMA_ECANCELED; + goto cancel; + } + + ib = s->imagebuf; /* get rptr and rend */ + ib.wptr = (uint8_t *) buf; + ib.wend = ib.wptr + len; + + if (s->underrun) + { + if (s->cur_image_size < s->param->image_size) + { + ib.wptr = fill_pixels (s, ib.wptr, ib.wend, 0xff); + } + else + { + PDBG (pixma_dbg + (3, "pixma_read_image(): completed (underrun detected)\n")); + s->scanning = 0; + } + return ib.wptr - (uint8_t *) buf; + } + + while (ib.wptr != ib.wend) + { + if (ib.rptr == ib.rend) + { + ib.rptr = ib.rend = NULL; + result = s->ops->fill_buffer (s, &ib); + if (result < 0) + goto cancel; + if (result == 0) + { /* end of image? */ + s->ops->finish_scan (s); + if ((s->cur_image_size != s->param->image_size) && !s->param->mode_jpeg) + { + pixma_dbg (1, "WARNING:image size mismatches\n"); + pixma_dbg (1, + " %"PRIu64" expected (%d lines) but %"PRIu64" received (%"PRIu64" lines)\n", + s->param->image_size, s->param->h, + s->cur_image_size, + s->cur_image_size / s->param->line_size); + if ((s->cur_image_size % s->param->line_size) != 0) + { + pixma_dbg (1, + "BUG:received data not multiple of line_size\n"); + } + } + if ((s->cur_image_size < s->param->image_size) && !s->param->mode_jpeg) + { + s->underrun = 1; + ib.wptr = fill_pixels (s, ib.wptr, ib.wend, 0xff); + } + else + { + PDBG (pixma_dbg (3, "pixma_read_image():completed\n")); + s->scanning = 0; + } + break; + } + s->cur_image_size += result; + + PASSERT (s->cur_image_size <= s->param->image_size); + } + if (ib.rptr) + { + unsigned count = MIN (ib.rend - ib.rptr, ib.wend - ib.wptr); + memcpy (ib.wptr, ib.rptr, count); + ib.rptr += count; + ib.wptr += count; + } + } + s->imagebuf = ib; /* store rptr and rend */ + return ib.wptr - (uint8_t *) buf; + +cancel: + s->ops->finish_scan (s); + s->scanning = 0; + if (result == PIXMA_ECANCELED) + { + PDBG (pixma_dbg (3, "pixma_read_image(): cancelled by %sware\n", + (s->cancel) ? "soft" : "hard")); + } + else + { + PDBG (pixma_dbg (3, "pixma_read_image() failed %s\n", + pixma_strerror (result))); + } + return result; +} + +void +pixma_cancel (pixma_t * s) +{ + s->cancel = 1; +} + +int +pixma_enable_background (pixma_t * s, int enabled) +{ + return pixma_set_interrupt_mode (s->io, enabled); +} + +int +pixma_activate_connection(pixma_t * s) +{ + return pixma_activate (s->io); +} + +int +pixma_deactivate_connection(pixma_t * s) +{ + return pixma_deactivate (s->io); +} + +uint32_t +pixma_wait_event (pixma_t * s, int timeout /*ms */ ) +{ + unsigned events; + + if (s->events == PIXMA_EV_NONE && s->ops->wait_event) + s->ops->wait_event (s, timeout); + events = s->events; + s->events = PIXMA_EV_NONE; + return events; +} + +#define CLAMP2(x,w,min,max,dpi) do { \ + unsigned m = (max) * (dpi) / 75; \ + x = MIN(x, m - min); \ + w = MIN(w, m - x); \ + if (w < min) w = min; \ +} while(0) + +int +pixma_check_scan_param (pixma_t * s, pixma_scan_param_t * sp) +{ + unsigned cfg_xdpi; + + if (!(sp->channels == 3 || + (sp->channels == 1 && (s->cfg->cap & PIXMA_CAP_GRAY) != 0))) + return PIXMA_EINVAL; + + /* flatbed: use s->cfg->xdpi + * TPU/ADF: use s->cfg->adftpu_max_dpi, if configured with dpi value */ + cfg_xdpi = ((sp->source == PIXMA_SOURCE_FLATBED + || s->cfg->adftpu_max_dpi == 0) ? s->cfg->xdpi + : s->cfg->adftpu_max_dpi); + + if (pixma_check_dpi (sp->xdpi, cfg_xdpi) < 0 || + pixma_check_dpi (sp->ydpi, s->cfg->ydpi) < 0) + return PIXMA_EINVAL; + + /* xdpi must be equal to ydpi except that + xdpi = max_xdpi and ydpi = max_ydpi. */ + if (!(sp->xdpi == sp->ydpi || + (sp->xdpi == cfg_xdpi && sp->ydpi == s->cfg->ydpi))) + return PIXMA_EINVAL; + + if (s->ops->check_param (s, sp) < 0) + return PIXMA_EINVAL; + + /* FIXME: I assume the same minimum width and height for every model. + * new scanners need minimum 16 px height + * minimum image size: 16 px x 16 px */ + CLAMP2 (sp->x, sp->w, 16, s->cfg->width, sp->xdpi); + CLAMP2 (sp->y, sp->h, 16, s->cfg->height, sp->ydpi); + + switch (sp->source) + { + case PIXMA_SOURCE_FLATBED: + break; + + case PIXMA_SOURCE_TPU: + if ((s->cfg->cap & PIXMA_CAP_TPU) != PIXMA_CAP_TPU) + { + sp->source = PIXMA_SOURCE_FLATBED; + PDBG (pixma_dbg + (1, "WARNING: TPU unsupported, fallback to flatbed.\n")); + } + break; + + case PIXMA_SOURCE_ADF: + if ((s->cfg->cap & PIXMA_CAP_ADF) != PIXMA_CAP_ADF) + { + sp->source = PIXMA_SOURCE_FLATBED; + PDBG (pixma_dbg + (1, "WARNING: ADF unsupported, fallback to flatbed.\n")); + } + break; + + case PIXMA_SOURCE_ADFDUP: + if ((s->cfg->cap & PIXMA_CAP_ADFDUP) != PIXMA_CAP_ADFDUP) + { + if (s->cfg->cap & PIXMA_CAP_ADF) + { + sp->source = PIXMA_SOURCE_ADF; + } + else + { + sp->source = PIXMA_SOURCE_FLATBED; + } + PDBG (pixma_dbg + (1, "WARNING: ADF duplex unsupported, fallback to %d.\n", + sp->source)); + } + break; + } + + if (sp->depth == 0) + sp->depth = 8; + if ((sp->depth % 8) != 0 && sp->depth != 1) + return PIXMA_EINVAL; + + sp->line_size = 0; + + if (s->ops->check_param (s, sp) < 0) + return PIXMA_EINVAL; + + if (sp->line_size == 0) + sp->line_size = sp->depth / 8 * sp->channels * sp->w; + sp->image_size = sp->line_size * sp->h; + + /* image_size for software lineart is counted in bits */ + if (sp->software_lineart == 1) + sp->image_size /= 8; + return 0; +} + +const char * +pixma_get_string (pixma_t * s, pixma_string_index_t i) +{ + switch (i) + { + case PIXMA_STRING_MODEL: + return s->cfg->name; + case PIXMA_STRING_ID: + return s->id; + case PIXMA_STRING_LAST: + return NULL; + } + return NULL; +} + +const pixma_config_t * +pixma_get_config (pixma_t * s) +{ + return s->cfg; +} + +void +pixma_fill_gamma_table (double gamma, uint8_t * table, unsigned n) +{ + int i; + double r_gamma = 1.0 / gamma; + double out_scale = 255.0; + double in_scale = 1.0 / (n - 1); + + for (i = 0; (unsigned) i != n; i++) + { + table[i] = (int) (out_scale * pow (i * in_scale, r_gamma) + 0.5); + } +} + +int +pixma_find_scanners (const char **conf_devices, SANE_Bool local_only) +{ + return pixma_collect_devices (conf_devices, pixma_devices, local_only); +} + +const char * +pixma_get_device_model (unsigned devnr) +{ + const pixma_config_t *cfg = pixma_get_device_config (devnr); + return (cfg) ? cfg->name : NULL; +} + + +int +pixma_get_device_status (pixma_t * s, pixma_device_status_t * status) +{ + if (!status) + return PIXMA_EINVAL; + memset (status, 0, sizeof (*status)); + return s->ops->get_status (s, status); +} |