/******************************************************************

Copyright (C) 1996 by Brian Scearce

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
and/or distribute copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:

1. The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

2. Redistribution for profit requires the express, written permission of
   the author.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL BRIAN SCEARCE BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
******************************************************************/

/** Fixdark
	Routine to repair dark current artifacts in qcam output.
	Basic idea: the Qcam CCD suffers from "dark current";
	that is, some of the CCD pixels will leak current under
	long exposures, even if they're in the dark, and this
	shows up as ugly speckling on images taken in low light.

	Fortunately, the leaky pixels are the same from shot to
	shot.  So, we can figure out which pixels are leaky by
	taking some establishing shots in the dark, and try to
	fix those pixels on subsequent shots.  The dark
	establishing shots need only be done once per camera.
*/

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include "qcam.h"
#define FNAME "qcam.darkfile"
#define TRUE 1
#define FALSE 0

static unsigned char master_darkmask[MAX_HEIGHT][MAX_WIDTH];

static
int
write_darkmask()
{
  char darkfile[BUFSIZ], *p;
  FILE *fp;

  strcpy(darkfile, CONFIG_FILE);
  if ( (p = strrchr(darkfile, '/'))) {
    strcpy(p+1, FNAME);
  } else {
    strcpy(darkfile, FNAME);
  }

  if (!(fp = fopen(darkfile, "w"))) {
#ifdef DEBUG
    fprintf(stderr, "Can't open darkfile %s\n", darkfile);
#endif
    return 0;
  }

  if (fwrite(master_darkmask, sizeof(unsigned char), MAX_WIDTH*MAX_HEIGHT, fp) !=
	MAX_WIDTH*MAX_HEIGHT) {
#ifdef DEBUG
    fprintf(stderr, "Error reading darkfile\n");
#endif
    return 0;
  }

  fclose(fp);
  return 1;
}

#define MAX_SHADES 64

static
void
mode_average(scanbuf *scan, int pixels[MAX_SHADES], int *mode, int *average)
{
  int i;
  int pixval;
  int modeindex = 32;
  int modeval = 0;
  int pixtotal = 0;

  for (i = 0; i < MAX_SHADES; i++) pixels[i] = 0;

  for (i = 0; i < MAX_WIDTH * MAX_HEIGHT; i++) {
    pixval = scan[i];
    assert(pixval < MAX_SHADES);
    pixels[pixval]++;
    pixtotal += pixval;
  }

  for (i = 0; i < MAX_SHADES; i++) {
    if (pixels[i] > modeval) {
      modeval = pixels[i];
      modeindex = i;
    }
  }

  *mode = modeindex;
  *average = pixtotal / (MAX_WIDTH * MAX_HEIGHT);
}


#define MAX_TRIES 10

