/* Copyright (C) 1998 Aladdin Enterprises.  All rights reserved.

   This file is part of Aladdin Ghostscript.

   Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
   or distributor accepts any responsibility for the consequences of using it,
   or for whether it serves any particular purpose or works at all, unless he
   or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
   License (the "License") for full details.

   Every copy of Aladdin Ghostscript must include a copy of the License,
   normally in a plain ASCII text file named PUBLIC.  The License grants you
   the right to copy, modify and redistribute Aladdin Ghostscript, but only
   under certain conditions described in the License.  Among other things, the
   License requires that the copyright notice and this notice be preserved on
   all copies.
 */

/*$Id: gscrdp.c $ */
/* CIE color rendering dictionary creation */
#include "math_.h"
#include "memory_.h"
#include "gx.h"
#include "gsdevice.h"
#include "gserrors.h"
#include "gsmatrix.h"		/* for gscolor2.h */
#include "gxcspace.h"
#include "gscolor2.h"		/* for gs_set/currentcolorrendering */
#include "gscrdp.h"
#include "gxarith.h"

/* Define the CRD type that we use here. */
#define CRD_TYPE 101

/* ---------------- Writing ---------------- */

/* Internal procedures for writing parameter values. */
private void
store_vector3(float *p, const gs_vector3 * pvec)
{
    p[0] = pvec->u, p[1] = pvec->v, p[2] = pvec->w;
}
private int
write_floats(gs_param_list * plist, gs_param_name key,
	     const float *values, int size, gs_memory_t * mem)
{
    float *p = (float *)
	gs_alloc_byte_array(mem, size, sizeof(float), "write_floats");
    gs_param_float_array fa;

    if (p == 0)
	return_error(gs_error_VMerror);
    memcpy(p, values, size * sizeof(float));

    fa.data = p;
    fa.size = size;
    fa.persistent = true;
    return param_write_float_array(plist, key, &fa);
}
private int
write_vector3(gs_param_list * plist, gs_param_name key,
	      const gs_vector3 * pvec, gs_memory_t * mem)
{
    float values[3];

    store_vector3(values, pvec);
    return write_floats(plist, key, values, 3, mem);
}
private int
write_matrix3(gs_param_list * plist, gs_param_name key,
	      const gs_matrix3 * pmat, gs_memory_t * mem)
{
    float values[9];

    if (!memcmp(pmat, &Matrix3_default, sizeof(*pmat)))
	return 0;
    store_vector3(values, &pmat->cu);
    store_vector3(values + 3, &pmat->cv);
    store_vector3(values + 6, &pmat->cw);
    return write_floats(plist, key, values, 9, mem);
}
private int
write_range3(gs_param_list * plist, gs_param_name key,
	     const gs_range3 * prange, gs_memory_t * mem)
{
    float values[6];

    if (!memcmp(prange, &Range3_default, sizeof(*prange)))
	return 0;
    values[0] = prange->ranges[0].rmin, values[1] = prange->ranges[0].rmax;
    values[2] = prange->ranges[1].rmin, values[3] = prange->ranges[1].rmax;
    values[4] = prange->ranges[2].rmin, values[5] = prange->ranges[2].rmax;
    return write_floats(plist, key, values, 6, mem);
}
private int
write_proc3(gs_param_list * plist, gs_param_name key,
	    const gs_cie_render * pcrd, const gs_cie_render_proc3 * procs,
	    const gs_range3 * domain, gs_memory_t * mem)
{
    float *values;
    uint size = gx_cie_cache_size;
    gs_param_float_array fa;
    int i;

    if (!memcmp(procs, &Encode_default, sizeof(*procs)))
	return 0;
    values = (float *)gs_alloc_byte_array(mem, size * 3, sizeof(float),
					  "write_proc3");

    if (values == 0)
	return_error(gs_error_VMerror);
    for (i = 0; i < 3; ++i) {
	double base = domain->ranges[i].rmin;
	double scale = (domain->ranges[i].rmax - base) / (size - 1);
	int j;

	for (j = 0; j < size; ++j)
	    values[i * size + j] =
		(*procs->procs[i]) (j * scale + base, pcrd);
    }
    fa.data = values;
    fa.size = size * 3;
    fa.persistent = true;
    return param_write_float_array(plist, key, &fa);
}

