/* Copyright (C) 1996, 2000 artofcode LLC.  All rights reserved.
  
  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.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gxdcolor.c,v 1.3.6.1.2.1 2003/01/17 00:49:03 giles Exp $ */
/* Pure and null device color implementation */
#include "gx.h"
#include "gserrors.h"
#include "gsbittab.h"
#include "gxdcolor.h"
#include "gxdevice.h"

/* Define the standard device color types. */

/* 'none' means the color is not defined. */
private dev_color_proc_load(gx_dc_no_load);
private dev_color_proc_fill_rectangle(gx_dc_no_fill_rectangle);
private dev_color_proc_fill_masked(gx_dc_no_fill_masked);
private dev_color_proc_equal(gx_dc_no_equal);
const gx_device_color_type_t gx_dc_type_data_none = {
    &st_bytes,
    gx_dc_no_load, gx_dc_no_fill_rectangle, gx_dc_no_fill_masked,
    gx_dc_no_equal
};
#undef gx_dc_type_none
const gx_device_color_type_t *const gx_dc_type_none = &gx_dc_type_data_none;
#define gx_dc_type_none (&gx_dc_type_data_none)

/* 'null' means the color has no effect when used for drawing. */
private dev_color_proc_load(gx_dc_null_load);
private dev_color_proc_fill_rectangle(gx_dc_null_fill_rectangle);
private dev_color_proc_fill_masked(gx_dc_null_fill_masked);
private dev_color_proc_equal(gx_dc_null_equal);
const gx_device_color_type_t gx_dc_type_data_null = {
    &st_bytes,
    gx_dc_null_load, gx_dc_null_fill_rectangle, gx_dc_null_fill_masked,
    gx_dc_null_equal
};
#undef gx_dc_type_null
const gx_device_color_type_t *const gx_dc_type_null = &gx_dc_type_data_null;
#define gx_dc_type_null (&gx_dc_type_data_null)

private dev_color_proc_load(gx_dc_pure_load);
private dev_color_proc_fill_rectangle(gx_dc_pure_fill_rectangle);
private dev_color_proc_fill_masked(gx_dc_pure_fill_masked);
private dev_color_proc_equal(gx_dc_pure_equal);
const gx_device_color_type_t gx_dc_type_data_pure = {
    &st_bytes,
    gx_dc_pure_load, gx_dc_pure_fill_rectangle, gx_dc_pure_fill_masked,
    gx_dc_pure_equal
};
#undef gx_dc_type_pure
const gx_device_color_type_t *const gx_dc_type_pure = &gx_dc_type_data_pure;
#define gx_dc_type_pure (&gx_dc_type_data_pure)

/*
 * Get the black and white pixel values of a device.  The documentation for
 * the driver API says that map_rgb_color will do the right thing on CMYK
 * devices.  Unfortunately, that isn't true at present, and fixing it is too
 * much work.
 */
gx_color_index
gx_device_black(gx_device *dev)
{
    if (dev->cached_colors.black == gx_no_color_index)
	dev->cached_colors.black =
	    (dev->color_info.num_components == 4 ?
	     (*dev_proc(dev, map_cmyk_color))
	     (dev, (gx_color_index)0, (gx_color_index)0, (gx_color_index)0,
	      gx_max_color_value) :
	     (*dev_proc(dev, map_rgb_color))
	     (dev, (gx_color_index)0, (gx_color_index)0, (gx_color_index)0));
    return dev->cached_colors.black;
}
gx_color_index
gx_device_white(gx_device *dev)
{
    if (dev->cached_colors.white == gx_no_color_index)
	dev->cached_colors.white =
	    (dev->color_info.num_components == 4 ?
	     (*dev_proc(dev, map_cmyk_color))
	     (dev, (gx_color_index)0, (gx_color_index)0, (gx_color_index)0,
	      (gx_color_index)0) :
	     (*dev_proc(dev, map_rgb_color))
	     (dev, gx_max_color_value, gx_max_color_value, gx_max_color_value));
    return dev->cached_colors.white;
}

/* Clear the color cache. */
void
gx_device_decache_colors(gx_device *dev)
{
    dev->cached_colors.black = dev->cached_colors.white = gx_no_color_index;
}

/* Set a null RasterOp source. */
private const gx_rop_source_t gx_rop_no_source_0 = {gx_rop_no_source_body(0)};
private const gx_rop_source_t gx_rop_no_source_1 = {gx_rop_no_source_body(1)};
void
gx_set_rop_no_source(const gx_rop_source_t **psource,
		     gx_rop_source_t *pno_source, gx_device *dev)
{
    gx_color_index black;

top:
    black = dev->cached_colors.black;
    if (black == 0)
	*psource = &gx_rop_no_source_0;
    else if (black == 1)
	*psource = &gx_rop_no_source_1;
    else if (black == gx_no_color_index) {	/* cache not loaded */
	discard(gx_device_black(dev));
	goto top;
    } else {
	*pno_source = gx_rop_no_source_0;
	gx_rop_source_set_color(pno_source, black);
	*psource = pno_source;
    }
}

