diff options
Diffstat (limited to 'backend/niash.c')
-rw-r--r-- | backend/niash.c | 1486 |
1 files changed, 1486 insertions, 0 deletions
diff --git a/backend/niash.c b/backend/niash.c new file mode 100644 index 0000000..7bc8a25 --- /dev/null +++ b/backend/niash.c @@ -0,0 +1,1486 @@ +/* + Copyright (C) 2001 Bertrik Sikken (bertrik@zonnet.nl) + + 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. + + $Id$ +*/ + +/* + Concept for a backend for scanners based on the NIASH chipset, + such as HP3300C, HP3400C, HP4300C, Agfa Touch. + Parts of this source were inspired by other backends. +*/ + +#include "../include/sane/config.h" +#include "../include/sane/sane.h" +#include "../include/sane/sanei.h" +#include "../include/sane/sanei_backend.h" +#include "../include/sane/sanei_config.h" +#include "../include/sane/saneopts.h" + +#include <stdlib.h> /* malloc, free */ +#include <string.h> /* memcpy */ +#include <stdio.h> +#include <sys/time.h> +#include <sys/wait.h> + +/* definitions for debug */ +#define BACKEND_NAME niash +#define BUILD 1 + +#define DBG_ASSERT 1 +#define DBG_ERR 16 +#define DBG_MSG 32 + +/* Just to avoid conflicts between niash backend and testtool */ +#define WITH_NIASH 1 + + +/* (source) includes for data transfer methods */ +#define STATIC static + +#include "niash_core.c" +#include "niash_xfer.c" + + +#define ASSERT(cond) (!(cond) ? DBG(DBG_ASSERT, "!!! ASSERT(%S) FAILED!!!\n",STRINGIFY(cond));) + + +#define MM_TO_PIXEL(_mm_, _dpi_) ((_mm_) * (_dpi_) / 25.4 ) +#define PIXEL_TO_MM(_pixel_, _dpi_) ((_pixel_) * 25.4 / (_dpi_) ) + + +/* options enumerator */ +typedef enum +{ + optCount = 0, + + optGroupGeometry, + optTLX, optTLY, optBRX, optBRY, + optDPI, + + optGroupImage, + optGammaTable, /* gamma table */ + + optGroupMode, + optMode, + + optGroupEnhancement, + optThreshold, + + + optLast, +/* put temporarily disabled options here after optLast */ + + optGroupMisc, + optLamp, + + optCalibrate, + optGamma /* analog gamma = single number */ +} EOptionIndex; + + +typedef union +{ + SANE_Word w; + SANE_Word *wa; /* word array */ + SANE_String s; +} TOptionValue; + +#define HW_GAMMA_SIZE 4096 +#define SANE_GAMMA_SIZE 4096 + +typedef struct +{ + SANE_Option_Descriptor aOptions[optLast]; + TOptionValue aValues[optLast]; + + TScanParams ScanParams; + THWParams HWParams; + + TDataPipe DataPipe; + int iLinesLeft; /* lines to scan */ + int iBytesLeft; /* bytes to read */ + int iPixelsPerLine; /* pixels in one scan line */ + + SANE_Int aGammaTable[SANE_GAMMA_SIZE]; /* a 12-to-8 bit color lookup table */ + + /* fCancelled needed to let sane issue the cancel message + instead of an error message */ + SANE_Bool fCancelled; /* SANE_TRUE if scanning cancelled */ + + SANE_Bool fScanning; /* SANE_TRUE if actively scanning */ + + int WarmUpTime; /* time to wait before a calibration starts */ + unsigned char CalWhite[3]; /* values for the last calibration of white */ + struct timeval WarmUpStarted; + /* system type to trace the time elapsed */ +} TScanner; + + +/* linked list of SANE_Device structures */ +typedef struct TDevListEntry +{ + struct TDevListEntry *pNext; + SANE_Device dev; +} TDevListEntry; + + +static TDevListEntry *_pFirstSaneDev = 0; +static int iNumSaneDev = 0; +static const SANE_Device **_pSaneDevList = 0; + + +/* option constraints */ +static const SANE_Range rangeGammaTable = { 0, 255, 1 }; + +/* available scanner resolutions */ +static const SANE_Int setResolutions[] = { 4, 75, 150, 300, 600 }; + +/* range of an analog gamma */ +static const SANE_Range rangeGamma = { SANE_FIX (0.25), SANE_FIX (4.0), + SANE_FIX (0.0) +}; + +/* interpolate a sane gamma table to a hardware appropriate one + just in case the sane gamma table would be smaller */ +static void +_ConvertGammaTable (SANE_Word * saneGamma, unsigned char *hwGamma) +{ + int i; + int current = 0; + for (i = 0; i < SANE_GAMMA_SIZE; ++i) + { + int j; + int next; + + /* highest range of copy indices */ + next = ((i + 1) * HW_GAMMA_SIZE) / SANE_GAMMA_SIZE; + + /* always copy the first */ + hwGamma[current] = saneGamma[i]; + + /* the interpolation of the rest depends on the gap */ + for (j = current + 1; j < HW_GAMMA_SIZE && j < next; ++j) + { + hwGamma[j] = + (saneGamma[i] * (next - j) + + saneGamma[i + 1] * (j - current)) / (next - current); + } + current = next; + } +} + +/* create a unity gamma table */ +static void +_UnityGammaTable (unsigned char *hwGamma) +{ + int i; + for (i = 0; i < HW_GAMMA_SIZE; ++i) + { + hwGamma[i] = (i * 256) / HW_GAMMA_SIZE; + } + +} + +static const SANE_Range rangeXmm = { 0, 220, 1 }; +static const SANE_Range rangeYmm = { 0, 296, 1 }; +static const SANE_Int startUpGamma = SANE_FIX (1.6); + +static const char colorStr[] = { SANE_VALUE_SCAN_MODE_COLOR }; +static const char grayStr[] = { SANE_VALUE_SCAN_MODE_GRAY }; +static const char lineartStr[] = { SANE_VALUE_SCAN_MODE_LINEART }; + +#define DEPTH_LINEART 1 +#define DEPTH_GRAY 8 +#define DEPTH_COLOR 8 + +#define BYTES_PER_PIXEL_GRAY 1 +#define BYTES_PER_PIXEL_COLOR 3 + +#define BITS_PER_PIXEL_LINEART 1 +#define BITS_PER_PIXEL_GRAY DEPTH_GRAY +#define BITS_PER_PIXEL_COLOR (DEPTH_COLOR*3) + +#define BITS_PER_BYTE 8 +#define BITS_PADDING (BITS_PER_BYTE-1) + +#define MODE_COLOR 0 +#define MODE_GRAY 1 +#define MODE_LINEART 2 + +/* lineart treshold range */ +static const SANE_Range rangeThreshold = { + 0, + 100, + 1 +}; + +/* scanning modes */ +static SANE_String_Const modeList[] = { + colorStr, + grayStr, + lineartStr, + NULL +}; + +static int +_bytesPerLineLineart (int pixelsPerLine) +{ + return (pixelsPerLine * BITS_PER_PIXEL_LINEART + + BITS_PADDING) / BITS_PER_BYTE; +} + +static int +_bytesPerLineGray (int pixelsPerLine) +{ + return (pixelsPerLine * BITS_PER_PIXEL_GRAY + BITS_PADDING) / BITS_PER_BYTE; +} + +static int +_bytesPerLineColor (int pixelsPerLine) +{ + return (pixelsPerLine * BITS_PER_PIXEL_COLOR + + BITS_PADDING) / BITS_PER_BYTE; +} + + +/* dummy*/ +static void +_rgb2rgb (unsigned char *buffer, int pixels, int threshold) +{ + /* make the compiler content */ + buffer = buffer; + pixels = pixels; + threshold = threshold; +} + + +/* convert 24bit RGB to 8bit GRAY */ +static void +_rgb2gray (unsigned char *buffer, int pixels, int threshold) +{ +#define WEIGHT_R 27 +#define WEIGHT_G 54 +#define WEIGHT_B 19 +#define WEIGHT_W (WEIGHT_R + WEIGHT_G + WEIGHT_B) + static int aWeight[BYTES_PER_PIXEL_COLOR] = + { WEIGHT_R, WEIGHT_G, WEIGHT_B }; + int nbyte = pixels * BYTES_PER_PIXEL_COLOR; + int acc = 0; + int x; + + /* make the compiler content */ + threshold = threshold; + + for (x = 0; x < nbyte; ++x) + { + acc += aWeight[x % BYTES_PER_PIXEL_COLOR] * buffer[x]; + if ((x + 1) % BYTES_PER_PIXEL_COLOR == 0) + { + buffer[x / BYTES_PER_PIXEL_COLOR] = + (unsigned char) (acc / WEIGHT_W); + acc = 0; + } + } +#undef WEIGHT_R +#undef WEIGHT_G +#undef WEIGHT_B +#undef WEIGHT_W +} + +/* convert 24bit RGB to 1bit B/W */ +static void +_rgb2lineart (unsigned char *buffer, int pixels, int threshold) +{ + static const int aMask[BITS_PER_BYTE] = { 128, 64, 32, 16, 8, 4, 2, 1 }; + int acc = 0; + int nx; + int x; + int thresh; + _rgb2gray (buffer, pixels, 0); + nx = ((pixels + BITS_PADDING) / BITS_PER_BYTE) * BITS_PER_BYTE; + thresh = 255 * threshold / rangeThreshold.max; + for (x = 0; x < nx; ++x) + { + if (x < pixels && buffer[x] < thresh) + { + acc |= aMask[x % BITS_PER_BYTE]; + } + if ((x + 1) % BITS_PER_BYTE == 0) + { + buffer[x / BITS_PER_BYTE] = (unsigned char) (acc); + acc = 0; + } + } +} + +typedef struct tgModeParam +{ + SANE_Int depth; + SANE_Frame format; + int (*bytesPerLine) (int pixelsPerLine); + void (*adaptFormat) (unsigned char *rgbBuffer, int pixels, int threshold); + +} TModeParam; + +static const TModeParam modeParam[] = { + {DEPTH_COLOR, SANE_FRAME_RGB, _bytesPerLineColor, _rgb2rgb}, + {DEPTH_GRAY, SANE_FRAME_GRAY, _bytesPerLineGray, _rgb2gray}, + {DEPTH_LINEART, SANE_FRAME_GRAY, _bytesPerLineLineart, _rgb2lineart} +}; + + +#define WARMUP_AFTERSTART 1 /* flag for 1st warm up */ +#define WARMUP_INSESSION 0 +#define WARMUP_TESTINTERVAL 15 /* test every 15sec */ +#define WARMUP_TIME 30 /* first wait is 30sec minimum */ +#define WARMUP_MAXTIME 90 /* after one and a half minute start latest */ + +#define CAL_DEV_MAX 15 +/* maximum deviation of cal values in percent between 2 tests */ + +/* different warm up after start and after automatic off */ +static const int aiWarmUpTime[] = { WARMUP_TESTINTERVAL, WARMUP_TIME }; + + + +/* returns 1, when the warm up time "iTime" has elasped */ +static int +_TimeElapsed (struct timeval *start, struct timeval *now, int iTime) +{ + + /* this is a bit strange, but can deal with overflows */ + if (start->tv_sec > now->tv_sec) + return (start->tv_sec / 2 - now->tv_sec / 2 > iTime / 2); + else + return (now->tv_sec - start->tv_sec >= iTime); +} + +static void +_WarmUpLamp (TScanner * s, int iMode) +{ + SANE_Bool fLampOn; + /* on startup don't care what was before + assume lamp was off, and the previous + cal values can never be reached */ + if (iMode == WARMUP_AFTERSTART) + { + fLampOn = SANE_FALSE; + s->CalWhite[0] = s->CalWhite[1] = s->CalWhite[2] = (unsigned char) (-1); + } + else + GetLamp (&s->HWParams, &fLampOn); + + if (!fLampOn) + { + /* get the current system time */ + gettimeofday (&s->WarmUpStarted, 0); + /* determine the time to wait at least */ + s->WarmUpTime = aiWarmUpTime[iMode]; + /* switch on the lamp */ + SetLamp (&s->HWParams, SANE_TRUE); + } +} + +static void +_WaitForLamp (TScanner * s, unsigned char *pabCalibTable) +{ + struct timeval now[2]; /* toggling time holder */ + int i; /* rgb loop */ + int iCal = 0; /* counter */ + int iCurrent = 0; /* buffer and time-holder swap flag */ + SANE_Bool fHasCal; + unsigned char CalWhite[2][3]; /* toggling buffer */ + int iDelay = 0; /* delay loop counter */ + _WarmUpLamp (s, SANE_FALSE); + + + /* get the time stamp for the wait loops */ + if (s->WarmUpTime) + gettimeofday (&now[iCurrent], 0); + SimpleCalibExt (&s->HWParams, pabCalibTable, CalWhite[iCurrent]); + fHasCal = SANE_TRUE; + + DBG (DBG_MSG, "_WaitForLamp: first calibration\n"); + + + /* wait until time has elapsed or for values to stabilze */ + while (s->WarmUpTime) + { + /* check if the last scan has lower calibration values than + the current one would have */ + if (s->WarmUpTime && fHasCal) + { + SANE_Bool fOver = SANE_TRUE; + for (i = 0; fOver && i < 3; ++i) + { + if (!s->CalWhite[i]) + fOver = SANE_FALSE; + else if (CalWhite[iCurrent][i] < s->CalWhite[i]) + fOver = SANE_FALSE; + } + + /* warm up is not needed, when calibration data is above + the calibration data of the last scan */ + if (fOver) + { + s->WarmUpTime = 0; + DBG (DBG_MSG, + "_WaitForLamp: Values seem stable, skipping next calibration cycle\n"); + } + } + + + /* break the loop, when the longest wait time has expired + to prevent a hanging application, + even if the values might not be good, yet */ + if (s->WarmUpTime && fHasCal && iCal) + { + /* abort, when we have waited long enough */ + if (_TimeElapsed + (&s->WarmUpStarted, &now[iCurrent], WARMUP_MAXTIME)) + { + /* stop idling */ + s->WarmUpTime = 0; + DBG (DBG_MSG, "_WaitForLamp: WARMUP_MAXTIME=%ds elapsed!\n", + WARMUP_MAXTIME); + } + } + + + /* enter a delay loop, when there is still time to wait */ + if (s->WarmUpTime) + { + /* if the (too low) calibration values have just been acquired + we start waiting */ + if (fHasCal) + DBG (DBG_MSG, "_WaitForLamp: entering delay loop\r"); + else + DBG (DBG_MSG, "_WaitForLamp: delay loop %d \r", ++iDelay); + sleep (1); + fHasCal = SANE_FALSE; + gettimeofday (&now[!iCurrent], 0); + } + + + /* look if we should check again */ + if (s->WarmUpTime /* did we have to wait at all */ + /* is the minimum time elapsed */ + && _TimeElapsed (&s->WarmUpStarted, &now[!iCurrent], s->WarmUpTime) + /* has the minimum time elapsed since the last calibration */ + && _TimeElapsed (&now[iCurrent], &now[!iCurrent], + WARMUP_TESTINTERVAL)) + { + int dev = 0; /* 0 percent deviation in cal value as default */ + iDelay = 0; /* all delays processed */ + /* new calibration */ + ++iCal; + iCurrent = !iCurrent; /* swap the test-buffer, and time-holder */ + SimpleCalibExt (&s->HWParams, pabCalibTable, CalWhite[iCurrent]); + fHasCal = SANE_TRUE; + + for (i = 0; i < 3; ++i) + { + /* copy for faster and clearer access */ + int cwa; + int cwb; + int ldev; + cwa = CalWhite[!iCurrent][i]; + cwb = CalWhite[iCurrent][i]; + /* find the biggest deviation of one color */ + if (cwa > cwb) + ldev = 0; + else if (cwa && cwb) + ldev = ((cwb - cwa) * 100) / cwb; + else + ldev = 100; + dev = MAX (dev, ldev); + } + + /* show the biggest deviation of the calibration values */ + DBG (DBG_MSG, "_WaitForLamp: recalibration #%d, deviation = %d%%\n", + iCal, dev); + + /* the deviation to the previous calibration is tolerable */ + if (dev <= CAL_DEV_MAX) + s->WarmUpTime = 0; + } + } + + /* remember the values of this calibration + for the next time */ + for (i = 0; i < 3; ++i) + { + s->CalWhite[i] = CalWhite[iCurrent][i]; + } +} + + +/* used, when setting gamma as 1 value */ +static void +_SetScalarGamma (SANE_Int * aiGamma, SANE_Int sfGamma) +{ + int j; + double fGamma; + fGamma = SANE_UNFIX (sfGamma); + for (j = 0; j < SANE_GAMMA_SIZE; j++) + { + int iData; + iData = + floor (256.0 * + pow (((double) j / (double) SANE_GAMMA_SIZE), 1.0 / fGamma)); + if (iData > 255) + iData = 255; + aiGamma[j] = iData; + } +} + + +/* return size of longest string in a string list */ +static size_t +_MaxStringSize (const SANE_String_Const strings[]) +{ + size_t size, max_size = 0; + int i; + + for (i = 0; strings[i]; ++i) + { + size = strlen (strings[i]) + 1; + if (size > max_size) + max_size = size; + } + return max_size; +} + + +/* change a sane cap and return true, when a change took place */ +static int +_ChangeCap (SANE_Word * pCap, SANE_Word cap, int isSet) +{ + SANE_Word prevCap = *pCap; + if (isSet) + { + *pCap |= cap; + } + else + { + *pCap &= ~cap; + } + return *pCap != prevCap; +} + + +static void +_InitOptions (TScanner * s) +{ + int i; + SANE_Option_Descriptor *pDesc; + TOptionValue *pVal; + _SetScalarGamma (s->aGammaTable, startUpGamma); + + for (i = optCount; i < optLast; i++) + { + + pDesc = &s->aOptions[i]; + pVal = &s->aValues[i]; + + /* defaults */ + pDesc->name = ""; + pDesc->title = ""; + pDesc->desc = ""; + pDesc->type = SANE_TYPE_INT; + pDesc->unit = SANE_UNIT_NONE; + pDesc->size = sizeof (SANE_Word); + pDesc->constraint_type = SANE_CONSTRAINT_NONE; + pDesc->cap = 0; + + switch (i) + { + + case optCount: + pDesc->title = SANE_TITLE_NUM_OPTIONS; + pDesc->desc = SANE_DESC_NUM_OPTIONS; + pDesc->cap = SANE_CAP_SOFT_DETECT; + pVal->w = (SANE_Word) optLast; + break; + + case optGroupGeometry: + pDesc->title = "Geometry"; + pDesc->type = SANE_TYPE_GROUP; + pDesc->size = 0; + break; + + case optTLX: + pDesc->name = SANE_NAME_SCAN_TL_X; + pDesc->title = SANE_TITLE_SCAN_TL_X; + pDesc->desc = SANE_DESC_SCAN_TL_X; + pDesc->unit = SANE_UNIT_MM; + pDesc->constraint_type = SANE_CONSTRAINT_RANGE; + pDesc->constraint.range = &rangeXmm; + pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + pVal->w = rangeXmm.min; + break; + + case optTLY: + pDesc->name = SANE_NAME_SCAN_TL_Y; + pDesc->title = SANE_TITLE_SCAN_TL_Y; + pDesc->desc = SANE_DESC_SCAN_TL_Y; + pDesc->unit = SANE_UNIT_MM; + pDesc->constraint_type = SANE_CONSTRAINT_RANGE; + pDesc->constraint.range = &rangeYmm; + pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + pVal->w = rangeYmm.min; + break; + + case optBRX: + pDesc->name = SANE_NAME_SCAN_BR_X; + pDesc->title = SANE_TITLE_SCAN_BR_X; + pDesc->desc = SANE_DESC_SCAN_BR_X; + pDesc->unit = SANE_UNIT_MM; + pDesc->constraint_type = SANE_CONSTRAINT_RANGE; + pDesc->constraint.range = &rangeXmm; + pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + pVal->w = 210 /* A4 width instead of rangeXmm.max */ ; + break; + + case optBRY: + pDesc->name = SANE_NAME_SCAN_BR_Y; + pDesc->title = SANE_TITLE_SCAN_BR_Y; + pDesc->desc = SANE_DESC_SCAN_BR_Y; + pDesc->unit = SANE_UNIT_MM; + pDesc->constraint_type = SANE_CONSTRAINT_RANGE; + pDesc->constraint.range = &rangeYmm; + pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + pVal->w = 290 /* have a bit reserve instaed of rangeYmm.max */ ; + break; + + case optDPI: + pDesc->name = SANE_NAME_SCAN_RESOLUTION; + pDesc->title = SANE_TITLE_SCAN_RESOLUTION; + pDesc->desc = SANE_DESC_SCAN_RESOLUTION; + pDesc->unit = SANE_UNIT_DPI; + pDesc->constraint_type = SANE_CONSTRAINT_WORD_LIST; + pDesc->constraint.word_list = setResolutions; + pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + pVal->w = setResolutions[2]; /* default to 150dpi */ + break; + + case optGroupImage: + pDesc->title = SANE_I18N ("Image"); + pDesc->type = SANE_TYPE_GROUP; + pDesc->size = 0; + break; + + case optGamma: + pDesc->name = SANE_NAME_ANALOG_GAMMA; + pDesc->title = SANE_TITLE_ANALOG_GAMMA; + pDesc->desc = SANE_DESC_ANALOG_GAMMA; + pDesc->type = SANE_TYPE_FIXED; + pDesc->constraint_type = SANE_CONSTRAINT_RANGE; + pDesc->constraint.range = &rangeGamma; + pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + pVal->w = startUpGamma; + break; + + case optGammaTable: + pDesc->name = SANE_NAME_GAMMA_VECTOR; + pDesc->title = SANE_TITLE_GAMMA_VECTOR; + pDesc->desc = SANE_DESC_GAMMA_VECTOR; + pDesc->size = sizeof (s->aGammaTable); + pDesc->constraint_type = SANE_CONSTRAINT_RANGE; + pDesc->constraint.range = &rangeGammaTable; + pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + pVal->wa = s->aGammaTable; + break; + + case optGroupMisc: + pDesc->title = SANE_I18N ("Miscellaneous"); + pDesc->type = SANE_TYPE_GROUP; + pDesc->size = 0; + break; + + case optLamp: + pDesc->name = "lamp"; + pDesc->title = SANE_I18N ("Lamp status"); + pDesc->desc = SANE_I18N ("Switches the lamp on or off."); + pDesc->type = SANE_TYPE_BOOL; + pDesc->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT; + /* switch the lamp on when starting for first the time */ + pVal->w = SANE_TRUE; + break; + + case optCalibrate: + pDesc->name = "calibrate"; + pDesc->title = SANE_I18N ("Calibrate"); + pDesc->desc = SANE_I18N ("Calibrates for black and white level."); + pDesc->type = SANE_TYPE_BUTTON; + pDesc->cap = SANE_CAP_SOFT_SELECT; + pDesc->size = 0; + break; + + case optGroupMode: + pDesc->title = SANE_I18N ("Scan Mode"); + pDesc->desc = ""; + pDesc->type = SANE_TYPE_GROUP; + break; + + case optMode: + /* scan mode */ + pDesc->name = SANE_NAME_SCAN_MODE; + pDesc->title = SANE_TITLE_SCAN_MODE; + pDesc->desc = SANE_DESC_SCAN_MODE; + pDesc->type = SANE_TYPE_STRING; + pDesc->size = _MaxStringSize (modeList); + pDesc->constraint_type = SANE_CONSTRAINT_STRING_LIST; + pDesc->constraint.string_list = modeList; + pDesc->cap = + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_EMULATED; + pVal->w = MODE_COLOR; + break; + + case optGroupEnhancement: + pDesc->title = SANE_I18N ("Enhancement"); + pDesc->desc = ""; + pDesc->type = SANE_TYPE_GROUP; + break; + + case optThreshold: + pDesc->name = SANE_NAME_THRESHOLD; + pDesc->title = SANE_TITLE_THRESHOLD; + pDesc->desc = SANE_DESC_THRESHOLD; + pDesc->type = SANE_TYPE_INT; + pDesc->unit = SANE_UNIT_PERCENT; + pDesc->constraint_type = SANE_CONSTRAINT_RANGE; + pDesc->constraint.range = &rangeThreshold; + pDesc->cap = + SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_INACTIVE | + SANE_CAP_EMULATED; + pVal->w = 50; + + default: + DBG (DBG_ERR, "Uninitialised option %d\n", i); + break; + } + } +} + + +static int +_ReportDevice (TScannerModel * pModel, const char *pszDeviceName) +{ + TDevListEntry *pNew, *pDev; + + DBG (DBG_MSG, "niash: _ReportDevice '%s'\n", pszDeviceName); + + pNew = malloc (sizeof (TDevListEntry)); + if (!pNew) + { + DBG (DBG_ERR, "no mem\n"); + return -1; + } + + /* add new element to the end of the list */ + if (_pFirstSaneDev == 0) + { + _pFirstSaneDev = pNew; + } + else + { + for (pDev = _pFirstSaneDev; pDev->pNext; pDev = pDev->pNext) + { + ; + } + pDev->pNext = pNew; + } + + /* fill in new element */ + pNew->pNext = 0; + pNew->dev.name = strdup (pszDeviceName); + pNew->dev.vendor = pModel->pszVendor; + pNew->dev.model = pModel->pszName; + pNew->dev.type = "flatbed scanner"; + + iNumSaneDev++; + + return 0; +} + + + +/*****************************************************************************/ + +SANE_Status +sane_init (SANE_Int * piVersion, SANE_Auth_Callback pfnAuth) +{ + /* prevent compiler from complaing about unused parameters */ + pfnAuth = pfnAuth; + + DBG_INIT (); + DBG (DBG_MSG, "sane_init\n"); + + if (piVersion != NULL) + { + *piVersion = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, BUILD); + } + + /* initialise transfer methods */ + iNumSaneDev = 0; + NiashXferInit (_ReportDevice); + + return SANE_STATUS_GOOD; +} + + +void +sane_exit (void) +{ + TDevListEntry *pDev, *pNext; + + DBG (DBG_MSG, "sane_exit\n"); + + /* free device list memory */ + if (_pSaneDevList) + { + for (pDev = _pFirstSaneDev; pDev; pDev = pNext) + { + pNext = pDev->pNext; + free ((void *) pDev->dev.name); + free (pDev); + } + _pFirstSaneDev = 0; + free (_pSaneDevList); + _pSaneDevList = 0; + } +} + + +SANE_Status +sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only) +{ + TDevListEntry *pDev; + int i; + + DBG (DBG_MSG, "sane_get_devices\n"); + + local_only = local_only; + + if (_pSaneDevList) + { + free (_pSaneDevList); + } + + _pSaneDevList = malloc (sizeof (*_pSaneDevList) * (iNumSaneDev + 1)); + if (!_pSaneDevList) + { + DBG (DBG_MSG, "no mem\n"); + return SANE_STATUS_NO_MEM; + } + i = 0; + for (pDev = _pFirstSaneDev; pDev; pDev = pDev->pNext) + { + _pSaneDevList[i++] = &pDev->dev; + } + _pSaneDevList[i++] = 0; /* last entry is 0 */ + + *device_list = _pSaneDevList; + + return SANE_STATUS_GOOD; +} + + +SANE_Status +sane_open (SANE_String_Const name, SANE_Handle * h) +{ + TScanner *s; + + DBG (DBG_MSG, "sane_open: %s\n", name); + + /* check the name */ + if (strlen (name) == 0) + { + /* default to first available device */ + name = _pFirstSaneDev->dev.name; + } + + s = malloc (sizeof (TScanner)); + if (!s) + { + DBG (DBG_MSG, "malloc failed\n"); + return SANE_STATUS_NO_MEM; + } + + if (NiashOpen (&s->HWParams, name) < 0) + { + /* is this OK ? */ + DBG (DBG_ERR, "NiashOpen failed\n"); + free ((void *) s); + return SANE_STATUS_DEVICE_BUSY; + } + _InitOptions (s); + s->fScanning = SANE_FALSE; + s->fCancelled = SANE_FALSE; + *h = s; + + /* Turn on lamp by default at startup */ + _WarmUpLamp (s, WARMUP_AFTERSTART); + + return SANE_STATUS_GOOD; +} + + +void +sane_close (SANE_Handle h) +{ + TScanner *s; + + DBG (DBG_MSG, "sane_close\n"); + + s = (TScanner *) h; + + /* turn off scanner lamp */ + SetLamp (&s->HWParams, SANE_FALSE); + + /* close scanner */ + NiashClose (&s->HWParams); + + /* free scanner object memory */ + free ((void *) s); +} + + +const SANE_Option_Descriptor * +sane_get_option_descriptor (SANE_Handle h, SANE_Int n) +{ + TScanner *s; + + DBG (DBG_MSG, "sane_get_option_descriptor %d\n", n); + + if ((n < optCount) || (n >= optLast)) + { + return NULL; + } + + s = (TScanner *) h; + return &s->aOptions[n]; +} + + +SANE_Status +sane_control_option (SANE_Handle h, SANE_Int n, SANE_Action Action, + void *pVal, SANE_Int * pInfo) +{ + TScanner *s; + SANE_Bool fVal; + static char szTable[100]; + int *pi; + int i; + SANE_Int info; + SANE_Bool fLampIsOn; + SANE_Status status; + SANE_Bool fSame; + + DBG (DBG_MSG, "sane_control_option: option %d, action %d\n", n, Action); + + s = (TScanner *) h; + info = 0; + + switch (Action) + { + case SANE_ACTION_GET_VALUE: + switch (n) + { + + /* Get options of type SANE_Word */ + case optCount: + case optDPI: + case optGamma: + case optTLX: + case optTLY: + case optBRX: + case optBRY: + case optThreshold: + DBG (DBG_MSG, + "sane_control_option: SANE_ACTION_GET_VALUE %d = %d\n", n, + (int) s->aValues[n].w); + *(SANE_Word *) pVal = s->aValues[n].w; + break; + + /* Get options of type SANE_Word array */ + case optGammaTable: + DBG (DBG_MSG, "Reading gamma table\n"); + memcpy (pVal, s->aValues[n].wa, s->aOptions[n].size); + break; + + case optMode: + DBG (DBG_MSG, "Reading scan mode %s\n", + modeList[s->aValues[optMode].w]); + strcpy ((char *) pVal, modeList[s->aValues[optMode].w]); + break; + + /* Get options of type SANE_Bool */ + case optLamp: + GetLamp (&s->HWParams, &fLampIsOn); + *(SANE_Bool *) pVal = fLampIsOn; + break; + + case optCalibrate: + /* although this option has nothing to read, + it's added here to avoid a warning when running scanimage --help */ + break; + + default: + DBG (DBG_MSG, "SANE_ACTION_GET_VALUE: Invalid option (%d)\n", n); + } + break; + + + case SANE_ACTION_SET_VALUE: + if (s->fScanning) + { + DBG (DBG_ERR, + "sane_control_option: SANE_ACTION_SET_VALUE not allowed during scan\n"); + return SANE_STATUS_INVAL; + } + switch (n) + { + + case optCount: + return SANE_STATUS_INVAL; + + case optGamma: + case optThreshold: + case optDPI: + + info |= SANE_INFO_RELOAD_PARAMS; + /* fall through */ + + case optTLX: + case optTLY: + case optBRX: + case optBRY: + + status = sanei_constrain_value (&s->aOptions[n], pVal, &info); + if (status != SANE_STATUS_GOOD) + { + DBG (DBG_ERR, "Failed to constrain option %d (%s)\n", n, + s->aOptions[n].title); + return status; + } + + /* check values if they are equal */ + fSame = s->aValues[n].w == *(SANE_Word *) pVal; + + /* set the values */ + s->aValues[n].w = *(SANE_Word *) pVal; + DBG (DBG_MSG, + "sane_control_option: SANE_ACTION_SET_VALUE %d = %d\n", n, + (int) s->aValues[n].w); + if (n == optGamma) + { + if (!fSame && optLast > optGammaTable) + { + info |= SANE_INFO_RELOAD_OPTIONS; + } + _SetScalarGamma (s->aGammaTable, s->aValues[n].w); + } + break; + + case optGammaTable: + DBG (DBG_MSG, "Writing gamma table\n"); + pi = (SANE_Int *) pVal; + memcpy (s->aValues[n].wa, pVal, s->aOptions[n].size); + + /* prepare table for debug */ + strcpy (szTable, "Gamma table summary:"); + for (i = 0; i < SANE_GAMMA_SIZE; i++) + { + if ((SANE_GAMMA_SIZE / 16) && (i % (SANE_GAMMA_SIZE / 16)) == 0) + { + DBG (DBG_MSG, "%s\n", szTable); + szTable[0] = '\0'; + } + /* test for number print */ + if ((SANE_GAMMA_SIZE / 64) && (i % (SANE_GAMMA_SIZE / 64)) == 0) + { + sprintf (szTable + strlen(szTable), " %04X", pi[i]); + } + } + if (strlen (szTable)) + { + DBG (DBG_MSG, "%s\n", szTable); + } + break; + + case optMode: + { + SANE_Word *pCap; + int fCapChanged = 0; + + pCap = &s->aOptions[optThreshold].cap; + + if (strcmp ((char const *) pVal, colorStr) == 0) + { + s->aValues[optMode].w = MODE_COLOR; + fCapChanged = _ChangeCap (pCap, SANE_CAP_INACTIVE, 1); + } + if (strcmp ((char const *) pVal, grayStr) == 0) + { + s->aValues[optMode].w = MODE_GRAY; + fCapChanged = _ChangeCap (pCap, SANE_CAP_INACTIVE, 1); + } + if (strcmp ((char const *) pVal, lineartStr) == 0) + { + s->aValues[optMode].w = MODE_LINEART; + fCapChanged = _ChangeCap (pCap, SANE_CAP_INACTIVE, 0); + + } + info |= SANE_INFO_RELOAD_PARAMS; + if (fCapChanged) + { + info |= SANE_INFO_RELOAD_OPTIONS; + } + DBG (DBG_MSG, "setting scan mode: %s\n", (char const *) pVal); + } + break; + + + + case optLamp: + fVal = *(SANE_Bool *) pVal; + DBG (DBG_MSG, "lamp %s\n", fVal ? "on" : "off"); + if (fVal) + _WarmUpLamp (s, WARMUP_INSESSION); + else + SetLamp (&s->HWParams, SANE_FALSE); + break; + + case optCalibrate: +/* SimpleCalib(&s->HWParams); */ + break; + + default: + DBG (DBG_ERR, "SANE_ACTION_SET_VALUE: Invalid option (%d)\n", n); + } + if (pInfo != NULL) + { + *pInfo |= info; + } + break; + + + case SANE_ACTION_SET_AUTO: + return SANE_STATUS_UNSUPPORTED; + + + default: + DBG (DBG_ERR, "Invalid action (%d)\n", Action); + return SANE_STATUS_INVAL; + } + + return SANE_STATUS_GOOD; +} + + + + + +SANE_Status +sane_get_parameters (SANE_Handle h, SANE_Parameters * p) +{ + TScanner *s; + TModeParam const *pMode; + + DBG (DBG_MSG, "sane_get_parameters\n"); + + s = (TScanner *) h; + + /* first do some checks */ + if (s->aValues[optTLX].w >= s->aValues[optBRX].w) + { + DBG (DBG_ERR, "TLX should be smaller than BRX\n"); + return SANE_STATUS_INVAL; /* proper error code? */ + } + if (s->aValues[optTLY].w >= s->aValues[optBRY].w) + { + DBG (DBG_ERR, "TLY should be smaller than BRY\n"); + return SANE_STATUS_INVAL; /* proper error code? */ + } + + + pMode = &modeParam[s->aValues[optMode].w]; + + /* return the data */ + p->format = pMode->format; + p->last_frame = SANE_TRUE; + + p->lines = MM_TO_PIXEL (s->aValues[optBRY].w - s->aValues[optTLY].w, + s->aValues[optDPI].w); + p->depth = pMode->depth; + p->pixels_per_line = + MM_TO_PIXEL (s->aValues[optBRX].w - s->aValues[optTLX].w, + s->aValues[optDPI].w); + p->bytes_per_line = pMode->bytesPerLine (p->pixels_per_line); + + return SANE_STATUS_GOOD; +} + + +/* get the scale down factor for a resolution that is + not supported by hardware */ +static int +_SaneEmulateScaling (int iDpi) +{ + if (iDpi == 75) + return 2; + else + return 1; +} + + +SANE_Status +sane_start (SANE_Handle h) +{ + TScanner *s; + SANE_Parameters par; + int iLineCorr; + int iScaleDown; + static unsigned char abGamma[HW_GAMMA_SIZE]; + static unsigned char abCalibTable[HW_PIXELS * 6]; + + DBG (DBG_MSG, "sane_start\n"); + + s = (TScanner *) h; + + if (sane_get_parameters (h, &par) != SANE_STATUS_GOOD) + { + DBG (DBG_MSG, "Invalid scan parameters\n"); + return SANE_STATUS_INVAL; + } + iScaleDown = _SaneEmulateScaling (s->aValues[optDPI].w); + s->iLinesLeft = par.lines; + + /* fill in the scanparams using the option values */ + s->ScanParams.iDpi = s->aValues[optDPI].w * iScaleDown; + s->ScanParams.iLpi = s->aValues[optDPI].w * iScaleDown; + + /* calculate correction for filling of circular buffer */ + iLineCorr = 3 * s->HWParams.iSensorSkew; /* usually 16 motor steps */ + /* calculate correction for garbage lines */ + iLineCorr += s->HWParams.iSkipLines * (HW_LPI / s->ScanParams.iLpi); + + s->ScanParams.iTop = + MM_TO_PIXEL (s->aValues[optTLY].w + s->HWParams.iTopLeftY, + HW_LPI) - iLineCorr; + s->ScanParams.iLeft = + MM_TO_PIXEL (s->aValues[optTLX].w + s->HWParams.iTopLeftX, HW_DPI); + + s->ScanParams.iWidth = par.pixels_per_line * iScaleDown; + s->ScanParams.iHeight = par.lines * iScaleDown; + s->ScanParams.iBottom = HP3300C_BOTTOM; + s->ScanParams.fCalib = SANE_FALSE; + + /* perform a simple calibration just before scanning */ + _WaitForLamp (s, abCalibTable); + + if (s->aValues[optMode].w == MODE_LINEART) + { + /* use a unity gamma table for lineart to be independent from Gamma settings */ + _UnityGammaTable (abGamma); + } + else + { + /* copy gamma table */ + _ConvertGammaTable (s->aGammaTable, abGamma); + } + + WriteGammaCalibTable (abGamma, abGamma, abGamma, abCalibTable, 0, 0, + &s->HWParams); + + /* prepare the actual scan */ + if (!InitScan (&s->ScanParams, &s->HWParams)) + { + DBG (DBG_MSG, "Invalid scan parameters\n"); + return SANE_STATUS_INVAL; + } + + /* init data pipe */ + s->DataPipe.iSkipLines = s->HWParams.iSkipLines; + /* on the hp3400 and hp4300 we cannot set the top of the scan area (yet), + so instead we just scan and throw away the data until the top */ + if (s->HWParams.fReg07) + { + s->DataPipe.iSkipLines += + MM_TO_PIXEL (s->aValues[optTLY].w + s->HWParams.iTopLeftY, + s->aValues[optDPI].w * iScaleDown); + } + s->iBytesLeft = 0; + s->iPixelsPerLine = par.pixels_per_line; + + /* hack */ + s->DataPipe.pabLineBuf = (unsigned char *) malloc (HW_PIXELS * 3); + CircBufferInit (s->HWParams.iXferHandle, &s->DataPipe, + par.pixels_per_line, s->ScanParams.iHeight, + s->ScanParams.iLpi * s->HWParams.iSensorSkew / HW_LPI, + s->HWParams.iReversedHead, iScaleDown, iScaleDown); + + s->fScanning = SANE_TRUE; + s->fCancelled = SANE_FALSE; + return SANE_STATUS_GOOD; +} + + +SANE_Status +sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len) +{ + TScanner *s; + TDataPipe *p; + TModeParam const *pMode; + + DBG (DBG_MSG, "sane_read: buf=%p, maxlen=%d, ", buf, maxlen); + + s = (TScanner *) h; + + pMode = &modeParam[s->aValues[optMode].w]; + + /* sane_read only allowed after sane_start */ + if (!s->fScanning) + { + if (s->fCancelled) + { + DBG (DBG_MSG, "\n"); + DBG (DBG_MSG, "sane_read: sane_read cancelled\n"); + s->fCancelled = SANE_FALSE; + return SANE_STATUS_CANCELLED; + } + else + { + DBG (DBG_ERR, + "sane_read: sane_read only allowed after sane_start\n"); + return SANE_STATUS_INVAL; + } + } + + p = &s->DataPipe; + + /* anything left to read? */ + if ((s->iLinesLeft == 0) && (s->iBytesLeft == 0)) + { + CircBufferExit (p); + free (p->pabLineBuf); + p->pabLineBuf = NULL; + FinishScan (&s->HWParams); + *len = 0; + DBG (DBG_MSG, "\n"); + DBG (DBG_MSG, "sane_read: end of scan\n"); + s->fCancelled = SANE_FALSE; + s->fScanning = SANE_FALSE; + return SANE_STATUS_EOF; + } + + /* time to read the next line? */ + if (s->iBytesLeft == 0) + { + /* read a line from the transfer buffer */ + if (CircBufferGetLineEx (s->HWParams.iXferHandle, p, p->pabLineBuf, + s->HWParams.iReversedHead, SANE_TRUE)) + { + pMode->adaptFormat (p->pabLineBuf, s->iPixelsPerLine, + s->aValues[optThreshold].w); + s->iBytesLeft = pMode->bytesPerLine (s->iPixelsPerLine); + s->iLinesLeft--; + } + /* stop scanning further, when the read action fails + because we try read after the end of the buffer */ + else + { + FinishScan (&s->HWParams); + CircBufferExit (p); + free (p->pabLineBuf); + p->pabLineBuf = NULL; + *len = 0; + DBG (DBG_MSG, "\n"); + DBG (DBG_MSG, "sane_read: read after end of buffer\n"); + s->fCancelled = SANE_FALSE; + s->fScanning = SANE_FALSE; + return SANE_STATUS_EOF; + } + + } + + /* copy (part of) a line */ + *len = MIN (maxlen, s->iBytesLeft); + memcpy (buf, + &p->pabLineBuf[pMode->bytesPerLine (s->iPixelsPerLine) - + s->iBytesLeft], *len); + s->iBytesLeft -= *len; + + DBG (DBG_MSG, " read=%d \n", *len); + + return SANE_STATUS_GOOD; +} + + +void +sane_cancel (SANE_Handle h) +{ + TScanner *s; + + DBG (DBG_MSG, "sane_cancel\n"); + + s = (TScanner *) h; + /* Make sure the scanner head returns home */ + FinishScan (&s->HWParams); + /* delete allocated data */ + if (s->fScanning) + { + CircBufferExit (&s->DataPipe); + free (s->DataPipe.pabLineBuf); + s->DataPipe.pabLineBuf = NULL; + DBG (DBG_MSG, "sane_cancel: freeing buffers\n"); + } + s->fCancelled = SANE_TRUE; + s->fScanning = SANE_FALSE; +} + + +SANE_Status +sane_set_io_mode (SANE_Handle h, SANE_Bool m) +{ + DBG (DBG_MSG, "sane_set_io_mode %s\n", m ? "non-blocking" : "blocking"); + + /* prevent compiler from complaining about unused parameters */ + h = h; + + if (m) + { + return SANE_STATUS_UNSUPPORTED; + } + return SANE_STATUS_GOOD; +} + + +SANE_Status +sane_get_select_fd (SANE_Handle h, SANE_Int * fd) +{ + DBG (DBG_MSG, "sane_select_fd\n"); + + /* prevent compiler from complaining about unused parameters */ + h = h; + fd = fd; + + return SANE_STATUS_UNSUPPORTED; +} |