/* Copyright (C) 1992, 1995, 1996, 1997, 1998, 1999 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: gxicolor.c,v 1.3.2.1.2.1 2003/01/17 00:49:03 giles Exp $ */
/* Color image rendering */
#include "gx.h"
#include "memory_.h"
#include "gpcheck.h"
#include "gserrors.h"
#include "gxfixed.h"
#include "gxfrac.h"
#include "gxarith.h"
#include "gxmatrix.h"
#include "gsccolor.h"
#include "gspaint.h"
#include "gzstate.h"
#include "gxdevice.h"
#include "gxcmap.h"
#include "gxdcconv.h"
#include "gxdcolor.h"
#include "gxistate.h"
#include "gxdevmem.h"
#include "gxcpath.h"
#include "gximage.h"

typedef union {
    byte v[GS_IMAGE_MAX_COLOR_COMPONENTS];
#define BYTES_PER_BITS32 4
#define BITS32_PER_COLOR_SAMPLES\
  ((GS_IMAGE_MAX_COLOR_COMPONENTS + BYTES_PER_BITS32 - 1) / BYTES_PER_BITS32)
    bits32 all[BITS32_PER_COLOR_SAMPLES];	/* for fast comparison */
} color_samples;

/* ------ Strategy procedure ------ */

/* Check the prototype. */
iclass_proc(gs_image_class_4_color);

private irender_proc(image_render_color);
irender_proc_t
gs_image_class_4_color(gx_image_enum * penum)
{
    if (penum->use_mask_color) {
	/*
	 * Scale the mask colors to match the scaling of each sample to
	 * a full byte, and set up the quick-filter parameters.
	 */
	int i;
	color_samples mask, test;
	bool exact = penum->spp <= BYTES_PER_BITS32;

	memset(&mask, 0, sizeof(mask));
	memset(&test, 0, sizeof(test));
	for (i = 0; i < penum->spp; ++i) {
	    byte v0, v1;
	    byte match = 0xff;

	    gx_image_scale_mask_colors(penum, i);
	    v0 = (byte)penum->mask_color.values[2 * i];
	    v1 = (byte)penum->mask_color.values[2 * i + 1];
	    while ((v0 & match) != (v1 & match))
		match <<= 1;
	    mask.v[i] = match;
	    test.v[i] = v0 & match;
	    exact &= (v0 == match && (v1 | match) == 0xff);
	}
	penum->mask_color.mask = mask.all[0];
	penum->mask_color.test = test.all[0];
	penum->mask_color.exact = exact;
    } else {
	penum->mask_color.mask = 0;
	penum->mask_color.test = ~0;
    }
    return image_render_color;
}

/* ------ Rendering procedures ------ */

/* Test whether a color is transparent. */
private bool
mask_color_matches(const byte *v, const gx_image_enum *penum,
		   int num_components)
{
    int i;

    for (i = num_components * 2, v += num_components - 1; (i -= 2) >= 0; --v)
	if (*v < penum->mask_color.values[i] ||
	    *v > penum->mask_color.values[i + 1]
	    )
	    return false;
    return true;
}

