/* Copyright (C) 1997, 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: gdevpsds.c,v 1.4.6.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* Image processing streams for PostScript and PDF writers */
#include "gx.h"
#include "memory_.h"
#include "gserrors.h"
#include "gxdcconv.h"
#include "gdevpsds.h"

/* ---------------- Convert between 1/2/4/12 and 8 bits ---------------- */

gs_private_st_simple(st_1248_state, stream_1248_state, "stream_1248_state");

/* Initialize an expansion or reduction stream. */
int
s_1248_init(stream_1248_state *ss, int Columns, int samples_per_pixel)
{
    ss->samples_per_row = Columns * samples_per_pixel;
    return ss->template->init((stream_state *)ss);
}

/* Initialize the state. */
private int
s_1_init(stream_state * st)
{
    stream_1248_state *const ss = (stream_1248_state *) st;

    ss->left = ss->samples_per_row;
    ss->bits_per_sample = 1;
    return 0;
}
private int
s_2_init(stream_state * st)
{
    stream_1248_state *const ss = (stream_1248_state *) st;

    ss->left = ss->samples_per_row;
    ss->bits_per_sample = 2;
    return 0;
}
private int
s_4_init(stream_state * st)
{
    stream_1248_state *const ss = (stream_1248_state *) st;

    ss->left = ss->samples_per_row;
    ss->bits_per_sample = 4;
    return 0;
}
private int
s_12_init(stream_state * st)
{
    stream_1248_state *const ss = (stream_1248_state *) st;

    ss->left = ss->samples_per_row;
    ss->bits_per_sample = 12;	/* not needed */
    return 0;
}

/* Process one buffer. */
#define BEGIN_1248\
	stream_1248_state * const ss = (stream_1248_state *)st;\
	const byte *p = pr->ptr;\
	const byte *rlimit = pr->limit;\
	byte *q = pw->ptr;\
	byte *wlimit = pw->limit;\
	uint left = ss->left;\
	int status;\
	int n
#define END_1248\
	pr->ptr = p;\
	pw->ptr = q;\
	ss->left = left;\
	return status

/* N-to-8 expansion */
#define FOREACH_N_8(in, nout)\
	status = 0;\
	for ( ; p < rlimit; left -= n, q += n, ++p ) {\
	  byte in = p[1];\
	  n = min(left, nout);\
	  if ( wlimit - q < n ) {\
	    status = 1;\
	    break;\
	  }\
	  switch ( n ) {\
	    case 0: left = ss->samples_per_row; --p; continue;
#define END_FOREACH_N_8\
	  }\
	}
private int
s_N_8_process(stream_state * st, stream_cursor_read * pr,
	      stream_cursor_write * pw, bool last)
{
    BEGIN_1248;

    switch (ss->bits_per_sample) {

	case 1:{
		FOREACH_N_8(in, 8)
	case 8:
		q[8] = (byte) - (in & 1);
	case 7:
		q[7] = (byte) - ((in >> 1) & 1);
	case 6:
		q[6] = (byte) - ((in >> 2) & 1);
	case 5:
		q[5] = (byte) - ((in >> 3) & 1);
	case 4:
		q[4] = (byte) - ((in >> 4) & 1);
	case 3:
		q[3] = (byte) - ((in >> 5) & 1);
	case 2:
		q[2] = (byte) - ((in >> 6) & 1);
	case 1:
		q[1] = (byte) - (in >> 7);
		END_FOREACH_N_8;
	    }
	    break;

	case 2:{
		static const byte b2[4] =
		{0x00, 0x55, 0xaa, 0xff};

		FOREACH_N_8(in, 4)
	case 4:
		q[4] = b2[in & 3];
	case 3:
		q[3] = b2[(in >> 2) & 3];
	case 2:
		q[2] = b2[(in >> 4) & 3];
	case 1:
		q[1] = b2[in >> 6];
		END_FOREACH_N_8;
	    }
	    break;

	case 4:{
		static const byte b4[16] =
		{
		    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
		    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
		};

		FOREACH_N_8(in, 2)
	case 2:
		q[2] = b4[in & 0xf];
	case 1:
		q[1] = b4[in >> 4];
		END_FOREACH_N_8;
	    }
	    break;

	default:
	    return ERRC;
    }

    END_1248;
}

