/* Portions Copyright (C) 2001 artofcode LLC.
   Portions Copyright (C) 1996, 2001 Artifex Software Inc.
   Portions Copyright (C) 1988, 2000 Aladdin Enterprises.
   This software is based in part on the work of the Independent JPEG Group.
   All Rights Reserved.

   This software is distributed under license and may not be copied, modified
   or distributed except as expressly authorized under the terms of that
   license.  Refer to licensing information at http://www.artifex.com/ or
   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
   San Rafael, CA  94903, (415)492-9861, for further information. */

/*$RCSfile: gxi12bit.c,v $ $Revision: 1.13 $ */
/* 12-bit image procedures */
#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 "gxdevice.h"
#include "gxcmap.h"
#include "gxdcolor.h"
#include "gxistate.h"
#include "gxdevmem.h"
#include "gxcpath.h"
#include "gximage.h"

/* ---------------- Unpacking procedures ---------------- */

private const byte *
sample_unpack_12(byte * bptr, int *pdata_x, const byte * data,
		 int data_x, uint dsize, const sample_lookup_t * ignore_ptab,
		 int spread)
{
    register frac *bufp = (frac *) bptr;
    uint dskip = (data_x >> 1) * 3;
    const byte *psrc = data + dskip;
#define inc_bufp(bp, n) bp = (frac *)((byte *)(bp) + (n))
    uint sample;
    int left = dsize - dskip;

    if ((data_x & 1) && left > 0)
	switch (left) {
	    default:
		sample = ((uint) (psrc[1] & 0xf) << 8) + psrc[2];
		*bufp = bits2frac(sample, 12);
		inc_bufp(bufp, spread);
		psrc += 3;
		left -= 3;
		break;
	    case 2:		/* xxxxxxxx xxxxdddd */
		*bufp = (psrc[1] & 0xf) * (frac_1 / 15);
	    case 1:		/* xxxxxxxx */
		left = 0;
	}
    while (left >= 3) {
	sample = ((uint) * psrc << 4) + (psrc[1] >> 4);
	*bufp = bits2frac(sample, 12);
	inc_bufp(bufp, spread);
	sample = ((uint) (psrc[1] & 0xf) << 8) + psrc[2];
	*bufp = bits2frac(sample, 12);
	inc_bufp(bufp, spread);
	psrc += 3;
	left -= 3;
    }
    /* Handle trailing bytes. */
    switch (left) {
	case 2:		/* dddddddd ddddxxxx */
	    sample = ((uint) * psrc << 4) + (psrc[1] >> 4);
	    *bufp = bits2frac(sample, 12);
	    inc_bufp(bufp, spread);
	    *bufp = (psrc[1] & 0xf) * (frac_1 / 15);
	    break;
	case 1:		/* dddddddd */
	    sample = (uint) * psrc << 4;
	    *bufp = bits2frac(sample, 12);
	    break;
	case 0:		/* Nothing more to do. */
	    ;
    }
    *pdata_x = 0;
    return bptr;
}

const sample_unpack_proc_t sample_unpack_12_proc = sample_unpack_12;

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

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

/* Use special (slow) logic for 12-bit source values. */
private irender_proc(image_render_frac);
irender_proc_t
gs_image_class_2_fracs(gx_image_enum * penum)
{
    if (penum->bps > 8) {
	if (penum->use_mask_color) {
	    /* Convert color mask values to fracs. */
	    int i;

	    for (i = 0; i < penum->spp * 2; ++i)
		penum->mask_color.values[i] =
		    bits2frac(penum->mask_color.values[i], 12);
	}
	if_debug0(penum->memory, 'b', "[b]render=frac\n");
	return &image_render_frac;
    }
    return 0;
}

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

/* ------ Rendering for 12-bit samples ------ */

#define FRACS_PER_LONG (arch_sizeof_long / arch_sizeof_frac)
typedef union {
    frac v[GS_IMAGE_MAX_COLOR_COMPONENTS];
#define LONGS_PER_COLOR_FRACS\
  ((GS_IMAGE_MAX_COLOR_COMPONENTS + FRACS_PER_LONG - 1) / FRACS_PER_LONG)
    long all[LONGS_PER_COLOR_FRACS];	/* for fast comparison */
} color_fracs;

