/*
 *  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)
 *
 */

/*
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  gifscan.c,v 0.0.2 1994/06/13 23:27:12
 */
                         
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <strings.h>
#include <vga.h>
#include <scanner.h>
#include "gifs.h"

#define MIN(a, b)	((a)<(b)?(a):(b))

#define MAXMATRIX 16

void usage(void)
{ fprintf(stderr,"Usage : scandemo [-g x] [save-file-name]\n");
  fprintf(stderr,"        -g x set greyscale-matrix size (1-16)\n");
  exit(1);
}

static struct modeinfo_struct mi;
static struct scanner_type sct;
static struct scanner_capabilities scc;
static struct scanner_option sco[32];
static int    greymatrix=1;
static int    greylevels=1;
static int    invert=0;
static int    speedcol=1;
static int    sl,ht,fd;
static dregs  gifpal;
static unsigned char line[2048];
static unsigned char *buf;
static char *modes[]={"Unknown","Mono","Greyscale","True-Color"};

void setpal(int r,int g,int b)
{ int n;
  for (n = 0; n < 256 && n <= greylevels ;n++)
    vga_setpalette(n, n*r/greylevels, n*g/greylevels, n*b/greylevels);
}

void setgifpal(int r,int g,int b)
{ int n;
  for (n = 0; n < 256 && n <= greylevels ;n++)
  { gifpal[n].r=n*r/greylevels;
    gifpal[n].g=n*g/greylevels;
    gifpal[n].b=n*b/greylevels; }
}

void getline(int blue)
{ int n,n1,n2,r,hlp;
  static unsigned long fill;

  for (n = 0; n < sizeof(line); n++) line[n] = 0;
  if (mi.sc_mode==SCM_MONO)
  { for (n2=0;n2<greymatrix;n2++)
    { if ((r = read(fd, buf, mi.bpl)) < 0) {
        vga_setmode(TEXT);
        perror("read");
        close(fd);
        exit(1);
      } 
      else if (r > 0) {
      for (n=n1=0,hlp=0;n<mi.bpl;n++)  {
	int mask = 0x80;	/* mask out 1 byte, maske is moving */
        for(r=0;r<8;r++) 
        { if (hlp++==greymatrix) {++n1;hlp=1;}
	  if (buf[n] & mask) if (line[n1]++==255) line[n1]--;
	  mask >>= 1;
        } }
      }
    }
  } else if (mi.sc_mode==SCM_GREY)
  { if ((r = read(fd, buf, mi.bpl)) < 0) {
      vga_setmode(TEXT);
      perror("read");
      close(fd);
      exit(1);
    } 
    else if (r > 0) {
    for (n=0;n<mi.bpl&&n<sizeof(line);n++) line[n]=buf[n];
    }
  }
  else return;
  
  for(n=0;n<mi.bpl*8/greymatrix;n++)
  { if (invert) line[n]=greylevels-line[n]; }
  if (speedcol)
  { if ((ioctl(fd, HSIOCGSIB, &fill)) < 0) {
      vga_setmode(TEXT);
      perror("ioctl (GSIB)");
      close(fd);
      exit(2);
    }
    if (fill>63) fill=63;
    setpal(fill,63-fill,blue);
  }
  vga_drawscanline(sl++, line);
  if (sl>=ht) sl=0;
}

static char *savname="scan.gif";

void save_pbm(void) {
  FILE *handle;
  int lines = 0;
  int i=0;

  setpal(0,63,0);/* Green for save-mode */

  if ((handle=fopen(savname,"w+b"))==NULL) return;
  fprintf(handle, "P4\n%05d %05d\n", 0, 0);
  do {
    getline(32);
    lines++;
    for (i = mi.bpl; i--; )
	buf[i] ^= 0xff;
    fwrite(buf, 1, mi.bpl, handle);
  } while (!vga_getkey());
  fseek(handle,0,SEEK_SET);
  fprintf(handle, "P4\n%05d %05d\n", mi.bpl*8/greylevels, lines);
  fclose(handle);
  return;
}

static int lcount,bcount,bmax;

