/* pbmscan.c  ---  Simple Logitech scanning program that produces a
   pbm file as output.

    Copyright (C) 1994  A.Matthias <andy@titan.central.de>

    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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.

    This program was inspired by the gifscan program from A.Beck
    <becka@hp.rz.uni-duesseldorf.de> and contains some code from
    that program. The copyright message of gifscan.c follows:
    
    gifscan.c - ScanDemo for Logitech scanner-driver
    Copyright (c) 1994 Andreas Beck 
    (becka@hp.rz.uni-duesseldorf.de)
    Parts copyright (c) 1994 Thomas Faehnle 
    (Thomas.Faehnle@student.uni-ulm.de)

    Thanks to A.Beck for his permission to duplicate parts of his code
    in this program.
    Thanks to Karl Eichwalder for the ScanMan 256.

    Recognized options:
    -i         : inverse scan
    -d         : Duplicate each output line (for low-res fax)
    -g         : same as -g2
    -g2        : Convert to grayscale using a 2x2 matrix (PGM output)
    -g4        : Convert to grayscale using a 4x4 matrix (PGM output)
    -g6        : Convert to grayscale using a 6x6 matrix (PGM output)
    -w         : warning bell
*/

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/ioctl.h>
#include <scanner.h>

#define VERSION "1.2"
#define NDEBUG

static struct modeinfo_struct mi;
static struct scanner_type sct;
static struct scanner_capabilities scc;
static struct scanner_option sco[32];
static unsigned char *buf;
unsigned char line[2048], lastline[6][2048], grayline[2048*4];
int    graymatrix=1;
int    graylevels=1;
int fd, pic_ht_cm, pic_ht, ht, pic_ht_out, outbpl, outppl;

/* The understood options */
int o_inverse = 0;
int o_doublescan = 0;
int o_gray = 0;
int o_warn = 0;


void process_options( int *count, char p[20][256] )
{
  int i, j;

  for (i=1; i<*count; i++ )
  {
    if (p[i][0]=='-')    /* This is an option */
    {
      switch (p[i][1])
      {
      case 'i':
	o_inverse=1;
	break;

      case 'w':
	o_warn=1;
	break;

      case 'd':
	o_doublescan=1;
	break;

      case 'g':
	o_gray=1;
	switch (p[i][2])
	{
	case '6':        /* 6x6 matrix */
	  graymatrix=6;
	  graylevels=37;
	  break;
	case '4':        /* 4x4 matrix */
	  graymatrix=4;
	  graylevels=17;
	  break;
	default:         /* 2x2 matrix */
	  graymatrix=2;
	  graylevels=5;
	  break;
	}
	break;

      default:
	fprintf( stderr, "Unrecognized option: %s\n", p[i] );
	exit(1);
      }

      /* Remove this option */
      for ( j=i; j<(*count-1); j++ )
	strcpy( p[j], p[j+1] );
      i--;
      (*count)--;
    }

  }

}


void convert2x2( char *one, char *two, char *out )
{
  int i;
  unsigned char value, mask1, mask2;
  signed char bit;
  int outindex=0;

  for ( i=0; i<mi.bpl; i++ )
  {
    for ( bit=6; bit>=0; bit-=2 )
    {
      mask1=1<<bit;
      mask2=1<<(bit+1);
      value=((one[i]&mask1)/mask1)+((one[i]&mask2)/mask2)+
	((two[i]&mask1)/mask1)+((two[i]&mask2)/mask2);
      if (!o_inverse)
	value=4-value;
      out[outindex++]=value*63;
    }

  }

}


void convert4x4( char *one, char *two, char *three, char *four, char *out )
{
  int i;
  unsigned char value, mask1, mask2, mask3, mask4;
  signed char bit;
  int outindex=0;

  for ( i=0; i<mi.bpl; i++ )
  {
    for ( bit=4; bit>=0; bit-=4 )
    {
      mask1=1<<bit;
      mask2=1<<(bit+1);
      mask3=1<<(bit+2);
      mask4=1<<(bit+3);
      value=((one[i]&mask1)/mask1)+((one[i]&mask2)/mask2)+
	((one[i]&mask3)/mask3)+((one[i]&mask4)/mask4)+
	((two[i]&mask1)/mask1)+((two[i]&mask2)/mask2)+
	((two[i]&mask3)/mask3)+((two[i]&mask4)/mask4)+
	((three[i]&mask1)/mask1)+((three[i]&mask2)/mask2)+
	((three[i]&mask3)/mask3)+((three[i]&mask4)/mask4)+
	((four[i]&mask1)/mask1)+((four[i]&mask2)/mask2)+
	((four[i]&mask3)/mask3)+((four[i]&mask4)/mask4);

      if (!o_inverse)
	value=16-value;
      out[outindex++]=value*15;
    }

  }

}