/* Write a CRD as a device parameter. */
int
param_write_cie_render1(gs_param_list * plist, gs_param_name key,
			const gs_cie_render * pcrd, gs_memory_t * mem)
{
    gs_param_dict dict;
    int code, dcode;

    dict.size = 20;
    if ((code = param_begin_write_dict(plist, key, &dict, false)) < 0)
	return code;
    code = param_put_cie_render1(dict.list, pcrd, mem);
    dcode = param_end_write_dict(plist, key, &dict);
    return (code < 0 ? code : dcode);
}

/* Write a CRD directly to a parameter list. */
int
param_put_cie_render1(gs_param_list * plist, const gs_cie_render * pcrd,
		      gs_memory_t * mem)
{
    int crd_type = CRD_TYPE;
    int code;

    if (pcrd->TransformPQR.proc_name) {
	gs_param_string pn, pd;

	param_string_from_string(pn, pcrd->TransformPQR.proc_name);
	pn.size++;		/* include terminating null */
	pd.data = pcrd->TransformPQR.proc_data.data;
	pd.size = pcrd->TransformPQR.proc_data.size;
	pd.persistent = true;  /****** WRONG ******/
	if ((code = param_write_name(plist, "TransformPQRName", &pn)) < 0 ||
	    (code = param_write_string(plist, "TransformPQRData", &pd)) < 0
	    )
	    return code;
    }
    else if (pcrd->TransformPQR.proc != TransformPQR_default.proc) {
	/* We have no way to represent the procedure, so return an error. */
	return_error(gs_error_rangecheck);
    }
    if ((code = param_write_int(plist, "ColorRenderingType", &crd_type)) < 0 ||
	(code = write_vector3(plist, "WhitePoint", &pcrd->points.WhitePoint, mem)) < 0
	)
	return code;
    if (memcmp(&pcrd->points.BlackPoint, &BlackPoint_default,
	       sizeof(pcrd->points.BlackPoint))) {
	if ((code = write_vector3(plist, "BlackPoint", &pcrd->points.BlackPoint, mem)) < 0)
	    return code;
    }
    if ((code = write_matrix3(plist, "MatrixPQR", &pcrd->MatrixPQR, mem)) < 0 ||
	(code = write_range3(plist, "RangePQR", &pcrd->RangePQR, mem)) < 0 ||
    /* TransformPQR is handled separately */
    (code = write_matrix3(plist, "MatrixLMN", &pcrd->MatrixLMN, mem)) < 0 ||
	(code = write_proc3(plist, "EncodeLMNValues", pcrd,
			    &pcrd->EncodeLMN, &pcrd->DomainLMN, mem)) < 0 ||
	(code = write_range3(plist, "RangeLMN", &pcrd->RangeLMN, mem)) < 0 ||
    (code = write_matrix3(plist, "MatrixABC", &pcrd->MatrixABC, mem)) < 0 ||
	(code = write_proc3(plist, "EncodeABCValues", pcrd,
			    &pcrd->EncodeABC, &pcrd->DomainABC, mem)) < 0 ||
	(code = write_range3(plist, "RangeABC", &pcrd->RangeABC, mem)) < 0
	)
	return code;
    if (pcrd->RenderTable.lookup.table) {
	int n = pcrd->RenderTable.lookup.n;
	int m = pcrd->RenderTable.lookup.m;
	int na = pcrd->RenderTable.lookup.dims[0];
	int *size = (int *)
	    gs_alloc_byte_array(mem, n + 1, sizeof(int), "RenderTableSize");

	/*
	 * In principle, we should use gs_alloc_struct_array with a
	 * type descriptor for gs_param_string.  However, it is widely
	 * assumed that parameter lists are transient, and don't require
	 * accurate GC information; so we can get away with allocating
	 * the string table as bytes.
	 */
	gs_param_string *table =
	    (gs_param_string *)
	    gs_alloc_byte_array(mem, na, sizeof(gs_param_string),
				"RenderTableTable");
	gs_param_int_array ia;

	if (size == 0 || table == 0)
	    code = gs_note_error(gs_error_VMerror);
	else {
	    memcpy(size, pcrd->RenderTable.lookup.dims, sizeof(int) * n);

	    size[n] = m;
	    ia.data = size;
	    ia.size = n + 1;
	    ia.persistent = true;
	    code = param_write_int_array(plist, "RenderTableSize", &ia);
	}
	if (code >= 0) {
	    gs_param_string_array sa;
	    int a;

	    for (a = 0; a < na; ++a)
		table[a].data = pcrd->RenderTable.lookup.table[a].data,
		    table[a].size = pcrd->RenderTable.lookup.table[a].size,
		    table[a].persistent = true;
	    sa.data = table;
	    sa.size = na;
	    sa.persistent = true;
	    code = param_write_string_array(plist, "RenderTableTable", &sa);
	    if (code >= 0 && !pcrd->caches.RenderTableT_is_identity) {
		/****** WRITE RenderTableTValues LIKE write_proc3 ******/
		uint size = gx_cie_cache_size;
		float *values =
		    (float *)gs_alloc_byte_array(mem, size * m,
						 sizeof(float),
						 "write_proc3");
		gs_param_float_array fa;
		int i;

		if (values == 0)
		    return_error(gs_error_VMerror);
		for (i = 0; i < m; ++i) {
		    double scale = 255.0 / (size - 1);
		    int j;

		    for (j = 0; j < size; ++j)
			values[i * size + j] =
			    frac2float((*pcrd->RenderTable.T.procs[i])
				       (j * scale, pcrd));
		}
		fa.data = values;
		fa.size = size * m;
		fa.persistent = true;
		code = param_write_float_array(plist, "RenderTableTValues",
					       &fa);
	    }
	}
	if (code < 0) {
	    gs_free_object(mem, table, "RenderTableTable");
	    gs_free_object(mem, size, "RenderTableSize");
	    return code;
	}
    }
    return code;
}