int getpix(void)
{ if (bcount==bmax) if (vga_getkey()) return -1;else bcount=0;
  if (bcount==0) {getline(0);lcount++;}
  return(line[bcount++]);
}

void save(void)
{ int handle,bpp;
  handle=greylevels;bpp=0;while(handle) {bpp++;handle>>=1;}
  lcount=bcount=0;bmax=mi.bpl*8/greymatrix;
  setpal(0,63,0);/* Green for save-mode */
  
  if ((handle=open(savname,O_CREAT|O_WRONLY|O_TRUNC,-1))==-1) return;
  if (GIF_saveghead(handle,bmax,lcount,bpp,&gifpal)!=0) {close(handle);return;}
  if (GIF_savelhead(handle,bmax,lcount,bpp,NULL)!=0) {close(handle);return;}
  if (GIF_saveimg  (handle,bpp,getpix)!=0) {close(handle);return;}
  if (GIF_saveend  (handle)!=0) {close(handle);return;}
  lseek(handle,0,SEEK_SET);
  if (GIF_saveghead(handle,bmax,lcount,bpp,&gifpal)!=0) {close(handle);return;}
  if (GIF_savelhead(handle,bmax,lcount,bpp,NULL)!=0) {close(handle);return;}
  close(handle);return;
}

void setmode(void)
{ mi.left=mi.right=mi.top=mi.bottom=0;
  if ((ioctl(fd, HSIOCSMOD, &mi)) < 0) {
    vga_setmode(TEXT);
    perror("ioctl (SetMode) ");
    close(fd);
    exit(2);
  }

  if (mi.sc_mode==SCM_GREY) { 
    greylevels=255;  /* We will get 256 colours */
    greymatrix=8;    /* and 8 Bits per Pixel */
  } else {
    greylevels=1;    /* We will get 2 colours */
    greymatrix=1;    /* and 1 Bit per Pixel */
  }  

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

void beep(void)
{ fprintf(stderr,"\a"); }

void parse_cmdline(int argc,char *argv[])
{ int n;
  for(n=1;n<argc;n++)
  { if (*argv[n]!='-') savname=argv[n];
    else switch(argv[n][1])
    { case 'g':n++;if (n<argc) greymatrix=atoi(argv[n]);
    	       if (greymatrix>16||greymatrix<1) usage();
    	       greylevels=greymatrix*greymatrix;
    	       if (greylevels>255) greylevels=255;
    	       fprintf(stderr,"Warning : -g option will go away in future releases.");
    	       break;
      default :usage();break;
    }
  } 
}

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

  printf("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 );

  printf("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++)
    printf("Nr.%2d Flags:0x%x  Name: '%s' %s \n",
           n,sco[n].options,sco[n].name,(mi.options&(1<<n)) ? "on " : "off");
}

void print_capabilities(void)
{
  printf("Supported modes: ");
  if(scc.o_scm_mono&&SCC_CANDO)
    printf("'%s' ", modes[1]);
  if(scc.o_scm_grey&&SCC_CANDO)
    printf("'%s' ", modes[2]);
  if(scc.o_scm_true&&SCC_CANDO)
    printf("'%s'", modes[3]);
  printf("\n\n");
}

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

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

  printf("Scanner type: Manufacturer : '%s'\n"  , sct.manufacturer);
  printf("              Model        : '%s'\n\n", sct.model);

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

  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) {
    printf("gifscan didn't support mode '%s' \n",modes[mi.sc_mode]);
    mode = (scc.o_scm_grey) ? SCM_GREY : SCM_MONO;
    printf("Trying to switch to '%s'.\n\n", modes[mode]);
    mi.sc_mode=mode;
    if (ioctl(fd, HSIOCSMOD, &mi) < 0) {
      perror("ioctl (SetMode) ");
      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,"Could not switch to '%s'. Stop.\n", modes[mode]);
      close(fd);
      exit(3);
    }
  }

  if (mi.sc_mode==SCM_GREY) { 
    greylevels=255;  /* We will get 256 colours */
    greymatrix=8;    /* and 8 Bits per Pixel */
  }

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

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

