/******************************************************************************
  File:     $Id: eprnfs.c,v 1.1 2002/07/12 16:11:41 tillkamppeter Exp $
  Contents: Floyd-Steinberg error diffusion for eprn
  Author:   Martin Lottermoser, Greifswaldstrasse 28, 38124 Braunschweig,
            Germany; e-mail: Martin.Lottermoser@t-online.de.

*******************************************************************************
*									      *
*	Copyright (C) 2001 by Martin Lottermoser			      *
*	All rights reserved						      *
*									      *
*******************************************************************************

  Information about Floyd-Steinberg error diffusion should be available in a
  number of places. I've used:

    James D. Foley, Andries van Dam, Steven K. Feiner, John F. Hughes
    "Computer Graphics"
    Second edition in C
    Reading/Massachusetts, etc.: Addison-Wesley, 1996
    ISBN 0-201-84840-6

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

/* Configuration management identification */
#ifndef lint
static const char
  cm_id[] = "@(#)$Id: eprnfs.c,v 1.1 2002/07/12 16:11:41 tillkamppeter Exp $";
#endif

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

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE	500
#endif

#include "gdeveprn.h"

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

/* Here follow some macros for code sections used in several routines. */

#define fit_to_octet(value)	((value) < 0? 0: (value) > 255? 255: (value))

#define FS_assign()				\
	new_value = *to + correction;		\
	if (new_value < 0) {			\
	  *to = 0;				\
	  remaining_error += new_value;		\
	}					\
	else if (255 < new_value) {		\
	  *to = 255;				\
	  remaining_error += new_value - 255;	\
	}					\
	else *to = new_value;

#define error_propagation_Gray()				\
  if (error != 0) {						\
    remaining_error = error;					\
								\
    /* 7/16 of the error goes to the right */			\
    correction = (7*error)/16;					\
    remaining_error -= correction;				\
    if (pixel < max_pixel) {					\
      to = from + 1;						\
      FS_assign()						\
      if (pixel == pixels - 1 && *to > 0) {			\
	pixels++;						\
	line->length++;						\
      }								\
    }								\
								\
    /* 3/16 of the error goes to the left and below */		\
    correction = (3*error)/16;					\
    remaining_error -= correction;				\
    if (pixel > 0) {						\
      to = next_line->str + (pixel - 1);			\
      FS_assign()						\
      if (next_line->length < pixel && *to > 0) next_line->length = pixel; \
    }								\
								\
    /* 5/16 of the error goes below */				\
    correction = (5*error)/16;					\
    remaining_error -= correction;				\
    to = next_line->str + pixel;				\
    FS_assign()							\
    if (next_line->length <= pixel && *to > 0) next_line->length = pixel + 1; \
								\
    /* The remainder (about 1/16 of the error) is added to the right and */ \
    /* below. */						\
    if (pixel < max_pixel) {					\
      to = next_line->str + (pixel+1);				\
      new_value = *to + remaining_error;			\
      *to = fit_to_octet(new_value);				\
      if (next_line->length < pixel + 2 && *to > 0)		\
	next_line->length = pixel + 2;				\
    }								\
  }

/* Number of octets per pixel for the non-monochrome cases */
#define OCTETS_PER_PIXEL	4