/* 12-to-8 "expansion" */
private int
s_12_8_process(stream_state * st, stream_cursor_read * pr,
	       stream_cursor_write * pw, bool last)
{
    BEGIN_1248;

    n = ss->samples_per_row;	/* misuse n to avoid a compiler warning */
    status = 0;
    for (; rlimit - p >= 2; ++q) {
	if (q >= wlimit) {
	    status = 1;
	    break;
	}
	if (left == 0)
	    left = n;
	if ((n - left) & 1) {
	    q[1] = (byte)((p[1] << 4) | (p[2] >> 4));
	    p += 2, --left;
	} else {
	    q[1] = *++p;
	    if (!--left)
		++p;
	}
    }

    END_1248;
}


/* 8-to-N reduction */
#define FOREACH_8_N(out, nin)\
	byte out;\
	status = 1;\
	for ( ; q < wlimit; left -= n, p += n, ++q ) {\
	  n = min(left, nin);\
	  if ( rlimit - p < n ) {\
	    status = 0;\
	    break;\
	  }\
	  out = 0;\
	  switch ( n ) {\
	    case 0: left = ss->samples_per_row; --q; continue;
#define END_FOREACH_8_N\
	    q[1] = out;\
	  }\
	}
private int
s_8_N_process(stream_state * st, stream_cursor_read * pr,
	      stream_cursor_write * pw, bool last)
{
    BEGIN_1248;

    switch (ss->bits_per_sample) {

	case 1:{
		FOREACH_8_N(out, 8)
	case 8:
		out = p[8] >> 7;
	case 7:
		out |= (p[7] >> 7) << 1;
	case 6:
		out |= (p[6] >> 7) << 2;
	case 5:
		out |= (p[5] >> 7) << 3;
	case 4:
		out |= (p[4] >> 7) << 4;
	case 3:
		out |= (p[3] >> 7) << 5;
	case 2:
		out |= (p[2] >> 7) << 6;
	case 1:
		out |= p[1] & 0x80;
		END_FOREACH_8_N;
	    }
	    break;

	case 2:{
		FOREACH_8_N(out, 4)
	case 4:
		out |= p[4] >> 6;
	case 3:
		out |= (p[3] >> 6) << 2;
	case 2:
		out |= (p[2] >> 6) << 4;
	case 1:
		out |= p[1] & 0xc0;
		END_FOREACH_8_N;
	    }
	    break;

	case 4:{
		FOREACH_8_N(out, 2)
	case 2:
		out |= p[2] >> 4;
	case 1:
		out |= p[1] & 0xf0;
		END_FOREACH_8_N;
	    }
	    break;

	default:
	    return ERRC;
    }

    END_1248;
}

const stream_template s_1_8_template = {
    &st_1248_state, s_1_init, s_N_8_process, 1, 8
};
const stream_template s_2_8_template = {
    &st_1248_state, s_2_init, s_N_8_process, 1, 4
};
const stream_template s_4_8_template = {
    &st_1248_state, s_4_init, s_N_8_process, 1, 2
};
const stream_template s_12_8_template = {
    &st_1248_state, s_12_init, s_12_8_process, 1, 2
};

const stream_template s_8_1_template = {
    &st_1248_state, s_1_init, s_8_N_process, 8, 1
};
const stream_template s_8_2_template = {
    &st_1248_state, s_2_init, s_8_N_process, 4, 1
};
const stream_template s_8_4_template = {
    &st_1248_state, s_4_init, s_8_N_process, 2, 1
};

/* ---------------- Color space conversion ---------------- */

/* ------ Convert CMYK to RGB ------ */

private_st_C2R_state();

/* Initialize a CMYK => RGB conversion stream. */
int
s_C2R_init(stream_C2R_state *ss, const gs_imager_state *pis)
{
    ss->pis = pis;
    return 0;
}

/* Set default parameter values (actually, just clear pointers). */
private void
s_C2R_set_defaults(stream_state * st)
{
    stream_C2R_state *const ss = (stream_C2R_state *) st;

    ss->pis = 0;
}

/* Process one buffer. */
private int
s_C2R_process(stream_state * st, stream_cursor_read * pr,
	      stream_cursor_write * pw, bool last)
{
    stream_C2R_state *const ss = (stream_C2R_state *) st;
    const byte *p = pr->ptr;
    const byte *rlimit = pr->limit;
    byte *q = pw->ptr;
    byte *wlimit = pw->limit;

    for (; rlimit - p >= 4 && wlimit - q >= 3; p += 4, q += 3) {
	byte bc = p[1], bm = p[2], by = p[3], bk = p[4];
	frac rgb[3];

	color_cmyk_to_rgb(byte2frac(bc), byte2frac(bm), byte2frac(by),
			  byte2frac(bk), ss->pis, rgb);
	q[1] = frac2byte(rgb[0]);
	q[2] = frac2byte(rgb[1]);
	q[3] = frac2byte(rgb[2]);
    }
    pr->ptr = p;
    pw->ptr = q;
    return (rlimit - p < 4 ? 0 : 1);
}