void convert6x6( unsigned char line[6][2048], unsigned char *out )
{
  int i, v, bit, bits=0;
  unsigned char value=0;
  int outindex=0;

  for ( i=0; i<mi.bpl; i++ ) 
  {
    for ( bit=7; bit>=0; bit-- )
    {
      for ( v=0; v<6; v++ )
	value+=(line[v][i]&(1<<bit))/(1<<bit);
	
      bits++;

      if ( bits%6==0 )
      {
	if (!o_inverse)
	  value=36-value;
	out[outindex++]=value*7;
	value=0;
      }

    }
  }

}



void print_settings(void)
{ int n;
  static char *modes[]={"Unknown","Mono","Greyscale","True-Color"};
  fprintf( stderr, "Scanner type: Manufacturer : '%s'\n"  , sct.manufacturer);
  fprintf( stderr, "              Model        : '%s'\n\n", sct.model);

  fprintf( stderr, 
	  "Scanner set to %s-Mode, %dx%d dpi (%d bytes per scanline)\n", 
          modes[mi.sc_mode], mi.xdpi, mi.ydpi, mi.bpl);
  fprintf( stderr, "Colors: %d\nBorders: X: %d-%d       Y:%d-%d\n\n", 
          mi.depth+1, mi.left, mi.right, mi.top, mi.bottom);

  fprintf( stderr, "Brightness : %d (%d-%d) \n"
         "Contrast   : %d (%d-%d) \n"
         "Hue        : %d (%d-%d) \n"
         "Saturation : %d (%d-%d) \n", 
          mi.bright  , scc.bright  .min, scc.bright  .max,
          mi.contrast, scc.contrast.min, scc.contrast.max,
          mi.hue     , scc.hue     .min, scc.hue     .max,
          mi.sat     , scc.sat     .min, scc.sat     .max );

  fprintf( stderr, "Scanner has %d options,active-mask is 0x%x \n", 
          scc.av_options,mi.options);
  for(n=0;n<scc.av_options&&n<32;n++)
    fprintf( stderr, "Nr.%2d Flags:0x%x  Name: '%s' %s \n",
           n,sco[n].options,sco[n].name,(mi.options&(1<<n)) ? "on " : "off");
}


void setup_scanner(void)
{ long hlp;
  
  if ((fd = open("/dev/scan", O_RDONLY)) < 0) {
    perror("open");
    exit(1);
  }

  if ((ioctl(fd, HSIOCGSCT, &sct)) < 0) {
    perror("ioctl (GetType) ");
    close(fd);
    exit(2);
  }

  if ((ioctl(fd, HSIOCGSCC, &scc)) < 0) {
    perror("ioctl (GetCapabilities) ");
    close(fd);
    exit(2);
  }

  if ((ioctl(fd, HSIOCGMOD, &mi)) < 0) {
    perror("ioctl (GetMode) ");
    close(fd);
    exit(2);
  }

  if ((ioctl(fd, HSIOCGOPT, &sco)) < 0) {
    perror("ioctl (GetOptions) ");
    close(fd);
    exit(2);
  }

  if (mi.sc_mode!=SCM_MONO&&mi.sc_mode!=SCM_GREY) {
    fprintf( stderr,"Unsupported mode %d \n",mi.sc_mode);
    close(fd);
    exit(2);
  }

  if (mi.sc_mode==SCM_GREY) { 
    graylevels=255;  /* We will get 256 colours */
    graymatrix=8;    /* no dithering */
  }

  if ((buf=malloc(mi.bpl))==NULL) {
    fprintf( stderr,"Cannot malloc(%d) for line-buffer ...\n",mi.bpl);
    close(fd);
    exit(2);
  }

  hlp=0;
  if ((ioctl(fd, HSIOCSBUF, &hlp)) < 0) {
    perror("ioctl (SetBufLen) ");
    close(fd);
    exit(2);
  }

  hlp=100;
  if ((ioctl(fd, HSIOCSBUF, &hlp)) < 0) {
    perror("ioctl (SetBufLen) ");
    close(fd);
    exit(2);
  }

}


void getline( void )
{ int n,r;

  for (n = 0; n < sizeof(line); n++) line[n] = 0;

  if (mi.sc_mode==SCM_MONO)
  { 
    if ((r = read(fd, buf, mi.bpl)) < 0) 
    {
      perror("read");
      close(fd);
      exit(1);
    } 
    else 
      if (r > 0) 
	for (n=0; n<r; n++)
	  if (o_inverse && !o_gray)
	    line[n]=buf[n];
	  else
	    line[n]=~buf[n];
  } 
  else 
    if (mi.sc_mode==SCM_GREY)
    {      
      if ((r = read(fd, buf, mi.bpl)) < 0) 
      {
	perror("read");
	close(fd);
	exit(1);
      } 
      else 
	if (r > 0) 
	  if (!o_inverse)
	    memcpy( line, buf, r );
	  else
	    for (n=0; n<r; n++)
	      line[n]=graylevels-buf[n];
    }
    else return;
  
}