#define LONGS_PER_4_FRACS ((FRACS_PER_LONG + 3) / 4)
#if LONGS_PER_4_FRACS == 1
#  define COLOR_FRACS_4_EQ(f1, f2)\
     ((f1).all[0] == (f2).all[0])
#else
#if LONGS_PER_4_FRACS == 2
#  define COLOR_FRACS_4_EQ(f1, f2)\
     ((f1).all[0] == (f2).all[0] && (f1).all[1] == (f2).all[1])
#endif
#endif

/* Test whether a color is transparent. */
private bool
mask_color12_matches(const frac *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 an image with more than 8 bits per sample. */
/* The samples have been expanded into fracs. */
private int
image_render_frac(gx_image_enum * penum, const byte * buffer, int data_x,
		  uint w, int h, gx_device * dev)
{
    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 xl, ytf;
    fixed pdyx, pdyy;		/* edge of parallelogram */
    int yt = penum->yci, iht = penum->hci;
    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_rgb)) = cmap_procs->map_rgb;
    cmap_proc_cmyk((*map_cmyk)) = cmap_procs->map_cmyk;
    bool use_mask_color = penum->use_mask_color;
    gx_device_color devc1, devc2;
    gx_device_color *pdevc = &devc1;
    gx_device_color *pdevc_next = &devc2;
    int spp = penum->spp;
    const frac *psrc_initial = (const frac *)buffer + data_x * spp;
    const frac *psrc = psrc_initial;
    const frac *rsrc = psrc + spp; /* psrc + spp at start of run */
    fixed xrun;			/* x at start of run */
    int irun;			/* int xrun */
    fixed yrun;			/* y ditto */
    color_fracs run;		/* run value */
    color_fracs next;		/* next sample value */
    const frac *bufend = psrc + w;
    int code = 0, mcode = 0;

    if (h == 0)
	return 0;
    pnext = penum->dda.pixel0;
    xrun = xl = dda_current(pnext.x);
    irun = fixed2int_var_rounded(xrun);
    yrun = ytf = dda_current(pnext.y);
    pdyx = dda_current(penum->dda.row.x) - penum->cur.x;
    pdyy = dda_current(penum->dda.row.y) - penum->cur.y;
    if_debug5(penum->memory, 'b', "[b]y=%d data_x=%d w=%d xt=%f yt=%f\n",
	      penum->y, data_x, w, fixed2float(xl), fixed2float(ytf));
    memset(&run, 0, sizeof(run));
    memset(&next, 0, sizeof(next));
    /* Ensure that we don't get any false dev_color_eq hits. */
    set_nonclient_dev_color(&devc1, gx_no_color_index);
    set_nonclient_dev_color(&devc2, gx_no_color_index);
    cs_full_init_color(&cc, pcs);
    run.v[0] = ~psrc[0];	/* force remap */

    while (psrc < bufend) {
	next.v[0] = psrc[0];
	switch (spp) {
	    case 4:		/* may be CMYK */
		next.v[1] = psrc[1];
		next.v[2] = psrc[2];
		next.v[3] = psrc[3];
		psrc += 4;
		if (COLOR_FRACS_4_EQ(next, run))
		    goto inc;
		if (use_mask_color && mask_color12_matches(next.v, penum, 4)) {
		    color_set_null(pdevc_next);
		    goto f;
		}
		if (device_color) {
		    (*map_cmyk) (next.v[0], next.v[1],
				 next.v[2], next.v[3],
				 pdevc_next, pis, dev,
				 gs_color_select_source);
		    goto f;
		}
		decode_frac(next.v[0], cc, 0);
		decode_frac(next.v[1], cc, 1);
		decode_frac(next.v[2], cc, 2);
		decode_frac(next.v[3], cc, 3);
		if_debug4(penum->memory, 'B', "[B]cc[0..3]=%g,%g,%g,%g\n",
			  cc.paint.values[0], cc.paint.values[1],
			  cc.paint.values[2], cc.paint.values[3]);
		if_debug1(penum->memory, 'B', "[B]cc[3]=%g\n",
			  cc.paint.values[3]);
		break;
	    case 3:		/* may be RGB */
		next.v[1] = psrc[1];
		next.v[2] = psrc[2];
		psrc += 3;
		if (COLOR_FRACS_4_EQ(next, run))
		    goto inc;
		if (use_mask_color && mask_color12_matches(next.v, penum, 3)) {
		    color_set_null(pdevc_next);
		    goto f;
		}
		if (device_color) {
		    (*map_rgb) (next.v[0], next.v[1],
				next.v[2], pdevc_next, pis, dev,
				gs_color_select_source);
		    goto f;
		}
		decode_frac(next.v[0], cc, 0);
		decode_frac(next.v[1], cc, 1);
		decode_frac(next.v[2], cc, 2);
		if_debug3(penum->memory, 'B', "[B]cc[0..2]=%g,%g,%g\n",
			  cc.paint.values[0], cc.paint.values[1],
			  cc.paint.values[2]);
		break;
	    case 1:		/* may be Gray */
		psrc++;
		if (next.v[0] == run.v[0])
		    goto inc;
		if (use_mask_color && mask_color12_matches(next.v, penum, 1)) {
		    color_set_null(pdevc_next);
		    goto f;
		}
		if (device_color) {
		    (*map_rgb) (next.v[0], next.v[0],
				next.v[0], pdevc_next, pis, dev,
				gs_color_select_source);
		    goto f;
		}
		decode_frac(next.v[0], cc, 0);
		if_debug1(penum->memory, 'B', "[B]cc[0]=%g\n",
			  cc.paint.values[0]);
		break;
	    default:		/* DeviceN */
		{
		    int i;

		    for (i = 1; i < spp; ++i)
			next.v[i] = psrc[i];
		    psrc += spp;
		    if (!memcmp(next.v, run.v, spp * sizeof(next.v[0])))
			goto inc;
		    if (use_mask_color &&
			mask_color12_matches(next.v, penum, spp)
			) {
			color_set_null(pdevc_next);
			goto f;
		    }
		    for (i = 0; i < spp; ++i)
			decode_frac(next.v[i], cc, i);
#ifdef DEBUG
		    if (gs_debug_c('B')) {
			dprintf2(penum->memory, "[B]cc[0..%d]=%g", spp - 1,
				 cc.paint.values[0]);
			for (i = 1; i < spp; ++i)
			    dprintf1(penum->memory, ",%g", cc.paint.values[i]);
			dputs(penum->memory, "\n");
		    }
#endif
		}
		break;
	}
	mcode = remap_color(&cc, pcs, pdevc_next, pis, dev,
			   gs_color_select_source);
	if (mcode < 0)
	    goto fill;
f:
	if_debug7(penum->memory, '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(devc1, devc2)) {
	    /* Fill the region between xrun/irun and xl */
	    gx_device_color *ptemp;

fill:
	    if (posture != image_portrait) {	/* Parallelogram */
		code = (*dev_proc(dev, fill_parallelogram))
		    (dev, xrun, yrun,
		     xl - xrun, ytf - yrun, pdyx, pdyy,
		     pdevc, lop);
	    } else {		/* Rectangle */
		int xi = irun;
		int wi = (irun = fixed2int_var_rounded(xl)) - xi;

		if (wi < 0)
		    xi += wi, wi = -wi;
		code = gx_fill_rectangle_device_rop(xi, yt,
						  wi, iht, pdevc, dev, lop);
	    }
	    if (code < 0)
		goto err;
	    rsrc = psrc;
	    if ((code = mcode) < 0)
		goto err;
	    ptemp = pdevc;
	    pdevc = pdevc_next;
	    pdevc_next = ptemp;
	    xrun = xl;
	    yrun = ytf;
	}
	run = next;
inc:
	xl = dda_next(pnext.x);
	ytf = dda_next(pnext.y);
    }
    /* Fill the final run. */
    code = (*dev_proc(dev, fill_parallelogram))
	(dev, xrun, yrun, xl - xrun, ytf - yrun, pdyx, pdyy, pdevc, lop);
    return (code < 0 ? code : 1);

    /* Save position if error, in case we resume. */
err:
    penum->used.x = (rsrc - spp - psrc_initial) / spp;
    penum->used.y = 0;
    return code;
}