const stream_template s_C2R_template = {
    &st_C2R_state, 0 /*NULL */ , s_C2R_process, 4, 3, 0, s_C2R_set_defaults
};

/* ------ Convert any color space to Indexed ------ */

private_st_IE_state();
private
ENUM_PTRS_WITH(ie_state_enum_ptrs, stream_IE_state *st) return 0;
case 0: return ENUM_OBJ(st->Decode);
case 1: return ENUM_BYTESTRING(&st->Table);
ENUM_PTRS_END
private
RELOC_PTRS_WITH(ie_state_reloc_ptrs, stream_IE_state *st)
{
    RELOC_VAR(st->Decode);
    RELOC_BYTESTRING_VAR(st->Table);
}
RELOC_PTRS_END

/* Set defaults. */
private void
s_IE_set_defaults(stream_state * st)
{
    stream_IE_state *const ss = (stream_IE_state *) st;

    ss->Decode = 0;		/* clear pointers */
    gs_bytestring_from_string(&ss->Table, 0, 0);
}

/* Initialize the state. */
private int
s_IE_init(stream_state * st)
{
    stream_IE_state *const ss = (stream_IE_state *) st;
    int key_index = (1 << ss->BitsPerIndex) * ss->NumComponents;
    int i;

    if (ss->Table.data == 0 || ss->Table.size < key_index)
	return ERRC;		/****** WRONG ******/
    /* Initialize Table with default values. */
    memset(ss->Table.data, 0, ss->NumComponents);
    ss->Table.data[ss->Table.size - 1] = 0;
    for (i = 0; i < countof(ss->hash_table); ++i)
	ss->hash_table[i] = key_index;
    ss->next_index = 0;
    ss->in_bits_left = 0;
    ss->next_component = 0;
    ss->byte_out = 1;
    ss->x = 0;
    return 0;
}

/* Process a buffer. */
private int
s_IE_process(stream_state * st, stream_cursor_read * pr,
	     stream_cursor_write * pw, bool last)
{
    stream_IE_state *const ss = (stream_IE_state *) st;
    /* Constant values from the state */
    const int bpc = ss->BitsPerComponent;
    const int num_components = ss->NumComponents;
    const int end_index = (1 << ss->BitsPerIndex) * num_components;
    byte *const table = ss->Table.data;
    byte *const key = table + end_index;
    /* Dynamic values from the state */
    uint byte_in = ss->byte_in;
    int in_bits_left = ss->in_bits_left;
    int next_component = ss->next_component;
    uint byte_out = ss->byte_out;
    /* Other dynamic values */
    const byte *p = pr->ptr;
    const byte *rlimit = pr->limit;
    byte *q = pw->ptr;
    byte *wlimit = pw->limit;
    int status = 0;

    for (;;) {
	uint hash, reprobe;
	int i, index;

	/* Check for a filled output byte. */
	if (byte_out >= 0x100) {
	    if (q >= wlimit) {
		status = 1;
		break;
	    }
	    *++q = (byte)byte_out;
	    byte_out = 1;
	}
	/* Acquire a complete input value. */
	while (next_component < num_components) {
	    const float *decode = &ss->Decode[next_component * 2];
	    int sample;

	    if (in_bits_left == 0) {
		if (p >= rlimit)
		    goto out;
		byte_in = *++p;
		in_bits_left = 8;
	    }
	    /* An input sample can never span a byte boundary. */
	    in_bits_left -= bpc;
	    sample = (byte_in >> in_bits_left) & ((1 << bpc) - 1);
	    /* Scale the sample according to Decode. */
	    sample = (int)((decode[0] +
			    (sample / (float)((1 << bpc) - 1) *
			     (decode[1] - decode[0]))) * 255 + 0.5);
	    key[next_component++] =
		(sample < 0 ? 0 : sample > 255 ? 255 : (byte)sample);
	}
	/* Look up the input value. */
	for (hash = 0, i = 0; i < num_components; ++i)
	    hash = hash + 23 * key[i];  /* adhoc */
	reprobe = (hash / countof(ss->hash_table)) | 137;  /* adhoc */
	for (hash %= countof(ss->hash_table);
	     memcmp(table + ss->hash_table[hash], key, num_components);
	     hash = (hash + reprobe) % countof(ss->hash_table)
	     )
	    DO_NOTHING;
	index = ss->hash_table[hash];
	if (index == end_index) {
	    /* The match was on an empty entry. */
	    if (ss->next_index == end_index) {
		/* Too many different values. */
		status = ERRC;
		break;
	    }
	    ss->hash_table[hash] = index = ss->next_index;
	    ss->next_index += num_components;
	    memcpy(table + index, key, num_components);
	}
	byte_out = (byte_out << ss->BitsPerIndex) + index / num_components;
	next_component = 0;
	if (++(ss->x) == ss->Width) {
	    /* Handle input and output padding. */
	    in_bits_left = 0;
	    if (byte_out != 1)
		while (byte_out < 0x100)
		    byte_out <<= 1;
	    ss->x = 0;
	}
    }
out:
    pr->ptr = p;
    pw->ptr = q;
    ss->byte_in = byte_in;
    ss->in_bits_left = in_bits_left;
    ss->next_component = next_component;
    ss->byte_out = byte_out;
    /* For simplicity, always update the record of the table size. */
    ss->Table.data[ss->Table.size - 1] =
	(ss->next_index == 0 ? 0 :
	 ss->next_index / ss->NumComponents - 1);
    return status;
}