/* -------------------------------------------------------------- */

int main(int argc, char *argv[])
{
int i, arg_count, fdflags, r, fill;
char arg_param[20][256];

graymatrix=1;
graylevels=2;

fprintf( stderr, "PBMSCAN %s (C) 1994 by A.Matthias\n\n", VERSION );

setup_scanner();

arg_count=argc;
for (i=0; i<arg_count; i++)
  strcpy( arg_param[i], argv[i] );

/* The following REMOVES all options from arg_param !!! */
process_options( &arg_count, arg_param );

if ( arg_count!=2 )
{
  fprintf( stderr, "Usage: %s [-i] [-g] [-d] <height in cm>\n", argv[0] );
  exit(1);
}
else pic_ht_cm = atoi( arg_param[1] );

print_settings();

if ( (o_gray == 1) || (mi.sc_mode==SCM_GREY) )
  fprintf( stderr, "Writing PGM file (greyscale)\n" );
else fprintf( stderr, "Writing PBM file (black and white)\n" );

/* Compute picture height in scanlines */
pic_ht = pic_ht_cm * mi.ydpi * (1/2.54);

if (o_doublescan) pic_ht_out = pic_ht*2;
else pic_ht_out = pic_ht;

if (o_gray) pic_ht_out = pic_ht_out/graymatrix;

fprintf( stderr, "Picture height in cm=%d, scanlines=%d\n", 
	pic_ht_cm, pic_ht );

if (o_inverse)
     fprintf( stderr, "Output will be inverted\n");
if (o_doublescan)
     fprintf( stderr, "Output lines will be doubled for low-res fax\n");

switch (graymatrix)
{
 case 1:
  outbpl = mi.bpl;
  outppl = mi.bpl*8;
  break;
 case 2:
  outbpl = mi.bpl*4;
  outppl = mi.bpl*4;
  break;
 case 4:
  outbpl = mi.bpl*2;
  outppl = mi.bpl*2;
  break;
 case 6:
  outbpl = (int)(mi.bpl*8/6);
  outppl = (int)(mi.bpl*8/6);
  fprintf( stderr, "Adjusted line width to %d bytes\n", mi.bpl );
 case 8:
  outbpl = mi.bpl;
  outppl = mi.bpl;
  break;
}

/* Print the header of the pbm file to stdout */
if ( (!o_gray) && (mi.sc_mode!=SCM_GREY) )
{
  printf( "P4\n" );  /* pbm magic */
  /* width <space> height */
  printf( "%d %d\n", outppl, pic_ht_out ); 
}
else
{
  printf( "P5\n" );  /* pgm magic */
  /* width <space> height */
  printf( "%d %d\n", outppl, pic_ht_out );
  printf( "255\n" );   /* max gray value */
}

/* The main scanning loop */
for ( ht=0; ht<pic_ht; ht++ )
{
  getline();

  if ( o_warn )
  {
    /* Test filling of buffer */
    if ((ioctl(fd, HSIOCGSIB, &fill)) < 0) 
    {
      perror("ioctl (GSIB) ");
      close(fd);
      exit(2);
    }
    else
    {
      if ( fill > 65 )
	fprintf( stderr, "\a" );
      if ( fill > 80 )
	fprintf( stderr, "\a" );
    }
  }

  if ( (!o_gray) || (mi.sc_mode==SCM_GREY) )
  {
    for (i=0; i<outbpl; i++)
      printf( "%c", line[i] );

    if (o_doublescan)
      for (i=0; i<outbpl; i++)
	printf( "%c", line[i] );
  }
  else  /* Convert output to grayscale */
  {
    if (ht%graymatrix<graymatrix-1)  /* First lines of a matrix - store them */
    {
      for (i=0; i<mi.bpl; i++)
	lastline[ht%graymatrix][i]=line[i];
    }
    else          /* Last line of a matrix - convert and print */
    {
      switch( graymatrix )
      {
      case 2:
	convert2x2( lastline[0], line, grayline );
	break;
      case 4:
	convert4x4( lastline[0], lastline[1], lastline[2], line, grayline );
	break;
      case 6:
	for (i=0; i<mi.bpl; i++)
	  lastline[5][i]=line[i];	
	convert6x6( lastline, grayline );
	break;
      }

      for (i=0; i<outbpl; i++)
	printf( "%c", grayline[i] );

      if (o_doublescan)
	for (i=0; i<outbpl; i++)
	  printf( "%c", grayline[i] );
    }

  }
  
}

printf( "\n" );

/* Now read everything that happens to hang around in the buffer */
fdflags = fcntl( fd, F_GETFL );
fcntl( fd, F_SETFL, O_NONBLOCK );
while ( (r = read(fd, buf, mi.bpl)) != 0 )
     ;
fcntl( fd, F_SETFL, 0 );
  
close(fd);
free(buf);

return(0);
}
