/* Copyright (C) 1998, 1999 Aladdin Enterprises.  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 version 2
  as published by the Free Software Foundation.


  This software is provided AS-IS with no warranty, either express or
  implied. That is, 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.
  
  For more information about licensing, please refer to
  http://www.ghostscript.com/licensing/. For information on
  commercial licensing, go to http://www.artifex.com/licensing/ or
  contact Artifex Software, Inc., 101 Lucas Valley Road #110,
  San Rafael, CA  94903, U.S.A., +1(415)492-9861.
*/

/* $Id: gscrdp.c,v 1.6 2002/10/08 00:49:49 dan Exp $ */
/* CIE color rendering dictionary creation */
#include "math_.h"
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gsdevice.h"
#include "gserrors.h"
#include "gsmatrix.h"		/* for gscolor2.h */
#include "gsstruct.h"
#include "gxcspace.h"
#include "gscolor2.h"		/* for gs_set/currentcolorrendering */
#include "gscrdp.h"
#include "gxarith.h"

/* ---------------- 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,
			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, gs_cie_render * pcrd,
		      gs_memory_t * mem)
{
    int crd_type = GX_DEVICE_CRD1_TYPE;
    int code = gs_cie_render_sample(pcrd); /* we need RenderTableT_is_id' */

    if (code < 0)
	return 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])
				       ((byte)(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;

    /* Reset the status to invalidate cached information. */
    pcrd->status = CIE_RENDER_STATUS_BUILT;
    if ((code = param_read_int(plist, "ColorRenderingType", &crd_type)) < 0 ||
	crd_type != GX_DEVICE_CRD1_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 = (const 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;

	for (j = 0; j < rt_size.size; ++j)
	    if (rt_size.data[j] < 1)
		return_error(gs_error_rangecheck);
	code = param_read_string_array(plist, "RenderTableTable", &rt_values);
	if (code < 0)
	    return code;
	if (code > 0 || rt_values.size != rt_size.data[0])
	    return_error(gs_error_rangecheck);
	/* Note: currently n = 3 (rt_size.size = 4) always. */
	for (j = 0; j < rt_values.size; ++j)
	    if (rt_values.data[j].size !=
		rt_size.data[1] * rt_size.data[2] * rt_size.data[3])
		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));
	table =
	    gs_alloc_struct_array(pcrd->rc.memory,
				  pcrd->RenderTable.lookup.dims[0],
				  gs_const_string, &st_const_string_element,
				  "RenderTable table");
	if (table == 0)
	    return_error(gs_error_VMerror);
	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;
}