const stream_template s_IE_template = {
    &st_IE_state, s_IE_init, s_IE_process, 1, 1,
    0 /* NULL */, s_IE_set_defaults
};

#if 0

/* Test code */
void
test_IE(void)
{
    const stream_template *const template = &s_IE_template;
    stream_IE_state state;
    stream_state *const ss = (stream_state *)&state;
    static const float decode[6] = {1, 0, 1, 0, 1, 0};
    static const byte in[] = {
	/*
	 * Each row is 3 pixels x 3 components x 4 bits.  Processing the
	 * first two rows doesn't cause an error; processing all 3 rows
	 * does.
	 */
	0x12, 0x35, 0x67, 0x9a, 0xb0,
	0x56, 0x7d, 0xef, 0x12, 0x30,
	0x88, 0x88, 0x88, 0x88, 0x80
    };
    byte table[3 * 5];
    int n;

    template->set_defaults(ss);
    state.BitsPerComponent = 4;
    state.NumComponents = 3;
    state.Width = 3;
    state.BitsPerIndex = 2;
    state.Decode = decode;
    gs_bytestring_from_bytes(&state.Table, table, 0, sizeof(table));
    for (n = 10; n <= 15; n += 5) {
	stream_cursor_read r;
	stream_cursor_write w;
	byte out[100];
	int status;

	s_IE_init(ss);
	r.ptr = in; --r.ptr;
	r.limit = r.ptr + n;
	w.ptr = out; --w.ptr;
	w.limit = w.ptr + sizeof(out);
	memset(table, 0xcc, sizeof(table));
	memset(out, 0xff, sizeof(out));
	dprintf1("processing %d bytes\n", n);
	status = template->process(ss, &r, &w, true);
	dprintf3("%d bytes read, %d bytes written, status = %d\n",
		 (int)(r.ptr + 1 - in), (int)(w.ptr + 1 - out), status);
	debug_dump_bytes(table, table + sizeof(table), "table");
	debug_dump_bytes(out, w.ptr + 1, "out");
    }
}

#endif

/* ---------------- Downsampling ---------------- */

/* Return the number of samples after downsampling. */
int
s_Downsample_size_out(int size_in, int factor, bool pad)
{
    return ((pad ? size_in + factor - 1 : size_in) / factor);
}

private void
s_Downsample_set_defaults(register stream_state * st)
{
    stream_Downsample_state *const ss = (stream_Downsample_state *)st;

    s_Downsample_set_defaults_inline(ss);
}

/* ------ Subsample ------ */

gs_private_st_simple(st_Subsample_state, stream_Subsample_state,
		     "stream_Subsample_state");

/* Initialize the state. */
private int
s_Subsample_init(stream_state * st)
{
    stream_Subsample_state *const ss = (stream_Subsample_state *) st;

    ss->x = ss->y = 0;
    return 0;
}