#define error_propagation_colour()				\
  if (error != 0) {						\
    remaining_error = error;					\
								\
    /* 7/16 of the error goes to the right */			\
    correction = (7*error)/16;					\
    remaining_error -= correction;				\
    if (pixel < max_pixel) {					\
      to = from + OCTETS_PER_PIXEL;				\
      FS_assign()						\
      if (pixel == pixels - 1 && *to > 0) {			\
	pixels++;						\
	line->length += OCTETS_PER_PIXEL;			\
      }								\
    }								\
								\
    /* 3/16 of the error goes to the left and below */		\
    correction = (3*error)/16;					\
    remaining_error -= correction;				\
    if (pixel > 0) {						\
      to = next_line->str + (pixel - 1)*OCTETS_PER_PIXEL + colorant; \
      FS_assign()						\
      if (next_line->length < pixel*OCTETS_PER_PIXEL && *to > 0) \
	next_line->length = pixel*OCTETS_PER_PIXEL;		\
    }								\
								\
    /* 5/16 of the error goes below */				\
    correction = (5*error)/16;					\
    remaining_error -= correction;				\
    to = next_line->str + pixel*OCTETS_PER_PIXEL + colorant;	\
    FS_assign()							\
    if (next_line->length <= pixel*OCTETS_PER_PIXEL && *to > 0)	\
      next_line->length = (pixel + 1)*OCTETS_PER_PIXEL;		\
								\
    /* The remainder (about 1/16 of the error) is added to the right and */ \
    /* below. */						\
    if (pixel < max_pixel) {					\
      to = next_line->str + (pixel+1)*OCTETS_PER_PIXEL + colorant; \
      new_value = *to + remaining_error;			\
      *to = fit_to_octet(new_value);				\
      if (next_line->length < (pixel + 2)*OCTETS_PER_PIXEL && *to > 0) \
	next_line->length = (pixel + 2)*OCTETS_PER_PIXEL;	\
    }								\
  }

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

  Function: split_Gray_2

  Floyd-Steinberg error diffusion for the process colour model Gray and
  2 intensity levels.

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

static void split_Gray_2(eprn_OctetString *line, eprn_OctetString *next_line,
  int max_octets, eprn_OctetString bitplanes[])
{
  const int
    max_pixel = max_octets - 1;
  int
    correction,
    error,
    new_value,
    pixel,
    pixel_mod_8,
    pixels = line->length,
    remaining_error;
  eprn_Octet
    approx,
    *from,
    *ptr,
    *to;

  ptr = bitplanes[0].str;

  /* Loop over pixels in the scan line. Note that 'pixels' may increase
     within the loop. */
  for (pixel = 0, pixel_mod_8 = 8; pixel < pixels; pixel++, pixel_mod_8++) {
    if (pixel_mod_8 == 8) {
      pixel_mod_8 = 0;
      *ptr = 0;
    }

    /* Determine approximation and error for this pixel */
    from = line->str + pixel;
    approx = *from >> 7;	/* take the most significant bit */
    error = *from - 255*approx;
     /* The sign of 'error' is chosen such that 'error' is positive if
	colorant intensity has to be added to the picture. */

    /* Insert the approximation into the output plane */
    *ptr = (*ptr << 1) | approx;

    error_propagation_Gray()

    if (pixel_mod_8 == 7) ptr++;
  }

  eprn_finalize(false, 0, 1, bitplanes, &ptr, pixels);

  return;
}

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

  Function: split_Gray()

  Floyd-Steinberg error diffusion for the process colour model Gray and an
  arbitrary number of intensity levels.

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

static void split_Gray(eprn_OctetString *line, eprn_OctetString *next_line,
  int max_octets, unsigned int black_levels, eprn_OctetString bitplanes[])
{
  const int
    max_pixel = max_octets - 1,
    planes = eprn_bits_for_levels(black_levels);
  int
    correction,
    error,
    new_value,
    pixel,
    pixel_mod_8,
    pixels = line->length,
    plane,
    remaining_error;
  eprn_Octet
    approx,
    *from,
    *ptr[8],
    *to;
  const unsigned int
    divisor = 256/black_levels,
    max_level = black_levels - 1;

  for (plane = 0; plane < planes; plane++) ptr[plane] = bitplanes[plane].str;

  /* Loop over pixels in the scan line. Note that 'pixels' may increase
     within the loop. */
  for (pixel = 0, pixel_mod_8 = 8; pixel < pixels; pixel++, pixel_mod_8++) {
    if (pixel_mod_8 == 8) {
      pixel_mod_8 = 0;
      for (plane = 0; plane < planes; plane++) *ptr[plane] = 0;
    }

    /* Determine approximation and error for this pixel */
    from = line->str + pixel;
    approx = *from/divisor;
    error = *from - (255*approx)/max_level;
     /* The sign of 'error' is chosen such that 'error' is positive if
	colorant intensity has to be added to the picture. */

    /* Distribute the approximation over the bit planes */
    for (plane = 0; plane < planes; plane++) {
      *ptr[plane] = (*ptr[plane] << 1) | approx & 0x01;
      approx >>= 1;
    }

    error_propagation_Gray()

    if (pixel_mod_8 == 7)
      for (plane = 0; plane < planes; plane++) ptr[plane]++;
  }

  eprn_finalize(false, 0, planes, bitplanes, ptr, pixels);

  return;
}

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

