diff --git a/backend/niash.c b/backend/niash.c new file mode 100644 index 000000000..7437109ba --- /dev/null +++ b/backend/niash.c @@ -0,0 +1,1113 @@ +/* + 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 /* malloc, free */ +#include /* memcpy */ +#include +#include +#include + +/* 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));) + +/* other definitions */ +#define TRUE 1 +#define FALSE 0 + +#define MM_TO_PIXEL(_mm_, _dpi_) ((_mm_) * (_dpi_) / 25.4 ) +#define PIXEL_TO_MM(_pixel_, _dpi_) ((_pixel_) * 25.4 / (_dpi_) ) + +/* set this value to 1, if you want to use 75dpi as lowest resolution */ +#define DPI75 1 + + + +/* options enumerator */ +typedef enum +{ + optCount = 0, + + optGroupGeometry, + optTLX, optTLY, optBRX, optBRY, + optDPI, + + optGroupImage, + optGammaTable, /* gamma table */ + + 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; + + +typedef struct +{ + SANE_Option_Descriptor aOptions[optLast]; + TOptionValue aValues[optLast]; + + TScanParams ScanParams; + THWParams HWParams; + + TDataPipe DataPipe; + int iLinesLeft; + + SANE_Int aGammaTable[4096]; /* a 12-to-8 bit color lookup table */ + + /* fCancelled needed to let sane issue the cancel message + instead of an error message */ + int fCancelled; /* TRUE if scanning cancelled */ + + int fScanning; /* 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[] = { +#if DPI75 + 4, 75, +#else + 3, +#endif + 150, 300, 600 +}; + +static const SANE_Range rangeGamma = { SANE_FIX (0.25), SANE_FIX (4.0), + SANE_FIX (0.0) +}; +static const SANE_Range rangeXmm = { 0, 220, 1 }; +static const SANE_Range rangeYmm = { 0, 290, 1 }; + + +#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 25 +/* maximum deviation of cal values in pecent + bewteen 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 cope 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) +{ + 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 = 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, 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 */ + bool fHasCal; + unsigned char CalWhite[2][3]; /* toggling buffer */ + int iDelay = 0; /* delay loop counter */ + _WarmUpLamp (s, FALSE); + + + /* get the time stamp for the wait loops */ + if (s->WarmUpTime) + gettimeofday (&now[iCurrent], 0); + SimpleCalibExt (&s->HWParams, pabCalibTable, CalWhite[iCurrent]); + fHasCal = 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) + { + bool fOver = TRUE; + for (i = 0; fOver && i < 3; ++i) + { + if (!s->CalWhite[i]) + fOver = FALSE; + else if (CalWhite[iCurrent][i] < s->CalWhite[i]) + fOver = 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 = 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 = 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]; + } +} + +static void +_InitOptions (TScanner * s) +{ + int i, j; + SANE_Option_Descriptor *pDesc; + TOptionValue *pVal; + + 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 = 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 = 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[1 + (DPI75 != 0)]; /* 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 = SANE_FIX (1.0); + 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; + /* set a neutral gamma */ + for (j = 0; j < 4096; j++) + { + s->aGammaTable[j] = j / 16; + } + 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; + + 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 (V_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 = FALSE; + s->fCancelled = 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, 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; + int fVal; + static char szTable[15000]; + char szTemp[16]; + int *pi; + int i; + SANE_Int info; + bool fLampIsOn; + SANE_Status status; + + 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: + 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; + + /* 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 optDPI: + case optTLX: + case optTLY: + case optBRX: + case optBRY: + info |= SANE_INFO_RELOAD_PARAMS; + /* fall through */ + + case optGamma: + 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; + } + 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); + break; + + case optGammaTable: + DBG (DBG_MSG, "Writing gamma table\n"); + pi = (SANE_Int *) pVal; + strcpy (szTable, ""); + for (i = 0; i < 4096; i++) + { + if ((i % 32) == 0) + { + sprintf (szTemp, " %04X", pi[i]); + strcat (szTable, szTemp); + } + } + memcpy (s->aValues[n].wa, pVal, s->aOptions[n].size); + DBG (DBG_MSG, "Gamma table summary:\n%s\n", szTable); + 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, 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; + 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? */ + } + + /* return the data */ + p->format = SANE_FRAME_RGB; + p->last_frame = SANE_TRUE; + + p->lines = MM_TO_PIXEL (s->aValues[optBRY].w - s->aValues[optTLY].w, + s->aValues[optDPI].w); + p->depth = 8; + p->pixels_per_line = + MM_TO_PIXEL (s->aValues[optBRX].w - s->aValues[optTLX].w, + s->aValues[optDPI].w); + p->bytes_per_line = p->pixels_per_line * 3; + + return SANE_STATUS_GOOD; +} + +/* get the scale don 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 i, iLineCorr; + int iScaleDown; + static unsigned char abGamma[4096]; + 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 = 14200UL; + s->ScanParams.fCalib = FALSE; + + /* perform a simple calibration just before scanning */ + _WaitForLamp (s, abCalibTable); + + /* copy gamma table */ + for (i = 0; i < 4096; i++) + { + abGamma[i] = s->aValues[optGammaTable].wa[i]; + } + + 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->DataPipe.iBytesLeft = 0; + /* 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 = TRUE; + s->fCancelled = 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; + + DBG (DBG_MSG, "sane_read: buf=%p, maxlen=%d, ", buf, maxlen); + + s = (TScanner *) h; + + /* 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 = 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) && (p->iBytesLeft == 0)) + { + CircBufferExit (&s->DataPipe); + free (p->pabLineBuf); + FinishScan (&s->HWParams); + *len = 0; + DBG (DBG_MSG, "\n"); + DBG (DBG_MSG, "sane_read: end of scan\n"); + s->fCancelled = FALSE; + s->fScanning = FALSE; + return SANE_STATUS_EOF; + } + + /* time to read the next line? */ + if (p->iBytesLeft == 0) + { + /* read a line from the transfer buffer */ + if (CircBufferGetLine (s->HWParams.iXferHandle, p, p->pabLineBuf, + s->HWParams.iReversedHead)) + { + p->iBytesLeft = p->iSaneBytesPerLine; + s->iLinesLeft--; + } + /* stop scanning further, when the read action fails + because we try read after the end of the buffer */ + else + { + CircBufferExit (&s->DataPipe); + free (p->pabLineBuf); + FinishScan (&s->HWParams); + *len = 0; + DBG (DBG_MSG, "\n"); + DBG (DBG_MSG, "sane_read: read after end of buffer\n"); + s->fCancelled = FALSE; + s->fScanning = FALSE; + return SANE_STATUS_EOF; + } + + } + + /* copy (part of) a line */ + *len = MIN (maxlen, p->iBytesLeft); + memcpy (buf, &p->pabLineBuf[p->iSaneBytesPerLine - p->iBytesLeft], *len); + p->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; + + /* to be implemented more thoroughly */ + + /* Make sure the scanner head returns home */ + FinishScan (&s->HWParams); + s->fCancelled = TRUE; + s->fScanning = 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; +} diff --git a/backend/niash_core.c b/backend/niash_core.c new file mode 100644 index 000000000..c508e0c4c --- /dev/null +++ b/backend/niash_core.c @@ -0,0 +1,1421 @@ +/* + 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$ +*/ + +/* + Core NIASH chip functions. +*/ + +#include /* fopen, fread, fwrite, fclose etc */ +#include /* va_list for vfprintf */ +#include /* memcpy, memset */ +#include /* unlink */ +#include /* malloc, free */ +#include /* exp, pow */ + +#include "niash_xfer.h" +#include "niash_core.h" + + +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + + +#define XFER_BUF_SIZE 0xF000 + + +/* HP3400 firmware data */ +static unsigned char abData0000[] = { + 0xfe, 0x9f, 0x58, 0x1b, 0x00, 0x03, 0xa4, 0x02, 0x63, 0x02, 0x33, 0x02, + 0x0d, 0x02, 0xf0, 0x01, + 0xd8, 0x01, 0xc5, 0x01, 0xb5, 0x01, 0xa8, 0x01, 0x9d, 0x01, 0x93, 0x01, + 0x8b, 0x01, 0x84, 0x01, + 0x7e, 0x01, 0x79, 0x01, 0x74, 0x01, 0x70, 0x01, 0x6d, 0x01, 0x69, 0x01, + 0x67, 0x01, 0x64, 0x01, + 0x62, 0x01, 0x60, 0x01, 0x5f, 0x01, 0x5d, 0x01, 0x5c, 0x01, 0x5b, 0x01, + 0x5a, 0x01, 0x59, 0x01, + 0x58, 0x01, 0x57, 0x01, 0x57, 0x01, 0x56, 0x01, 0x56, 0x01, 0x55, 0x01, + 0x55, 0x01, 0x54, 0x01, + 0x54, 0x01, 0x54, 0x01, 0x54, 0x01, 0x53, 0x01, 0x53, 0x01, 0x53, 0x01, + 0x53, 0x01, 0x52, 0x81 +}; + +/* 1st word : 0x9ffe = 40958, strip 15th bit: 0x1ffe = 8190 + 2nd word : 0x1b58 = 7000 -> coincidence ? + other words: formula: y = 676 / (2 - exp(0.113 * (1-x)) ), where x = 0 for first entry +*/ + +/* more HP3400 firmware data */ +static unsigned char abData0400[] = { + 0xa4, 0x82, 0x00, 0x80, 0xa4, 0x82, 0xaa, 0x02, 0xc0, 0x02, 0xe8, 0x02, + 0x3e, 0x03, 0xc8, 0x03, + 0x58, 0x1b, 0xfe, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + + + +static void +_ConvertMotorTable (unsigned char *pabOld, unsigned char *pabNew, int iSize, + int iLpi) +{ + int iData, i, iBit15; + + for (i = 0; i < (iSize / 2); i++) + { + iData = pabOld[2 * i + 0] + (pabOld[2 * i + 1] << 8); + iBit15 = (iData & 0x8000); + iData = (iData & 0x7FFF); + if (iData <= 0x400) + { + iData = iData * iLpi / 300; + } + if (iBit15 != 0) + { + iData |= 0x8000; + } + pabNew[2 * i + 0] = iData & 255; + pabNew[2 * i + 1] = (iData >> 8) & 255; + } +} + + +/************************************************************************* + _ProbeRegisters + =============== + Tries to determine certain hardware properties. + + This is done by checking the writeability of some scanner registers. + We cannot rely simply on the scanner model to contain a specific + chip. The HP3300c for example uses one of at least three slightly + different scanner ASICs (NIASH00012, NIASH00013 and NIASH00014). + + OUT pHWParams Hardware parameters, updated fields: + fGamma16 TRUE if 16 bit gamma tables can be used + fReg07 TRUE if reg07 is writeable + iBufferSize Size of scanner's internal buffer + + Returns TRUE if a NIASH chipset was found. +*************************************************************************/ +static bool +_ProbeRegisters (THWParams * pHWParams) +{ + unsigned char bData1, bData2; + int iHandle; + + iHandle = pHWParams->iXferHandle; + + DBG (DBG_MSG, "Probing scanner...\n"); + + /* check register 0x04 */ + NiashWriteReg (iHandle, 0x04, 0x55); + NiashReadReg (iHandle, 0x04, &bData1); + NiashWriteReg (iHandle, 0x04, 0xAA); + NiashReadReg (iHandle, 0x04, &bData2); + NiashWriteReg (iHandle, 0x04, 0x07); + if ((bData1 != 0x55) || (bData2 != 0xAA)) + { + DBG (DBG_ERR, " No NIASH chipset found!\n"); + return FALSE; + } + + /* check writeability of register 3 bit 1 */ + NiashReadReg (iHandle, 0x03, &bData1); + NiashWriteReg (iHandle, 0x03, bData1 | 0x02); + NiashReadReg (iHandle, 0x03, &bData2); + NiashWriteReg (iHandle, 0x03, bData1); + pHWParams->fGamma16 = ((bData2 & 0x02) != 0); + DBG (DBG_MSG, " Gamma table entries are %d bit\n", + pHWParams->fGamma16 ? 16 : 8); + + /* check register 0x07 */ + NiashReadReg (iHandle, 0x07, &bData1); + NiashWriteReg (iHandle, 0x07, 0x1C); + NiashReadReg (iHandle, 0x07, &bData2); + NiashWriteReg (iHandle, 0x07, bData1); + pHWParams->fReg07 = (bData2 == 0x1C); + + if (!pHWParams->fGamma16) + { + /* internal scan buffer size is an educated guess, but seems to correlate + well with the size calculated from several windows driver log files + size = 128kB - 44088 unsigned chars (space required for gamma/calibration table) + */ + pHWParams->iBufferSize = 86984L; + DBG (DBG_MSG, " NIASH version < 00014\n"); + } + else + { + pHWParams->iBufferSize = 0x60000L; + if (!pHWParams->fReg07) + { + DBG (DBG_MSG, " NIASH version = 00014\n"); + } + else + { + DBG (DBG_MSG, " NIASH version > 00014\n"); + } + } + + return TRUE; +} + + +/* returns 0 on success, < 0 otherwise */ +STATIC int +NiashOpen (THWParams * pHWParams, const char *pszName) +{ + int iXferHandle; + + iXferHandle = NiashXferOpen (pszName, &pHWParams->eModel); + if (iXferHandle < 0) + { + DBG (DBG_ERR, "NiashXferOpen failed for '%s'\n", pszName); + return -1; + } + + pHWParams->iXferHandle = iXferHandle; + + NiashWakeup (pHWParams->iXferHandle); + + /* default HW params */ + pHWParams->iSensorSkew = 8; + pHWParams->iTopLeftX = 0; + pHWParams->iTopLeftY = 3; + pHWParams->fReg07 = FALSE; + pHWParams->iSkipLines = 0; + pHWParams->iExpTime = 5408; + pHWParams->iReversedHead = TRUE; + + switch (pHWParams->eModel) + { + + case eHp3300c: + DBG (DBG_MSG, "Setting params for Hp3300\n"); + pHWParams->iTopLeftX = 4; + pHWParams->iTopLeftY = 11; + pHWParams->iSkipLines = 14; + break; + + case eHp3400c: + case eHp4300c: + DBG (DBG_MSG, "Setting params for Hp3400c/Hp4300c\n"); + pHWParams->iTopLeftX = 3; + pHWParams->iTopLeftY = 14; + pHWParams->fReg07 = TRUE; + break; + + case eAgfaTouch: + DBG (DBG_MSG, "Setting params for AgfaTouch\n"); + pHWParams->iReversedHead = FALSE; /* head not reversed on Agfa Touch */ + pHWParams->iTopLeftX = 3; + pHWParams->iTopLeftY = 10; + pHWParams->iSkipLines = 7; + break; + + case eUnknownModel: + DBG (DBG_MSG, "Setting params for UnknownModel\n"); + break; + + default: + DBG (DBG_ERR, "ERROR: internal error! (%d)\n", (int) pHWParams->eModel); + return -1; + } + + /* autodetect some hardware properties */ + if (!_ProbeRegisters (pHWParams)) + { + DBG (DBG_ERR, "_ProbeRegisters failed!\n"); + return -1; + } + + return 0; +} + + +STATIC void +NiashClose (THWParams * pHWPar) +{ + NiashXferClose (pHWPar->iXferHandle); + pHWPar->iXferHandle = 0; +} + + +static void +WriteRegWord (int iHandle, unsigned char bReg, word wData) +{ + NiashWriteReg (iHandle, bReg, wData & 0xFF); + NiashWriteReg (iHandle, bReg + 1, (wData >> 8) & 0xFF); +} + + +/* calculate a 4096 unsigned char gamma table */ +STATIC void +CalcGamma (unsigned char *pabTable, double Gamma) +{ + int i, iData; + + /* fill gamma table */ + for (i = 0; i < 4096; i++) + { + iData = floor (256.0 * pow (((double) i / 4096.0), 1.0 / Gamma)); + pabTable[i] = iData; + } +} + + +/* + Hp3400WriteFw + ============= + Writes data to scanners with a NIASH00019 chipset, e.g. + gamma, calibration and motor control data. + + IN pabData pointer to firmware data + iLen Size of firmware date (unsigned chars) + iAddr Scanner address to write to +*/ +static void +Hp3400cWriteFW (int iXferHandle, unsigned char *pabData, int iLen, int iAddr) +{ + iAddr--; + NiashWriteReg (iXferHandle, 0x21, iAddr & 0xFF); + NiashWriteReg (iXferHandle, 0x22, (iAddr >> 8) & 0xFF); + NiashWriteReg (iXferHandle, 0x23, (iAddr >> 16) & 0xFF); + NiashWriteBulk (iXferHandle, pabData, iLen); +} + + +/* Writes the gamma and offset/gain tables to the scanner. + In case a calibration file exist, it will be used for offset/gain */ +STATIC void +WriteGammaCalibTable (unsigned char *pabGammaR, unsigned char *pabGammaG, + unsigned char *pabGammaB, unsigned char *pabCalibTable, + int iGain, int iOffset, THWParams * pHWPar) +{ + int i, j, k; + static unsigned char abGamma[60000]; + int iData; + int iHandle; + + iHandle = pHWPar->iXferHandle; + + j = 0; + /* fill gamma table for red component */ + /* pad entries with 0 for 16-bit gamma table */ + for (i = 0; i < 4096; i++) + { + if (pHWPar->fGamma16) + { + abGamma[j++] = 0; + } + abGamma[j++] = pabGammaR[i]; + } + /* fill gamma table for green component */ + for (i = 0; i < 4096; i++) + { + if (pHWPar->fGamma16) + { + abGamma[j++] = 0; + } + abGamma[j++] = pabGammaG[i]; + } + /* fill gamma table for blue component */ + for (i = 0; i < 4096; i++) + { + if (pHWPar->fGamma16) + { + abGamma[j++] = 0; + } + abGamma[j++] = pabGammaB[i]; + } + + if (pabCalibTable == NULL) + { + iData = (iGain << 6) + iOffset; + for (i = 0; i < HW_PIXELS; i++) + { + for (k = 0; k < 3; k++) + { + abGamma[j++] = (iData) & 255; + abGamma[j++] = (iData >> 8) & 255; + } + } + } + else + { + memcpy (&abGamma[j], pabCalibTable, HW_PIXELS * 6); + j += HW_PIXELS * 6; + } + + NiashWriteReg (iHandle, 0x02, 0x80); + NiashWriteReg (iHandle, 0x03, 0x01); + NiashWriteReg (iHandle, 0x03, 0x11); + NiashWriteReg (iHandle, 0x02, 0x84); + + if (pHWPar->fReg07) + { + Hp3400cWriteFW (iHandle, abGamma, j, 0x2000); + } + else + { + NiashWriteBulk (iHandle, abGamma, j); + } + + NiashWriteReg (iHandle, 0x02, 0x80); +} + + +static void +WriteAFEReg (int iHandle, int iReg, int iData) +{ + NiashWriteReg (iHandle, 0x25, iReg); + NiashWriteReg (iHandle, 0x26, iData); +} + + +/* setup the analog front-end -> coarse calibration */ +static void +WriteAFE (int iHandle) +{ + /* see WM8143 datasheet */ + + WriteAFEReg (iHandle, 0x04, 0x00); + WriteAFEReg (iHandle, 0x03, 0x12); + WriteAFEReg (iHandle, 0x02, 0x04); + WriteAFEReg (iHandle, 0x05, 0x10); + WriteAFEReg (iHandle, 0x01, 0x03); + + WriteAFEReg (iHandle, 0x20, 0xc0); /*c8 *//* red offset */ + WriteAFEReg (iHandle, 0x21, 0xc0); /*c8 *//* green offset */ + WriteAFEReg (iHandle, 0x22, 0xc0); /*d0 *//* blue offset */ + + WriteAFEReg (iHandle, 0x28, 0x05); /*5 *//* red gain */ + WriteAFEReg (iHandle, 0x29, 0x03); /*3 *//* green gain */ + WriteAFEReg (iHandle, 0x2A, 0x04); /*4 *//* blue gain */ +} + + +/* wait for the carriage to return */ +static void +WaitReadyBit (int iHandle) +{ + unsigned char bData; + + do + { + NiashReadReg (iHandle, 0x03, &bData); + } + while ((bData & 8) == 0); +} + + +/* + Initialisation specific for NIASH00014 and lower chips +*/ +static void +InitNiash00014 (TScanParams * pParams, THWParams * pHWParams) +{ + int iHandle, iLpiCode; + + iHandle = pHWParams->iXferHandle; + + /* exposure time (in units 24/Fcrystal)? */ + WriteRegWord (iHandle, 0x08, pHWParams->iExpTime - 1); + + /* width in pixels */ + WriteRegWord (iHandle, 0x12, pParams->iWidth - 1); + + /* top */ + WriteRegWord (iHandle, 0x17, pParams->iTop); + WriteRegWord (iHandle, 0x19, pParams->iTop); + + /* time between stepper motor steps (in units of 24/Fcrystal)? */ + iLpiCode = pParams->iLpi * pHWParams->iExpTime / 1200L; + + if (!pHWParams->fGamma16) + { + /* NIASH 00012 / 00013 init */ + + /* LPI specific settings */ + if (pParams->iLpi < 600) + { + /* set halfres bit */ + NiashWriteReg (iHandle, 0x06, 0x01); + /* double lpi code because of halfres bit */ + iLpiCode *= 2; + } + else + { + /* clear halfres bit */ + NiashWriteReg (iHandle, 0x06, 0x00); + /* add exptime to make it scan slower */ + iLpiCode += pHWParams->iExpTime; + } + + /* unknown setting */ + WriteRegWord (iHandle, 0x27, 0x7FD2); + WriteRegWord (iHandle, 0x29, 0x6421); + + } + else + { + /* NIASH 00014 init */ + + /* halfres bit always cleared */ + NiashWriteReg (iHandle, 0x06, 0x00); + + /* LPI specific settings */ + if (pParams->iLpi >= 600) + { + /* add exptime to make it scan slower */ + iLpiCode += pHWParams->iExpTime; + } + + /* unknown setting */ + WriteRegWord (iHandle, 0x27, 0xc862); /*c862 */ + WriteRegWord (iHandle, 0x29, 0xb853); /*b853 */ + } + + /* LPI code */ + WriteRegWord (iHandle, 0x0A, iLpiCode - 1); + + /* backtrack reversing speed */ + NiashWriteReg (iHandle, 0x1E, (iLpiCode - 1) / 32); +} + + +/* + Initialisation specific for NIASH00019 chips +*/ +static void +InitNiash00019 (TScanParams * pParams, THWParams * pHWParams) +{ + int iHandle, iLpiCode; + static unsigned char abMotor[512]; + + + iHandle = pHWParams->iXferHandle; + + /* exposure time (in units 24/Fcrystal)? */ + WriteRegWord (iHandle, 0x08, pHWParams->iExpTime); + + /* width in pixels */ + WriteRegWord (iHandle, 0x12, pParams->iWidth); + + /* ? */ + WriteRegWord (iHandle, 0x27, 0xc862); /*c862 */ + WriteRegWord (iHandle, 0x29, 0xb853); /*b853 */ + + /* specific handling of 150 dpi resolution */ + if (pParams->iLpi == 150) + { + /* use 300 LPI but skip every other line */ + pParams->iLpi = 300; + NiashWriteReg (iHandle, 0x06, 0x01); + } + else + { + NiashWriteReg (iHandle, 0x06, 0x00); + } + + /* DPI and position table */ + NiashWriteReg (iHandle, 0x07, 0x02); + _ConvertMotorTable (abData0000, abMotor, sizeof (abData0000), + pParams->iLpi); + Hp3400cWriteFW (iHandle, abMotor, sizeof (abData0000), 0x000); + _ConvertMotorTable (abData0400, abMotor, sizeof (abData0400), + pParams->iLpi); + Hp3400cWriteFW (iHandle, abMotor, sizeof (abData0400), 0x400); + + /* backtrack reversing speed */ + iLpiCode = pParams->iLpi * pHWParams->iExpTime / 1200L; + NiashWriteReg (iHandle, 0x1E, (iLpiCode - 1) / 32); +} + + +/* + Scanner initialisation common to all NIASH chips +*/ +static void +InitNiashCommon (TScanParams * pParams, THWParams * pHWParams) +{ + int iWidthHW, iHandle, iMaxLevel; + + + iHandle = pHWParams->iXferHandle; + + NiashWriteReg (iHandle, 0x02, 0x80); + NiashWriteReg (iHandle, 0x03, 0x11); + NiashWriteReg (iHandle, 0x01, 0x8B); + NiashWriteReg (iHandle, 0x05, 0x01); + + /* dpi */ + WriteRegWord (iHandle, 0x0C, pParams->iDpi); + + /* calculate width in units of HW resolution */ + iWidthHW = pParams->iWidth * (HW_DPI / pParams->iDpi); + + /* set left and right limits */ + if (pHWParams->iReversedHead) + { + /* head is reversed */ + /* right */ + WriteRegWord (iHandle, 0x0E, + 3 * (HW_PIXELS - (pParams->iLeft + iWidthHW))); + + /* left */ + WriteRegWord (iHandle, 0x10, 3 * (HW_PIXELS - pParams->iLeft) - 1); + } + else + { + /* head is not reversed */ + /*left */ + WriteRegWord (iHandle, 0x0E, 3 * pParams->iLeft); + + /* right */ + WriteRegWord (iHandle, 0x10, 3 * (pParams->iLeft + iWidthHW) - 1); + } + + /* bottom */ + WriteRegWord (iHandle, 0x1B, pParams->iBottom); /* 0x393C); */ + + /* forward jogging speed */ + NiashWriteReg (iHandle, 0x1D, 0x60); + + /* backtrack reversing speed? */ + NiashWriteReg (iHandle, 0x2B, 0x15); + + /* backtrack distance */ + if (pParams->iLpi < 600) + { + NiashWriteReg (iHandle, 0x1F, 0x30); + } + else + { + NiashWriteReg (iHandle, 0x1F, 0x18); + } + + /* max buffer level before backtrace */ + iMaxLevel = MIN (pHWParams->iBufferSize / pParams->iWidth, 250); + NiashWriteReg (iHandle, 0x14, iMaxLevel - 1); + + /* lamp PWM, max = 0x1ff? */ + WriteRegWord (iHandle, 0x2C, 0x01FF); + + /* not needed? */ + NiashWriteReg (iHandle, 0x15, 0x90); /* 90 */ + NiashWriteReg (iHandle, 0x16, 0x70); /* 70 */ + + WriteAFE (iHandle); + + WaitReadyBit (iHandle); + + NiashWriteReg (iHandle, 0x03, 0x05); + + NiashWriteReg (iHandle, 0x02, pParams->fCalib ? 0x88 : 0xA8); +} + + +/* write registers */ +STATIC bool +InitScan (TScanParams * pParams, THWParams * pHWParams) +{ + int iHeight; + int iExpTime; + TScanParams Params; + int iHandle; + + iHandle = pHWParams->iXferHandle; + + /* check validity of scanparameters */ + switch (pParams->iDpi) + { + case 150: + case 300: + case 600: + break; + default: + DBG (DBG_ERR, "Invalid dpi (%d)\n", pParams->iDpi); + return FALSE; + } + + iHeight = (pParams->iBottom - pParams->iTop + 1); + if (iHeight <= 0) + { + DBG (DBG_ERR, "Invalid height (%d)\n", iHeight); + return FALSE; + } + + if (pParams->iWidth <= 0) + { + DBG (DBG_ERR, "Invalid width (%d)\n", pParams->iWidth); + return FALSE; + } + + switch (pParams->iLpi) + { + case 150: + case 300: + case 600: + break; + default: + DBG (DBG_ERR, "Invalid lpi (%d)\n", pParams->iLpi); + return FALSE; + } + + /* exposure time (in units of 24/Fcrystal?), must be divisible by 8 !!! */ + iExpTime = 5408; + if ((iExpTime % 8) != 0) + { + DBG (DBG_ERR, "Invalid exposure time (%d)\n", iExpTime); + return FALSE; + } + + /* + *** Done checking scan parameters validity *** + */ + + /* + copy the parameters locally and make pParams point to the local copy + */ + memcpy (&Params, pParams, sizeof (Params)); + pParams = &Params; + + if (!pHWParams->fReg07) + { + /* init NIASH00014 and lower */ + InitNiash00014 (pParams, pHWParams); + } + else + { + /* init NIASH00019 */ + InitNiash00019 (pParams, pHWParams); + } + + /* common NIASH init */ + InitNiashCommon (pParams, pHWParams); + + return TRUE; +} + + +/************************************************************************/ + +static bool +XferBufferGetLine (int iHandle, TDataPipe * p, unsigned char *pabLine) +{ + unsigned char bData; + + /* all calculated transfers done ? */ + if (p->iLinesLeft == 0) + return FALSE; + + /* time for a fresh read? */ + if (p->iCurLine == 0) + { + /* DBG(DBG_MSG, "Reading buffer %d unsigned chars\n", _iLinesPerXferBuf + * _iBytesPerLine); */ + NiashReadReg (iHandle, 0x20, &bData); + DBG (DBG_MSG, "buffer level = %3d, , ", + (int) bData, p->iLinesPerXferBuf * p->iBytesPerLine); + NiashReadBulk (iHandle, p->pabXferBuf, + p->iLinesPerXferBuf * p->iBytesPerLine); + NiashReadReg (iHandle, 0x20, &bData); + DBG (DBG_MSG, "buffer level = %3d\r", bData); + fflush (stdout); + } + /* copy one line */ + if (pabLine != NULL) + { + memcpy (pabLine, &p->pabXferBuf[p->iCurLine * p->iBytesPerLine], + p->iBytesPerLine); + } + /* advance pointer */ + p->iCurLine = (p->iCurLine + 1) % p->iLinesPerXferBuf; + + /* one transfer line less to the XFerBuffer */ + if (p->iLinesLeft > 0) + --(p->iLinesLeft); + return TRUE; +} + + +static void +XferBufferInit (int iHandle, TDataPipe * p) +{ + int i; + + p->pabXferBuf = (unsigned char *) malloc (XFER_BUF_SIZE); + p->iCurLine = 0; + + /* skip garbage lines */ + for (i = 0; i < p->iSkipLines; i++) + { + XferBufferGetLine (iHandle, p, NULL); + } +} + +/* static procedure that fills the circular buffer in advance to any + circular buffer data retrieval */ +static void +CircBufferFill (int iHandle, TDataPipe * p, bool iReversedHead) +{ + int i; + for (i = 0; i < p->iLinesPerCircBuf; i++) + { + if (iReversedHead) + { + XferBufferGetLine (iHandle, p, + &p->pabCircBuf[p->iRedLine * p->iBytesPerLine]); + } + else + { + XferBufferGetLine (iHandle, p, + &p->pabCircBuf[p->iBluLine * p->iBytesPerLine]); + } + /* advance pointers */ + p->iRedLine = (p->iRedLine + 1) % p->iLinesPerCircBuf; + p->iGrnLine = (p->iGrnLine + 1) % p->iLinesPerCircBuf; + p->iBluLine = (p->iBluLine + 1) % p->iLinesPerCircBuf; + } +} + +static void +XferBufferExit (TDataPipe * p) +{ + if (p->pabXferBuf != NULL) + { + free (p->pabXferBuf); + p->pabXferBuf = NULL; + } + else + { + DBG (DBG_ERR, "XferBufExit: Xfer buffer not initialised!\n"); + } +} + + +/* unscrambles a line: + - combining the proper R, G and B lines and converting them to interpixel RGB + - mirroring left to right +*/ +static void +_UnscrambleLine (unsigned char *pabLine, + unsigned char *pabRed, unsigned char *pabGrn, + unsigned char *pabBlu, int iWidth, bool iReversedHead, + int iScaleDownDpi, int iBufWeight) +{ + /* never change an approved algorithm ... + so take Bertriks original source for this special case */ + if (iScaleDownDpi == 1 && iBufWeight == 0) + { + int i, j; + if (iReversedHead) + { + /* reversed */ + for (i = 0; i < iWidth; i++) + { + j = (iWidth - i) * 3; + pabLine[j - 3] = pabRed[i]; + pabLine[j - 2] = pabGrn[i + iWidth]; + pabLine[j - 1] = pabBlu[i + iWidth * 2]; + } + } + else + { + /* not reversed */ + for (i = 0; i < iWidth; i++) + { + pabLine[3 * i] = pabRed[i]; + pabLine[3 * i + 1] = pabGrn[i + iWidth]; + pabLine[3 * i + 2] = pabBlu[i + iWidth * 2]; + } + } + } + else + { + int i, j; /* loop variables */ + int c; /* color buffer accumulator for horizontal avarage */ + + /* initialize for incremental color buffer access */ + int iInc = 1; + int iStart = 0; + + /* set for "from the end to the front" of the circular color buffers */ + if (iReversedHead) + { + iStart = iWidth - iScaleDownDpi; + iInc = -1; + } + + /* each pixel is the mean of iScaleDownDpi + so set the skip width accordingly */ + iInc *= iScaleDownDpi; + + for (i = iStart; i >= 0 && i < iWidth; i += iInc) + { + /* collect the red pixels */ + for (c = j = 0; j < iScaleDownDpi; ++j) + c += pabRed[i + j]; + *pabLine = + (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1); + pabLine++; + + /* collect the green pixels */ + for (c = j = 0; j < iScaleDownDpi; ++j) + c += pabGrn[i + iWidth + j]; + *pabLine = + (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1); + pabLine++; + + /* collect the blue pixels */ + for (c = j = 0; j < iScaleDownDpi; ++j) + c += pabBlu[i + 2 * iWidth + j]; + *pabLine = + (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1); + pabLine++; + } + } + +} + + +/* gets an unscrambled line from the circular buffer. the first couple of lines contain garbage */ +STATIC bool +CircBufferGetLine (int iHandle, TDataPipe * p, unsigned char *pabLine, + bool iReversedHead) +{ + int iLineCount; + for (iLineCount = 0; iLineCount < p->iScaleDownLpi; ++iLineCount) + { + if (iReversedHead) + { + if (!XferBufferGetLine (iHandle, p, + &p->pabCircBuf[p->iRedLine * + p->iBytesPerLine])) + return FALSE; + } + else + { + if (!XferBufferGetLine (iHandle, p, + &p->pabCircBuf[p->iBluLine * + p->iBytesPerLine])) + return FALSE; + } + + if (pabLine != NULL) + { + _UnscrambleLine (pabLine, + &p->pabCircBuf[p->iRedLine * p->iBytesPerLine], + &p->pabCircBuf[p->iGrnLine * p->iBytesPerLine], + &p->pabCircBuf[p->iBluLine * p->iBytesPerLine], + p->iWidth * p->iScaleDownDpi, iReversedHead, + p->iScaleDownDpi, p->iScaleDownLpi); + } + + /* advance pointers */ + p->iRedLine = (p->iRedLine + 1) % p->iLinesPerCircBuf; + p->iGrnLine = (p->iGrnLine + 1) % p->iLinesPerCircBuf; + p->iBluLine = (p->iBluLine + 1) % p->iLinesPerCircBuf; + } + return TRUE; +} + + +STATIC void +CircBufferInit (int iHandle, TDataPipe * p, + int iWidth, int iHeight, + int iMisAlignment, bool iReversedHead, + int iScaleDownDpi, int iScaleDownLpi) +{ + + long iXFerSize; + /* relevant for internal read and write functions */ + p->iScaleDownLpi = iScaleDownLpi; + p->iScaleDownDpi = iScaleDownDpi; + p->iWidth = iWidth; + p->iBytesPerLine = iWidth * iScaleDownDpi * BYTES_PER_PIXEL; + p->iSaneBytesPerLine = iWidth * BYTES_PER_PIXEL; + if (iMisAlignment == 0) + { + p->iLinesPerCircBuf = 1; + } + else + { + p->iLinesPerCircBuf = 3 * iMisAlignment; + } + + DBG (DBG_MSG, "_iScaleDown (Dpi,Lpi) = (%d,%d)\n", p->iScaleDownDpi, + p->iScaleDownLpi); + DBG (DBG_MSG, "_iBytesPerLine = %d\n", p->iBytesPerLine); + DBG (DBG_MSG, "_iLinesPerCircBuf = %d\n", p->iLinesPerCircBuf); + p->pabCircBuf = + (unsigned char *) malloc (p->iBytesPerLine * p->iLinesPerCircBuf); + if (p->pabCircBuf == NULL) + { + DBG (DBG_ERR, + "Unable to allocate %d unsigned chars for circular buffer\n", + (int) (p->iBytesPerLine * p->iLinesPerCircBuf)); + return; + } + DBG (DBG_MSG, "Allocated %d unsigned chars for circular buffer\n", + p->iBytesPerLine * p->iLinesPerCircBuf); + + if (iReversedHead) + { + p->iBluLine = 0; + p->iGrnLine = iMisAlignment; + p->iRedLine = iMisAlignment * 2; + } + else + { + p->iRedLine = 0; + p->iGrnLine = iMisAlignment; + p->iBluLine = iMisAlignment * 2; + } + + /* negative height is an indication for "no Check" */ + if (iHeight < 0) + { + p->iLinesLeft = -1; + p->iLinesPerXferBuf = XFER_BUF_SIZE / p->iBytesPerLine; + DBG (DBG_MSG, "using unchecked XFER_BUF_SIZE\n"); + DBG (DBG_MSG, "_iXFerSize = %d\n", + p->iBytesPerLine * p->iLinesPerXferBuf); + } + else + { + /* estimate of number of unsigned chars to transfer at all via the USB */ + /* add 20 lines for securtiy */ + int iTransfers = 1; + + p->iLinesLeft = p->iLinesPerXferBuf = (iHeight + p->iSkipLines + 20); + iXFerSize = p->iBytesPerLine * p->iLinesLeft; + + /* is the buffersize too big ? */ + /* or ... 800 vertical dots to be scanned in one pass are too many */ + while (iXFerSize > XFER_BUF_SIZE || p->iLinesPerXferBuf > 800) + { + if (iTransfers == 1 && iXFerSize > XFER_BUF_SIZE) + { + /* get the number of optimized transfers for a needed size */ + iTransfers = (iXFerSize + (XFER_BUF_SIZE - 1)) / XFER_BUF_SIZE; + } + else + { + /* either size is still too big */ + /* or height to be scanned in one pass is too big */ + /* try with more transfers, to get a smaller size of buffer */ + iXFerSize = p->iBytesPerLine * p->iLinesLeft; + ++iTransfers; + } + + /* get the new buffer size */ + iXFerSize = (iXFerSize + (iTransfers - 1)) / iTransfers; + /* make sure that it is dividable by the number of unsigned chars per line */ + iXFerSize = + ((iXFerSize + + (p->iBytesPerLine - 1)) / p->iBytesPerLine) * p->iBytesPerLine; + p->iLinesPerXferBuf = (int) iXFerSize / p->iBytesPerLine; + } + DBG (DBG_MSG, "_iXFerSize = %d for %d transfer(s)\n", (int) iXFerSize, + iTransfers); + } + DBG (DBG_MSG, "_iLinesPerXferBuf = %d\n", p->iLinesPerXferBuf); + + /* init transfer buffer */ + XferBufferInit (iHandle, p); + + /* fill circular buffer */ + CircBufferFill (iHandle, p, iReversedHead); +} + + +STATIC void +CircBufferExit (TDataPipe * p) +{ + XferBufferExit (p); + if (p->pabCircBuf != NULL) + { + DBG (DBG_MSG, "\n"); + free (p->pabCircBuf); + p->pabCircBuf = NULL; + } + else + { + DBG (DBG_ERR, "CircBufferExit: Circular buffer not initialised!\n"); + } +} + + +/************************************************************************/ + + + +static int +_CalcAvg (unsigned char *pabBuf, int n, int iStep) +{ + int i, j, x; + + for (i = j = x = 0; i < n; i++) + { + x += pabBuf[j]; + j += iStep; + } + return (x / n); +} + + +/* converts white line data and black point data into a calibration table */ +static void +CreateCalibTable (unsigned char *abWhite, unsigned char bBlackR, + unsigned char bBlackG, unsigned char bBlackB, + int iReversedHead, unsigned char *pabCalibTable) +{ + int i, j, iGain, iOffset, iData; + unsigned char *pabPixel; + + j = 0; + for (i = 0; i < HW_PIXELS; i++) + { + if (iReversedHead) + { + pabPixel = &abWhite[(HW_PIXELS - i - 1) * 3]; + } + else + { + pabPixel = &abWhite[i * 3]; + } + /* red */ + if (bBlackR > 16) + bBlackR = 16; + iGain = 65536 / MAX (1, pabPixel[0] - bBlackR); + iOffset = bBlackR * 4; + if (iOffset > 63) + iOffset = 63; + iData = (iGain << 6) + iOffset; + pabCalibTable[j++] = (iData) & 255; + pabCalibTable[j++] = (iData >> 8) & 255; + /* green */ + if (bBlackG > 16) + bBlackG = 16; + iGain = 65536 / MAX (1, pabPixel[1] - bBlackG); + iOffset = bBlackG * 4; + if (iOffset > 63) + iOffset = 63; + iData = (iGain << 6) + iOffset; + pabCalibTable[j++] = (iData) & 255; + pabCalibTable[j++] = (iData >> 8) & 255; + /* blue */ + if (bBlackB > 16) + bBlackB = 16; + iGain = 65536 / MAX (1, pabPixel[2] - bBlackB); + iOffset = bBlackB * 4; + if (iOffset > 63) + iOffset = 63; + iData = (iGain << 6) + iOffset; + pabCalibTable[j++] = (iData) & 255; + pabCalibTable[j++] = (iData >> 8) & 255; + } +} + + +/************************************************************************* + Lamp control functions +*************************************************************************/ +STATIC bool +GetLamp (THWParams * pHWParams, bool * pfLampIsOn) +{ + unsigned char bData; + + NiashReadReg (pHWParams->iXferHandle, 0x03, &bData); + *pfLampIsOn = ((bData & 0x01) != 0); + return TRUE; +} + + +STATIC bool +SetLamp (THWParams * pHWParams, bool fLampOn) +{ + unsigned char bData; + int iHandle; + + iHandle = pHWParams->iXferHandle; + + NiashReadReg (iHandle, 0x03, &bData); + if (fLampOn) + { + NiashWriteReg (iHandle, 0x03, bData | 0x01); + } + else + { + NiashWriteReg (iHandle, 0x03, bData & ~0x01); + } + return TRUE; +} + + +/************************************************************************* + Experimental simple calibration, but also returning the white levels +*************************************************************************/ +STATIC bool +SimpleCalibExt (THWParams * pHWPar, unsigned char *pabCalibTable, + unsigned char *pabCalWhite) +{ + unsigned char bMinR, bMinG, bMinB; + TDataPipe DataPipe; + TScanParams Params; + unsigned char abGamma[4096]; + int i, j; + static unsigned char abBuf[HW_PIXELS * 3 * 71]; /* Carefull : see startWhite and endWhite below */ + static unsigned char abLine[HW_PIXELS * 3]; + static unsigned char abWhite[HW_PIXELS * 3]; + unsigned char *pabWhite; + int iWhiteR, iWhiteG, iWhiteB; + int iHandle; + bool iReversedHead; + int startWhiteY, endWhiteY; + int startBlackY, endBlackY; + int startBlackX, endBlackX; + + iHandle = pHWPar->iXferHandle; + iReversedHead = pHWPar->iReversedHead; + + DataPipe.iSkipLines = pHWPar->iSkipLines; + + Params.iDpi = HW_DPI; + Params.iLpi = HW_DPI; + if (iReversedHead) /* hp scanners */ + Params.iTop = 60; + else /* agfa scanners */ + Params.iTop = 30; + Params.iBottom = HP3300C_BOTTOM; + Params.iLeft = 0; + Params.iWidth = HW_PIXELS; + Params.iHeight = 54; + Params.fCalib = TRUE; + + /* write gamma table with neutral gain / offset */ + CalcGamma (abGamma, 1.0); + WriteGammaCalibTable (abGamma, abGamma, abGamma, NULL, 256, 0, pHWPar); + + if (!InitScan (&Params, pHWPar)) + { + if (pabCalWhite) + pabCalWhite[0] = pabCalWhite[1] = pabCalWhite[2] = 0; + return FALSE; + } + + /* Definition of white and black areas */ + if (iReversedHead) + { /* hp scanners */ + startWhiteY = 0; + endWhiteY = 15; + startBlackY = 16; + endBlackY = 135; + startBlackX = 0; + endBlackX = HW_PIXELS; + } + else + { /* agfa scanners */ + startWhiteY = 0; + endWhiteY = 70; + startBlackY = 86; + endBlackY = 135; + startBlackX = 1666; + endBlackX = 3374; + } + + CircBufferInit (iHandle, &DataPipe, HW_PIXELS, -1, Params.iLpi / 150, + iReversedHead, 1, 1); + /* white level */ + /* skip some lines */ + for (i = 0; i < startWhiteY; i++) + { + CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead); + } + /* Get white lines */ + for (i = 0; i < endWhiteY - startWhiteY + 1; i++) + { + CircBufferGetLine (iHandle, &DataPipe, &abBuf[i * HW_PIXELS * 3], + iReversedHead); + } + /* black level */ + bMinR = 255; + bMinG = 255; + bMinB = 255; + /* Skip some lines */ + for (i = 0; i < startBlackY; i++) + { + CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead); + } + for (i = 0; i < endBlackY - startBlackY + 1; i++) + { + CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead); + for (j = 0; j < endBlackX; j++) + { + bMinR = MIN (abLine[j * 3 + 0], bMinR); + bMinG = MIN (abLine[j * 3 + 1], bMinG); + bMinB = MIN (abLine[j * 3 + 2], bMinB); + } + } + CircBufferExit (&DataPipe); + FinishScan (pHWPar); + + /* calc average white level */ + pabWhite = abBuf; + for (i = 0; i < HW_PIXELS; i++) + { + abWhite[i * 3 + 0] = + _CalcAvg (&pabWhite[i * 3 + 0], endWhiteY - startWhiteY + 1, + HW_PIXELS * 3); + abWhite[i * 3 + 1] = + _CalcAvg (&pabWhite[i * 3 + 1], endWhiteY - startWhiteY + 1, + HW_PIXELS * 3); + abWhite[i * 3 + 2] = + _CalcAvg (&pabWhite[i * 3 + 2], endWhiteY - startWhiteY + 1, + HW_PIXELS * 3); + } + iWhiteR = _CalcAvg (&abWhite[0], HW_PIXELS, 3); + iWhiteG = _CalcAvg (&abWhite[1], HW_PIXELS, 3); + iWhiteB = _CalcAvg (&abWhite[2], HW_PIXELS, 3); + + DBG (DBG_MSG, "Black level (%d,%d,%d), White level (%d,%d,%d)\n", + (int) bMinR, (int) bMinG, (int) bMinB, iWhiteR, iWhiteG, iWhiteB); + + /* convert the white line and black point into a calibration table */ + CreateCalibTable (abWhite, bMinR, bMinG, bMinB, iReversedHead, + pabCalibTable); + /* assign the White Levels */ + if (pabCalWhite) + { + pabCalWhite[0] = iWhiteR; + pabCalWhite[1] = iWhiteG; + pabCalWhite[2] = iWhiteB; + } + return TRUE; +} + + +/************************************************************************* + FinishScan + ========== + Finishes the scan. Makes the scanner head move back to the home position. + +*************************************************************************/ +STATIC void +FinishScan (THWParams * pHWParams) +{ + NiashWriteReg (pHWParams->iXferHandle, 0x02, 0x80); +} + + + +/************************************************************************* + These function will only be available when compiled without the + sane-niash main file +*************************************************************************/ + +#ifndef WITH_NIASH +/************************************************************************* + Experimental simple calibration + + Basic idea: + * a strip starting at position 0 is scanned in + * per-pixel white level is determined by average of first 4 lines + * global black level is determined by minimum value of all lines +*************************************************************************/ +bool +SimpleCalib (THWParams * pHWPar, unsigned char *pabCalibTable) +{ + return SimpleCalibExt (pHWPar, pabCalibTable, NULL); +} + + +/* utility function to show a hexdump of a buffer */ +void +DumpHex (unsigned char *pabData, int iLen, int iWidth) +{ + int i; + for (i = 0; i < iLen; i++) + { + if ((i % iWidth) == 0) + { + printf ("\n%04X", i); + } + printf (" %02X", pabData[i]); + } + printf ("\n"); +} + +/************************************************************************* + ScanLines + ========= + Helper function to scan in an image and output the result to a file. + + IN pFile File to write image data to + pParams User-defineable settings describing the image to be scanned + pHWParams Scanner hardware settings + +*************************************************************************/ +void +ScanLines (FILE * pFile, TScanParams * pParams, THWParams * pHWParams) +{ + static unsigned char abBuf[HW_PIXELS * 3]; + int iBytesPerLine; + int iHeight; + int iHandle; + bool iReversedHead; + TDataPipe DataPipe; + + iHandle = pHWParams->iXferHandle; + + iHeight = pParams->iHeight; + iBytesPerLine = pParams->iWidth * 3; + DataPipe.iSkipLines = 0; + if (pHWParams->fReg07) + { + DataPipe.iSkipLines += + (pHWParams->iTopLeftY * pParams->iLpi * 10) / 254; + } + iReversedHead = pHWParams->iReversedHead; + + if (InitScan (pParams, pHWParams)) + { + CircBufferInit (iHandle, &DataPipe, + pParams->iWidth, pParams->iHeight, + pHWParams->iSensorSkew * pParams->iLpi / HW_LPI, + iReversedHead, 1, 1); + while (iHeight-- > 0) + { + CircBufferGetLine (iHandle, &DataPipe, abBuf, iReversedHead); + fwrite (abBuf, 1, iBytesPerLine, pFile); + } + CircBufferExit (&DataPipe); + } + FinishScan (pHWParams); +} + +#endif /* NO WITH_NIASH */ diff --git a/backend/niash_core.h b/backend/niash_core.h new file mode 100644 index 000000000..b19eee1d3 --- /dev/null +++ b/backend/niash_core.h @@ -0,0 +1,134 @@ +/* + 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$ +*/ + +/* + Core NIASH chip functions. +*/ + + +#ifndef _NIASH_CORE_H_ +#define _NIASH_CORE_H_ + +#include + +#include "niash_types.h" +#include "niash_xfer.h" /* for EScannerModel */ + +#define HP3300C_RIGHT 330 +#define HP3300C_TOP 452 +#define HP3300C_BOTTOM (HP3300C_TOP + 14200UL) + +#define HW_PIXELS 5300 /* number of pixels supported by hardware */ +#define HW_DPI 600 /* horizontal resolution of hardware */ +#define HW_LPI 1200 /* vertical resolution of hardware */ + +#define BYTES_PER_PIXEL 3 + +typedef struct +{ + int iXferHandle; /* handle used for data transfer to HW */ + int iTopLeftX; /* in mm */ + int iTopLeftY; /* in mm */ + int iSensorSkew; /* in units of 1/1200 inch */ + int iSkipLines; /* lines of garbage to skip */ + bool fReg07; /* NIASH00019 */ + bool fGamma16; /* if TRUE, gamma entries are 16 bit */ + int iExpTime; + bool iReversedHead; /* Head is reversed */ + int iBufferSize; /* Size of internal scan buffer */ + EScannerModel eModel; +} THWParams; + + +typedef struct +{ + int iDpi; /* horizontal resolution */ + int iLpi; /* vertical resolution */ + int iTop; /* in HW coordinates */ + int iLeft; /* in HW coordinates */ + int iWidth; /* pixels */ + int iHeight; /* lines */ + int iBottom; + + int fCalib; /* if TRUE, disable backtracking? */ +} TScanParams; + + +typedef struct +{ + unsigned char *pabXferBuf; /* transfer buffer */ + int iCurLine; /* current line in the transfer buffer */ + int iBytesPerLine; /* unsigned chars in one scan line */ + int iLinesPerXferBuf; /* number of lines held in the transfer buffer */ + int iLinesLeft; /* transfer (down) counter for pabXFerBuf */ + int iSaneBytesPerLine; /* how many unsigned chars to be read by SANE per line */ + int iScaleDownDpi; /* factors used to emulate lower resolutions */ + int iScaleDownLpi; /* than those offered by hardware */ + int iSkipLines; /* line to skip at the start of scan */ + int iWidth; /* number of pixels expected by SANE */ + unsigned char *pabCircBuf; /* circular buffer */ + int iLinesPerCircBuf; /* lines held in the circular buffer */ + int iRedLine, iGrnLine, /* start indices for the color information */ + iBluLine; /* in the circular buffer */ + unsigned char *pabLineBuf; /* buffer used to pass data to SANE */ + int iBytesLeft; /* number of buffer line transfers */ +} TDataPipe; + + +STATIC int NiashOpen (THWParams * pHWParams, const char *pszName); +STATIC void NiashClose (THWParams * pHWParams); + +/* more sof. method that also returns the values of the white (RGB) value */ +STATIC bool SimpleCalibExt (THWParams * pHWPar, unsigned char *pabCalibTable, + unsigned char *pabCalWhite); + +STATIC bool GetLamp (THWParams * pHWParams, bool * pfLampIsOn); +STATIC bool SetLamp (THWParams * pHWParams, bool fLampOn); + +STATIC bool InitScan (TScanParams * pParams, THWParams * pHWParams); +STATIC void FinishScan (THWParams * pHWParams); + +STATIC void CalcGamma (unsigned char *pabTable, double Gamma); +STATIC void WriteGammaCalibTable (unsigned char *pabGammaR, + unsigned char *pabGammaG, + unsigned char *pabGammaB, + unsigned char *pabCalibTable, int iGain, + int iOffset, THWParams * pHWPar); + +/* set -1 for iHeight to disable all checks on buffer transfers */ +/* iWidth is in pixels of SANE */ +/* iHeight is lines in scanner resolution */ +STATIC void CircBufferInit (int iHandle, TDataPipe * p, + int iWidth, int iHeight, + int iMisAlignment, bool iReversedHead, + int iScaleDownDpi, int iScaleDownLpi); + +/* returns false, when trying to read after end of buffer */ +STATIC bool CircBufferGetLine (int iHandle, TDataPipe * p, + unsigned char *pabLine, bool iReversedHead); +STATIC void CircBufferExit (TDataPipe * p); + +#ifndef WITH_NIASH +void DumpHex (unsigned char *pabData, int iLen, int iWidth); +void ScanLines (FILE * pFile, TScanParams * pParams, THWParams * pHWParams); +bool SimpleCalib (THWParams * pHWPar, unsigned char *pabCalibTable); +#endif /* NO WITH_NIASH */ + +#endif /* _NIASH_CORE_H_ */ diff --git a/backend/niash_types.h b/backend/niash_types.h new file mode 100644 index 000000000..7f5e6be9b --- /dev/null +++ b/backend/niash_types.h @@ -0,0 +1,31 @@ +/* + Defines my types + + $Id$ +*/ + + +#ifndef _NIASH_TYPES_H_ +#define _NIASH_TYPES_H_ + +#ifndef byte +typedef unsigned char byte; +#endif + +#ifndef word +typedef int word; +#endif + +#ifndef bool +typedef int bool; +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#endif /* _NIASH_TYPES_H_ */ diff --git a/backend/niash_xfer.c b/backend/niash_xfer.c new file mode 100644 index 000000000..86a2e97c0 --- /dev/null +++ b/backend/niash_xfer.c @@ -0,0 +1,326 @@ +/* + 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$ +*/ + +/* + Provides a simple interface to read and write data from the scanner, + without any knowledge whether it's a parallel or USB scanner +*/ + +#include /* printf */ +#include /* better error reports */ +#include /* better error reports */ + +#include "niash_xfer.h" + +#include "../include/sane/sanei_usb.h" + +/* list of supported models */ +STATIC TScannerModel ScannerModels[] = { + {"Hewlett-Packard", "ScanJet 3300C", 0x3F0, 0x205, eHp3300c} + , + {"Hewlett-Packard", "ScanJet 3400C", 0x3F0, 0x405, eHp3400c} + , + {"Hewlett-Packard", "ScanJet 4300C", 0x3F0, 0x305, eHp4300c} + , + {"Agfa", "Snapscan Touch", 0x6BD, 0x100, eAgfaTouch} + , +/* last entry all zeros */ + {0, 0, 0, 0, 0} +}; + +static TFnReportDevice *_pfnReportDevice; +static TScannerModel *_pModel; + +/* + MatchUsbDevice + ============== + Matches a given USB vendor and product id against a list of + supported scanners. + + IN iVendor USB vendor ID + iProduct USB product ID + OUT *ppModel Pointer to TScannerModel structure + + Returns TRUE if a matching USB scanner was found +*/ +STATIC bool +MatchUsbDevice (int iVendor, int iProduct, TScannerModel ** ppModel) +{ + TScannerModel *pModels = ScannerModels; + + DBG (DBG_MSG, "Matching USB device 0x%04X-0x%04X ... ", iVendor, iProduct); + while (pModels->pszName != NULL) + { + if ((pModels->iVendor == iVendor) && (pModels->iProduct == iProduct)) + { + DBG (DBG_MSG, "found %s %s\n", pModels->pszVendor, + pModels->pszName); + *ppModel = pModels; + return TRUE; + } + /* next model to match */ + pModels++; + } + DBG (DBG_MSG, "nothing found\n"); + return FALSE; +} + +/************************************************************************ + Public functions for the SANE compilation +************************************************************************/ + + +/* callback for sanei_usb_attach_matching_devices */ +static SANE_Status +_AttachUsb (SANE_String_Const devname) +{ + DBG (DBG_MSG, "_AttachUsb: found %s\n", devname); + + _pfnReportDevice (_pModel, (const char *) devname); + + return SANE_STATUS_GOOD; +} + + +/* + NiashXferInit + =============== + Initialises all registered data transfer modules, which causes + them to report any devices found through the pfnReport callback. + + IN pfnReport Function to call to report a transfer device +*/ +static void +NiashXferInit (TFnReportDevice * pfnReport) +{ + TScannerModel *pModels = ScannerModels; + + sanei_usb_init (); + _pfnReportDevice = pfnReport; + + /* loop over all scanner models */ + while (pModels->pszName != NULL) + { + DBG (DBG_MSG, "Looking for %s...\n", pModels->pszName); + _pModel = pModels; + if (sanei_usb_find_devices ((SANE_Int) pModels->iVendor, + (SANE_Int) pModels->iProduct, + _AttachUsb) != SANE_STATUS_GOOD) + { + + DBG (DBG_ERR, "Error invoking sanei_usb_find_devices"); + break; + } + pModels++; + } +} + + +static int +NiashXferOpen (const char *pszName, EScannerModel * peModel) +{ + SANE_Status status; + SANE_Word vendor, product; + int fd; + TScannerModel *pModel; + + DBG (DBG_MSG, "Trying to open %s...\n", pszName); + + status = sanei_usb_open (pszName, &fd); + if (status != SANE_STATUS_GOOD) + { + return -1; + } + + status = sanei_usb_get_vendor_product (fd, &vendor, &product); + if (status == SANE_STATUS_GOOD) + { + MatchUsbDevice (vendor, product, &pModel); + *peModel = pModel->eModel; + } + + DBG (DBG_MSG, "handle = %d\n", (int) fd); + return fd; +} + + +static void +NiashXferClose (int iHandle) +{ + /* close usb device */ + if (iHandle != -1) + { + sanei_usb_close (iHandle); + } +} + + +static void +parusb_write_reg (int fd, unsigned char bReg, unsigned char bValue) +{ + sanei_usb_control_msg (fd, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x0C, bReg, 0, 1, &bValue); +} + + +static void +parusb_read_reg (int fd, unsigned char bReg, unsigned char *pbValue) +{ + sanei_usb_control_msg (fd, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0C, bReg, 0, 1, pbValue); +} + + +static void +NiashWriteReg (int iHandle, unsigned char bReg, unsigned char bData) +{ + if (iHandle < 0) + { + DBG (DBG_MSG, "Invalid handle %d\n", iHandle); + return; + } + + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + parusb_write_reg (iHandle, EPP_ADDR, bReg); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + parusb_write_reg (iHandle, EPP_DATA_WRITE, bData); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); +} + + +static void +NiashReadReg (int iHandle, unsigned char bReg, unsigned char *pbData) +{ + if (iHandle < 0) + { + return; + } + + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + parusb_write_reg (iHandle, EPP_ADDR, bReg); + parusb_write_reg (iHandle, SPP_CONTROL, 0x34); + parusb_read_reg (iHandle, EPP_DATA_READ, pbData); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); +} + + +static void +NiashWriteBulk (int iHandle, unsigned char *pabBuf, int iSize) +{ + /* byte abSetup[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + HP3400 probably needs 0x01, 0x01 */ + byte abSetup[8] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + size_t size; + + if (iHandle < 0) + { + return; + } + + /* select scanner register 0x24 */ + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + parusb_write_reg (iHandle, EPP_ADDR, 0x24); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + + /* tell scanner that a bulk transfer follows */ + abSetup[4] = (iSize) & 0xFF; + abSetup[5] = (iSize >> 8) & 0xFF; + sanei_usb_control_msg (iHandle, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x04, USB_SETUP, 0, 8, abSetup); + + /* do the bulk write */ + size = iSize; + if (sanei_usb_write_bulk (iHandle, pabBuf, &size) != SANE_STATUS_GOOD) + { + DBG (DBG_ERR, "ERROR: Bulk write failed\n"); + } +} + + +static void +NiashReadBulk (int iHandle, unsigned char *pabBuf, int iSize) +{ + byte abSetup[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + size_t size; + + if (iHandle < 0) + { + return; + } + + /* select scanner register 0x24 */ + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + parusb_write_reg (iHandle, EPP_ADDR, 0x24); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + + /* tell scanner that a bulk transfer follows */ + abSetup[4] = (iSize) & 0xFF; + abSetup[5] = (iSize >> 8) & 0xFF; + sanei_usb_control_msg (iHandle, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x04, USB_SETUP, 0, 8, abSetup); + + /* do the bulk read */ + size = iSize; + if (sanei_usb_read_bulk (iHandle, pabBuf, &size) != SANE_STATUS_GOOD) + { + DBG (DBG_ERR, "ERROR: Bulk read failed\n"); + } +} + + +static void +NiashWakeup (int iHandle) +{ + unsigned char abMagic[] = { 0xA0, 0xA8, 0x50, 0x58, 0x90, 0x98, 0xC0, 0xC8, + 0x90, 0x98, 0xE0, 0xE8 + }; + int i; + + if (iHandle < 0) + { + return; + } + + /* write magic startup sequence */ + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + for (i = 0; i < (int) sizeof (abMagic); i++) + { + parusb_write_reg (iHandle, SPP_DATA, abMagic[i]); + } + + /* write 0x04 to scanner register 0x00 the hard way */ + parusb_write_reg (iHandle, SPP_DATA, 0x00); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + parusb_write_reg (iHandle, SPP_CONTROL, 0x15); + parusb_write_reg (iHandle, SPP_CONTROL, 0x1D); + parusb_write_reg (iHandle, SPP_CONTROL, 0x15); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + + parusb_write_reg (iHandle, SPP_DATA, 0x04); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); + parusb_write_reg (iHandle, SPP_CONTROL, 0x15); + parusb_write_reg (iHandle, SPP_CONTROL, 0x17); + parusb_write_reg (iHandle, SPP_CONTROL, 0x15); + parusb_write_reg (iHandle, SPP_CONTROL, 0x14); +} diff --git a/backend/niash_xfer.h b/backend/niash_xfer.h new file mode 100644 index 000000000..19ec8ab13 --- /dev/null +++ b/backend/niash_xfer.h @@ -0,0 +1,96 @@ +/* + 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$ +*/ + +/* + Provides a simple interface to read and write data from the scanner, + without any knowledge whether it's a parallel or USB scanner +*/ + +#ifndef _NIASH_XFER_H_ +#define _NIASH_XFER_H_ + +#include /* for FILE * */ +#include "niash_types.h" + +/* register codes for the USB - IEEE1284 bridge */ +#define USB_SETUP 0x82 +#define EPP_ADDR 0x83 +#define EPP_DATA_READ 0x84 +#define EPP_DATA_WRITE 0x85 +#define SPP_STATUS 0x86 +#define SPP_CONTROL 0x87 +#define SPP_DATA 0x88 + + +typedef enum +{ + eUnknownModel = 0, + eHp3300c, + eHp3400c, + eHp4300c, + eAgfaTouch +} EScannerModel; + + +typedef struct +{ + char *pszVendor; + char *pszName; + int iVendor; + int iProduct; + EScannerModel eModel; +} TScannerModel; + + +typedef int (TFnReportDevice) (TScannerModel * pModel, + const char *pszDeviceName); + + +/* Creates our own DBG definitions, externs are define in main.c*/ +#ifndef WITH_NIASH +#define DBG fprintf +extern FILE *DBG_MSG; +extern FILE *DBG_ERR; +extern FILE *BG_ASSERT; +#endif /* NO WITH_NIASH */ + +/* we do not make data prototypes */ +#ifndef WITH_NIASH +/* list of supported models, the actual list is in niash_xfer.c */ +extern TScannerModel ScannerModels[]; +#endif /* NO WITH_NIASH */ + +STATIC void NiashXferInit (TFnReportDevice * pfnReport); +STATIC int NiashXferOpen (const char *pszName, EScannerModel * peModel); +STATIC void NiashXferClose (int iXferHandle); + +STATIC void NiashWriteReg (int iXferHandle, unsigned char bReg, + unsigned char bData); +STATIC void NiashReadReg (int iXferHandle, unsigned char bReg, + unsigned char *pbData); +STATIC void NiashWriteBulk (int iXferHandle, unsigned char *pabBuf, + int iSize); +STATIC void NiashReadBulk (int iXferHandle, unsigned char *pabBuf, int iSize); +STATIC void NiashWakeup (int iXferHandle); + +STATIC bool MatchUsbDevice (int iVendor, int iProduct, + TScannerModel ** ppeModel); + +#endif /* _NIASH_XFER_H_ */