/* Render a color image with 8 or fewer bits per sample. */
private int
image_render_color(gx_image_enum *penum_orig, const byte *buffer, int data_x,
		   uint w, int h, gx_device * dev)
{
    const gx_image_enum *const penum = penum_orig; /* const within proc */
    gx_image_clue *const clues = penum_orig->clues; /* not const */
    const gs_imager_state *pis = penum->pis;
    gs_logical_operation_t lop = penum->log_op;
    gx_dda_fixed_point pnext;
    image_posture posture = penum->posture;
    fixed xprev, yprev;
    fixed pdyx, pdyy;		/* edge of parallelogram */
    int vci, vdi;
    const gs_color_space *pcs = penum->pcs;
    cs_proc_remap_color((*remap_color)) = pcs->type->remap_color;
    gs_client_color cc;
    bool device_color = penum->device_color;
    const gx_color_map_procs *cmap_procs = gx_get_cmap_procs(pis, dev);
    cmap_proc_rgb((*map_3)) = cmap_procs->map_rgb;
    cmap_proc_cmyk((*map_4)) =
	(penum->alpha ? cmap_procs->map_rgb_alpha : cmap_procs->map_cmyk);
    bits32 mask = penum->mask_color.mask;
    bits32 test = penum->mask_color.test;
    gx_image_clue *pic = &clues[0];
#define pdevc (&pic->dev_color)
    gx_image_clue *pic_next = &clues[1];
#define pdevc_next (&pic_next->dev_color)
    gx_image_clue empty_clue;
    gx_image_clue clue_temp;
    int spp = penum->spp;
    const byte *psrc_initial = buffer + data_x * spp;
    const byte *psrc = psrc_initial;
    const byte *rsrc = psrc + spp; /* psrc + spp at start of run */
    fixed xrun;			/* x ditto */
    fixed yrun;			/* y ditto */
    int irun;			/* int x/rrun */
    color_samples run;		/* run value */
    color_samples next;		/* next sample value */
    const byte *bufend = psrc + w;
    bool use_cache = spp * penum->bps <= 12;
    int code = 0, mcode = 0;

    if (h == 0)
	return 0;
    pnext = penum->dda.pixel0;
    xrun = xprev = dda_current(pnext.x);
    yrun = yprev = dda_current(pnext.y);
    pdyx = dda_current(penum->dda.row.x) - penum->cur.x;
    pdyy = dda_current(penum->dda.row.y) - penum->cur.y;
    switch (posture) {
	case image_portrait:
	    vci = penum->yci, vdi = penum->hci;
	    irun = fixed2int_var_rounded(xrun);
	    break;
	case image_landscape:
	    vci = penum->xci, vdi = penum->wci;
	    irun = fixed2int_var_rounded(yrun);
	    break;
	default:
	    break;
    }

    if_debug5('b', "[b]y=%d data_x=%d w=%d xt=%f yt=%f\n",
	      penum->y, data_x, w, fixed2float(xprev), fixed2float(yprev));
    memset(&run, 0, sizeof(run));
    memset(&next, 0, sizeof(next));
    /* Ensure that we don't get any false dev_color_eq hits. */
    if (use_cache) {
	color_set_pure(&empty_clue.dev_color, gx_no_color_index);
	pic = &empty_clue;
    }
    cs_full_init_color(&cc, pcs);
    run.v[0] = ~psrc[0];	/* force remap */
    while (psrc < bufend) {
	dda_next(pnext.x);
	dda_next(pnext.y);
#define CLUE_HASH3(penum, next)\
  &clues[(next.v[0] + (next.v[1] << 2) + (next.v[2] << 4)) & 255];
#define CLUE_HASH4(penum, next)\
  &clues[(next.v[0] + (next.v[1] << 2) + (next.v[2] << 4) +\
		 (next.v[3] << 6)) & 255]

	if (spp == 4) {		/* may be CMYK or RGBA */
	    next.v[0] = psrc[0];
	    next.v[1] = psrc[1];
	    next.v[2] = psrc[2];
	    next.v[3] = psrc[3];
	    psrc += 4;
map4:	    if (next.all[0] == run.all[0])
		goto inc;
	    if (use_cache) {
		pic_next = CLUE_HASH4(penum, next);
		if (pic_next->key == next.all[0])
		    goto f;
		/*
		 * If we are really unlucky, pic_next == pic,
		 * so mapping this color would clobber the one
		 * we're about to use for filling the run.
		 */
		if (pic_next == pic) {
		    clue_temp = *pic;
		    pic = &clue_temp;
		}
		pic_next->key = next.all[0];
	    }
	    /* Check for transparent color. */
	    if ((next.all[0] & mask) == test &&
		(penum->mask_color.exact ||
		 mask_color_matches(next.v, penum, 4))
		) {
		color_set_null(pdevc_next);
		goto mapped;
	    }
	    if (device_color) {
		(*map_4)(byte2frac(next.v[0]), byte2frac(next.v[1]),
			 byte2frac(next.v[2]), byte2frac(next.v[3]),
			 pdevc_next, pis, dev,
			 gs_color_select_source);
		goto mapped;
	    }
	    decode_sample(next.v[3], cc, 3);
	    if_debug1('B', "[B]cc[3]=%g\n", cc.paint.values[3]);
do3:	    decode_sample(next.v[0], cc, 0);
	    decode_sample(next.v[1], cc, 1);
	    decode_sample(next.v[2], cc, 2);
	    if_debug3('B', "[B]cc[0..2]=%g,%g,%g\n",
		      cc.paint.values[0], cc.paint.values[1],
		      cc.paint.values[2]);
	} else if (spp == 3) {	    /* may be RGB */
	    next.v[0] = psrc[0];
	    next.v[1] = psrc[1];
	    next.v[2] = psrc[2];
	    psrc += 3;
	    if (next.all[0] == run.all[0])
		goto inc;
	    if (use_cache) {
		pic_next = CLUE_HASH3(penum, next);
		if (pic_next->key == next.all[0])
		    goto f;
		/* See above re the following check. */
		if (pic_next == pic) {
		    clue_temp = *pic;
		    pic = &clue_temp;
		}
		pic_next->key = next.all[0];
	    }
	    /* Check for transparent color. */
	    if ((next.all[0] & mask) == test &&
		(penum->mask_color.exact ||
		 mask_color_matches(next.v, penum, 3))
		) {
		color_set_null(pdevc_next);
		goto mapped;
	    }
	    if (device_color) {
		(*map_3)(byte2frac(next.v[0]), byte2frac(next.v[1]),
			 byte2frac(next.v[2]),
			 pdevc_next, pis, dev,
			 gs_color_select_source);
		goto mapped;
	    }
	    goto do3;
	} else if (penum->alpha) {
	    if (spp == 2) {	/* might be Gray + alpha */
		next.v[2] = next.v[1] = next.v[0] = psrc[0];
		next.v[3] = psrc[1];
		psrc += 2;
		goto map4;
	    } else if (spp == 5) {	/* might be CMYK + alpha */
		/* Convert CMYK to RGB. */
		frac rgb[3];

		color_cmyk_to_rgb(byte2frac(psrc[0]), byte2frac(psrc[1]),
				  byte2frac(psrc[2]), byte2frac(psrc[3]),
				  pis, rgb);
		/*
		 * It seems silly to do all this converting between
		 * fracs and bytes, but that's what the current
		 * APIs require.
		 */
		next.v[0] = frac2byte(rgb[0]);
		next.v[1] = frac2byte(rgb[1]);
		next.v[2] = frac2byte(rgb[2]);
		next.v[3] = psrc[4];
		psrc += 5;
		goto map4;
	    }
	} else {		/* DeviceN */
	    int i;

	    use_cache = false;	/* should do in initialization */
	    if (!memcmp(psrc, run.v, spp)) {
		psrc += spp;
		goto inc;
	    }
	    memcpy(next.v, psrc, spp);
	    psrc += spp;
	    if ((next.all[0] & mask) == test &&
		(penum->mask_color.exact ||
		 mask_color_matches(next.v, penum, spp))
		) {
		color_set_null(pdevc_next);
		goto mapped;
	    }
	    for (i = 0; i < spp; ++i)
		decode_sample(next.v[i], cc, i);
#ifdef DEBUG
	    if (gs_debug_c('B')) {
		dprintf2("[B]cc[0..%d]=%g", spp - 1,
			 cc.paint.values[0]);
		for (i = 1; i < spp; ++i)
		    dprintf1(",%g", cc.paint.values[i]);
		dputs("\n");
	    }
#endif
	}
	mcode = remap_color(&cc, pcs, pdevc_next, pis, dev,
			   gs_color_select_source);
	if (mcode < 0)
	    goto fill;
mapped:	if (pic == pic_next)
	    goto fill;
f:	if_debug7('B', "[B]0x%x,0x%x,0x%x,0x%x -> %ld,%ld,0x%lx\n",
		  next.v[0], next.v[1], next.v[2], next.v[3],
		  pdevc_next->colors.binary.color[0],
		  pdevc_next->colors.binary.color[1],
		  (ulong) pdevc_next->type);
	/* Even though the supplied colors don't match, */
	/* the device colors might. */
	if (dev_color_eq(*pdevc, *pdevc_next))
	    goto set;
fill:	/* Fill the region between */
	/* xrun/irun and xprev */
        /*
	 * Note;  This section is nearly a copy of a simlar section below
         * for processing the image pixel in the loop.  This would have been
         * made into a subroutine except for complications about the number of
         * variables that would have been needed to be passed to the routine.
	 */
	switch (posture) {
	case image_portrait:
	    {		/* Rectangle */
		int xi = irun;
		int wi = (irun = fixed2int_var_rounded(xprev)) - xi;

		if (wi < 0)
		    xi += wi, wi = -wi;
		if (wi > 0)
		    code = gx_fill_rectangle_device_rop(xi, vci, wi, vdi,
							pdevc, dev, lop);
	    }
	    break;
	case image_landscape:
	    {		/* 90 degree rotated rectangle */
		int yi = irun;
		int hi = (irun = fixed2int_var_rounded(yprev)) - yi;

		if (hi < 0)
		    yi += hi, hi = -hi;
		if (hi > 0)
		    code = gx_fill_rectangle_device_rop(vci, yi, vdi, hi,
							pdevc, dev, lop);
	    }
	    break;
	default:
	    {		/* Parallelogram */
		code = (*dev_proc(dev, fill_parallelogram))
		    (dev, xrun, yrun, xprev - xrun, yprev - yrun, pdyx, pdyy,
		     pdevc, lop);
		xrun = xprev;
		yrun = yprev;
	    }
	}
	if (code < 0)
	    goto err;
	rsrc = psrc;
	if ((code = mcode) < 0) {
	    /* Invalidate any partially built cache entry. */
	    if (use_cache)
		pic_next->key = ~next.all[0];
	    goto err;
	}
	if (use_cache)
	    pic = pic_next;
	else {
	    gx_image_clue *ptemp = pic;

	    pic = pic_next;
	    pic_next = ptemp;
	}
set:	run = next;
inc:	xprev = dda_current(pnext.x);
	yprev = dda_current(pnext.y);	/* harmless if no skew */
    }
    /* Fill the last run. */
    /*
     * Note;  This section is nearly a copy of a simlar section above
     * for processing an image pixel in the loop.  This would have been
     * made into a subroutine except for complications about the number
     * variables that would have been needed to be passed to the routine.
     */
    switch (posture) {
    	case image_portrait:
	    {		/* Rectangle */
		int xi = irun;
		int wi = (irun = fixed2int_var_rounded(xprev)) - xi;

		if (wi < 0)
		    xi += wi, wi = -wi;
		if (wi > 0)
		    code = gx_fill_rectangle_device_rop(xi, vci, wi, vdi,
							pdevc, dev, lop);
	    }
	    break;
	case image_landscape:
	    {		/* 90 degree rotated rectangle */
		int yi = irun;
		int hi = (irun = fixed2int_var_rounded(yprev)) - yi;

		if (hi < 0)
		    yi += hi, hi = -hi;
		if (hi > 0)
		    code = gx_fill_rectangle_device_rop(vci, yi, vdi, hi,
							pdevc, dev, lop);
	    }
	    break;
	default:
	    {		/* Parallelogram */
		code = (*dev_proc(dev, fill_parallelogram))
		    (dev, xrun, yrun, xprev - xrun, yprev - yrun, pdyx, pdyy,
		     pdevc, lop);
	    }
    }
    return (code < 0 ? code : 1);
    /* Save position if error, in case we resume. */
err:
    penum_orig->used.x = (rsrc - spp - psrc_initial) / spp;
    penum_orig->used.y = 0;
    return code;
}