/* Index of the black colorant in a pixel value (gx_color_index) for the
   non-monochrome cases */
#define BLACK_INDEX		3

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

  Function: split_colour_CMYK_2()

  Floyd-Steinberg error diffusion for the CMYK colour model using 2 intensity
  levels for all colorants.

  This function is about 14 % faster than split_colour_at_most_2(), and every
  bit helps.

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

#define PLANES		4

static void split_colour_CMYK_2(eprn_OctetString *line,
  eprn_OctetString *next_line, int max_octets, eprn_OctetString bitplanes[])
{
  const int
    max_pixel = max_octets/OCTETS_PER_PIXEL - 1;
  int
    colorant,
    correction,
    error,
    new_value,
    pixel,
    pixel_mod_8,
    pixels = line->length/OCTETS_PER_PIXEL,
    plane,
    remaining_error;
  eprn_Octet
    approx,
    *from,
    *ptr[4],
    *to;

  for (plane = 0; plane < PLANES; plane++) ptr[plane] = bitplanes[plane].str;

  /* Loop over pixels in the scan line. Note that 'pixels' may increase
     within the loop. */
  for (pixel = 0, pixel_mod_8 = 8; pixel < pixels; pixel++, pixel_mod_8++) {
    if (pixel_mod_8 == 8) {
      pixel_mod_8 = 0;
      for (plane = 0; plane < PLANES; plane++) *ptr[plane] = 0;
    }

    /* Loop over colorants within a scan line. Remember that the order within
       a pixel is YMCK. */
    for (colorant = BLACK_INDEX; colorant >= 0; colorant--) {
      from = line->str + pixel*OCTETS_PER_PIXEL + colorant;

      /* Determine approximation and error for this pixel */
      approx = *from >> 7;
      error = *from - 255*approx;
       /* The sign of 'error' is chosen such that 'error' is positive if
	  colorant intensity has to be added to the picture. */

      /* Insert the approximation in the bit plane */
      plane = BLACK_INDEX - colorant;
      *ptr[plane] = (*ptr[plane] << 1) | approx;

      error_propagation_colour()
    }

    if (pixel_mod_8 == 7)
      for (plane = 0; plane < PLANES; plane++) ptr[plane]++;
  }

  eprn_finalize(false, 2, PLANES, bitplanes, ptr, pixels);

  return;
}

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

  Function: split_colour_at_most_2()

  Floyd-Steinberg error diffusion for the non-monochrome process colour models
  using 2 intensity levels for the CMY colorants and at most 2 for the black
  colorant.

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