/* ---------------- Reading ---------------- */

/* Internal procedures for reading parameter values. */
private void
load_vector3(gs_vector3 * pvec, const float *p)
{
    pvec->u = p[0], pvec->v = p[1], pvec->w = p[2];
}
private int
read_floats(gs_param_list * plist, gs_param_name key, float *values, int count)
{
    gs_param_float_array fa;
    int code = param_read_float_array(plist, key, &fa);

    if (code)
	return code;
    if (fa.size != count)
	return_error(gs_error_rangecheck);
    memcpy(values, fa.data, sizeof(float) * count);

    return 0;
}
private int
read_vector3(gs_param_list * plist, gs_param_name key,
	     gs_vector3 * pvec, const gs_vector3 * dflt)
{
    float values[3];
    int code = read_floats(plist, key, values, 3);

    switch (code) {
	case 1:		/* not defined */
	    if (dflt)
		*pvec = *dflt;
	    break;
	case 0:
	    load_vector3(pvec, values);
	default:		/* error */
	    break;
    }
    return code;
}
private int
read_matrix3(gs_param_list * plist, gs_param_name key, gs_matrix3 * pmat)
{
    float values[9];
    int code = read_floats(plist, key, values, 9);

    switch (code) {
	case 1:		/* not defined */
	    *pmat = Matrix3_default;
	    break;
	case 0:
	    load_vector3(&pmat->cu, values);
	    load_vector3(&pmat->cv, values + 3);
	    load_vector3(&pmat->cw, values + 6);
	default:		/* error */
	    break;
    }
    return code;
}
private int
read_range3(gs_param_list * plist, gs_param_name key, gs_range3 * prange)
{
    float values[6];
    int code = read_floats(plist, key, values, 6);

    switch (code) {
	case 1:		/* not defined */
	    *prange = Range3_default;
	    break;
	case 0:
	    prange->ranges[0].rmin = values[0];
	    prange->ranges[0].rmax = values[1];
	    prange->ranges[1].rmin = values[2];
	    prange->ranges[1].rmax = values[3];
	    prange->ranges[2].rmin = values[4];
	    prange->ranges[2].rmax = values[5];
	default:		/* error */
	    break;
    }
    return code;
}
private int
read_proc3(gs_param_list * plist, gs_param_name key,
	   float values[gx_cie_cache_size * 3])
{
    return read_floats(plist, key, values, gx_cie_cache_size * 3);
}

/* Read a CRD from a device parameter. */
int
gs_cie_render1_param_initialize(gs_cie_render * pcrd, gs_param_list * plist,
				gs_param_name key, gx_device * dev)
{
    gs_param_dict dict;
    int code = param_begin_read_dict(plist, key, &dict, false);
    int dcode;

    if (code < 0)
	return code;
    code = param_get_cie_render1(pcrd, dict.list, dev);
    dcode = param_end_read_dict(plist, key, &dict);
    if (code < 0)
	return code;
    if (dcode < 0)
	return dcode;
    gs_cie_render_init(pcrd);
    gs_cie_render_sample(pcrd);
    return gs_cie_render_complete(pcrd);
}

