kopia lustrzana https://gitlab.com/sane-project/backends
1612 wiersze
38 KiB
C
1612 wiersze
38 KiB
C
/**
|
|
* Description of the Primax PagePartner model
|
|
*/
|
|
static P5_Model pagepartner_model = {
|
|
"Primax PagePartner",
|
|
"Primax",
|
|
"PagePartner",
|
|
SANE_I18N ("sheetfed scanner"),
|
|
|
|
{300, 200, 150, 100, 0},
|
|
/* 500 seems also possible */
|
|
{600, 400, 300, 200, 150, 100, 0},
|
|
|
|
300,
|
|
600,
|
|
100,
|
|
100,
|
|
16,
|
|
|
|
SANE_FIX (0.0),
|
|
SANE_FIX (0.0),
|
|
SANE_FIX (215.9),
|
|
SANE_FIX (300.0),
|
|
};
|
|
|
|
#ifdef HAVE_LINUX_PPDEV_H
|
|
static char *
|
|
addr_name (uint16_t addr)
|
|
{
|
|
switch (addr)
|
|
{
|
|
case DATA:
|
|
return "DATA";
|
|
break;
|
|
case STATUS:
|
|
return "STATUS";
|
|
break;
|
|
case CONTROL:
|
|
return "CONTROL";
|
|
break;
|
|
case EPPADR:
|
|
return "EPPADR";
|
|
break;
|
|
case EPPDATA:
|
|
return "EPPDATA";
|
|
break;
|
|
default:
|
|
return "*ERROR*";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/** @brief low level hardware access functions
|
|
* @{
|
|
*/
|
|
|
|
static uint8_t
|
|
inb (int fd, uint16_t addr)
|
|
{
|
|
#ifdef HAVE_LINUX_PPDEV_H
|
|
uint8_t val = 0xff;
|
|
int rc, mode = 0xff;
|
|
|
|
switch (addr)
|
|
{
|
|
case DATA:
|
|
rc = ioctl (fd, PPRDATA, &val);
|
|
break;
|
|
case STATUS:
|
|
rc = ioctl (fd, PPRSTATUS, &val);
|
|
break;
|
|
case CONTROL:
|
|
rc = ioctl (fd, PPRCONTROL, &val);
|
|
break;
|
|
case EPPDATA:
|
|
mode = 1; /* data_reverse */
|
|
rc = ioctl (fd, PPDATADIR, &mode);
|
|
mode = IEEE1284_MODE_EPP | IEEE1284_DATA;
|
|
rc = ioctl (fd, PPSETMODE, &mode);
|
|
#ifdef PPSETFLAGS
|
|
mode = PP_FASTREAD;
|
|
rc = ioctl (fd, PPSETFLAGS, &mode);
|
|
#endif
|
|
rc = read (fd, &val, 1);
|
|
break;
|
|
default:
|
|
DBG (DBG_error, "inb(%s) escaped ppdev\n", addr_name (addr));
|
|
return 0xFF;
|
|
}
|
|
if (rc < 0)
|
|
{
|
|
DBG (DBG_error, "ppdev ioctl returned <%s>\n", strerror (errno));
|
|
}
|
|
return val;
|
|
#else
|
|
if(fd && addr)
|
|
return 0;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
outb (int fd, uint16_t addr, uint8_t value)
|
|
{
|
|
#ifdef HAVE_LINUX_PPDEV_H
|
|
int rc = 0, mode = 0xff;
|
|
|
|
switch (addr)
|
|
{
|
|
case DATA:
|
|
rc = ioctl (fd, PPWDATA, &value);
|
|
break;
|
|
case CONTROL:
|
|
mode = value & 0x20;
|
|
rc = ioctl (fd, PPDATADIR, &mode);
|
|
if (!rc)
|
|
{
|
|
value = value & 0xDF;
|
|
rc = ioctl (fd, PPWCONTROL, &value);
|
|
}
|
|
break;
|
|
case EPPDATA:
|
|
mode = 0; /* data forward */
|
|
rc = ioctl (fd, PPDATADIR, &mode);
|
|
mode = IEEE1284_MODE_EPP | IEEE1284_DATA;
|
|
rc = ioctl (fd, PPSETMODE, &mode);
|
|
rc = write (fd, &value, 1);
|
|
break;
|
|
case EPPADR:
|
|
mode = 0; /* data forward */
|
|
rc = ioctl (fd, PPDATADIR, &mode);
|
|
mode = IEEE1284_MODE_EPP | IEEE1284_ADDR;
|
|
rc = ioctl (fd, PPSETMODE, &mode);
|
|
rc = write (fd, &value, 1);
|
|
break;
|
|
default:
|
|
DBG (DBG_error, "outb(%s,0x%02x) escaped ppdev\n", addr_name (addr),
|
|
value);
|
|
break;
|
|
}
|
|
if (rc < 0)
|
|
{
|
|
DBG (DBG_error, "ppdev ioctl returned <%s>\n", strerror (errno));
|
|
}
|
|
#else
|
|
if(fd && addr && value)
|
|
return;
|
|
#endif /* HAVE_LINUX_PPDEV_H */
|
|
}
|
|
|
|
static void
|
|
write_reg (int fd, uint8_t index, uint8_t value)
|
|
{
|
|
uint8_t idx;
|
|
|
|
/* both nibbles hold the same value */
|
|
idx = index & 0x0F;
|
|
DBG (DBG_io2, "write_reg(REG%X,0x%x)\n", idx, value);
|
|
idx = idx << 4 | idx;
|
|
outb (fd, EPPADR, idx);
|
|
outb (fd, EPPDATA, value);
|
|
}
|
|
|
|
static uint8_t
|
|
read_reg (int fd, uint8_t index)
|
|
{
|
|
uint8_t idx;
|
|
|
|
/* both nibbles hold the same value */
|
|
idx = index & 0x0F;
|
|
idx = idx << 4 | idx;
|
|
outb (fd, EPPADR, idx);
|
|
return inb (fd, EPPDATA);
|
|
}
|
|
|
|
#ifdef HAVE_LINUX_PPDEV_H
|
|
static int
|
|
read_data (int fd, uint8_t * data, int length)
|
|
{
|
|
int mode, rc, nb;
|
|
unsigned char bval;
|
|
|
|
bval = REG8;
|
|
mode = IEEE1284_MODE_EPP | IEEE1284_ADDR;
|
|
rc = ioctl (fd, PPSETMODE, &mode);
|
|
rc = write (fd, &bval, 1);
|
|
|
|
mode = 1; /* data_reverse */
|
|
rc = ioctl (fd, PPDATADIR, &mode);
|
|
#ifdef PPSETFLAGS
|
|
mode = PP_FASTREAD;
|
|
rc = ioctl (fd, PPSETFLAGS, &mode);
|
|
#endif
|
|
mode = IEEE1284_MODE_EPP | IEEE1284_DATA;
|
|
rc = ioctl (fd, PPSETMODE, &mode);
|
|
nb = 0;
|
|
while (nb < length)
|
|
{
|
|
rc = read (fd, data + nb, length - nb);
|
|
if (rc < 0)
|
|
{
|
|
DBG (DBG_error, "memtest: error reading data back!\n");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
nb += rc;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
index_write_data (int fd, uint8_t index, uint8_t * data, int length)
|
|
{
|
|
int mode, rc;
|
|
unsigned char bval;
|
|
|
|
bval = index;
|
|
mode = IEEE1284_MODE_EPP | IEEE1284_ADDR;
|
|
rc = ioctl (fd, PPSETMODE, &mode);
|
|
rc = write (fd, &bval, 1);
|
|
|
|
mode = IEEE1284_MODE_EPP | IEEE1284_DATA;
|
|
rc = ioctl (fd, PPSETMODE, &mode);
|
|
mode = 0; /* data forward */
|
|
rc = ioctl (fd, PPDATADIR, &mode);
|
|
rc = write (fd, data, length);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
write_data (int fd, uint8_t * data, int length)
|
|
{
|
|
index_write_data (fd, REG8, data, length);
|
|
}
|
|
|
|
static void
|
|
write_reg2 (int fd, uint8_t index, uint16_t value)
|
|
{
|
|
uint8_t data2[2];
|
|
|
|
data2[0] = value & 0xff;
|
|
data2[1] = value >> 8;
|
|
index_write_data (fd, index, data2, 2);
|
|
}
|
|
#else
|
|
|
|
static int
|
|
read_data (int fd, uint8_t * data, int length)
|
|
{
|
|
if(fd && data && length)
|
|
return -1;
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
write_data (int fd, uint8_t * data, int length)
|
|
{
|
|
if(fd && data && length)
|
|
return;
|
|
}
|
|
|
|
static void
|
|
write_reg2 (int fd, uint8_t index, uint16_t value)
|
|
{
|
|
if(fd && index && value)
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @}
|
|
*/
|
|
|
|
|
|
/** @brief This function checks a memory buffer.
|
|
* This function writes at the given memory address then read it back
|
|
* to check the scanner is correctly working.
|
|
* @param fd file descriptor used to access hardware
|
|
* @param addr address where to write and read
|
|
* @return SANE_TRUE on succes, SANE_FALSE otherwise
|
|
*/
|
|
static int
|
|
memtest (int fd, uint16_t addr)
|
|
{
|
|
uint8_t sent[256];
|
|
uint8_t back[256];
|
|
int i;
|
|
|
|
write_reg2 (fd, REG1, addr);
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
sent[i] = (uint8_t) i;
|
|
back[i] = 0;
|
|
}
|
|
write_data (fd, sent, 256);
|
|
read_data (fd, back, 256);
|
|
|
|
/* check if data read back is the same that the one sent */
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
if (back[i] != sent[i])
|
|
{
|
|
return SANE_FALSE;
|
|
}
|
|
}
|
|
|
|
return SANE_TRUE;
|
|
}
|
|
|
|
|
|
#define INB(k,y,z) val=inb(k,y); if(val!=z) { DBG(DBG_error,"expected 0x%02x, got 0x%02x\n",z, val); return SANE_FALSE; }
|
|
|
|
/** @brief connect to scanner
|
|
* This function sends the connect sequence for the scanner.
|
|
* @param fd filedescriptor of the parallel port communication channel
|
|
* @return SANE_TRUE in case of success, SANE_FALSE otherwise
|
|
*/
|
|
static int
|
|
connect (int fd)
|
|
{
|
|
uint8_t val;
|
|
|
|
inb (fd, CONTROL);
|
|
outb (fd, CONTROL, 0x04);
|
|
outb (fd, DATA, 0x02);
|
|
INB (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x03);
|
|
INB (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x83);
|
|
INB (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x82);
|
|
INB (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x82);
|
|
INB (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x82);
|
|
INB (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x82);
|
|
INB (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x83);
|
|
INB (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x83);
|
|
INB (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x82);
|
|
INB (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x82);
|
|
INB (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x83);
|
|
INB (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x83);
|
|
INB (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x83);
|
|
INB (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x03);
|
|
outb (fd, DATA, 0x83);
|
|
INB (fd, DATA, 0x83);
|
|
outb (fd, DATA, 0x82);
|
|
INB (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0x02);
|
|
outb (fd, DATA, 0x82);
|
|
outb (fd, DATA, 0xFF);
|
|
DBG (DBG_info, "connect() OK...\n");
|
|
return SANE_TRUE;
|
|
}
|
|
|
|
static int
|
|
disconnect (int fd)
|
|
{
|
|
uint8_t val;
|
|
|
|
outb (fd, CONTROL, 0x04);
|
|
outb (fd, DATA, 0x00);
|
|
INB (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x01);
|
|
INB (fd, DATA, 0x01);
|
|
outb (fd, DATA, 0x01);
|
|
outb (fd, DATA, 0x81);
|
|
outb (fd, DATA, 0x01);
|
|
outb (fd, DATA, 0x81);
|
|
INB (fd, DATA, 0x81);
|
|
outb (fd, DATA, 0x80);
|
|
INB (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
INB (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x80);
|
|
INB (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
INB (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x81);
|
|
INB (fd, DATA, 0x81);
|
|
outb (fd, DATA, 0x01);
|
|
outb (fd, DATA, 0x81);
|
|
outb (fd, DATA, 0x01);
|
|
outb (fd, DATA, 0x81);
|
|
INB (fd, DATA, 0x81);
|
|
outb (fd, DATA, 0x80);
|
|
INB (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
INB (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
INB (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
INB (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
outb (fd, DATA, 0x00);
|
|
outb (fd, DATA, 0x80);
|
|
inb (fd, CONTROL);
|
|
outb (fd, CONTROL, 0x0C);
|
|
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
static void
|
|
setadresses (int fd, uint16_t start, uint16_t end)
|
|
{
|
|
write_reg (fd, REG3, start & 0xff);
|
|
write_reg (fd, REG4, start >> 8);
|
|
write_reg (fd, REG5, end & 0xff);
|
|
write_reg (fd, REG6, end >> 8);
|
|
DBG (DBG_io, "setadresses(0x%x,0x%x); OK...\n", start, end);
|
|
}
|
|
|
|
#ifdef HAVE_LINUX_PPDEV_H
|
|
/** @brief open parallel port device
|
|
* opens parallel port's low level device in EPP mode
|
|
* @param devicename nam of the real device or the special value 'auto'
|
|
* @return file descriptor in cas of successn -1 otherwise
|
|
*/
|
|
static int
|
|
open_pp (const char *devicename)
|
|
{
|
|
int fd, rc, mode = 0;
|
|
char *name;
|
|
|
|
DBG (DBG_proc, "open_pp: start, devicename=%s\n", devicename);
|
|
/* TODO improve auto device finding */
|
|
if (strncmp (devicename, "auto", 4) == 0)
|
|
{
|
|
name = strdup("/dev/parport0");
|
|
}
|
|
else
|
|
{
|
|
name = strdup(devicename);
|
|
}
|
|
|
|
/* open device */
|
|
fd = open (name, O_RDWR);
|
|
if (fd < 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case ENOENT:
|
|
#ifdef ENIO
|
|
case ENXIO:
|
|
#endif
|
|
#ifdef ENODEV
|
|
case ENODEV:
|
|
#endif
|
|
DBG (DBG_error, "open_pp: no %s device ...\n", name);
|
|
break;
|
|
case EACCES:
|
|
DBG (DBG_error,
|
|
"open_pp: current user cannot use existing %s device ...\n",
|
|
name);
|
|
break;
|
|
default:
|
|
DBG (DBG_error, "open_pp: %s while opening %s\n", strerror (errno),
|
|
name);
|
|
}
|
|
return -1;
|
|
}
|
|
free(name);
|
|
|
|
/* claim device and set it to EPP */
|
|
rc = ioctl (fd, PPCLAIM);
|
|
rc = ioctl (fd, PPGETMODES, &mode);
|
|
if (mode & PARPORT_MODE_PCSPP)
|
|
DBG (DBG_io, "PARPORT_MODE_PCSPP\n");
|
|
if (mode & PARPORT_MODE_TRISTATE)
|
|
DBG (DBG_io, "PARPORT_MODE_TRISTATE\n");
|
|
if (mode & PARPORT_MODE_EPP)
|
|
DBG (DBG_io, "PARPORT_MODE_EPP\n");
|
|
if (mode & PARPORT_MODE_ECP)
|
|
DBG (DBG_io, "PARPORT_MODE_ECP\n");
|
|
if (mode & PARPORT_MODE_COMPAT)
|
|
DBG (DBG_io, "PARPORT_MODE_COMPAT\n");
|
|
if (mode & PARPORT_MODE_DMA)
|
|
DBG (DBG_io, "PARPORT_MODE_DMA\n");
|
|
if (mode & PARPORT_MODE_EPP)
|
|
{
|
|
mode = IEEE1284_MODE_EPP;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
if (mode & PARPORT_MODE_ECP)
|
|
{
|
|
mode = IEEE1284_MODE_ECP;
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
mode = -1;
|
|
}
|
|
}
|
|
if (mode == -1)
|
|
{
|
|
DBG (DBG_error, "open_pp: no EPP mode, giving up ...\n");
|
|
rc = ioctl (fd, PPRELEASE);
|
|
close (fd);
|
|
return -1;
|
|
}
|
|
rc = ioctl (fd, PPNEGOT, &mode);
|
|
rc = ioctl (fd, PPSETMODE, &mode);
|
|
DBG (DBG_proc, "open_pp: exit\n");
|
|
return fd;
|
|
}
|
|
|
|
/** close low level device
|
|
* release and close low level hardware device
|
|
*/
|
|
static void
|
|
close_pp (int fd)
|
|
{
|
|
int mode = IEEE1284_MODE_COMPAT;
|
|
|
|
if (fd > 2)
|
|
{
|
|
ioctl (fd, PPNEGOT, &mode);
|
|
ioctl (fd, PPRELEASE);
|
|
close (fd);
|
|
}
|
|
}
|
|
|
|
#else /* HAVE_LINUX_PPDEV_H */
|
|
|
|
static int
|
|
open_pp (const char *devicename)
|
|
{
|
|
if(devicename)
|
|
return -1;
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
close_pp (int fd)
|
|
{
|
|
if(fd)
|
|
return;
|
|
}
|
|
#endif /* HAVE_LINUX_PPDEV_H */
|
|
|
|
/** @brief test if a document is inserted
|
|
* Test if a document is inserted by reading register E
|
|
* @param fd file descriptor to access scanner
|
|
* @return SANE_STATUS_NO_DOCS if no document or SANE_STATUS_GOOD
|
|
* if something is present.
|
|
*/
|
|
static SANE_Status
|
|
test_document (int fd)
|
|
{
|
|
int detector;
|
|
|
|
/* check for document presence 0xC6: present, 0xC3 no document */
|
|
detector = read_reg (fd, REGE);
|
|
DBG (DBG_io, "test_document: detector=0x%02X\n", detector);
|
|
|
|
/* document inserted */
|
|
if (detector & 0x04)
|
|
return SANE_STATUS_GOOD;
|
|
|
|
return SANE_STATUS_NO_DOCS;
|
|
}
|
|
|
|
/**
|
|
* return the amount of scanned data available
|
|
* @param fd file descriptor to access scanner
|
|
* @return avaible byte number
|
|
*/
|
|
static int
|
|
available_bytes (int fd)
|
|
{
|
|
int counter;
|
|
|
|
/* read the number of 256 bytes block of scanned data */
|
|
counter = read_reg (fd, REG9);
|
|
DBG (DBG_io, "available_bytes: available_bytes=0x%02X\n", counter);
|
|
return 256 * counter;
|
|
}
|
|
|
|
static SANE_Status
|
|
build_correction (P5_Device * dev, unsigned int dpi, unsigned int mode,
|
|
unsigned int start, unsigned int width)
|
|
{
|
|
unsigned int i, j, shift, step;
|
|
|
|
DBG (DBG_proc, "build_correction: start=%d, width=%d\n", start, width);
|
|
DBG (DBG_trace, "build_correction: dpi=%d, mode=%d\n", dpi, mode);
|
|
|
|
/* loop on calibration data to find the matching one */
|
|
j = 0;
|
|
while (dev->calibration_data[j]->dpi != dpi)
|
|
{
|
|
j++;
|
|
if (j > MAX_RESOLUTIONS)
|
|
{
|
|
DBG (DBG_error, "build_correction: couldn't find calibration!\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
}
|
|
|
|
if (dev->gain != NULL)
|
|
{
|
|
free (dev->gain);
|
|
dev->gain = NULL;
|
|
}
|
|
if (dev->offset != NULL)
|
|
{
|
|
free (dev->offset);
|
|
dev->offset = NULL;
|
|
}
|
|
dev->gain = (float *) malloc (width * sizeof (float));
|
|
if (dev->gain == NULL)
|
|
{
|
|
DBG (DBG_error,
|
|
"build_correction: failed to allocate memory for gain!\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
dev->offset = (uint8_t *) malloc (width);
|
|
if (dev->offset == NULL)
|
|
{
|
|
DBG (DBG_error,
|
|
"build_correction: failed to allocate memory for offset!\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
|
|
/* compute starting point of calibration data to use */
|
|
shift = start;
|
|
step = 1;
|
|
if (mode == MODE_GRAY)
|
|
{
|
|
/* we use green data */
|
|
shift += 1;
|
|
step = 3;
|
|
}
|
|
for (i = 0; i < width; i += step)
|
|
{
|
|
if (dev->calibration_data[j]->white_data[shift + i] -
|
|
dev->calibration_data[0]->black_data[shift + i] > BLACK_LEVEL)
|
|
{
|
|
dev->gain[i] =
|
|
WHITE_TARGET /
|
|
((float)
|
|
(dev->calibration_data[j]->white_data[shift + i] -
|
|
dev->calibration_data[j]->black_data[shift + i]));
|
|
dev->offset[i] = dev->calibration_data[j]->black_data[shift + i];
|
|
}
|
|
else
|
|
{
|
|
dev->gain[i] = 1.0;
|
|
dev->offset[i] = 0;
|
|
}
|
|
}
|
|
return SANE_STATUS_GOOD;
|
|
DBG (DBG_proc, "build_correction: end\n");
|
|
}
|
|
|
|
/** @brief start up a real scan
|
|
* This function starts the scan with the given parameters.
|
|
* @param dev device describing hardware
|
|
* @param mode color, gray level or lineart.
|
|
* @param dpi desired scan resolution.
|
|
* @param startx coordinate of the first pixel to scan in
|
|
* scan's resolution coordinate
|
|
* @param width width of the scanned area
|
|
* scanner's physical scan aread.
|
|
* @return SANE_STATUS_GOOD if scan is successfully started
|
|
*/
|
|
static SANE_Status
|
|
start_scan (P5_Device * dev, int mode, unsigned int dpi, unsigned int startx,
|
|
unsigned int width)
|
|
{
|
|
uint8_t reg0=0;
|
|
uint8_t reg2=0;
|
|
uint8_t regF=0;
|
|
uint16_t addr=0;
|
|
uint16_t start, end;
|
|
unsigned int xdpi;
|
|
|
|
DBG (DBG_proc, "start_scan: start \n");
|
|
DBG (DBG_io, "start_scan: startx=%d, width=%d, dpi=%d\n", startx, width,
|
|
dpi);
|
|
|
|
/** @brief register values
|
|
* - reg2 : reg2 seems related to x dpi and provides only 100,
|
|
* 150, 200 and 300 resolutions.
|
|
* - regF : lower nibble gives y dpi resolution ranging from 150
|
|
* to 1200 dpi.
|
|
*/
|
|
xdpi = dpi;
|
|
switch (dpi)
|
|
{
|
|
case 100:
|
|
reg2 = 0x90;
|
|
regF = 0xA2;
|
|
break;
|
|
case 150:
|
|
reg2 = 0x10;
|
|
regF = 0xA4;
|
|
break;
|
|
case 200:
|
|
reg2 = 0x80;
|
|
regF = 0xA6;
|
|
break;
|
|
case 300:
|
|
reg2 = 0x00;
|
|
regF = 0xA8;
|
|
break;
|
|
case 400:
|
|
reg2 = 0x80; /* xdpi=200 */
|
|
regF = 0xAA;
|
|
xdpi = 200;
|
|
break;
|
|
case 500:
|
|
reg2 = 0x00;
|
|
regF = 0xAC;
|
|
xdpi = 300;
|
|
break;
|
|
case 600:
|
|
reg2 = 0x00;
|
|
regF = 0xAE;
|
|
xdpi = 300;
|
|
break;
|
|
}
|
|
|
|
switch (mode)
|
|
{
|
|
case MODE_COLOR:
|
|
reg0 = 0x00;
|
|
addr = 0x0100;
|
|
break;
|
|
case MODE_GRAY:
|
|
/* green channel only */
|
|
reg0 = 0x20;
|
|
addr = 0x0100;
|
|
break;
|
|
case MODE_LINEART:
|
|
reg0 = 0x40;
|
|
addr = 0x0908;
|
|
break;
|
|
}
|
|
|
|
write_reg (dev->fd, REG1, 0x01);
|
|
write_reg (dev->fd, REG7, 0x00);
|
|
write_reg (dev->fd, REG0, reg0);
|
|
write_reg (dev->fd, REG1, 0x00);
|
|
write_reg (dev->fd, REGF, regF);
|
|
/* the memory addr used to test need not to be related
|
|
* to resolution, 0x0100 could be always used */
|
|
/* TODO get rid of it */
|
|
memtest (dev->fd, addr);
|
|
|
|
/* handle case where dpi>xdpi */
|
|
start = startx;
|
|
if (dpi > xdpi)
|
|
{
|
|
width = (width * xdpi) / dpi;
|
|
start = (startx * xdpi) / dpi;
|
|
}
|
|
|
|
/* compute and set start addr */
|
|
if (mode == MODE_COLOR)
|
|
{
|
|
start = start * 3;
|
|
width = width * 3;
|
|
}
|
|
end = start + width + 1;
|
|
|
|
/* build calibration data for the scan */
|
|
if (dev->calibrated)
|
|
{
|
|
build_correction (dev, xdpi, mode, start, width);
|
|
}
|
|
|
|
setadresses (dev->fd, start, end);
|
|
|
|
write_reg (dev->fd, REG1, addr >> 8);
|
|
write_reg (dev->fd, REG2, reg2);
|
|
regF = (regF & 0x0F) | 0x80;
|
|
write_reg (dev->fd, REGF, regF);
|
|
write_reg (dev->fd, REG0, reg0);
|
|
if (mode == MODE_LINEART)
|
|
{
|
|
write_reg (dev->fd, 0x07, 0x04);
|
|
}
|
|
else
|
|
{
|
|
write_reg (dev->fd, 0x07, 0x00);
|
|
}
|
|
write_reg (dev->fd, REG1, addr >> 8);
|
|
write_reg2 (dev->fd, REG1, addr);
|
|
write_reg (dev->fd, REGF, regF | 0x01);
|
|
write_reg (dev->fd, REG0, reg0 | 0x0C);
|
|
if (mode == MODE_LINEART)
|
|
{
|
|
write_reg (dev->fd, REG1, 0x19);
|
|
}
|
|
else
|
|
{
|
|
write_reg (dev->fd, REG1, 0x11);
|
|
}
|
|
DBG (DBG_proc, "start_scan: exit\n");
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/** read a line of scan data
|
|
* @param dev device to read
|
|
* @param data pointer where to store data
|
|
* @param length total bytes to read on one line
|
|
* @param ltr total number of lines to read
|
|
* @param retry signals that the function must read as much lines it can
|
|
* @param x2 tells that lines must be enlarged by a 2 factor
|
|
* @param mode COLOR_MODE if color mode
|
|
* @returns number of data lines read, -1 in case of error
|
|
*/
|
|
static int
|
|
read_line (P5_Device * dev, uint8_t * data, size_t length, int ltr,
|
|
SANE_Bool retry, SANE_Bool x2, int mode, SANE_Bool correction)
|
|
{
|
|
uint8_t counter, read, cnt;
|
|
uint8_t inbuffer[MAX_SENSOR_PIXELS * 2 * 3 + 2];
|
|
unsigned int i, factor;
|
|
float val;
|
|
|
|
DBG (DBG_proc, "read_line: trying to read %d lines of %lu bytes\n", ltr,
|
|
(unsigned long)length);
|
|
|
|
counter = read_reg (dev->fd, REG9);
|
|
DBG (DBG_io, "read_line: %d bytes available\n", counter * 256);
|
|
read = 0;
|
|
if (x2 == SANE_FALSE)
|
|
{
|
|
factor = 1;
|
|
}
|
|
else
|
|
{
|
|
factor = 2;
|
|
}
|
|
|
|
/* in retry mode we read until not enough data, but in no retry
|
|
* read only one line , counter give us 256 bytes block available
|
|
* and we want an number multiple of color channels */
|
|
cnt = (255 + length / factor) / 256;
|
|
while ((counter > cnt && retry == 1) || (counter > cnt && read == 0))
|
|
{
|
|
/* read data from scanner, first and last byte aren't picture data */
|
|
read_data (dev->fd, inbuffer, length / factor + 2);
|
|
|
|
/* image correction */
|
|
if (correction == SANE_TRUE)
|
|
{
|
|
for (i = 0; i < length / factor; i++)
|
|
{
|
|
val = inbuffer[i + 1] - dev->offset[i];
|
|
if (val > 0)
|
|
{
|
|
val = val * dev->gain[i];
|
|
if (val < 255)
|
|
inbuffer[i + 1] = val;
|
|
else
|
|
inbuffer[i + 1] = 255;
|
|
}
|
|
else
|
|
{
|
|
inbuffer[i + 1] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* handle horizontal data doubling */
|
|
if (x2 == SANE_FALSE)
|
|
{
|
|
memcpy (data + read * length, inbuffer + 1, length);
|
|
}
|
|
else
|
|
{
|
|
if (mode == MODE_COLOR)
|
|
{
|
|
for (i = 0; i < length / factor; i += 3)
|
|
{
|
|
data[read * length + i * factor] = inbuffer[i + 1];
|
|
data[read * length + i * factor + 1] = inbuffer[i + 2];
|
|
data[read * length + i * factor + 2] = inbuffer[i + 3];
|
|
data[read * length + i * factor + 3] = inbuffer[i + 1];
|
|
data[read * length + i * factor + 4] = inbuffer[i + 2];
|
|
data[read * length + i * factor + 5] = inbuffer[i + 3];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < length / factor; i++)
|
|
{
|
|
data[read * length + i * factor] = inbuffer[i + 1];
|
|
data[read * length + i * factor + 1] = inbuffer[i + 1];
|
|
}
|
|
}
|
|
}
|
|
read++;
|
|
if (retry == SANE_TRUE)
|
|
{
|
|
read_reg (dev->fd, REGF);
|
|
read_reg (dev->fd, REGA);
|
|
read_reg (dev->fd, REG9);
|
|
counter = read_reg (dev->fd, REG9);
|
|
read_reg (dev->fd, REGA);
|
|
if (read >= ltr)
|
|
{
|
|
DBG (DBG_io, "read_line returning %d lines\n", read);
|
|
return read;
|
|
}
|
|
counter = read_reg (dev->fd, REG9);
|
|
}
|
|
}
|
|
read_reg (dev->fd, REGF);
|
|
read_reg (dev->fd, REGA);
|
|
read_reg (dev->fd, REG9);
|
|
counter = read_reg (dev->fd, REG9);
|
|
read_reg (dev->fd, REGA);
|
|
DBG (DBG_io, "read_line returning %d lines\n", read);
|
|
return read;
|
|
}
|
|
|
|
|
|
static SANE_Status
|
|
eject (int fd)
|
|
{
|
|
int detector;
|
|
|
|
DBG (DBG_proc, "eject: start ...\n");
|
|
|
|
do
|
|
{
|
|
write_reg2 (fd, REG1, 0x1110);
|
|
detector = read_reg (fd, REGE);
|
|
detector = read_reg (fd, REGE);
|
|
}
|
|
while ((detector & 0x04) != 0);
|
|
write_reg (fd, REG0, 0x00);
|
|
write_reg (fd, REG1, 0x00);
|
|
write_reg (fd, REGF, 0x82);
|
|
write_reg (fd, REG7, 0x00);
|
|
|
|
DBG (DBG_proc, "eject: end.\n");
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/** @brief wait for document to be present in feeder
|
|
* Polls document sensor until something is present. Give up after 20 seconds
|
|
* @param fd file descriptor of the physical device
|
|
*/
|
|
/* static int
|
|
wait_document (int fd, uint8_t detector)
|
|
{
|
|
int count = 0;
|
|
uint8_t val;
|
|
|
|
write_reg (fd, REG1, 0x00);
|
|
write_reg (fd, REG7, 0x00);
|
|
detector = read_reg (fd, REGE);
|
|
while (detector == 0xc3 && count < 20)
|
|
{
|
|
sleep (1);
|
|
count++;
|
|
detector = read_reg (fd, REGE);
|
|
}
|
|
setadresses (fd, 0x002d, 0x09c7);
|
|
write_reg (fd, REG1, 0x00);
|
|
write_reg (fd, REG2, 0x90);
|
|
write_reg (fd, REGF, 0x82);
|
|
write_reg (fd, REG0, 0x00);
|
|
val = inb (fd, STATUS) & 0xf8;
|
|
if (val != 0xf8)
|
|
{
|
|
DBG (DBG_error, "wait_document: unexpected STATUS value 0x%02x instead of 0xf8", val);
|
|
}
|
|
if (count >= 20)
|
|
{
|
|
DBG (DBG_error, "wait_document: failed to detect document!\n");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
} */
|
|
|
|
/** @brief move at 150 dpi
|
|
* move the paper at 150 dpi motor speed by the amount specified
|
|
* @params dev pointer to the device structure
|
|
*/
|
|
static SANE_Status
|
|
move (P5_Device * dev)
|
|
{
|
|
int skip, done, read, count;
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
unsigned char buffer[256];
|
|
|
|
DBG (DBG_proc, "move: start\n");
|
|
|
|
/* compute number of lines to skip */
|
|
skip = dev->ystart;
|
|
|
|
/* works, but remains to be explained ... */
|
|
if (dev->ydpi > 300)
|
|
skip = skip / 2;
|
|
|
|
DBG (DBG_io, "move: skipping %d lines at %d dpi\n", skip, dev->ydpi);
|
|
|
|
/* we do a real scan of small width, discarding data */
|
|
done = 0;
|
|
status = start_scan (dev, MODE_GRAY, dev->ydpi, 0, 256);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
DBG (DBG_error, "move: failed to start scan\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
do
|
|
{
|
|
/* test if document left the feeder */
|
|
status = test_document (dev->fd);
|
|
if (status == SANE_STATUS_NO_DOCS)
|
|
{
|
|
DBG (DBG_info,
|
|
"move: document was shorter than the required move\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
/* test if data is available */
|
|
count = available_bytes (dev->fd);
|
|
if (count)
|
|
{
|
|
read =
|
|
read_line (dev, buffer, 256, 1, SANE_FALSE, SANE_FALSE,
|
|
MODE_GRAY, SANE_FALSE);
|
|
if (read == -1)
|
|
{
|
|
DBG (DBG_error, "move: failed to read data\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
done += read;
|
|
}
|
|
}
|
|
while (done < skip);
|
|
|
|
/* reset scanner */
|
|
write_reg2 (dev->fd, REG1, 0x1110);
|
|
count = read_reg (dev->fd, REGE);
|
|
count = read_reg (dev->fd, REGE);
|
|
write_reg (dev->fd, REG0, 0x00);
|
|
write_reg (dev->fd, REG1, 0x00);
|
|
write_reg (dev->fd, REGF, 0x82);
|
|
write_reg (dev->fd, REG7, 0x00);
|
|
|
|
DBG (DBG_proc, "move: exit\n");
|
|
return status;
|
|
}
|
|
|
|
/** clean up calibration data
|
|
* @param dev device to clean up
|
|
*/
|
|
static void
|
|
cleanup_calibration (P5_Device * dev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_RESOLUTIONS * 2; i++)
|
|
{
|
|
if (dev->calibration_data[i] != NULL)
|
|
{
|
|
free (dev->calibration_data[i]);
|
|
dev->calibration_data[i] = NULL;
|
|
}
|
|
}
|
|
dev->calibrated = SANE_FALSE;
|
|
}
|
|
|
|
/** detect a black scan line
|
|
* parses the given buffer and retrun SANE_TRUE if the line is an
|
|
* acceptable black line for calibration
|
|
* @param buffer data line to parse
|
|
* @param pixels number of pixels
|
|
* @param mode MODE_COLOR or MODE_GRAY
|
|
* @returns SANE_TRUE if it is considered as a white line
|
|
*/
|
|
static SANE_Bool
|
|
is_black_line (uint8_t * buffer, unsigned int pixels, int mode)
|
|
{
|
|
unsigned int i, start, end, count, width;
|
|
|
|
/* compute width in bytes */
|
|
if (mode == MODE_COLOR)
|
|
{
|
|
width = pixels * 3;
|
|
}
|
|
else
|
|
{
|
|
width = pixels;
|
|
}
|
|
|
|
/* we allow the calibration target to be narrower than full width, ie
|
|
* black margin at both ends of the line */
|
|
start = (5 * width) / 100;
|
|
end = (95 * width) / 100;
|
|
count = 0;
|
|
|
|
/* count number of black bytes */
|
|
for (i = start; i < end; i++)
|
|
{
|
|
if (buffer[i] > BLACK_LEVEL)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
/* we allow 3% black pixels maximum */
|
|
if (count > (3 * width) / 100)
|
|
{
|
|
DBG (DBG_io, "is_black_line=SANE_FALSE\n");
|
|
return SANE_FALSE;
|
|
}
|
|
|
|
DBG (DBG_io, "is_black_line=SANE_TRUE\n");
|
|
return SANE_TRUE;
|
|
}
|
|
|
|
/** detect a white scan line
|
|
* parses the given buffer and retrun SANE_TRUE if the line is an
|
|
* acceptable white line for calibration
|
|
* @param buffer data line to parse
|
|
* @param pixels number of pixels
|
|
* @param mode MODE_COLOR or MODE_GRAY
|
|
* @returns SANE_TRUE if it is considered as a white line
|
|
*/
|
|
static SANE_Bool
|
|
is_white_line (uint8_t * buffer, unsigned int pixels, int mode)
|
|
{
|
|
unsigned int i, start, end, count, width;
|
|
|
|
/* compute width in bytes */
|
|
if (mode == MODE_COLOR)
|
|
{
|
|
width = pixels * 3;
|
|
}
|
|
else
|
|
{
|
|
width = pixels;
|
|
}
|
|
|
|
/* we allow the calibration target to be narrower than full width, ie
|
|
* black margin at both ends of the line */
|
|
start = (5 * width) / 100;
|
|
end = (95 * width) / 100;
|
|
count = 0;
|
|
|
|
/* count number of black bytes */
|
|
for (i = start; i < end; i++)
|
|
{
|
|
if (buffer[i] < BLACK_LEVEL)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
/* we allow 3% black pixels maximum */
|
|
if (count > (3 * width) / 100)
|
|
{
|
|
DBG (DBG_io, "is_white_line=SANE_FALSE\n");
|
|
return SANE_FALSE;
|
|
}
|
|
|
|
DBG (DBG_io, "is_white_line=SANE_TRUE\n");
|
|
return SANE_TRUE;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* writes gray data to a pnm file */
|
|
/*
|
|
static void
|
|
write_gray_data (unsigned char *image, char *name, SANE_Int width,
|
|
SANE_Int height)
|
|
{
|
|
FILE *fdbg = NULL;
|
|
|
|
fdbg = fopen (name, "wb");
|
|
if (fdbg == NULL)
|
|
return;
|
|
fprintf (fdbg, "P5\n%d %d\n255\n", width, height);
|
|
fwrite (image, width, height, fdbg);
|
|
fclose (fdbg);
|
|
}
|
|
*/
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* writes rgb data to a pnm file */
|
|
static void
|
|
write_rgb_data (char *name, unsigned char *image, SANE_Int width,
|
|
SANE_Int height)
|
|
{
|
|
FILE *fdbg = NULL;
|
|
|
|
fdbg = fopen (name, "wb");
|
|
if (fdbg == NULL)
|
|
return;
|
|
fprintf (fdbg, "P6\n%d %d\n255\n", width, height);
|
|
fwrite (image, width * 3, height, fdbg);
|
|
fclose (fdbg);
|
|
}
|
|
|
|
/** give calibration file name
|
|
* computes the calibration file name to use based on the
|
|
* backend name and device
|
|
*/
|
|
static char *
|
|
calibration_file (const char *devicename)
|
|
{
|
|
char *ptr = NULL;
|
|
char tmp_str[PATH_MAX];
|
|
|
|
ptr = getenv ("HOME");
|
|
if (ptr != NULL)
|
|
{
|
|
sprintf (tmp_str, "%s/.sane/p5-%s.cal", ptr, devicename);
|
|
}
|
|
else
|
|
{
|
|
ptr = getenv ("TMPDIR");
|
|
if (ptr != NULL)
|
|
{
|
|
sprintf (tmp_str, "%s/p5-%s.cal", ptr, devicename);
|
|
}
|
|
else
|
|
{
|
|
sprintf (tmp_str, "/tmp/p5-%s.cal", devicename);
|
|
}
|
|
}
|
|
DBG (DBG_trace, "calibration_file: using >%s< for calibration file name\n",
|
|
tmp_str);
|
|
return strdup (tmp_str);
|
|
}
|
|
|
|
/** restore calibration data
|
|
* restore calibration data by loading previously saved calibration data
|
|
* @param dev device to restore
|
|
* @return SANE_STATUS_GOOD on success, otherwise error code
|
|
*/
|
|
static SANE_Status
|
|
restore_calibration (P5_Device * dev)
|
|
{
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
char *fname = NULL;
|
|
FILE *fcalib = NULL;
|
|
size_t size;
|
|
int i;
|
|
|
|
DBG (DBG_proc, "restore_calibration: start\n");
|
|
cleanup_calibration (dev);
|
|
fname = calibration_file (dev->model->name);
|
|
fcalib = fopen (fname, "rb");
|
|
if (fcalib == NULL)
|
|
{
|
|
DBG (DBG_error, "restore_calibration: failed to open %s!\n", fname);
|
|
free (fname);
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
/* loop filling calibration data until EOF reached */
|
|
i = 0;
|
|
while (!feof (fcalib) && (i < 2 * MAX_RESOLUTIONS))
|
|
{
|
|
dev->calibration_data[i] = malloc (sizeof (P5_Calibration_Data));
|
|
if (dev->calibration_data[i] == NULL)
|
|
{
|
|
cleanup_calibration (dev);
|
|
free (fname);
|
|
fclose (fcalib);
|
|
DBG (DBG_error,
|
|
"restore_calibration: failed to allocate memory for calibration\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
size =
|
|
fread (dev->calibration_data[i], 1, sizeof (P5_Calibration_Data),
|
|
fcalib);
|
|
if (feof (fcalib))
|
|
{
|
|
free (dev->calibration_data[i]);
|
|
dev->calibration_data[i] = NULL;
|
|
}
|
|
else if (size != sizeof (P5_Calibration_Data))
|
|
{
|
|
cleanup_calibration (dev);
|
|
free (fname);
|
|
fclose (fcalib);
|
|
DBG (DBG_error, "restore_calibration: failed to read from file\n");
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
DBG (DBG_trace,
|
|
"restore_calibration: read 1 calibration structure from file\n");
|
|
i++;
|
|
}
|
|
|
|
dev->calibrated = SANE_TRUE;
|
|
fclose (fcalib);
|
|
free (fname);
|
|
|
|
DBG (DBG_proc, "restore_calibration: end\n");
|
|
return status;
|
|
}
|
|
|
|
/** save calibration data
|
|
* save calibration data from memory to file
|
|
* @param dev device calibration to save
|
|
* @return SANE_STATUS_GOOD on success, otherwise error code
|
|
*/
|
|
static SANE_Status
|
|
save_calibration (P5_Device * dev)
|
|
{
|
|
SANE_Status status = SANE_STATUS_GOOD;
|
|
char *fname = NULL;
|
|
FILE *fcalib = NULL;
|
|
int i;
|
|
size_t size;
|
|
|
|
DBG (DBG_proc, "save_calibration: start\n");
|
|
fname = calibration_file (dev->model->name);
|
|
fcalib = fopen (fname, "wb");
|
|
if (fcalib == NULL)
|
|
{
|
|
DBG (DBG_error, "save_calibration: failed to open %s!\n", fname);
|
|
free (fname);
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
|
|
/* loop filling calibration data until EOF reached */
|
|
i = 0;
|
|
while (dev->calibration_data[i] != NULL && (i < 2 * MAX_RESOLUTIONS))
|
|
{
|
|
size =
|
|
fwrite (dev->calibration_data[i], sizeof (P5_Calibration_Data), 1,
|
|
fcalib);
|
|
if (size != sizeof (P5_Calibration_Data))
|
|
{
|
|
free (fname);
|
|
fclose (fcalib);
|
|
DBG (DBG_error, "save_calibration: failed to write to file\n");
|
|
return SANE_STATUS_IO_ERROR;
|
|
}
|
|
DBG (DBG_trace,
|
|
"save_calibration: wrote 1 calibration structure to file\n");
|
|
i++;
|
|
}
|
|
|
|
fclose (fcalib);
|
|
free (fname);
|
|
|
|
DBG (DBG_proc, "save_calibration: end\n");
|
|
return status;
|
|
}
|
|
|
|
/** calibrate scanner
|
|
* calibrates scanner by scanning a white sheet to get
|
|
* reference data. The black reference data is extracted from the lines
|
|
* that precede the physical document.
|
|
* Calibration is done at 300 color, then data is built for other modes
|
|
* and resolutions.
|
|
* @param dev device to calibrate
|
|
*/
|
|
static SANE_Status
|
|
sheetfed_calibration (P5_Device * dev)
|
|
{
|
|
uint8_t buffer[MAX_SENSOR_PIXELS * 3];
|
|
uint16_t white_data[MAX_SENSOR_PIXELS * 3];
|
|
uint16_t black_data[MAX_SENSOR_PIXELS * 3];
|
|
unsigned int i, j, k, dpi, pixels, read, black, white;
|
|
float coeff;
|
|
unsigned int red, green, blue;
|
|
int line;
|
|
SANE_Status status;
|
|
char title[40];
|
|
|
|
FILE *dbg = fopen ("debug.pnm", "wb");
|
|
fprintf (dbg, "P6\n%d %d\n255\n", MAX_SENSOR_PIXELS,
|
|
CALIBRATION_SKIP_LINES * 4);
|
|
|
|
DBG (DBG_proc, "sheetfed_calibration: start\n");
|
|
|
|
/* check calibration target has been loaded in ADF */
|
|
status = test_document (dev->fd);
|
|
if (status == SANE_STATUS_NO_DOCS)
|
|
{
|
|
DBG (DBG_error,
|
|
"sheetfed_calibration: no calibration target present!\n");
|
|
return SANE_STATUS_NO_DOCS;
|
|
}
|
|
|
|
/* clean up calibration data */
|
|
cleanup_calibration (dev);
|
|
|
|
/* a RGB scan to get reference data */
|
|
/* initialize calibration slot for the resolution */
|
|
i = 0;
|
|
dpi = dev->model->max_xdpi;
|
|
pixels = MAX_SENSOR_PIXELS;
|
|
dev->calibration_data[i] =
|
|
(P5_Calibration_Data *) malloc (sizeof (P5_Calibration_Data));
|
|
if (dev->calibration_data[i] == NULL)
|
|
{
|
|
cleanup_calibration (dev);
|
|
DBG (DBG_error,
|
|
"sheetfed_calibration: failed to allocate memory for calibration\n");
|
|
return SANE_STATUS_NO_MEM;
|
|
}
|
|
dev->calibration_data[i]->dpi = dpi;
|
|
|
|
/* start scan */
|
|
status = start_scan (dev, MODE_COLOR, dpi, 0, pixels);
|
|
if (status != SANE_STATUS_GOOD)
|
|
{
|
|
cleanup_calibration (dev);
|
|
DBG (DBG_error,
|
|
"sheetfed_calibration: failed to start scan at %d dpi\n", dpi);
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
white = 0;
|
|
black = 0;
|
|
read = 0;
|
|
for (j = 0; j < pixels * 3; j++)
|
|
{
|
|
black_data[j] = 0;
|
|
white_data[j] = 0;
|
|
}
|
|
|
|
/* read lines and gather black and white ones until enough for sensor's
|
|
* native resolution */
|
|
do
|
|
{
|
|
status = test_document (dev->fd);
|
|
if (status == SANE_STATUS_NO_DOCS && (white < 10 || black < 10))
|
|
{
|
|
cleanup_calibration (dev);
|
|
DBG (DBG_error,
|
|
"sheetfed_calibration: calibration sheet too short!\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
memset (buffer, 0x00, MAX_SENSOR_PIXELS * 3);
|
|
line =
|
|
read_line (dev, buffer, pixels * 3, 1, SANE_FALSE, SANE_FALSE,
|
|
MODE_COLOR, SANE_FALSE);
|
|
if (line == -1)
|
|
{
|
|
DBG (DBG_error, "sheetfed_calibration: failed to read data\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
|
|
/* if a data line has been read, add it to reference data */
|
|
if (line)
|
|
{
|
|
read++;
|
|
fwrite (buffer, pixels * 3, 1, dbg);
|
|
if (is_white_line (buffer, pixels, MODE_COLOR) && white < 256)
|
|
{
|
|
white++;
|
|
/* first calibration lines are skipped */
|
|
for (j = 0; j < pixels * 3 && read > CALIBRATION_SKIP_LINES;
|
|
j++)
|
|
{
|
|
white_data[j] += buffer[j];
|
|
}
|
|
}
|
|
if (is_black_line (buffer, pixels, MODE_COLOR) && black < 256)
|
|
{
|
|
black++;
|
|
for (j = 0; j < pixels * 3; j++)
|
|
{
|
|
black_data[j] += buffer[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (test_document (dev->fd) != SANE_STATUS_NO_DOCS);
|
|
DBG (DBG_trace, "sheetfed_calibration: white lines=%d, black lines=%d\n",
|
|
white, black);
|
|
|
|
/* average pixels and store in per dpi calibration data */
|
|
for (j = 0; j < pixels * 3; j++)
|
|
{
|
|
dev->calibration_data[i]->white_data[j] = white_data[j] / white;
|
|
dev->calibration_data[i]->black_data[j] = black_data[j] / black;
|
|
}
|
|
|
|
/* we average red, green and blue offset on the full sensor */
|
|
red = 0;
|
|
green = 0;
|
|
blue = 0;
|
|
for (j = 0; j < pixels * 3; j += 3)
|
|
{
|
|
red += dev->calibration_data[i]->black_data[j];
|
|
green += dev->calibration_data[i]->black_data[j + 1];
|
|
blue += dev->calibration_data[i]->black_data[j + 2];
|
|
}
|
|
for (j = 0; j < pixels * 3; j += 3)
|
|
{
|
|
dev->calibration_data[i]->black_data[j] = red / pixels;
|
|
dev->calibration_data[i]->black_data[j + 1] = green / pixels;
|
|
dev->calibration_data[i]->black_data[j + 2] = blue / pixels;
|
|
}
|
|
|
|
/* trace calibration data for debug */
|
|
if (DBG_LEVEL > DBG_data)
|
|
{
|
|
sprintf (title, "calibration-white-%d.pnm",
|
|
dev->calibration_data[i]->dpi);
|
|
write_rgb_data (title, dev->calibration_data[i]->white_data, pixels, 1);
|
|
sprintf (title, "calibration-black-%d.pnm",
|
|
dev->calibration_data[i]->dpi);
|
|
write_rgb_data (title, dev->calibration_data[i]->black_data, pixels, 1);
|
|
}
|
|
|
|
/* loop on all remaining resolution and compute calibration data from it */
|
|
for (i = 1; i < MAX_RESOLUTIONS && dev->model->xdpi_values[i] > 0; i++)
|
|
{
|
|
dev->calibration_data[i] =
|
|
(P5_Calibration_Data *) malloc (sizeof (P5_Calibration_Data));
|
|
if (dev->calibration_data[i] == NULL)
|
|
{
|
|
cleanup_calibration (dev);
|
|
DBG (DBG_error,
|
|
"sheetfed_calibration: failed to allocate memory for calibration\n");
|
|
return SANE_STATUS_INVAL;
|
|
}
|
|
dev->calibration_data[i]->dpi = dev->model->xdpi_values[i];
|
|
|
|
coeff = ((float) dev->model->xdpi_values[i]) / (float) dpi;
|
|
|
|
/* generate data by decimation */
|
|
for (j = 0; j < pixels / coeff; j++)
|
|
{
|
|
k = j * coeff;
|
|
dev->calibration_data[i]->white_data[j] =
|
|
dev->calibration_data[0]->white_data[k];
|
|
dev->calibration_data[i]->white_data[j + 1] =
|
|
dev->calibration_data[0]->white_data[k + 1];
|
|
dev->calibration_data[i]->white_data[j + 2] =
|
|
dev->calibration_data[0]->white_data[k + 2];
|
|
dev->calibration_data[i]->black_data[j] =
|
|
dev->calibration_data[0]->black_data[k];
|
|
dev->calibration_data[i]->black_data[j + 1] =
|
|
dev->calibration_data[0]->black_data[k + 1];
|
|
dev->calibration_data[i]->black_data[j + 2] =
|
|
dev->calibration_data[0]->black_data[k + 2];
|
|
}
|
|
}
|
|
|
|
fclose (dbg);
|
|
dev->calibrated = SANE_TRUE;
|
|
|
|
/* eject calibration target */
|
|
eject (dev->fd);
|
|
|
|
DBG (DBG_proc, "sheetfed_calibration: end\n");
|
|
return SANE_STATUS_GOOD;
|
|
}
|
|
|
|
/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */
|