static void split_colour_at_most_2(eprn_OctetString *line,
  eprn_OctetString *next_line, int max_octets, eprn_ColourModel colour_model,
  eprn_OctetString bitplanes[])
{
  const int
    last_colorant =
      colour_model == eprn_DeviceCMY_plus_K || colour_model == eprn_DeviceCMYK?
	BLACK_INDEX: 2,
    max_pixel = max_octets/OCTETS_PER_PIXEL - 1,
    planes =
      colour_model == eprn_DeviceCMY_plus_K || colour_model == eprn_DeviceCMYK?
	4: 3;
  int
    colorant,
    correction,
    error,
    new_value,
    pixel,
    pixel_mod_8,
    pixels = line->length/OCTETS_PER_PIXEL,
    plane,
    remaining_error;
  eprn_Octet
    approx[4],
    *from,
    *ptr[4],
    *to;

  for (plane = 0; plane < planes; plane++) ptr[plane] = bitplanes[plane].str;

  /* Loop over pixels in the scan line. Note that 'pixels' may increase
     within the loop. */
  for (pixel = 0, pixel_mod_8 = 8; pixel < pixels; pixel++, pixel_mod_8++) {
    if (pixel_mod_8 == 8) {
      pixel_mod_8 = 0;
      for (plane = 0; plane < planes; plane++) *ptr[plane] = 0;
    }

    /* Loop over colorants within a scan line. Remember that the order within
       a pixel is YMCK or BGR-. */
    for (colorant = last_colorant; colorant >= 0; colorant--) {
      from = line->str + pixel*OCTETS_PER_PIXEL + colorant;

      /* Determine approximation and error for this pixel */
      approx[colorant] = *from >> 7;
      error = *from - 255*approx[colorant];
       /* The sign of 'error' is chosen such that 'error' is positive if
	  colorant intensity has to be added to the picture. */

      error_propagation_colour()
    }

    /* Determine the black component for CMY+K */
    if (colour_model == eprn_DeviceCMY_plus_K &&
	approx[0] == approx[1] && approx[1] == approx[2] && approx[0] > 0) {
      approx[BLACK_INDEX] = approx[0];
      approx[0] = approx[1] = approx[2] = 0;
    }

    /* Distribute the approximation over the bit planes */
    for (colorant = last_colorant, plane = 0; colorant >= 0;
	colorant--, plane++) {
      *ptr[plane] = (*ptr[plane] << 1) | approx[colorant];
    }

    if (pixel_mod_8 == 7)
      for (plane = 0; plane < planes; plane++) ptr[plane]++;
  }

  eprn_finalize(colour_model == eprn_DeviceRGB, 2, planes, bitplanes, ptr,
    pixels);

  return;
}

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

  Function: split_colour()

  Floyd-Steinberg error diffusion for the non-monochrome process colour models
  and an arbitrary number of intensity levels.

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

