/*
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, see .
*/
/*
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 SANE_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 SANE_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 SANE_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 = SANE_FALSE;
pHWParams->iSkipLines = 0;
pHWParams->iExpTime = 5408;
pHWParams->iReversedHead = SANE_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 = SANE_TRUE;
break;
case eAgfaTouch:
DBG (DBG_MSG, "Setting params for AgfaTouch\n");
pHWParams->iReversedHead = SANE_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, SANE_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 SANE_Bool
InitScan (TScanParams * pParams, THWParams * pHWParams)
{
int iHeight;
int iExpTime;
TScanParams Params;
/* 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 SANE_FALSE;
}
iHeight = (pParams->iBottom - pParams->iTop + 1);
if (iHeight <= 0)
{
DBG (DBG_ERR, "Invalid height (%d)\n", iHeight);
return SANE_FALSE;
}
if (pParams->iWidth <= 0)
{
DBG (DBG_ERR, "Invalid width (%d)\n", pParams->iWidth);
return SANE_FALSE;
}
switch (pParams->iLpi)
{
case 150:
case 300:
case 600:
break;
default:
DBG (DBG_ERR, "Invalid lpi (%d)\n", pParams->iLpi);
return SANE_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 SANE_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 SANE_TRUE;
}
/************************************************************************/
static SANE_Bool
XferBufferGetLine (int iHandle, TDataPipe * p, unsigned char *pabLine,
SANE_Bool fReturn)
{
unsigned char bData, bData2;
SANE_Bool fJustDone = SANE_FALSE;
/* all calculated transfers done ? */
if (p->iLinesLeft == 0)
return SANE_FALSE;
/* time for a fresh read? */
if (p->iCurLine == 0)
{
int iLines;
iLines = p->iLinesPerXferBuf;
/* read only as many lines as needed */
if (p->iLinesLeft > 0 && p->iLinesLeft <= iLines)
{
iLines = p->iLinesLeft;
DBG (DBG_MSG, "\n");
DBG (DBG_MSG, "last bulk read\n");
if (iLines < p->iLinesPerXferBuf)
{
DBG (DBG_MSG,
"reading reduced number of lines: %d instead of %d\n",
iLines, p->iLinesPerXferBuf);
}
fJustDone = SANE_TRUE;
}
/* reading old buffer level */
NiashReadReg (iHandle, 0x20, &bData);
NiashReadBulk (iHandle, p->pabXferBuf, iLines * p->iBytesPerLine);
/* reding new buffer level */
NiashReadReg (iHandle, 0x20, &bData2);
if (fJustDone && fReturn)
{
NiashWriteReg (iHandle, 0x02, 0x80);
DBG (DBG_MSG, "returning scanner head\n");
}
DBG (DBG_MSG,
"buffer level = %3d, , buffer level = %3d\r",
(int) bData, iLines * p->iBytesPerLine, (int) bData2);
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 SANE_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, SANE_FALSE);
}
}
/* static procedure that fills the circular buffer in advance to any
circular buffer data retrieval */
static void
CircBufferFill (int iHandle, TDataPipe * p, SANE_Bool iReversedHead)
{
int i;
for (i = 0; i < p->iLinesPerCircBuf; i++)
{
if (iReversedHead)
{
XferBufferGetLine (iHandle, p,
&p->pabCircBuf[p->iRedLine * p->iBytesPerLine],
SANE_FALSE);
}
else
{
XferBufferGetLine (iHandle, p,
&p->pabCircBuf[p->iBluLine * p->iBytesPerLine],
SANE_FALSE);
}
/* 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, SANE_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 average */
/* 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,
if fReturn==SANE_TRUE, the head will return automatically on an end of scan */
STATIC SANE_Bool
CircBufferGetLineEx (int iHandle, TDataPipe * p, unsigned char *pabLine,
SANE_Bool iReversedHead, SANE_Bool fReturn)
{
int iLineCount;
for (iLineCount = 0; iLineCount < p->iScaleDownLpi; ++iLineCount)
{
if (iReversedHead)
{
if (!XferBufferGetLine (iHandle, p,
&p->pabCircBuf[p->iRedLine *
p->iBytesPerLine], fReturn))
return SANE_FALSE;
}
else
{
if (!XferBufferGetLine (iHandle, p,
&p->pabCircBuf[p->iBluLine *
p->iBytesPerLine], fReturn))
return SANE_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, iLineCount);
}
/* advance pointers */
p->iRedLine = (p->iRedLine + 1) % p->iLinesPerCircBuf;
p->iGrnLine = (p->iGrnLine + 1) % p->iLinesPerCircBuf;
p->iBluLine = (p->iBluLine + 1) % p->iLinesPerCircBuf;
}
return SANE_TRUE;
}
/* gets an unscrambled line from the circular buffer. the first couple of lines contain garbage */
STATIC SANE_Bool
CircBufferGetLine (int iHandle, TDataPipe * p, unsigned char *pabLine,
SANE_Bool iReversedHead)
{
return CircBufferGetLineEx (iHandle, p, pabLine, iReversedHead, SANE_FALSE);
}
/* try to keep the number of transfers the same, but make them all
as good as possible the same size to avoid cranking in critical
situations
*/
static int
_OptimizeXferSize (int iLines, int iLinesPerXfer)
{
int iXfers;
iXfers = (iLines + iLinesPerXfer - 1) / iLinesPerXfer;
while (--iLinesPerXfer > 0
&& (iLines + iLinesPerXfer - 1) / iLinesPerXfer == iXfers);
return iLinesPerXfer + 1;
}
STATIC void
CircBufferInit (int iHandle, TDataPipe * p,
int iWidth, int iHeight,
int iMisAlignment, SANE_Bool iReversedHead,
int iScaleDownDpi, int iScaleDownLpi)
{
/* 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
{
#define SAFETY_LINES 0
#define MAX_LINES_PER_XFERBUF 800
/* estimate of number of unsigned chars to transfer at all via the USB */
/* add some lines for security */
p->iLinesLeft =
iHeight + p->iSkipLines + p->iLinesPerCircBuf + SAFETY_LINES;
p->iLinesPerXferBuf = XFER_BUF_SIZE / p->iBytesPerLine;
/* with more than 800 lines the timing is spoiled */
if (p->iLinesPerXferBuf > MAX_LINES_PER_XFERBUF)
{
p->iLinesPerXferBuf = MAX_LINES_PER_XFERBUF;
}
/* final optimization to keep critical scans smooth */
p->iLinesPerXferBuf =
_OptimizeXferSize (p->iLinesLeft, p->iLinesPerXferBuf);
DBG (DBG_MSG, "_iXFerSize = %d for %d transfer(s)\n",
(int) p->iLinesPerXferBuf * p->iBytesPerLine,
(p->iLinesLeft + p->iLinesPerXferBuf - 1) / p->iLinesPerXferBuf);
}
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 SANE_Bool
GetLamp (THWParams * pHWParams, SANE_Bool * pfLampIsOn)
{
unsigned char bData;
NiashReadReg (pHWParams->iXferHandle, 0x03, &bData);
*pfLampIsOn = ((bData & 0x01) != 0);
return SANE_TRUE;
}
STATIC SANE_Bool
SetLamp (THWParams * pHWParams, SANE_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 SANE_TRUE;
}
/*************************************************************************
Experimental simple calibration, but also returning the white levels
*************************************************************************/
STATIC SANE_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]; /* Careful : 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;
SANE_Bool iReversedHead;
int startWhiteY, endWhiteY;
int startBlackY, endBlackY;
int 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 = SANE_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 SANE_FALSE;
}
/* Definition of white and black areas */
if (iReversedHead)
{ /* hp scanners */
startWhiteY = 0;
endWhiteY = 15;
startBlackY = 16;
endBlackY = 135;
endBlackX = HW_PIXELS;
}
else
{ /* agfa scanners */
startWhiteY = 0;
endWhiteY = 70;
startBlackY = 86;
endBlackY = 135;
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 SANE_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);
}