int
main(int ac, char *av[])
{
  struct qcam *q;
  scanbuf *scan;
  int pixels[MAX_SHADES];
  int again, tries;
  int countdown;
  int toohigh;
  int pixelcount;
  int whitebal, brightness, contrast;
  int whitebal_low, whitebal_high;
  int contrast_low, contrast_high;
  int mode, average;
  int total;
  int x, y;
  int i;

  printf("Your QuickCam must be in the dark for this to work.\n");
  printf("Do not run this program with the QuickCam exposed to light!\n");
  printf("You have ten seconds to kill this program before it proceeds.\n");
  sleep(10);
  printf("Running...\n");

  for (y = 0; y < MAX_HEIGHT; y++)
    for (x = 0; x < MAX_WIDTH; x++)
      master_darkmask[y][x] = 255;

  if(geteuid()) {
    fprintf(stderr,"%s: Not installed SUID or run as root.  Exiting.\n",
	    av[0]);
    exit(1);
  }

  q=qc_init();
  qc_initfile(q, 0);

  qc_setbitdepth(q, 6);
  qc_settransfer_scale(q, 1);
  qc_settop(q, 1);
  qc_setleft(q, 2);
  qc_setresolution(q, MAX_WIDTH, MAX_HEIGHT);

  whitebal = qc_getwhitebal(q);
  contrast = qc_getcontrast(q);

  qc_setbrightness(q, 254);
  brightness = qc_getbrightness(q);
  if (brightness != 254) {
    fprintf(stderr, "Brightness not correctly set");
    exit(1);
  }
  
  if (qc_open(q)) {
    fprintf(stderr,"Cannot open QuickCam; exiting.\n");
    exit(1);
  }

  setuid(getuid());

  countdown = 15;
  for (brightness = 254; countdown-- > 0 && brightness > 0; brightness--) {
    qc_setbrightness(q, brightness);
    qc_set(q);
    scan = qc_scan(q);
 
    mode_average(scan, pixels, &mode, &average);

    /* Part One: adjust white balance so mode is middle of range */

    whitebal_low = 0; whitebal_high = 255; tries = 0;

    do {
#ifdef DEBUG
      fprintf(stderr, "Whitebal try %d, whitebal = %d, mode = %d\n",
	tries, whitebal, mode);
#endif
      again = FALSE;

      if (mode < MAX_SHADES/2 - 2) { /* too low */
        whitebal_low = whitebal;
        whitebal = (whitebal_low + whitebal_high) / 2;
        qc_setwhitebal(q, whitebal);
        again = 1;
      }

      if (mode > MAX_SHADES/2 + 2) { /* too high */
        assert(!again); /* can't be too bright AND too dark */
        whitebal_high = whitebal;
        whitebal = (whitebal_low + whitebal_high) / 2;
        qc_setwhitebal(q, whitebal);
        again = 1;
      }

      if (again) {
	free(scan);
	qc_set(q);
	scan = qc_scan(q);
	mode_average(scan, pixels, &mode, &average);
      }
    } while (tries++ < MAX_TRIES && again && whitebal_low < whitebal_high-1);

    /* Part 2: set contrast */

    contrast_low = 0; contrast_high = 255; tries = 0;

    do {
      again = FALSE;

      for (total = 0, i = mode - MAX_SHADES/16; i < mode + MAX_SHADES/16; i++)
	total += pixels[i];

#ifdef DEBUG
      fprintf(stderr, "Contrast try %d, contrast = %d, midrange = %d%%\n",
	tries, contrast, 100 * total/(MAX_WIDTH * MAX_HEIGHT) );
#endif

      if (total > 7 * MAX_WIDTH * MAX_HEIGHT / 8) { /* too high */
        contrast_low = contrast;
        contrast = (contrast_low + contrast_high) / 2;
        qc_setcontrast(q, contrast);
        again = 1;
      }

      if (total < 3 * MAX_WIDTH * MAX_HEIGHT / 4) { /* too low */
        assert(!again); /* can't be too wide AND too narrow */
        contrast_high = contrast;
        contrast = (contrast_low + contrast_high) / 2;
        qc_setcontrast(q, contrast);
        again = 1;
      }

      if (again) {
	free(scan);
	qc_set(q);
	scan = qc_scan(q);
	mode_average(scan, pixels, &mode, &average);
      }
    } while (tries++ < MAX_TRIES && again && contrast_low < contrast_high-1);

    again = FALSE;
    toohigh = mode + MAX_SHADES/4;

    pixelcount = 0;
    for (y = 0; y < MAX_HEIGHT; y++) {
      for (x = 0; x < MAX_WIDTH; x++) {
	if (scan[y*MAX_WIDTH+x] > toohigh) {
	  master_darkmask[y][x] = brightness;
	  pixelcount++;
	  again = TRUE;
	}
      }
    }

#ifdef DEBUG
#define CHARTHEIGHT 15
    for (i = CHARTHEIGHT; i > 0; i--) {
      int j;
      for (j = 0; j < MAX_SHADES; j++) {
	if (CHARTHEIGHT * pixels[j] >= i*pixels[mode]) {
	  if (j == toohigh) fprintf(stderr, "+");
	  else fprintf(stderr, ".");
        } else if (j == toohigh) fprintf(stderr, "|");
	else fprintf(stderr, " ");
      }
      fprintf(stderr, "\n");
    }
    for (i = 0; i < MAX_SHADES; i++) fprintf(stderr, "%01d", i / 10);
    fprintf(stderr, "\n");
    for (i = 0; i < MAX_SHADES; i++) fprintf(stderr, "%01d", i % 10);
    fflush(stderr);
#endif
    printf("Brightness %3d gives whitebal %3d, contrast %3d, %5d bad pixel%s\n",
	brightness, whitebal, contrast, pixelcount, (pixelcount==1)?"":"s");

    free(scan);
    if (again) countdown = 5;
  }

  qc_close(q);
  write_darkmask();

  return 0;
}

