/*
	NAME
   	dither - routines to do Floyd-Steinberg dithering

   DESCRIPTION
      The assumption is that you have input values in the range
      minIn..maxIn that you want to reduce to the range
      minOut..maxOut.  (For color images, you would dither each color
      component separately.)

   	Dither a serpentine version of Floyd-Steinberg dithering.  The
      skeleton code to use these routines is:

      	DitherInit();
         onOddRow = FALSE;
         for (each row) {
         	Dither(row, onOddRow);
            onOddRow = !onOddRow;
         }
         DitherTerm();
*/

#include "jinclude.h"
#include "jmemsys.h"
#include "viewer.h"
#include "hicolor.h"
#include "dither.h"


#define DInflateBase 15

extern BYTE ColorLUT256[];
/*
	NAME
   	Dither - apply Floyd-Steinberg dithering to one row

   DESCRIPTION
   	Adapted from the JPEG source code.

      Note that it is acceptable to have pIn == pOut, i.e., to do the
      dithering in place.
*/

void 
Dither (
	 DCtrlType * pCtrl,	/* structure with control parameters */
     int *pIn,      /* input values */
     int *pOut,     /* output values */
	 int onOddRow		/* TRUE => current row is odd-numbered */
)
{
  int col;			/* column counter */
  int dir;			/* direction flag */
  int error;			/* value of current error */
  int *nextRowErr;		/* pointer to error buffer for next row */
  int *thisRowErr;		/* pointer to error buffer for this row */
  ulong scale = pCtrl->scale;
  ulong invscale = pCtrl->invscale;
  int val;			/* holds dithered value */
  int width = pCtrl->numCols;

  if (onOddRow)
    {				/* work right to left in this row */
      pIn += width - 1;
      pOut += width - 1;
      dir = -1;
      thisRowErr = pCtrl->oddRowErrs + 1;
      nextRowErr = pCtrl->evenRowErrs + width;
    }
  else
    {				/* work left to right in this row */
      dir = 1;
      thisRowErr = pCtrl->evenRowErrs + 1;
      nextRowErr = pCtrl->oddRowErrs + width;
    }
  nextRowErr[0] = 0;		/* need only initialize this one entry */
  for (col = width; col > 0; col--)
    {

      /* get current value and adjust for accumulated error */
      val = ((*pIn) << 4) + thisRowErr[0];    /* errors are in units of 1/16 */
      if (val < 0)
	val = 0;
      else
	{
	  val += 8;		/* for rounding */
      val >>= 4;
	  if (val > pCtrl->maxIn)
	    val = pCtrl->maxIn;
	}

      /* reduce input value to dithered value */
      *pOut = (int) (pCtrl->minOut +
		     ((scale * (val - pCtrl->minIn) + (1L << (DInflateBase - 1))) >> DInflateBase));

      /* calculate error (in units of 1/16 value) in dithered value */
      error = -(int) (pCtrl->minIn + (((*pOut - pCtrl->minOut) * invscale
		      + (1L << (DInflateBase - 1))) >> DInflateBase) - val);

      /* store the error */
      val = 2 * error;
      nextRowErr[-1] = error;	/* not +=, since not initialized yet */
      error += val;		/* form error * 3 */
      nextRowErr[1] += error;
      error += val;		/* form error * 5 */
      nextRowErr[0] += error;
      error += val;		/* form error * 7 */
      thisRowErr[1] += error;
      pIn += dir;
      pOut += dir;
      thisRowErr += 1;
      nextRowErr -= 1;
    }
}