/* Define the structure for passing Encode values as "client data". */
typedef struct encode_data_s {
    float lmn[gx_cie_cache_size * 3]; /* EncodeLMN */
    float abc[gx_cie_cache_size * 3]; /* EncodeABC */
    float t[gx_cie_cache_size * 4]; /* RenderTable.T */
} encode_data_t;

/* Define procedures that retrieve the Encode values read from the list. */
private float
encode_from_data(floatp v, const float values[gx_cie_cache_size],
		 const gs_range * range)
{
    return (v <= range->rmin ? values[0] :
	    v >= range->rmax ? values[gx_cie_cache_size - 1] :
	    values[(int)((v - range->rmin) / (range->rmax - range->rmin) *
			 (gx_cie_cache_size - 1) + 0.5)]);
}
/*
 * The repetitive boilerplate in the next 10 procedures really sticks in
 * my craw, but I've got a mandate not to use macros....
 */
private float
encode_lmn_0_from_data(floatp v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->lmn[0],
			    &pcrd->DomainLMN.ranges[0]);
}
private float
encode_lmn_1_from_data(floatp v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->lmn[gx_cie_cache_size],
			    &pcrd->DomainLMN.ranges[1]);
}
private float
encode_lmn_2_from_data(floatp v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->lmn[gx_cie_cache_size * 2],
			    &pcrd->DomainLMN.ranges[2]);
}
private float
encode_abc_0_from_data(floatp v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->abc[0],
			    &pcrd->DomainABC.ranges[0]);
}
private float
encode_abc_1_from_data(floatp v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->abc[gx_cie_cache_size],
			    &pcrd->DomainABC.ranges[1]);
}
private float
encode_abc_2_from_data(floatp v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return encode_from_data(v, &data->abc[gx_cie_cache_size * 2],
			    &pcrd->DomainABC.ranges[2]);
}
private frac
render_table_t_0_from_data(byte v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return float2frac(encode_from_data(v / 255.0,
				       &data->t[0],
				       &Range3_default.ranges[0]));
}
private frac
render_table_t_1_from_data(byte v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return float2frac(encode_from_data(v / 255.0,
				       &data->t[gx_cie_cache_size],
				       &Range3_default.ranges[0]));
}
private frac
render_table_t_2_from_data(byte v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return float2frac(encode_from_data(v / 255.0,
				       &data->t[gx_cie_cache_size * 2],
				       &Range3_default.ranges[0]));
}
private frac
render_table_t_3_from_data(byte v, const gs_cie_render * pcrd)
{
    const encode_data_t *data = pcrd->client_data;

    return float2frac(encode_from_data(v / 255.0,
				       &data->t[gx_cie_cache_size * 3],
				       &Range3_default.ranges[0]));
}
private const gs_cie_render_proc3 EncodeLMN_from_data = {
    {encode_lmn_0_from_data, encode_lmn_1_from_data, encode_lmn_2_from_data}
};
private const gs_cie_render_proc3 EncodeABC_from_data = {
    {encode_abc_0_from_data, encode_abc_1_from_data, encode_abc_2_from_data}
};
private const gs_cie_render_table_procs RenderTableT_from_data = {
    {render_table_t_0_from_data, render_table_t_1_from_data,
     render_table_t_2_from_data, render_table_t_3_from_data
    }
};