/* Test device colors for equality. */
bool
gx_device_color_equal(const gx_device_color *pdevc1,
		      const gx_device_color *pdevc2)
{
    return pdevc1->type->equal(pdevc1, pdevc2);
}

/* ------ Undefined color ------ */

private int
gx_dc_no_load(gx_device_color *pdevc, const gs_imager_state *ignore_pis,
	      gx_device *ignore_dev, gs_color_select_t ignore_select)
{
    return 0;
}

private int
gx_dc_no_fill_rectangle(const gx_device_color *pdevc, int x, int y,
			int w, int h, gx_device *dev,
			gs_logical_operation_t lop,
			const gx_rop_source_t *source)
{
    gx_device_color filler;

    if (w <= 0 || h <= 0)
	return 0;
    if (lop_uses_T(lop))
	return_error(gs_error_Fatal);
    color_set_pure(&filler, 0);	 /* any valid value for dev will do */
    return gx_dc_pure_fill_rectangle(&filler, x, y, w, h, dev, lop, source);
}

private int
gx_dc_no_fill_masked(const gx_device_color *pdevc, const byte *data,
		     int data_x, int raster, gx_bitmap_id id,
		     int x, int y, int w, int h, gx_device *dev,
		     gs_logical_operation_t lop, bool invert)
{
    if (w <= 0 || h <= 0)
	return 0;
    return_error(gs_error_Fatal);
}

private bool
gx_dc_no_equal(const gx_device_color *pdevc1, const gx_device_color *pdevc2)
{
    return false;
}

/* ------ Null color ------ */

private int
gx_dc_null_load(gx_device_color *pdevc, const gs_imager_state *ignore_pis,
		gx_device *ignore_dev, gs_color_select_t ignore_select)
{
    return 0;
}

private int
gx_dc_null_fill_rectangle(const gx_device_color * pdevc, int x, int y,
			  int w, int h, gx_device * dev,
			  gs_logical_operation_t lop,
			  const gx_rop_source_t * source)
{
    return 0;
}

private int
gx_dc_null_fill_masked(const gx_device_color * pdevc, const byte * data,
		       int data_x, int raster, gx_bitmap_id id,
		       int x, int y, int w, int h, gx_device * dev,
		       gs_logical_operation_t lop, bool invert)
{
    return 0;
}

private bool
gx_dc_null_equal(const gx_device_color * pdevc1, const gx_device_color * pdevc2)
{
    return pdevc2->type == pdevc1->type;
}

/* ------ Pure color ------ */

private int
gx_dc_pure_load(gx_device_color * pdevc, const gs_imager_state * ignore_pis,
		gx_device * ignore_dev, gs_color_select_t ignore_select)
{
    return 0;
}

/* Fill a rectangle with a pure color. */
/* Note that we treat this as "texture" for RasterOp. */
private int
gx_dc_pure_fill_rectangle(const gx_device_color * pdevc, int x, int y,
		  int w, int h, gx_device * dev, gs_logical_operation_t lop,
			  const gx_rop_source_t * source)
{
    if (source == NULL && lop_no_S_is_T(lop))
	return (*dev_proc(dev, fill_rectangle)) (dev, x, y, w, h,
						 pdevc->colors.pure);
    {
	gx_color_index colors[2];
	gx_rop_source_t no_source;

	colors[0] = colors[1] = pdevc->colors.pure;
	if (source == NULL)
	    set_rop_no_source(source, no_source, dev);
	return (*dev_proc(dev, strip_copy_rop))
	    (dev, source->sdata, source->sourcex, source->sraster,
	     source->id, (source->use_scolors ? source->scolors : NULL),
	     NULL /*arbitrary */ , colors, x, y, w, h, 0, 0, lop);
    }
}

