diff options
Diffstat (limited to 'backend/canon_pp-dev.c')
-rw-r--r-- | backend/canon_pp-dev.c | 1368 |
1 files changed, 1368 insertions, 0 deletions
diff --git a/backend/canon_pp-dev.c b/backend/canon_pp-dev.c new file mode 100644 index 0000000..a357cf0 --- /dev/null +++ b/backend/canon_pp-dev.c @@ -0,0 +1,1368 @@ +/* sane - Scanner Access Now Easy. + Copyright (C) 2001-2002 Matthew C. Duggan and Simon Krix + This file is part of the SANE package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + As a special exception, the authors of SANE give permission for + additional uses of the libraries contained in this release of SANE. + + The exception is that, if you link a SANE library with other files + to produce an executable, this does not by itself cause the + resulting executable to be covered by the GNU General Public + License. Your use of that executable is in no way restricted on + account of linking the SANE library code into it. + + This exception does not, however, invalidate any other reasons why + the executable file might be covered by the GNU General Public + License. + + If you submit changes to SANE to the maintainers to be included in + a subsequent release, you agree by submitting the changes that + those changes may be distributed with this exception intact. + + If you write modifications of your own for SANE, it is your choice + whether to permit this exception to apply to your modifications. + If you do not wish that, delete this exception notice. + + ----- + + This file is part of the canon_pp backend, supporting Canon CanoScan + Parallel scanners and also distributed as part of the stand-alone driver. + + canon_pp-dev.c: $Revision$ + + Misc constants for Canon CanoScan Parallel scanners and high-level scan + functions. + + Simon Krix <kinsei@users.sourceforge.net> + */ + +#ifdef _AIX +#include <lalloca.h> +#endif + +#ifndef NOSANE +#include "../include/sane/config.h" +#endif + +#include <sys/time.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <ieee1284.h> +#include "canon_pp-io.h" +#include "canon_pp-dev.h" + +#ifdef NOSANE + +/* No SANE, Things that only apply to stand-alone */ +#include <stdio.h> +#include <stdarg.h> + +static void DBG(int level, const char *format, ...) +{ + va_list args; + va_start(args, format); + if (level < 50) vfprintf(stderr, format, args); + va_end(args); +} +#else + +/* Definitions which only apply to SANE compiles */ +#ifndef VERSION +#define VERSION "$Revision$" +#endif + +#define DEBUG_DECLARE_ONLY +#include "canon_pp.h" +#include "../include/sane/sanei_config.h" +#include "../include/sane/sanei_backend.h" + +#endif + +struct scanner_hardware_desc { + char *name; + unsigned int natural_xresolution; + unsigned int natural_yresolution; + unsigned int scanbedlength; + unsigned int scanheadwidth; /* 0 means provided by scanner */ + unsigned int type; +}; + +static const struct scanner_hardware_desc + /* The known scanner types */ + hw_fb320p = { "FB320P", 2, 2, 3508, 2552, 0 }, + hw_fb330p = { "FB330P", 2, 2, 3508, 0, 1 }, + hw_fb620p = { "FB620P", 3, 3, 7016, 5104, 0 }, + hw_fb630p = { "FB630P", 3, 3, 7016, 0, 1 }, + hw_n640p = { "N640P", 3, 3, 7016, 0, 1 }, + hw_n340p = { "N340P", 2, 2, 3508, 0, 1 }, + + /* A few generic scanner descriptions for aliens */ + hw_alien600 = { "Unknown 600dpi", 3, 3, 7016, 0, 1 }, + hw_alien300 = { "Unknown 300dpi", 2, 2, 3508, 0, 1 }, + hw_alien = { "Unknown (600dpi?)", 3, 3, 7016, 0, 1 }; + +/* ID table linking ID strings with hardware descriptions */ +struct scanner_id { + char *id; + const struct scanner_hardware_desc *hw; +}; +static const struct scanner_id scanner_id_table[] = { + { "CANON IX-03055C", &hw_fb320p }, + { "CANON IX-06025C", &hw_fb620p }, + { "CANON IX-03075E", &hw_fb330p }, + { "CANON IX-06075E", &hw_fb630p }, + { "CANON IX-03095G", &hw_n340p }, + { "CANON IX-06115G", &hw_n640p }, + { NULL, NULL } }; + +/*const int scanline_count = 6;*/ +static const char *header = "#CANONPP"; +static const int fileversion = 3; + +/* Internal functions */ +static unsigned long column_sum(image_segment *image, int x); +static int adjust_output(image_segment *image, scan_parameters *scanp, + scanner_parameters *scannerp); +static int check8(unsigned char *p, int s); +/* Converts from weird scanner format -> sequential data */ +static void convdata(unsigned char *srcbuffer, unsigned char *dstbuffer, + int width, int mode); +/* Sets up the scan command. This could use a better name + (and a rewrite). */ +static int scanner_setup_params(unsigned char *buf, scanner_parameters *sp, + scan_parameters *scanp); + +/* file reading and writing helpers */ +static int safe_write(int fd, const char *p, unsigned long len); +static int safe_read(int fd, char *p, unsigned long len); + +/* Command sending loop (waiting for ready status) */ +static int send_command(struct parport *port, unsigned char *buf, int bufsize, + int delay, int timeout); + +/* Commands ================================================ */ + +/* Command_1[] moved to canon_pp-io.c for neatness */ + + +/* Read device ID command */ +/* after this 0x26 (38) bytes are read */ +static unsigned char cmd_readid[] = { 0xfe, 0x20, 0, 0, 0, 0, 0, 0, 0x26, 0 }; + +/* Reads 12 bytes of unknown information */ +static unsigned char cmd_readinfo[] = { 0xf3, 0x20, 0, 0, 0, 0, 0, 0, 0x0c, 0 }; + +/* Scan init command: Always followed immediately by command cmd_scan */ +static unsigned char cmd_initscan[] = { 0xde, 0x20, 0, 0, 0, 0, 0, 0, 0x2e, 0 }; + +/* Scan information block */ +static unsigned char cmd_scan[45] = +{ 0x11, 0x2c, 0x11, 0x2c, 0x10, 0x4b, 0x10, 0x4b, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0x08, 0x08, 0x01, 0x01, 0x80, 0x01, + 0x80, 0x80, 0x02, 0, 0, 0xc1, 0, 0x08, 0x01, 0x01, + 0, 0, 0, 0, 0 +}; + +/* Read 6 byte buffer status block */ +static unsigned char cmd_buf_status[] = { 0xf3, 0x21, 0, 0, 0, 0, 0, 0, 0x06, 0 }; + +/* Request a block of image data */ +static unsigned char cmd_packet_req[] = {0xd4, 0x20, 0, 0, 0, 0, 0, 0x09, 0x64, 0}; + +/* "*SCANEND" command - returns the scanner to transparent mode */ +static unsigned char cmd_scanend[] = +{ 0x1b, 0x2a, 0x53, 0x43, 0x41, 0x4e, 0x45, 0x4e, 0x44, 0x0d }; + +/* Reads BLACK calibration image */ +static unsigned char cmd_calblack[] ={0xf8, 0x20, 0, 0, 0, 0, 0, 0x4a, 0xc4, 0}; + +/* Clear the existing gamma table and create a new one */ +static unsigned char cmd_cleargamma[] = {0xc5, 0x20, 0, 0, 0, 0, 0, 0, 0, 0}; + +/* Read back the gamma table values */ +static unsigned char cmd_readgamma[] = {0xf6, 0x20, 0, 0, 0, 0, 0, 0, 0x20, 0}; + +/* Reads COLOUR (R,G or B) calibration image */ +static unsigned char cmd_calcolour[]={0xf9, 0x20, 0, 0, 0, 0, 0, 0x4a, 0xc4, 0}; + +/* Abort scan */ +static unsigned char cmd_abort[] = {0xef, 0x20, 0, 0, 0, 0, 0, 0, 0, 0}; + +/* Upload the gamma table (followed by 32 byte write) */ +static unsigned char cmd_setgamma[] = {0xe6, 0x20, 0, 0, 0, 0, 0, 0, 0x20, 0}; + +#if 0 +/* Something about RGB gamma/gain values? Not currently used by this code */ +static unsigned char command_14[32] = +{ 0x2, 0x0, 0x3, 0x7f, + 0x2, 0x0, 0x3, 0x7f, + 0x2, 0x0, 0x3, 0x7f, + 0, 0, 0, 0, + 0x12, 0xd1, 0x14, 0x82, + 0, 0, 0, 0, + 0x0f, 0xff, + 0x0f, 0xff, + 0x0f, 0xff, 0, 0 }; +#endif + + +/* Misc functions =================================== */ + +/* + * safe_write(): a small wrapper which ensures all the data is written in calls + * to write(), since the POSIX call doesn't ensure it. + */ +static int safe_write(int fd, const char *p, unsigned long len) { + int diff; + unsigned long total = 0; + + do { + diff = write(fd, p+total, len-total); + if (diff < 0) + { + if (errno == EINTR) continue; + return -1; + } + total += diff; + } while (len > total); + + return 0; + +} + +/* same dealie for read, except in the case of read the return of 0 bytes with + * no INTR error indicates EOF */ +static int safe_read(int fd, char *p, unsigned long len) { + int diff; + unsigned long total = 0; + + do { + diff = read(fd, p+total, len-total); + if (diff <= 0) + { + if (errno == EINTR) continue; + if (diff == 0) return -2; + return -1; + + } + total += diff; + } while (len > total); + + return 0; + +} + + +/* Scan-related functions =================================== */ + +int sanei_canon_pp_init_scan(scanner_parameters *sp, scan_parameters *scanp) +{ + /* Command for: Initialise and begin the scan procedure */ + unsigned char command_b[56]; + + /* Buffer for buffer info block */ + unsigned char buffer_info_block[6]; + + /* The image size the scanner says we asked for + (based on the scanner's replies) */ + int true_scanline_size, true_scanline_count; + + /* The image size we expect to get (based on *scanp) */ + int expected_scanline_size, expected_scanline_count; + + /* Set up the default scan command packet */ + memcpy(command_b, cmd_initscan, 10); + memcpy(command_b+10, cmd_scan, 45); + + /* Load the proper settings into it */ + scanner_setup_params(command_b+10, sp, scanp); + + /* Add checksum byte */ + command_b[55] = check8(command_b+10, 45); + + if (send_command(sp->port, command_b, 56, 50000, 1000000)) + return -1; + + /* Ask the scanner about the buffer */ + if (send_command(sp->port, cmd_buf_status, 10, 50000, 1000000)) + return -1; + + /* Read buffer information block */ + sanei_canon_pp_read(sp->port, 6, buffer_info_block); + + if (check8(buffer_info_block, 6)) + DBG(1, "init_scan: ** Warning: Checksum error reading buffer " + "info block.\n"); + + expected_scanline_count = scanp->height; + + switch(scanp->mode) + { + case 0: /* greyscale; 10 bits per pixel */ + expected_scanline_size = scanp->width * 1.25; break; + case 1: /* true-colour; 30 bits per pixel */ + expected_scanline_size = scanp->width * 3.75; break; + default: + DBG(1, "init_scan: Illegal mode %i requested in " + "init_scan().\n", scanp->mode); + DBG(1, "This is a bug. Please report it.\n"); + return -1; + } + + /* The scanner's idea of the length of each scanline in bytes */ + true_scanline_size = (buffer_info_block[0]<<8) | buffer_info_block[1]; + /* The scanner's idea of the number of scanlines in total */ + true_scanline_count = (buffer_info_block[2]<<8) | buffer_info_block[3]; + + if ((expected_scanline_size != true_scanline_size) + || (expected_scanline_count != true_scanline_count)) + { + DBG(10, "init_scan: Warning: Scanner is producing an image " + "of unexpected size:\n"); + DBG(10, "expected: %i bytes wide, %i scanlines tall.\n", + expected_scanline_size, + expected_scanline_count); + DBG(10, "true: %i bytes wide, %i scanlines tall.\n", + true_scanline_size, true_scanline_count); + + if (scanp->mode == 0) + scanp->width = true_scanline_size / 1.25; + else + scanp->width = true_scanline_size / 3.75; + + scanp->height = true_scanline_count; + } + return 0; +} + + +/* Wake the scanner, detect it, and fill sp with stuff */ +int sanei_canon_pp_initialise(scanner_parameters *sp, int mode) +{ + unsigned char scanner_info[12]; + const struct scanner_id *cur_id; + const struct scanner_hardware_desc *hw; + + /* Hopefully take the scanner out of transparent mode */ + if (sanei_canon_pp_wake_scanner(sp->port, mode)) + { + DBG(10, "initialise: could not wake scanner\n"); + return 1; + } + + /* This block of code does something unknown but necessary */ + DBG(50, "initialise: >> scanner_init\n"); + if (sanei_canon_pp_scanner_init(sp->port)) + { + /* If we're using an unsupported ieee1284 mode here, this is + * where it will fail, so fall back to nibble. */ + sanei_canon_pp_set_ieee1284_mode(M1284_NIBBLE); + if (sanei_canon_pp_scanner_init(sp->port)) + { + DBG(10, "initialise: Could not init scanner.\n"); + return 1; + } + } + DBG(50, "initialise: << scanner_init\n"); + + /* Read Device ID */ + memset(sp->id_string, 0, sizeof sp->id_string); + if (send_command(sp->port, cmd_readid, 10, 10000, 100000)) + return -1; + sanei_canon_pp_read(sp->port, 38, (unsigned char *)(sp->id_string)); + + /* Read partially unknown data */ + if (send_command(sp->port, cmd_readinfo, 10, 10000, 100000)) + return -1; + sanei_canon_pp_read(sp->port, 12, scanner_info); + + if (check8(scanner_info, 12)) + { + DBG(10, "initialise: Checksum error reading Info Block.\n"); + return 2; + } + + sp->scanheadwidth = (scanner_info[2] << 8) | scanner_info[3]; + + /* Set up various known values */ + cur_id = scanner_id_table; + while (cur_id->id) + { + if (!strncmp(sp->id_string+8, cur_id->id, strlen(cur_id->id))) + break; + cur_id++; + } + + if (cur_id->id) + { + hw = cur_id->hw; + } + else if (sp->scanheadwidth == 5104) + { + /* Guess 600dpi scanner */ + hw = &hw_alien600; + } + else if (sp->scanheadwidth == 2552) + { + /* Guess 300dpi scanner */ + hw = &hw_alien300; + } + else + { + /* Guinea Pigs :) */ + hw = &hw_alien; + } + + strcpy(sp->name, hw->name); + sp->natural_xresolution = hw->natural_xresolution; + sp->natural_yresolution = hw->natural_yresolution; + sp->scanbedlength = hw->scanbedlength; + if (hw->scanheadwidth) + sp->scanheadwidth = hw->scanheadwidth; + sp->type = hw->type; + + return 0; +} + +/* Shut scanner down */ +int sanei_canon_pp_close_scanner(scanner_parameters *sp) +{ + /* Put scanner in transparent mode */ + sanei_canon_pp_sleep_scanner(sp->port); + + /* Free memory (with purchase of memory of equal or greater value) */ + if (sp->blackweight != NULL) + { + free(sp->blackweight); + sp->blackweight = NULL; + } + if (sp->redweight != NULL) + { + free(sp->redweight); + sp->redweight = NULL; + } + if (sp->greenweight != NULL) + { + free(sp->greenweight); + sp->greenweight = NULL; + } + if (sp->blueweight != NULL) + { + free(sp->blueweight); + sp->blueweight = NULL; + } + + return 0; +} + +/* Read the calibration information from file */ +int sanei_canon_pp_load_weights(const char *filename, scanner_parameters *sp) +{ + int fd; + int cal_data_size = sp->scanheadwidth * sizeof(unsigned long); + int cal_file_size; + + char buffer[10]; + int temp, ret; + + /* Open file */ + if ((fd = open(filename, O_RDONLY)) == -1) + return -1; + + /* Read header and check it's right */ + ret = safe_read(fd, buffer, strlen(header) + 1); + if ((ret < 0) || strcmp(buffer, header) != 0) + { + DBG(1,"Calibration file header is wrong, recalibrate please\n"); + close(fd); + return -2; + } + + /* Read and check file version (the calibrate file + format changes from time to time) */ + ret = safe_read(fd, (char *)&temp, sizeof(int)); + + if ((ret < 0) || (temp != fileversion)) + { + DBG(1,"Calibration file is wrong version, recalibrate please\n"); + close(fd); + return -3; + } + + /* Allocate memory for calibration values */ + if (((sp->blueweight = malloc(cal_data_size)) == NULL) + || ((sp->redweight = malloc(cal_data_size)) == NULL) + || ((sp->greenweight = malloc(cal_data_size)) == NULL) + || ((sp->blackweight = malloc(cal_data_size)) == NULL)) + return -4; + + /* Read width of calibration data */ + ret = safe_read(fd, (char *)&cal_file_size, sizeof(cal_file_size)); + + if ((ret < 0) || (cal_file_size != sp->scanheadwidth)) + { + DBG(1, "Calibration doesn't match scanner, recalibrate?\n"); + close(fd); + return -5; + } + + /* Read calibration data */ + if (safe_read(fd, (char *)(sp->blackweight), cal_data_size) < 0) + { + DBG(1, "Error reading black calibration data, recalibrate?\n"); + close(fd); + return -6; + } + + if (safe_read(fd, (char *)sp->redweight, cal_data_size) < 0) + { + DBG(1, "Error reading red calibration data, recalibrate?\n"); + close(fd); + return -7; + } + + if (safe_read(fd, (char *)sp->greenweight, cal_data_size) < 0) + { + DBG(1, "Error reading green calibration data, recalibrate?\n"); + close(fd); + return -8; + } + + if (safe_read(fd, (char *)sp->blueweight, cal_data_size) < 0) + { + DBG(1, "Error reading blue calibration data, recalibrate?\n"); + close(fd); + return -9; + } + + /* Read white-balance/gamma data */ + + if (safe_read(fd, (char *)&(sp->gamma), 32) < 0) + { + close(fd); + return -10; + } + + close(fd); + + return 0; +} + +/* Mode is 0 for greyscale source data or 1 for RGB */ +static void convert_to_rgb(image_segment *dest, unsigned char *src, + int width, int scanlines, int mode) +{ + int curline; + + const int colour_size = width * 1.25; + const int scanline_size = (mode == 0 ? colour_size : colour_size * 3); + + for (curline = 0; curline < scanlines; curline++) + { + + if (mode == 0) /* Grey */ + { + convdata(src + (curline * scanline_size), + dest->image_data + + (curline * width * 2), width, 1); + } + else if (mode == 1) /* Truecolour */ + { + /* Red */ + convdata(src + (curline * scanline_size), + dest->image_data + + (curline * width *3*2) + 4, width, 2); + /* Green */ + convdata(src + (curline * scanline_size) + colour_size, + dest->image_data + + (curline * width *3*2) + 2, width, 2); + /* Blue */ + convdata(src + (curline * scanline_size) + + (2 * colour_size), dest->image_data + + (curline * width *3*2), width, 2); + } + + } /* End of scanline loop */ + +} + +int sanei_canon_pp_read_segment(image_segment **dest, scanner_parameters *sp, + scan_parameters *scanp, int scanline_number, int do_adjust, + int scanlines_left) +{ + unsigned char *input_buffer = NULL; + image_segment *output_image = NULL; + + unsigned char packet_header[4]; + unsigned char packet_req_command[10]; + + int read_data_size; + int scanline_size; + + if (scanp->mode == 1) /* RGB */ + scanline_size = scanp->width * 3.75; + else /* Greyscale */ + scanline_size = scanp->width * 1.25; + + read_data_size = scanline_size * scanline_number; + + /* Allocate output_image struct */ + if ((output_image = malloc(sizeof(*output_image))) == NULL) + { + DBG(1, "read_segment: Error: Not enough memory for scanner " + "input buffer\n"); + goto error_out; + } + + /* Allocate memory for input buffer */ + if ((input_buffer = malloc(scanline_size * scanline_number)) == NULL) + { + DBG(1, "read_segment: Error: Not enough memory for scanner " + "input buffer\n"); + goto error_out; + } + + output_image->width = scanp->width; + output_image->height = scanline_number; + + /* Allocate memory for dest image segment */ + + output_image->image_data = + malloc(output_image->width * output_image->height * + (scanp->mode ? 3 : 1) * 2); + + if (output_image->image_data == NULL) + { + DBG(1, "read_segment: Error: Not enough memory for " + "image data\n"); + goto error_out; + } + + /* Set up packet request command */ + memcpy(packet_req_command, cmd_packet_req, 10); + packet_req_command[7] = ((read_data_size + 4) & 0xFF00) >> 8; + packet_req_command[8] = (read_data_size + 4) & 0xFF; + + /* Send packet req. and wait for the scanner's READY signal */ + if (send_command(sp->port, packet_req_command, 10, 9000, 2000000)) + { + DBG(1, "read_segment: Error: didn't get response within 2s " + "of sending request"); + goto error_out; + } + + /* Read packet header */ + if (sanei_canon_pp_read(sp->port, 4, packet_header)) + { + DBG(1, "read_segment: Error reading packet header\n"); + goto error_out; + } + + if ((packet_header[2]<<8) + packet_header[3] != read_data_size) + { + DBG(1, "read_segment: Error: Expected data size: %i bytes.\n", + read_data_size); + DBG(1, "read_segment: Expecting %i bytes times %i " + "scanlines.\n", scanline_size, scanline_number); + DBG(1, "read_segment: Actual data size: %i bytes.\n", + (packet_header[2] << 8) + packet_header[3]); + goto error_out; + } + + /* Read scanlines_this_packet scanlines into the input buf */ + + if (sanei_canon_pp_read(sp->port, read_data_size, input_buffer)) + { + DBG(1, "read_segment: Segment read incorrectly, and we don't " + "know how to recover.\n"); + goto error_out; + } + + /* This is the only place we can abort safely - + * between reading one segment and requesting the next one. */ + if (sp->abort_now) goto error_out; + + if (scanlines_left >= (scanline_number * 2)) + { + DBG(100, "read_segment: Speculatively starting more scanning " + "(%d left)\n", scanlines_left); + sanei_canon_pp_write(sp->port, 10, packet_req_command); + /* Don't read status, it's unlikely to be ready *just* yet */ + } + + DBG(100, "read_segment: Convert to RGB\n"); + /* Convert data */ + convert_to_rgb(output_image, input_buffer, scanp->width, + scanline_number, scanp->mode); + + /* Adjust pixel readings according to calibration data */ + if (do_adjust) { + DBG(100, "read_segment: Adjust output\n"); + adjust_output(output_image, scanp, sp); + } + + /* output */ + *dest = output_image; + /* finished with this now */ + free(input_buffer); + return 0; + + error_out: + if (output_image && output_image->image_data) + free(output_image->image_data); + if (output_image) free(output_image); + if (input_buffer) free(input_buffer); + sp->abort_now = 0; + return -1; +} + +/* +check8: Calculates the checksum-8 for s bytes pointed to by p. + +For messages from the scanner, this should normally end up returning +0, since the last byte of most packets is the value that makes the +total up to 0 (or 256 if you're left-handed). +Hence, usage: if (check8(buffer, size)) {DBG(10, "checksum error!\n");} + +Can also be used to generate valid checksums for sending to the scanner. +*/ +static int check8(unsigned char *p, int s) { + int total=0,i; + for(i=0;i<s;i++) + total-=(signed char)p[i]; + total &=0xFF; + return total; +} + +/* Converts from scanner format -> linear + width is in pixels, not bytes. */ +/* This function could use a rewrite */ +static void convdata(unsigned char *srcbuffer, unsigned char *dstbuffer, + int width, int mode) +/* This is a tricky (read: crap) function (read: hack) which is why I probably + spent more time commenting it than programming it. The thing to remember + here is that the scanner uses interpolated scanlines, so it's + RRRRRRRGGGGGGBBBBBB not RGBRGBRGBRGBRGB. So, the calling function just + increments the destination pointer slightly to handle green, then a bit + more for blue. If you don't understand, tough. */ +{ + int count; + int i, j, k; + + for (count = 0; count < width; count++) + { + /* The scanner stores data in a bizzare butchered 10-bit + format. I'll try to explain it in 100 words or less: + + Scanlines are made up of groups of 4 pixels. Each group of + 4 is stored inside 5 bytes. The first 4 bytes of the group + contain the lowest 8 bits of one pixel each (in the right + order). The 5th byte contains the most significant 2 bits + of each pixel in the same order. */ + + i = srcbuffer[count + (count >> 2)]; /* Low byte for pixel */ + j = srcbuffer[(((count / 4) + 1) * 5) - 1]; /* "5th" byte */ + j = j >> ((count % 4) * 2); /* Get upper 2 bits of intensity */ + j = j & 0x03; /* Can't hurt */ + /* And the final 10-bit pixel value is: */ + k = (j << 8) | i; + + /* now we return this as a 16 bit value */ + k = k << 6; + + if (mode == 1) /* Scanner -> Grey */ + { + dstbuffer[count * 2] = HIGH_BYTE(k); + dstbuffer[(count * 2) + 1] = LOW_BYTE(k); + } + else if (mode == 2) /* Scanner -> RGB */ + { + dstbuffer[count * 3 * 2] = HIGH_BYTE(k); + dstbuffer[(count * 3 * 2) + 1] = LOW_BYTE(k); + } + + } +} + +static int adjust_output(image_segment *image, scan_parameters *scanp, + scanner_parameters *scannerp) +/* Needing a good cleanup */ +{ + /* light and dark points for the CCD sensor in question + * (stored in file as 0-1024, scaled to 0-65536) */ + unsigned long hi, lo; + /* The result of our calculations */ + unsigned long result; + unsigned long temp; + /* The CCD sensor which read the current pixel - this is a tricky value + to get right. */ + int ccd, scaled_xoff; + /* Loop variables */ + unsigned int scanline, pixelnum, colour; + unsigned long int pixel_address; + unsigned int cols = scanp->mode ? 3 : 1; + + for (scanline = 0; scanline < image->height; scanline++) + { + for (pixelnum = 0; pixelnum < image->width; pixelnum++) + { + /* Figure out CCD sensor number */ + /* MAGIC FORMULA ALERT! */ + ccd = (pixelnum << (scannerp->natural_xresolution - + scanp->xresolution)) + (1 << + (scannerp->natural_xresolution + - scanp->xresolution)) - 1; + + scaled_xoff = scanp->xoffset << + (scannerp->natural_xresolution - + scanp->xresolution); + + ccd += scaled_xoff; + + for (colour = 0; colour < cols; colour++) + { + /* Address of pixel under scrutiny */ + pixel_address = + (scanline * image->width * cols * 2) + + (pixelnum * cols * 2) + (colour * 2); + + /* Dark value is easy + * Range of lo is 0-18k */ + lo = (scannerp->blackweight[ccd]) * 3; + + /* Light value depends on the colour, + * and is an average in greyscale mode. */ + if (scanp->mode == 1) /* RGB */ + { + switch (colour) + { + case 0: hi = scannerp->redweight[ccd] * 3; + break; + case 1: hi = scannerp->greenweight[ccd] * 3; + break; + default: hi = scannerp->blueweight[ccd] * 3; + break; + } + } + else /* Grey - scanned using green */ + { + hi = scannerp->greenweight[ccd] * 3; + } + + /* Check for bad calibration data as it + can cause a divide-by-0 error */ + if (hi <= lo) + { + DBG(1, "adjust_output: Bad cal data!" + " hi: %ld lo: %ld\n" + "Recalibrate, that " + "should fix it.\n", + hi, lo); + return -1; + } + + /* Start with the pixel value in result */ + result = MAKE_SHORT(*(image->image_data + + pixel_address), + *(image->image_data + + pixel_address + 1)); + + result = result >> 6; /* Range now = 0-1023 */ + /* + if (scanline == 10) + DBG(200, "adjust_output: Initial pixel" + " value: %ld\n", + result); + */ + result *= 54; /* Range now = 0-54k */ + + /* Clip to dark and light values */ + if (result < lo) result = lo; + if (result > hi) result = hi; + + /* result = (base-lo) * max_value / (hi-lo) */ + temp = result - lo; + temp *= 65536; + temp /= (hi - lo); + + /* Clip output result has been clipped to lo, + * and hi >= lo, so temp can't be < 0 */ + if (temp > 65535) + temp = 65535; + /* + if (scanline == 10) + { + DBG(200, "adjust_output: %d: base = " + "%lu, result %lu (%lu " + "- %lu)\n", pixelnum, + result, temp, lo, hi); + } + */ + result = temp; + + /* Store the value back where it came + * from (always bigendian) */ + *(image->image_data + pixel_address) + = HIGH_BYTE(result); + *(image->image_data + pixel_address+1) + = LOW_BYTE(result); + } + } + } + /*DBG(100, "Finished adjusting output\n");*/ + return 0; +} + +/* Calibration run. Aborting allowed at "safe" points where the scanner won't + * be left in a crap state. */ +int sanei_canon_pp_calibrate(scanner_parameters *sp, char *cal_file) +{ + int count, readnum, colournum, scanlinenum; + int outfile; + + int scanline_size; + + int scanline_count = 6; + /* Don't change this unless you also want to change do_adjust */ + const int calibration_reads = 3; + + unsigned char command_buffer[10]; + + image_segment image; + unsigned char *databuf; + + char colours[3][6] = {"Red", "Green", "Blue"}; + + /* Calibration data is monochromatic (greyscale format) */ + scanline_size = sp->scanheadwidth * 1.25; + + /* 620P has to be difficult here... */ + if (!(sp->type) ) scanline_count = 8; + + /* Probably shouldn't have to abort *just* yet, but may as well check */ + if (sp->abort_now) return -1; + + DBG(40, "Calibrating %ix%i pixels calibration image " + "(%i bytes each scan).\n", + sp->scanheadwidth, scanline_count, + scanline_size * scanline_count); + + /* Allocate memory for calibration data */ + sp->blackweight = (unsigned long *) + calloc(sizeof(unsigned long), sp->scanheadwidth); + sp->redweight = (unsigned long *) + calloc(sizeof(unsigned long), sp->scanheadwidth); + sp->greenweight = (unsigned long *) + calloc(sizeof(unsigned long), sp->scanheadwidth); + sp->blueweight = (unsigned long *) + calloc(sizeof(unsigned long), sp->scanheadwidth); + + /* The data buffer needs to hold a number of images (calibration_reads) + * per colour, each sp->scanheadwidth x scanline_count */ + databuf = malloc(scanline_size * scanline_count * calibration_reads*3); + + /* And allocate space for converted image data in this image_segment */ + image.image_data = malloc(scanline_count * sp->scanheadwidth * 2 * + calibration_reads); + image.width = sp->scanheadwidth; + image.height = scanline_count * calibration_reads; + + /* Sending the "dark calibration" command */ + memcpy(command_buffer, cmd_calblack, 10); + + /* Which includes the size of data we expect the scanner to return */ + command_buffer[7] = ((scanline_size * scanline_count) & 0xff00) >> 8; + command_buffer[8] = (scanline_size * scanline_count) & 0xff; + + DBG(40, "Step 1/3: Calibrating black level...\n"); + for (readnum = 0; readnum < calibration_reads; readnum++) + { + DBG(40, " * Black scan number %d/%d.\n", readnum + 1, + calibration_reads); + + if (sp->abort_now) return -1; + + if (send_command(sp->port, command_buffer, 10, 100000, 5000000)) + { + DBG(1, "Error reading black level!\n"); + free (image.image_data); + free(databuf); + return -1; + + } + + /* Black reference data */ + sanei_canon_pp_read(sp->port, scanline_size * scanline_count, + databuf + + (readnum * scanline_size * scanline_count)); + } + + /* Convert scanner format to a greyscale 16bpp image */ + for (scanlinenum = 0; + scanlinenum < scanline_count * calibration_reads; + scanlinenum++) + { + convdata(databuf + (scanlinenum * scanline_size), + image.image_data + + (scanlinenum * sp->scanheadwidth*2), + sp->scanheadwidth, 1); + } + + /* Take column totals */ + for (count = 0; count < sp->scanheadwidth; count++) + { + /* Value is normalised as if we took 6 scanlines, even if we + * didn't (620P I'm looking at you!) */ + sp->blackweight[count] = (column_sum(&image, count) * 6) + / scanline_count >> 6; + } + + /* 620P has to be difficult here... */ + if (!(sp->type) ) + { + scanline_count = 6; + image.height = scanline_count * calibration_reads; + } + + DBG(40, "Step 2/3: Gamma tables...\n"); + DBG(40, " * Requesting creation of new of gamma tables...\n"); + if (sp->abort_now) return -1; + if (send_command(sp->port, cmd_cleargamma, 10, 100000, 5000000)) + { + DBG(1,"Error sending gamma command!\n"); + free (image.image_data); + free(databuf); + return -1; + } + + DBG(20, " * Snoozing for 15 seconds while the scanner calibrates..."); + usleep(15000000); + DBG(40, "done.\n"); + + DBG(40, " * Requesting gamma table values..."); + if (send_command(sp->port, cmd_readgamma, 10, 100000, 10000000)) + { + DBG(1,"Error sending gamma table request!\n"); + free (image.image_data); + free(databuf); + return -1; + } + DBG(40, "done.\n"); + + DBG(40, " * Reading white-balance/gamma data... "); + sanei_canon_pp_read(sp->port, 32, sp->gamma); + DBG(40, "done.\n"); + + if (sp->abort_now) return -1; + + memcpy(command_buffer, cmd_calcolour, 10); + + /* Set up returned data size */ + command_buffer[7] = ((scanline_size * scanline_count) & 0xff00) >> 8; + command_buffer[8] = (scanline_size * scanline_count) & 0xff; + + DBG(40, "Step 3/3: Calibrating sensors...\n"); + /* Now for the RGB high-points */ + for (colournum = 1; colournum < 4; colournum++) + { + /* Set the colour we want to read */ + command_buffer[3] = colournum; + for (readnum = 0; readnum < 3; readnum++) + { + DBG(10, " * %s sensors, scan number %d/%d.\n", + colours[colournum-1], readnum + 1, + calibration_reads); + + if (sp->abort_now) return -1; + if (send_command(sp->port, command_buffer, 10, + 100000, 5000000)) + { + DBG(1,"Error sending scan request!"); + free (image.image_data); + free(databuf); + return -1; + } + + sanei_canon_pp_read(sp->port, scanline_size * + scanline_count, databuf + + (readnum * scanline_size * + scanline_count)); + + } + + /* Convert colour data from scanner format to RGB data */ + for (scanlinenum = 0; scanlinenum < scanline_count * + calibration_reads; scanlinenum++) + { + convdata(databuf + (scanlinenum * scanline_size), + image.image_data + + (scanlinenum * sp->scanheadwidth * 2), + sp->scanheadwidth, 1); + } + + /* Sum each column of the image and store the results in sp */ + for (count = 0; count < sp->scanheadwidth; count++) + { + if (colournum == 1) + sp->redweight[count] = + column_sum(&image, count) >> 6; + else if (colournum == 2) + sp->greenweight[count] = + column_sum(&image, count) >> 6; + else + sp->blueweight[count] = + column_sum(&image, count) >> 6; + } + + } + + if (sp->abort_now) return -1; + + /* cal_file == NUL indicates we want an in-memory scan only */ + if (cal_file != NULL) + { + DBG(40, "Writing calibration to %s\n", cal_file); + outfile = open(cal_file, O_WRONLY | O_TRUNC | O_CREAT, 0600); + if (outfile < 0) + { + DBG(10, "Error opening cal file for writing\n"); + } + + /* Header */ + if (safe_write(outfile, header, strlen(header) + 1) < 0) + DBG(10, "Write error on calibration file %s", cal_file); + if (safe_write(outfile, (const char *)&fileversion, sizeof(int)) < 0) + DBG(10, "Write error on calibration file %s", cal_file); + + /* Data */ + if (safe_write(outfile, (char *)&(sp->scanheadwidth), + sizeof(sp->scanheadwidth)) < 0) + DBG(10, "Write error on calibration file %s", cal_file); + if (safe_write(outfile, (char *)(sp->blackweight), + sp->scanheadwidth * sizeof(long)) < 0) + DBG(10, "Write error on calibration file %s", cal_file); + if (safe_write(outfile, (char *)(sp->redweight), + sp->scanheadwidth * sizeof(long)) < 0) + DBG(10, "Write error on calibration file %s", cal_file); + if (safe_write(outfile, (char *)(sp->greenweight), + sp->scanheadwidth * sizeof(long)) < 0) + DBG(10, "Write error on calibration file %s", cal_file); + if (safe_write(outfile, (char *)(sp->blueweight), + sp->scanheadwidth * sizeof(long)) < 0) + DBG(10, "Write error on calibration file %s", cal_file); + if (safe_write(outfile, (char *)(sp->gamma), 32) < 0) + DBG(10, "Write error on calibration file %s", cal_file); + + close(outfile); + } + + free(databuf); + free(image.image_data); + + return 0; +} + +static unsigned long column_sum(image_segment *image, int x) +/* This gives us a number from 0-n*65535 where n is the height of the image */ +{ + unsigned int row, p; + unsigned long total = 0; + + p = x; + for (row = 0; row < image->height; row++) + { + total+= MAKE_SHORT(image->image_data[2*p], + image->image_data[2*p+1]); + p += image->width; + } + return total; +} + + +static int scanner_setup_params(unsigned char *buf, scanner_parameters *sp, + scan_parameters *scanp) +{ + int scaled_width, scaled_height; + int scaled_xoff, scaled_yoff; + + /* Natural resolution (I think) */ + if (sp->scanheadwidth == 2552) + { + buf[0] = 0x11; /* 300 | 0x1000 */ + buf[1] = 0x2c; + buf[2] = 0x11; + buf[3] = 0x2c; + } else { + buf[0] = 0x12; /* 600 | 0x1000*/ + buf[1] = 0x58; + buf[2] = 0x12; + buf[3] = 0x58; + } + + scaled_width = scanp->width << + (sp->natural_xresolution - scanp->xresolution); + /* YO! This needs fixing if we ever use yresolution! */ + scaled_height = scanp->height << + (sp->natural_xresolution - scanp->xresolution); + scaled_xoff = scanp->xoffset << + (sp->natural_xresolution - scanp->xresolution); + scaled_yoff = scanp->yoffset << + (sp->natural_xresolution - scanp->xresolution); + + /* Input resolution */ + buf[4] = (((75 << scanp->xresolution) & 0xff00) >> 8) | 0x10; + buf[5] = (75 << scanp->xresolution) & 0xff; + /* Interpolated resolution */ + buf[6] = (((75 << scanp->xresolution) & 0xff00) >> 8) | 0x10;; + buf[7] = (75 << scanp->xresolution) & 0xff; + + /* X offset */ + buf[8] = (scaled_xoff & 0xff000000) >> 24; + buf[9] = (scaled_xoff & 0xff0000) >> 16; + buf[10] = (scaled_xoff & 0xff00) >> 8; + buf[11] = scaled_xoff & 0xff; + + /* Y offset */ + buf[12] = (scaled_yoff & 0xff000000) >> 24; + buf[13] = (scaled_yoff & 0xff0000) >> 16; + buf[14] = (scaled_yoff & 0xff00) >> 8; + buf[15] = scaled_yoff & 0xff; + + /* Width of image to be scanned */ + buf[16] = (scaled_width & 0xff000000) >> 24; + buf[17] = (scaled_width & 0xff0000) >> 16; + buf[18] = (scaled_width & 0xff00) >> 8; + buf[19] = scaled_width & 0xff; + + /* Height of image to be scanned */ + buf[20] = (scaled_height & 0xff000000) >> 24; + buf[21] = (scaled_height & 0xff0000) >> 16; + buf[22] = (scaled_height & 0xff00) >> 8; + buf[23] = scaled_height & 0xff; + + + /* These appear to be the only two colour mode possibilities. + Pure black-and-white mode probably just uses greyscale and + then gets its contrast adjusted by the driver. I forget. */ + if (scanp->mode == 1) /* Truecolour */ + buf[24] = 0x08; + else /* Greyscale */ + buf[24] = 0x04; + + return 0; +} + +int sanei_canon_pp_abort_scan(scanner_parameters *sp) +{ + /* The abort command (hopefully) */ + sanei_canon_pp_write(sp->port, 10, cmd_abort); + sanei_canon_pp_check_status(sp->port); + return 0; +} + +/* adjust_gamma: Upload a gamma profile to the scanner */ +int sanei_canon_pp_adjust_gamma(scanner_parameters *sp) +{ + sp->gamma[31] = check8(sp->gamma, 31); + if (sanei_canon_pp_write(sp->port, 10, cmd_setgamma)) + return -1; + if (sanei_canon_pp_write(sp->port, 32, sp->gamma)) + return -1; + + return 0; +} + +int sanei_canon_pp_sleep_scanner(struct parport *port) +{ + /* *SCANEND Command - puts scanner to sleep */ + sanei_canon_pp_write(port, 10, cmd_scanend); + sanei_canon_pp_check_status(port); + + ieee1284_terminate(port); + + return 0; + /* FIXME: I murdered Simon's code here */ + /* expect(port, "Enter Transparent Mode", 0x1f, 0x1f, 1000000); */ +} + +int sanei_canon_pp_detect(struct parport *port, int mode) +{ + /*int caps;*/ + /* This code needs to detect whether or not a scanner is present on + * the port, quickly and reliably. Fast version of + * sanei_canon_pp_initialise() + * + * If this detect returns true, a more comprehensive check will + * be conducted + * Return values: + * 0 = scanner present + * anything else = scanner not present + * PRE: port is open/unclaimed + * POST: port is closed/unclaimed + */ + + /* port is already open, just need to claim it */ + + if (ieee1284_claim(port) != E1284_OK) + { + DBG(0,"detect: Unable to claim port\n"); + return 2; + } + if (sanei_canon_pp_wake_scanner(port, mode)) + { + DBG(10, "detect: could not wake scanner\n"); + ieee1284_release(port); + return 3; + } + + /* Goodo, sleep (snaps fingers) */ + sanei_canon_pp_sleep_scanner(port); + + ieee1284_release(port); + /* ieee1284_close(port); */ + + return 0; +} + +static int send_command(struct parport *port, unsigned char *buf, int bufsize, + int delay, int timeout) +/* Sends a command until the scanner says it is ready. + * sleeps for delay microsecs between reads + * returns -1 on error, -2 on timeout */ +{ + int retries = 0; + + do + { + /* Send command */ + if (sanei_canon_pp_write(port, bufsize, buf)) + return -1; + + /* sleep a bit */ + usleep(delay); + } while (sanei_canon_pp_check_status(port) && + retries++ < (timeout/delay)); + + if (retries >= (timeout/delay)) return -2; + return 0; + +} |