void 
DitherChar (
	 DCtrlType * pCtrl,	/* structure with control parameters */
     JSAMPLE *pIn,      /* input values */
     JSAMPLE *pOut,     /* output values */
	 int onOddRow		/* TRUE => current row is odd-numbered */
)
{
  int col;			/* column counter */
  int dir;			/* direction flag */
  int error;			/* value of current error */
  int *nextRowErr;		/* pointer to error buffer for next row */
  int *thisRowErr;		/* pointer to error buffer for this row */
  ulong scale = pCtrl->scale;
  ulong invscale = pCtrl->invscale;
  int val;			/* holds dithered value */
  int width = pCtrl->numCols;

  if (onOddRow)
    {				/* work right to left in this row */
      pIn += width - 1;
      pOut += width - 1;
      dir = -1;
      thisRowErr = pCtrl->oddRowErrs + 1;
      nextRowErr = pCtrl->evenRowErrs + width;
    }
  else
    {				/* work left to right in this row */
      dir = 1;
      thisRowErr = pCtrl->evenRowErrs + 1;
      nextRowErr = pCtrl->oddRowErrs + width;
    }
  nextRowErr[0] = 0;		/* need only initialize this one entry */
  for (col = width; col > 0; col--)
    {

      /* get current value and adjust for accumulated error */
      val = ((ColorLUT256[GETJSAMPLE(*pIn)]) << 4) + thisRowErr[0];    /* errors are in units of 1/16 */
      if (val < 0)
	val = 0;
      else
	{
	  val += 8;		/* for rounding */
      val >>= 4;
	  if (val > pCtrl->maxIn)
	    val = pCtrl->maxIn;
	}

      /* reduce input value to dithered value */
      *pOut = (JSAMPLE) (pCtrl->minOut +
		     ((scale * (val - pCtrl->minIn) + (1L << (DInflateBase - 1))) >> DInflateBase));

      /* calculate error (in units of 1/16 value) in dithered value */
      error = -(int) (pCtrl->minIn + (((*pOut - pCtrl->minOut) * invscale
		      + (1L << (DInflateBase - 1))) >> DInflateBase) - val);

      /* store the error */
      val = 2 * error;
      nextRowErr[-1] = error;	/* not +=, since not initialized yet */
      error += val;		/* form error * 3 */
      nextRowErr[1] += error;
      error += val;		/* form error * 5 */
      nextRowErr[0] += error;
      error += val;		/* form error * 7 */
      thisRowErr[1] += error;
      pIn += dir;
      pOut += dir;
      thisRowErr += 1;
      nextRowErr -= 1;
    }
}

/*
	NAME
   	DitherInit - initialize dither buffers and control parameters

   RETURN VALUES
   	0		if all OK
      1		on error (probably insufficient memory)
*/

int 
DitherInit (
	     BYTE minIn,	/* minimum input value */
	     BYTE maxIn,	/* maximum input value */
	     BYTE minOut,	/* minimum output value */
	     BYTE maxOut,	/* maximum output value */
	     int numCols,	/* number of columns in each row */
	     DCtrlType * pCtrl	/* structure with control parameters */
)
{
  pCtrl->minIn = minIn;
  pCtrl->maxIn = maxIn;
  pCtrl->minOut = minOut;
  pCtrl->maxOut = maxOut;
  pCtrl->numCols = numCols;
  pCtrl->scale = ((maxOut - minOut) << DInflateBase) / (maxIn - minIn);
  pCtrl->invscale = ((maxIn - minIn) << DInflateBase) / (maxOut - minOut);
  pCtrl->oddRowErrs = (int *) jget_small ((numCols + 2) * sizeof (int));
  if (pCtrl->oddRowErrs == NULL)
    return 1;
  memset (pCtrl->oddRowErrs, 0, (numCols + 2) * sizeof (int));
  pCtrl->evenRowErrs = (int *) jget_small ((numCols + 2) * sizeof (int));
  if (pCtrl->evenRowErrs == NULL)
    return 1;
  memset (pCtrl->evenRowErrs, 0, (numCols + 2) * sizeof (int));
  return 0;
}

/*
	NAME
   	DitherTerm - clean up after dither operation

   DESCRIPTION
   	Frees up memory used for error buffers.
*/

void 
DitherTerm (
	     DCtrlType * pCtrl	/* structure with control parameters */
)
{
  jfree_small (pCtrl->oddRowErrs);
  jfree_small (pCtrl->evenRowErrs);
}