/* Read a CRD directly from a parameter list. */
int
param_get_cie_render1(gs_cie_render * pcrd, gs_param_list * plist,
		      gx_device * dev)
{
    encode_data_t data;
    gs_param_int_array rt_size;
    int crd_type;
    int code, code_lmn, code_abc, code_rt, code_t;
    gs_param_string pname, pdata;

    if ((code = param_read_int(plist, "ColorRenderingType", &crd_type)) < 0 ||
	crd_type != CRD_TYPE ||
	(code = read_vector3(plist, "WhitePoint", &pcrd->points.WhitePoint,
			     NULL)) < 0 ||
	(code = read_vector3(plist, "BlackPoint", &pcrd->points.BlackPoint,
			     &BlackPoint_default)) < 0 ||
	(code = read_matrix3(plist, "MatrixPQR", &pcrd->MatrixPQR)) < 0 ||
	(code = read_range3(plist, "RangePQR", &pcrd->RangePQR)) < 0 ||
	/* TransformPQR is handled specially below. */
	(code = read_matrix3(plist, "MatrixLMN", &pcrd->MatrixLMN)) < 0 ||
	(code_lmn = code =
	 read_proc3(plist, "EncodeLMNValues", data.lmn)) < 0 ||
	(code = read_range3(plist, "RangeLMN", &pcrd->RangeLMN)) < 0 ||
	(code = read_matrix3(plist, "MatrixABC", &pcrd->MatrixABC)) < 0 ||
	(code_abc = code =
	 read_proc3(plist, "EncodeABCValues", data.abc)) < 0 ||
	(code = read_range3(plist, "RangeABC", &pcrd->RangeABC)) < 0
	)
	return code;
    /* Handle the sampled functions. */
    switch (code = param_read_string(plist, "TransformPQRName", &pname)) {
	default:		/* error */
	    return code;
	case 1:			/* missing */
	    pcrd->TransformPQR = TransformPQR_default;
	    break;
	case 0:			/* specified */
	    /* The procedure name must be null-terminated: */
	    /* see param_put_cie_render1 above. */
	    if (pname.size < 1 || pname.data[pname.size - 1] != 0)
		return_error(gs_error_rangecheck);
	    pcrd->TransformPQR.proc = TransformPQR_lookup_proc_name;
	    pcrd->TransformPQR.proc_name = (char *)pname.data;
	    switch (code = param_read_string(plist, "TransformPQRData", &pdata)) {
		default:	/* error */
		    return code;
		case 1:		/* missing */
		    pcrd->TransformPQR.proc_data.data = 0;
		    pcrd->TransformPQR.proc_data.size = 0;
		    break;
		case 0:
		    pcrd->TransformPQR.proc_data.data = pdata.data;
		    pcrd->TransformPQR.proc_data.size = pdata.size;
	    }
	    pcrd->TransformPQR.driver_name = gs_devicename(dev);
	    break;
    }
    pcrd->client_data = &data;
    if (code_lmn > 0)
	pcrd->EncodeLMN = Encode_default;
    else
	pcrd->EncodeLMN = EncodeLMN_from_data;
    if (code_abc > 0)
	pcrd->EncodeABC = Encode_default;
    else
	pcrd->EncodeABC = EncodeABC_from_data;
    code_rt = code = param_read_int_array(plist, "RenderTableSize", &rt_size);
    if (code == 1) {
	if (pcrd->RenderTable.lookup.table) {
	    gs_free_object(pcrd->rc.memory,
		(void *)pcrd->RenderTable.lookup.table, /* break const */
		"param_get_cie_render1(RenderTable)");
	    pcrd->RenderTable.lookup.table = 0;
	}
	pcrd->RenderTable.T = RenderTableT_default;
	code_t = 1;
    } else if (code < 0)
	return code;
    else if (rt_size.size != 4)
	return_error(gs_error_rangecheck);
    else {
	gs_param_string_array rt_values;
	gs_const_string *table;
	int n, m, j;

	code = param_read_string_array(plist, "RenderTableTable", &rt_values);
	if (code < 0)
	    return code;
	else if (code > 0 ||
		 rt_values.size != rt_size.data[3] *
		 rt_size.data[1] * rt_size.data[2])
	    return_error(gs_error_rangecheck);
	pcrd->RenderTable.lookup.n = n = rt_size.size - 1;
	pcrd->RenderTable.lookup.m = m = rt_size.data[n];
	if (n > 4 || m > 4)
	    return_error(gs_error_rangecheck);
	memcpy(pcrd->RenderTable.lookup.dims, rt_size.data, n * sizeof(int));
	/****** ALLOCATE table = RenderTable.lookup.table ******/
	for (j = 0; j < pcrd->RenderTable.lookup.dims[0]; ++j) {
	    table[j].data = rt_values.data[j].data;
	    table[j].size = rt_values.data[j].size;
	}
	pcrd->RenderTable.lookup.table = table;
	pcrd->RenderTable.T = RenderTableT_from_data;
	code_t = code = read_floats(plist, "RenderTableTValues", data.t,
				    gx_cie_cache_size * m);
	if (code > 0)
	    pcrd->RenderTable.T = RenderTableT_default;
	else if (code == 0)
	    pcrd->RenderTable.T = RenderTableT_from_data;
    }
    if ((code = gs_cie_render_init(pcrd)) >= 0 &&
	(code = gs_cie_render_sample(pcrd)) >= 0
	)
	code = gs_cie_render_complete(pcrd);
    /* Clean up before exiting. */
    pcrd->client_data = 0;
    if (code_lmn == 0)
	pcrd->EncodeLMN = EncodeLMN_from_cache;
    if (code_abc == 0)
	pcrd->EncodeABC = EncodeABC_from_cache;
    if (code_t == 0)
	pcrd->RenderTable.T = RenderTableT_from_cache;
    return code;
}