void setup_vga(void)
{ int n;

  vga_init();

  if ((n = vga_getdefaultmode()) < 0) {
    fprintf(stderr, "GSVGAMODE environment variable contains no valid mode." \
	    "Please change this.\n");
    close(fd);
    exit(3);
  }
  vga_setmode(n);

  if (vga_getcolors() != 256) {
    fprintf(stderr, "Please select a 256 color mode\n");
    vga_setmode(TEXT);
    close(fd);
    exit(4);
  }
  /*wd = vga_getxdim(); not needed */
  ht = vga_getydim();
  setpal(63,63,63);
  setgifpal(255,255,255);
}

int main(int argc,char *argv[])
{
  int chgopt;
  int forcemode=SCM_UNKNOWN; /* Auto-Detect as default */

  parse_cmdline(argc,argv);

  setup_scanner();
  
  print_settings();

  setup_vga();

  while (1)
  { chgopt=0;
    switch(vga_getkey())
    { case 'q':				/* Quit */
      case 'Q':
      case 'x':				/* eXit */
      case 'X':
      case  27:goto breakout;		/* Esc  */
      case 'i':
      case 'I':invert^=1;break;		/* Invert */
      case 'w':
      case 'W':speedcol^=1;break;	/* Speed-Warning */
      case 'p':
      case 'P':save_pbm();			
      	       setpal(63,63,63);break;	/* File : Save to PBM */
      case 'f':
      case 'F':save();			
      	       setpal(63,63,63);break;	/* File : Save to Gif */

      case '0':mi.options^=  1;chgopt=1;break;	/* (De-) activate scanner- */
      case '1':mi.options^=  2;chgopt=1;break;	/* specific options        */
      case '2':mi.options^=  4;chgopt=1;break;
      case '3':mi.options^=  8;chgopt=1;break;
      case '4':mi.options^= 16;chgopt=1;break;
      case '5':mi.options^= 32;chgopt=1;break;
      case '6':mi.options^= 64;chgopt=1;break;
      case '7':mi.options^=128;chgopt=1;break;
      case '8':mi.options^=256;chgopt=1;break;
      case '9':mi.options^=512;chgopt=1;break;	/* Hope we never need >9   */
      
      case 'b':if (mi.bright<scc.bright.max) 	/* Increase Brightness     */
                 { chgopt=1;mi.bright++;}
               else beep();
               break;
      case 'B':if (mi.bright>scc.bright.min) 	/* Decrease Brightness     */
                 { chgopt=1;mi.bright--;}
               else beep();
               break;
      case 'c':if (mi.contrast<scc.contrast.max)/* Increase Contrast       */
                 { chgopt=1;mi.contrast++;}
               else beep();
               break;
      case 'C':if (mi.contrast>scc.contrast.min)/* Decrease Contrast       */
                 { chgopt=1;mi.contrast--;}
               else beep();
               break;    
      case 'h':if (mi.hue<scc.hue.max) 		/* Increase Hue-Adjustment */
                 { chgopt=1;mi.hue++;}
               else beep();
               break;
      case 'H':if (mi.hue>scc.hue.min) 		/* Decrease Hue-Adjustment */
                 { chgopt=1;mi.hue--;}
               else beep();
               break;    
      case 's':if (mi.sat<scc.sat.max) 		/* Increase Saturation     */
                 { chgopt=1;mi.sat++;}
               else beep();
               break;
      case 'S':if (mi.sat>scc.sat.min)		/* Decrease Saturation     */
                 { chgopt=1;mi.sat--;}
               else beep();
               break;
      case 'm':forcemode=SCM_MONO;chgopt=1;break;	/* Force MONO-Mode  */
      case 'g':forcemode=SCM_GREY;chgopt=1;break;	/* Force GREY-Mode  */
      case 'a':forcemode=SCM_UNKNOWN;chgopt=1;break;	/* Force Autodetect */
    }
    if (chgopt)
    { int opt; /* Beep,if option was not accepted - will work only for
                  options ...*/
      mi.sc_mode=forcemode;
      opt=mi.options;setmode();if (opt!=mi.options) beep();
      opt=mi.options;}
    getline(63);
  }
  breakout:
  free(buf);
  vga_setmode(TEXT);
  close(fd);
  exit(0);
}