/* Fill a mask with a pure color. */
/* Note that there is no source in this case: the mask is the source. */
private int
gx_dc_pure_fill_masked(const gx_device_color * pdevc, const byte * data,
	int data_x, int raster, gx_bitmap_id id, int x, int y, int w, int h,
		   gx_device * dev, gs_logical_operation_t lop, bool invert)
{
    if (lop_no_S_is_T(lop)) {
	gx_color_index color0, color1;

	if (invert)
	    color0 = pdevc->colors.pure, color1 = gx_no_color_index;
	else
	    color1 = pdevc->colors.pure, color0 = gx_no_color_index;
	return (*dev_proc(dev, copy_mono))
	    (dev, data, data_x, raster, id, x, y, w, h, color0, color1);
    } {
	gx_color_index scolors[2];
	gx_color_index tcolors[2];

	scolors[0] = gx_device_black(dev);
	scolors[1] = gx_device_white(dev);
	tcolors[0] = tcolors[1] = pdevc->colors.pure;
	return (*dev_proc(dev, strip_copy_rop))
	    (dev, data, data_x, raster, id, scolors,
	     NULL, tcolors, x, y, w, h, 0, 0,
	     (invert ? rop3_invert_S(lop) : lop) | lop_S_transparent);
    }
}

private bool
gx_dc_pure_equal(const gx_device_color * pdevc1, const gx_device_color * pdevc2)
{
    return pdevc2->type == pdevc1->type &&
	gx_dc_pure_color(pdevc1) == gx_dc_pure_color(pdevc2);
}

/* ------ Halftone color initialization ------ */

void
gx_complete_rgb_halftone(gx_device_color *pdevc, gx_device_halftone *pdht)
{
    pdevc->type = gx_dc_type_ht_colored;
    pdevc->colors.colored.c_ht = pdht;
    pdevc->colors.colored.plane_mask =
	(pdevc->colors.colored.c_level[0] != 0) |
	((pdevc->colors.colored.c_level[1] != 0) << 1) |
	((pdevc->colors.colored.c_level[2] != 0) << 2);
    /*
     * Color rendering won't use the fourth component, but the code that
     * writes and reads colored halftones in the band list doesn't know that.
     */
    pdevc->colors.colored.c_base[3] = 0;
    pdevc->colors.colored.c_level[3] = 0;
}

void
gx_complete_cmyk_halftone(gx_device_color *pdevc, gx_device_halftone *pdht)
{
    pdevc->type = gx_dc_type_ht_colored;
    pdevc->colors.colored.c_ht = pdht;
    pdevc->colors.colored.alpha = max_ushort;
    pdevc->colors.colored.plane_mask =
	(pdevc->colors.colored.c_level[0] != 0) |
	((pdevc->colors.colored.c_level[1] != 0) << 1) |
	((pdevc->colors.colored.c_level[2] != 0) << 2) |
	((pdevc->colors.colored.c_level[3] != 0) << 3);
}

/* ------ Default implementations ------ */

/* Fill a mask with a color by parsing the mask into rectangles. */
int
gx_dc_default_fill_masked(const gx_device_color * pdevc, const byte * data,
	int data_x, int raster, gx_bitmap_id id, int x, int y, int w, int h,
		   gx_device * dev, gs_logical_operation_t lop, bool invert)
{
    int lbit = data_x & 7;
    const byte *row = data + (data_x >> 3);
    uint one = (invert ? 0 : 0xff);
    uint zero = one ^ 0xff;
    int iy;

    for (iy = 0; iy < h; ++iy, row += raster) {
	const byte *p = row;
	int bit = lbit;
	int left = w;
	int l0;

	while (left) {
	    int run, code;

	    /* Skip a run of zeros. */
	    run = byte_bit_run_length[bit][*p ^ one];
	    if (run) {
		if (run < 8) {
		    if (run >= left)
			break;	/* end of row while skipping */
		    bit += run, left -= run;
		} else if ((run -= 8) >= left)
		    break;	/* end of row while skipping */
		else {
		    left -= run;
		    ++p;
		    while (left > 8 && *p == zero)
			left -= 8, ++p;
		    run = byte_bit_run_length_0[*p ^ one];
		    if (run >= left)	/* run < 8 unless very last byte */
			break;	/* end of row while skipping */
		    else
			bit = run & 7, left -= run;
		}
	    }
	    l0 = left;
	    /* Scan a run of ones, and then paint it. */
	    run = byte_bit_run_length[bit][*p ^ zero];
	    if (run < 8) {
		if (run >= left)
		    left = 0;
		else
		    bit += run, left -= run;
	    } else if ((run -= 8) >= left)
		left = 0;
	    else {
		left -= run;
		++p;
		while (left > 8 && *p == one)
		    left -= 8, ++p;
		run = byte_bit_run_length_0[*p ^ zero];
		if (run >= left)	/* run < 8 unless very last byte */
		    left = 0;
		else
		    bit = run & 7, left -= run;
	    }
	    code = gx_device_color_fill_rectangle(pdevc,
			  x + w - l0, y + iy, l0 - left, 1, dev, lop, NULL);
	    if (code < 0)
		return code;
	}
    }
    return 0;
}
