/*
 *   Dithering routines for CUPS.
 *
 *   Copyright 2007 by Apple Inc.
 *   Copyright 1993-2005 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "COPYING"
 *   which should have been included with this file.
 *
 * Contents:
 *
 *   cupsDitherDelete() - Free a dithering buffer.
 *   cupsDitherLine()   - Dither a line of pixels...
 *   cupsDitherNew()    - Create a dithering buffer.
 */

/*
 * Include necessary headers.
 */

#include <config.h>
#include "driver.h"


/*
 * 'cupsDitherDelete()' - Free a dithering buffer.
 *
 * Returns 0 on success, -1 on failure.
 */

void
cupsDitherDelete(cups_dither_t *d)	/* I - Dithering buffer */
{
  if (d != NULL)
    free(d);
}


/*
 * 'cupsDitherLine()' - Dither a line of pixels...
 */

void
cupsDitherLine(cups_dither_t    *d,	/* I - Dither data */
               const cups_lut_t *lut,	/* I - Lookup table */
	       const short      *data,	/* I - Separation data */
	       int              num_channels,
					/* I - Number of components */
	       unsigned char    *p)	/* O - Pixels */
{
  register int	x,			/* Horizontal position in line... */
		pixel,			/* Current adjusted pixel... */
		e,			/* Current error */
		e0,e1,e2;		/* Error values */
  register int	errval0,		/* First half of error value */
  		errval1,		/* Second half of error value */
		errbase,		/* Base multiplier */
		errbase0,		/* Base multiplier for large values */
		errbase1,		/* Base multiplier for small values */
		errrange;		/* Range of random multiplier */
  register int	*p0,			/* Error buffer pointers... */
		*p1;
  static char	logtable[16384];	/* Error magnitude for randomness */
  static char	loginit = 0;		/* Has the table been initialized? */


  if (!loginit)
  {
   /*
    * Initialize a logarithmic table for the magnitude of randomness
    * that is introduced.
    */

    loginit = 1;

    logtable[0] = 0;
    for (x = 1; x < 2049; x ++)
      logtable[x] = (int)(log(x / 16.0) / log(2.0) + 1.0);
    for (; x < 16384; x ++)
      logtable[x] = logtable[2049];
  }

  if (d->row == 0)
  {
   /*
    * Dither from left to right:
    *
    *       e0   ==        p0[0]
    *    e1 e2   == p1[-1] p1[0]
    */

    p0 = d->errors + 2;
    p1 = d->errors + 2 + d->width + 4;
    e0 = p0[0];
    e1 = 0;
    e2 = 0;

   /*
    * Error diffuse each output pixel...
    */

    for (x = d->width;
	 x > 0;
	 x --, p0 ++, p1 ++, p ++, data += num_channels)
    {
     /*
      * Skip blank pixels...
      */

      if (*data == 0)
      {
        *p     = 0;
	e0     = p0[1];
	p1[-1] = e1;
	e1     = e2;
	e2     = 0;
	continue;
      }

     /*
      * Compute the net pixel brightness and brightness error.  Set a dot
      * if necessary...
      */

      pixel = lut[*data].intensity + e0 / 128;

      if (pixel > CUPS_MAX_LUT)
	pixel = CUPS_MAX_LUT;
      else if (pixel < 0)
	pixel = 0;

      *p = lut[pixel].pixel;
      e  = lut[pixel].error;

     /*
      * Set the randomness factor...
      */

      if (e > 0)
        errrange = logtable[e];
      else
        errrange = logtable[-e];

      errbase  = 8 - errrange;
      errrange = errrange * 2 + 1;

     /*
      * Randomize the error value.
      */

      if (errrange > 1)
      {
        errbase0 = errbase + (CUPS_RAND() % errrange);
        errbase1 = errbase + (CUPS_RAND() % errrange);
      }
      else
        errbase0 = errbase1 = errbase;

     /*
      *       X   7/16 =    X  e0
      * 3/16 5/16 1/16 =    e1 e2
      */

      errval0 = errbase0 * e;
      errval1 = (16 - errbase0) * e;
      e0      = p0[1] + 7 * errval0;
      e1      = e2 + 5 * errval1;

      errval0 = errbase1 * e;
      errval1 = (16 - errbase1) * e;
      e2      = errval0;
      p1[-1]  = e1 + 3 * errval1;
    }
  }
  else
  {
   /*
    * Dither from right to left:
    *
    *    e0      == p0[0]
    *    e2 e1   == p1[0] p1[1]
    */

    p0   = d->errors + d->width + 1 + d->width + 4;
    p1   = d->errors + d->width + 1;
    p    += d->width - 1;
    data += num_channels * (d->width - 1);
    e0   = p0[0];
    e1   = 0;
    e2   = 0;

   /*
    * Error diffuse each output pixel...
    */

    for (x = d->width;
	 x > 0;
	 x --, p0 --, p1 --, p --, data -= num_channels)
    {
     /*
      * Skip blank pixels...
      */

      if (*data == 0)
      {
        *p    = 0;
	e0    = p0[-1];
	p1[1] = e1;
	e1    = e2;
	e2    = 0;
	continue;
      }

     /*
      * Compute the net pixel brightness and brightness error.  Set a dot
      * if necessary...
      */

      pixel = lut[*data].intensity + e0 / 128;

      if (pixel > CUPS_MAX_LUT)
	pixel = CUPS_MAX_LUT;
      else if (pixel < 0)
	pixel = 0;

      *p = lut[pixel].pixel;
      e  = lut[pixel].error;

     /*
      * Set the randomness factor...
      */

      if (e > 0)
        errrange = logtable[e];
      else
        errrange = logtable[-e];

      errbase  = 8 - errrange;
      errrange = errrange * 2 + 1;

     /*
      * Randomize the error value.
      */

      if (errrange > 1)
      {
        errbase0 = errbase + (CUPS_RAND() % errrange);
        errbase1 = errbase + (CUPS_RAND() % errrange);
      }
      else
        errbase0 = errbase1 = errbase;

     /*
      *       X   7/16 =    X  e0
      * 3/16 5/16 1/16 =    e1 e2
      */

      errval0 = errbase0 * e;
      errval1 = (16 - errbase0) * e;
      e0      = p0[-1] + 7 * errval0;
      e1      = e2 + 5 * errval1;

      errval0 = errbase1 * e;
      errval1 = (16 - errbase1) * e;
      e2      = errval0;
      p1[1]   = e1 + 3 * errval1;
    }
  }

 /*
  * Update to the next row...
  */

  d->row = 1 - d->row;
}


/*
 * 'cupsDitherNew()' - Create an error-diffusion dithering buffer.
 */

cups_dither_t *			/* O - New state array */
cupsDitherNew(int width)	/* I - Width of output in pixels */
{
  cups_dither_t	*d;		/* New dithering buffer */


  if ((d = (cups_dither_t *)calloc(1, sizeof(cups_dither_t) +
                                   2 * (width + 4) *
				       sizeof(int))) == NULL)
    return (NULL);

  d->width = width;

  return (d);
}

