/* Copyright (C) 2001-2023 Artifex Software, Inc. All Rights Reserved. This software is provided AS-IS with no warranty, either express or implied. This software is distributed under license and may not be copied, modified or distributed except as expressly authorized under the terms of the license contained in the file LICENSE in this distribution. Refer to licensing information at http://www.artifex.com or contact Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, CA 94129, USA, for further information. */ /* 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. */ static void store_vector3(float *p, const gs_vector3 * pvec) { p[0] = pvec->u, p[1] = pvec->v, p[2] = pvec->w; } static 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); } static 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); } static int write_matrix3(gs_param_list * plist, gs_param_name key, const gs_matrix3 * pmat, gs_memory_t * mem) { float values[9]; if(matrix_equal(pmat, &Matrix3_default)) 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); } static int write_range3(gs_param_list * plist, gs_param_name key, const gs_range3 * prange, gs_memory_t * mem) { float values[6]; if (range_equal(prange, &Range3_default)) 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); } static bool render_proc3_equal(const gs_cie_render_proc3 *p1, const gs_cie_render_proc3 *p2) { int k; for (k = 0; k < 3; k++) { if (p1->procs[k] != p2->procs[k]) return false; } return true; } static 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 (render_proc3_equal(procs, &Encode_default)) 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 (!vector_equal(&pcrd->points.BlackPoint, &BlackPoint_default)) { 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. */ static void load_vector3(gs_vector3 * pvec, const float *p) { pvec->u = p[0], pvec->v = p[1], pvec->w = p[2]; } static 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; } static 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; } static 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; } static 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; } static 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. */ static float encode_from_data(double 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.... */ static float encode_lmn_0_from_data(double 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]); } static float encode_lmn_1_from_data(double 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]); } static float encode_lmn_2_from_data(double 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]); } static float encode_abc_0_from_data(double 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]); } static float encode_abc_1_from_data(double 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]); } static float encode_abc_2_from_data(double 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]); } static 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])); } static 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])); } static 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])); } static 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])); } static const gs_cie_render_proc3 EncodeLMN_from_data = { {encode_lmn_0_from_data, encode_lmn_1_from_data, encode_lmn_2_from_data} }; static const gs_cie_render_proc3 EncodeABC_from_data = { {encode_abc_0_from_data, encode_abc_1_from_data, encode_abc_2_from_data} }; static 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 = param_read_int_array(plist, "RenderTableSize", &rt_size); if (code_rt == 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_rt < 0) return code_rt; 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; }