static void split_colour(eprn_OctetString *line, eprn_OctetString *next_line,
  int max_octets, eprn_ColourModel colour_model,
  unsigned int black_levels, unsigned int non_black_levels,
  eprn_OctetString bitplanes[])
{
  const int
    black_planes = eprn_bits_for_levels(black_levels),
    last_colorant = black_levels > 0? BLACK_INDEX: 2,
    max_pixel = max_octets/OCTETS_PER_PIXEL - 1,
    non_black_planes = eprn_bits_for_levels(non_black_levels),
    planes = black_planes + 3*non_black_planes;
  int
    colorant,
    correction,
    error,
    new_value,
    next_plane[4],
    pixel,
    pixel_mod_8,
    pixels = line->length/OCTETS_PER_PIXEL,
    plane,
    remaining_error;
  eprn_Octet
    approx[4],
    *from,
    *ptr[32],
    *to;
  unsigned int
    divisor[4],
    max_level[4];

  if (black_levels > 0) {
    divisor[BLACK_INDEX] = 256/black_levels;
    max_level[BLACK_INDEX] = black_levels - 1;
  }
  else {
    divisor[BLACK_INDEX] = 0;
    max_level[BLACK_INDEX] = 0;
  }
  next_plane[BLACK_INDEX] = black_planes;

  for (colorant = 0; colorant < BLACK_INDEX; colorant++) {
    divisor[colorant] = 256/non_black_levels;
    max_level[colorant] = non_black_levels - 1;
    next_plane[colorant] = (3 - colorant)*non_black_planes + black_planes;
  }

  for (plane = 0; plane < planes; plane++) ptr[plane] = bitplanes[plane].str;

  /* Loop over pixels in the scan line. Note that 'pixels' may increase
     within the loop. */
  for (pixel = 0, pixel_mod_8 = 8; pixel < pixels; pixel++, pixel_mod_8++) {
    if (pixel_mod_8 == 8) {
      pixel_mod_8 = 0;
      for (plane = 0; plane < planes; plane++) *ptr[plane] = 0;
    }

    /* Loop over colorants within a scan line */
    for (colorant = last_colorant; colorant >= 0; colorant--) {
      from = line->str + pixel*OCTETS_PER_PIXEL + colorant;

      /* Determine approximation and error for this pixel */
      approx[colorant] = *from/divisor[colorant];
      error = *from - (255*approx[colorant])/max_level[colorant];
       /* The sign of 'error' is chosen such that 'error' is positive if
	  colorant intensity has to be added to the picture. */

      error_propagation_colour()
    }

    /* Determine the black component for CMY+K */
    if (colour_model == eprn_DeviceCMY_plus_K &&
	approx[0] == approx[1] && approx[1] == approx[2] && approx[0] > 0) {
      int value = approx[0]*(black_levels - 1);
      if (value % (non_black_levels - 1) == 0) {
	/* Black does have a level at the same intensity as the CMY levels */
	approx[BLACK_INDEX] = value/(non_black_levels - 1);
	approx[0] = approx[1] = approx[2] = 0;
      }
    }

    /* Distribute the approximation over the bit planes */
    plane = 0;
    for (colorant = last_colorant; colorant >= 0; colorant--) {
      while (plane < next_plane[colorant]) {
	*ptr[plane] = (*ptr[plane] << 1) | approx[colorant] & 0x01;
	approx[colorant] >>= 1;
	plane++;
      }
    }

    if (pixel_mod_8 == 7) {
      int j;
      for (j = 0; j < planes; j++) ptr[j]++;
    }
  }

  eprn_finalize(colour_model == eprn_DeviceRGB, non_black_levels, planes,
    bitplanes, ptr, pixels);

  return;
}

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

  Function: eprn_split_FS

  This function performs Floyd-Steinberg error diffusion on a scan line
  and returns the result as bitplanes.

  'line' points to the scan line to be split, 'next_line' to the following one.
  Both lines will be modified by this process. This modification assumes that
  the function is called successively for all lines, starting with the first.
  All octets up to 'max_octets' must be available in the input lines and, as
  far as they have not been included in the length fields, must be zero.
  The parameter 'colour_model' specifies the process colour model used.
  'black_levels' is the number of intensity levels for the black colorant,
  'non_black_levels' the corresponding number for the other colorants.
  'bitplanes' is an array of bitplanes into which the result will be stored
  in the usual format.

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

void eprn_split_FS(eprn_OctetString *line, eprn_OctetString *next_line,
  int max_octets, eprn_ColourModel colour_model,
  unsigned int black_levels, unsigned int non_black_levels,
  eprn_OctetString bitplanes[])
{
  if (colour_model == eprn_DeviceGray) {
    if (black_levels == 2)
      split_Gray_2(line, next_line, max_octets, bitplanes);
    else
      split_Gray(line, next_line, max_octets, black_levels, bitplanes);
  }
  else if (colour_model == eprn_DeviceCMYK &&
      black_levels == 2 && non_black_levels == 2)
    split_colour_CMYK_2(line, next_line, max_octets, bitplanes);
  else {
    if (black_levels <= 2 && non_black_levels == 2)
      split_colour_at_most_2(line, next_line, max_octets, colour_model,
	bitplanes);
    else
      split_colour(line, next_line, max_octets, colour_model, black_levels,
	non_black_levels, bitplanes);
  }

  return;
}