/* Process one buffer. */
private int
s_Subsample_process(stream_state * st, stream_cursor_read * pr,
		    stream_cursor_write * pw, bool last)
{
    stream_Subsample_state *const ss = (stream_Subsample_state *) st;
    const byte *p = pr->ptr;
    const byte *rlimit = pr->limit;
    byte *q = pw->ptr;
    byte *wlimit = pw->limit;
    int spp = ss->Colors;
    int width = ss->WidthIn, height = ss->HeightIn;
    int xf = ss->XFactor, yf = ss->YFactor;
    int xf2 = xf / 2, yf2 = yf / 2;
    int xlimit = (width / xf) * xf, ylimit = (height / yf) * yf;
    int xlast =
	(ss->padX && xlimit < width ? xlimit + (width % xf) / 2 : -1);
    int ylast =
	(ss->padY && ylimit < height ? ylimit + (height % yf) / 2 : -1);
    int x = ss->x, y = ss->y;
    int status = 0;

    if_debug4('w', "[w]subsample: x=%d, y=%d, rcount=%ld, wcount=%ld\n",
	      x, y, (long)(rlimit - p), (long)(wlimit - q));
    for (; rlimit - p >= spp; p += spp) {
	if (((y % yf == yf2 && y < ylimit) || y == ylast) &&
	    ((x % xf == xf2 && x < xlimit) || x == xlast)
	    ) {
	    if (wlimit - q < spp) {
		status = 1;
		break;
	    }
	    memcpy(q + 1, p + 1, spp);
	    q += spp;
	}
	if (++x == width)
	    x = 0, ++y;
    }
    if_debug5('w',
	      "[w]subsample: x'=%d, y'=%d, read %ld, wrote %ld, status = %d\n",
	      x, y, (long)(p - pr->ptr), (long)(q - pw->ptr), status);
    pr->ptr = p;
    pw->ptr = q;
    ss->x = x, ss->y = y;
    return status;
}

const stream_template s_Subsample_template = {
    &st_Subsample_state, s_Subsample_init, s_Subsample_process, 4, 4,
    0 /* NULL */, s_Downsample_set_defaults
};

/* ------ Average ------ */

private_st_Average_state();

/* Set default parameter values (actually, just clear pointers). */
private void
s_Average_set_defaults(stream_state * st)
{
    stream_Average_state *const ss = (stream_Average_state *) st;

    s_Downsample_set_defaults(st);
    /* Clear pointers */
    ss->sums = 0;
}

/* Initialize the state. */
private int
s_Average_init(stream_state * st)
{
    stream_Average_state *const ss = (stream_Average_state *) st;

    ss->sum_size =
	ss->Colors * ((ss->WidthIn + ss->XFactor - 1) / ss->XFactor);
    ss->copy_size = ss->sum_size -
	(ss->padX || (ss->WidthIn % ss->XFactor == 0) ? 0 : ss->Colors);
    ss->sums =
	(uint *)gs_alloc_byte_array(st->memory, ss->sum_size,
				    sizeof(uint), "Average sums");
    if (ss->sums == 0)
	return ERRC;	/****** WRONG ******/
    memset(ss->sums, 0, ss->sum_size * sizeof(uint));
    return s_Subsample_init(st);
}

/* Release the state. */
private void
s_Average_release(stream_state * st)
{
    stream_Average_state *const ss = (stream_Average_state *) st;

    gs_free_object(st->memory, ss->sums, "Average sums");
}

/* Process one buffer. */
private int
s_Average_process(stream_state * st, stream_cursor_read * pr,
		  stream_cursor_write * pw, bool last)
{
    stream_Average_state *const ss = (stream_Average_state *) st;
    const byte *p = pr->ptr;
    const byte *rlimit = pr->limit;
    byte *q = pw->ptr;
    byte *wlimit = pw->limit;
    int spp = ss->Colors;
    int width = ss->WidthIn;
    int xf = ss->XFactor, yf = ss->YFactor;
    int x = ss->x, y = ss->y;
    uint *sums = ss->sums;
    int status = 0;

top:
    if (y == yf || (last && p >= rlimit && ss->padY && y != 0)) {
	/* We're copying averaged values to the output. */
	int ncopy = min(ss->copy_size - x, wlimit - q);

	if (ncopy) {
	    int scale = xf * y;

	    while (--ncopy >= 0)
		*++q = (byte) (sums[x++] / scale);
	}
	if (x < ss->copy_size) {
	    status = 1;
	    goto out;
	}
	/* Done copying. */
	x = y = 0;
	memset(sums, 0, ss->sum_size * sizeof(uint));
    }
    while (rlimit - p >= spp) {
	uint *bp = sums + x / xf * spp;
	int i;

	for (i = spp; --i >= 0;)
	    *bp++ += *++p;
	if (++x == width) {
	    x = 0;
	    ++y;
	    goto top;
	}
    }
out:
    pr->ptr = p;
    pw->ptr = q;
    ss->x = x, ss->y = y;
    return status;
}

const stream_template s_Average_template = {
    &st_Average_state, s_Average_init, s_Average_process, 4, 4,
    s_Average_release, s_Average_set_defaults
};
