Files
argyllcms/icc/icc_xf.c
T
2026-08-20 20:28:40 +01:00

4739 lines
138 KiB
C

/* ============================== */
/* icmPe transform implementation */
/* This is #included in icc.c */
#undef DEBUG_GET_LU /* [und] debug get_lu function */
#undef ALWAYSLUTRACE /* [und] create all icmLu with trace set */
// ~8 need to check that _init() functions set & check error code ??
/* ---------------------------------------------------------- */
/* icmPe: A base object. (Not really usable as is) */
/* ---------------------------------------------------------- */
/* icmPeSeq: A sequence of icmPe's. */
/* All classes derived from icmPeSeq can choose to use these */
/* for their lookup methods. */
static icmPe_lurv icmPeSeq_trace_lookup_fwd(icmPeSeq *p, double *out, double *in);
static icmPe_lurv icmPeSeq_trace_lookup_bwd(icmPeSeq *p, double *out, double *in);
static icmPe_lurv icmPeSeq_lookup_fwd(icmPeSeq *p, double *out, double *in) {
int n, m;
icmPe_lurv rv = icmPe_lurv_OK;
if (p->trace > 0)
return icmPeSeq_trace_lookup_fwd(p, out, in);
if (p->nncount == 0) { /* No usable member Pe's */
if (out != in) {
if (p->inputChan == 0 || p->outputChan == 0 /* No default set */
|| p->inputChan != p->outputChan) {
rv |= icmPe_lurv_cfg;
/* Implement a NOP */
} else {
for (m = 0; m < p->inputChan; m++)
out[m] = in[m];
}
}
} else {
double tmp[MAX_CHAN];
for (m = 0; m < p->inputChan; m++)
tmp[m] = in[m];
for (n = 0; n < p->count; n++) {
if (p->pe[n] != NULL && p->pe[n]->attr.op != icmPeOp_NOP) {
if (p->pe[n]->lookup_fwd != NULL && p->pe[n]->attr.fwd)
rv |= p->pe[n]->lookup_fwd(p->pe[n], tmp, tmp);
else
rv |= icmPe_lurv_imp;
}
}
for (m = 0; m < p->outputChan; m++)
out[m] = tmp[m];
}
return rv;
}
static icmPe_lurv icmPeSeq_lookup_bwd(icmPeSeq *p, double *out, double *in) {
unsigned int n, m;
icmPe_lurv rv = icmPe_lurv_OK;
if (p->trace > 0)
return icmPeSeq_trace_lookup_bwd(p, out, in);
if (p->nncount == 0) { /* No usable member Pe's */
if (out != in) {
if (p->inputChan == 0 || p->outputChan == 0 /* No default set */
|| p->inputChan != p->outputChan) {
rv |= icmPe_lurv_cfg;
/* Implement a NOP */
} else {
for (m = 0; m < p->inputChan; m++)
out[m] = in[m];
}
}
} else {
double tmp[MAX_CHAN];
for (m = 0; m < p->outputChan; m++)
tmp[m] = in[m];
for (n = p->count; n-- > 0;) {
if (p->pe[n] != NULL && p->pe[n]->attr.op != icmPeOp_NOP) {
if (p->pe[n]->lookup_bwd != NULL && p->pe[n]->attr.bwd)
rv |= p->pe[n]->lookup_bwd(p->pe[n], tmp, tmp);
else
rv |= icmPe_lurv_imp;
}
}
for (m = 0; m < p->inputChan; m++)
out[m] = tmp[m];
}
return rv;
}
/* Trace versions of the above */
static icmPe_lurv icmPeSeq_trace_lookup_fwd(icmPeSeq *p, double *out, double *in) {
int n, m;
icmPe_lurv rv = icmPe_lurv_OK;
int pad = p->trace > 0 ? p->trace -1 : 0;
printf(PAD("PeSeq %s fwd, count %d\n"),icmPeSig2str(p->etype), p->count);
if (p->trace <= 1)
printf(PAD(" Input %s\n"),icmPdv(p->inputChan, in));
if (p->nncount == 0) { /* No usable member Pe's */
printf(PAD(" (No Pe's in sequence)\n"));
if (out != in) {
if (p->inputChan == 0 || p->outputChan == 0 /* No default set */
|| p->inputChan != p->outputChan) {
rv |= icmPe_lurv_cfg;
/* Implement a NOP */
} else {
for (m = 0; m < p->inputChan && m < p->outputChan; m++)
out[m] = in[m];
}
}
} else {
double tmp[MAX_CHAN];
for (m = 0; m < p->inputChan; m++)
tmp[m] = in[m];
for (n = 0; n < p->count; n++) {
if (p->pe[n] != NULL && p->pe[n]->attr.op != icmPeOp_NOP) {
if (p->pe[n]->lookup_fwd != NULL && p->pe[n]->attr.fwd) {
int ctr = p->pe[n]->trace;
if (!p->pe[n]->attr.comp)
printf(PAD(" Pe %s %s:\n"),icmPeSig2str(p->pe[n]->etype),
p->pe[n]->attr.inv ? "bwd (ivt)" : "fwd");
p->pe[n]->trace = p->trace + 1;
rv |= p->pe[n]->lookup_fwd(p->pe[n], tmp, tmp);
p->pe[n]->trace = ctr;
} else {
printf(PAD(" Pe %s (No %s)\n"),icmPeSig2str(p->pe[n]->etype),
p->pe[n]->attr.inv ? "bwd (ivt)" : "fwd");
rv |= icmPe_lurv_imp;
}
if (!p->pe[n]->attr.comp)
printf(PAD(" Output %s\n"),icmPdv(p->pe[n]->outputChan, tmp));
} else {
printf(PAD(" (Pe[%d] is NULL)\n"),n);
}
}
for (m = 0; m < p->outputChan; m++)
out[m] = tmp[m];
}
if (rv != icmPe_lurv_OK)
printf(PAD(" returning %s\n"),icmPe_lurv2str(rv));
return rv;
}
static icmPe_lurv icmPeSeq_trace_lookup_bwd(icmPeSeq *p, double *out, double *in) {
unsigned int n, m;
icmPe_lurv rv = icmPe_lurv_OK;
int pad = p->trace > 0 ? p->trace -1 : 0;
printf(PAD("PeSeq %s bwd, count %d:\n"),icmPeSig2str(p->etype), p->count);
if (p->trace <= 1)
printf(PAD(" Input %s\n"),icmPdv(p->outputChan, in));
if (p->nncount == 0) { /* No usable member Pe's */
printf(PAD(" (No Pe's in sequence)\n"));
if (out != in) {
if (p->inputChan == 0 || p->outputChan == 0 /* No default set */
|| p->inputChan != p->outputChan) {
rv |= icmPe_lurv_cfg;
/* Implement a NOP */
} else {
for (m = 0; m < p->inputChan && m < p->outputChan; m++)
out[m] = in[m];
}
}
} else {
double tmp[MAX_CHAN];
for (m = 0; m < p->outputChan; m++)
tmp[m] = in[m];
for (n = p->count; n-- > 0;) {
if (p->pe[n] != NULL && p->pe[n]->attr.op != icmPeOp_NOP) {
if (p->pe[n]->lookup_bwd != NULL && p->pe[n]->attr.bwd) {
int ctr = p->pe[n]->trace;
if (!p->pe[n]->attr.comp)
printf(PAD(" Pe %s %s:\n"),icmPeSig2str(p->pe[n]->etype),
p->pe[n]->attr.inv ? "fwd (ivt)" : "bwd");
p->pe[n]->trace = p->trace + 1;
rv |= p->pe[n]->lookup_bwd(p->pe[n], tmp, tmp);
p->pe[n]->trace = ctr;
} else {
printf(PAD(" Pe %s (No %s)\n"),icmPeSig2str(p->pe[n]->etype),
p->pe[n]->attr.inv ? "fwd (ivt)" : "bwd");
rv |= icmPe_lurv_imp;
}
if (!p->pe[n]->attr.comp)
printf(PAD(" Output %s\n"),icmPdv(p->pe[n]->inputChan, tmp));
} else {
printf(PAD(" (Pe[%d] is NULL))\n"),n);
}
}
for (m = 0; m < p->inputChan; m++)
out[m] = tmp[m];
}
if (rv != icmPe_lurv_OK)
printf(PAD(" returning %s\n"),icmPe_lurv2str(rv));
return rv;
}
/* ---------------------------------------------------------- */
/* icmPeCurveSet: A set of N x 1d Curves */
static icmPe_lurv icmPeCurveSet_trace_lookup_fwd(icmPeCurveSet *p, double *out, double *in);
static icmPe_lurv icmPeCurveSet_trace_lookup_bwd(icmPeCurveSet *p, double *out, double *in);
static icmPe_lurv icmPeCurveSet_lookup_fwd(icmPeCurveSet *p, double *out, double *in) {
unsigned int n;
icmPe_lurv rv = icmPe_lurv_OK;
if (p->trace > 0)
return icmPeCurveSet_trace_lookup_fwd(p, out, in);
for (n = 0; n < p->inputChan; n++) {
if (p->pe[n] != NULL && p->pe[n]->lookup_fwd != NULL)
rv |= p->pe[n]->lookup_fwd(p->pe[n], &out[n], &in[n]);
else {
out[n] = in[n];
rv |= icmPe_lurv_imp;
}
}
return rv;
}
static icmPe_lurv icmPeCurveSet_lookup_bwd(icmPeCurveSet *p, double *out, double *in) {
unsigned int n;
icmPe_lurv rv = icmPe_lurv_OK;
if (p->trace > 0)
return icmPeCurveSet_trace_lookup_bwd(p, out, in);
for (n = 0; n < p->inputChan; n++) {
if (p->pe[n] != NULL && p->pe[n]->lookup_bwd != NULL)
rv |= p->pe[n]->lookup_bwd(p->pe[n], &out[n], &in[n]);
else {
out[n] = in[n];
rv |= icmPe_lurv_imp;
}
}
return rv;
}
/* Trace versions of the above */
static icmPe_lurv icmPeCurveSet_trace_lookup_fwd(icmPeCurveSet *p, double *out, double *in) {
unsigned int n;
icmPe_lurv rv = icmPe_lurv_OK;
int pad = p->trace > 0 ? p->trace -1 : 0;
printf(PAD("PeCurveSet fwd, noChan %d\n"),p->inputChan);
if (p->trace <= 1)
printf(PAD(" Input %s\n"),icmPdv(p->inputChan, in));
for (n = 0; n < p->inputChan; n++) {
if (p->pe[n] != NULL && p->pe[n]->lookup_fwd != NULL) {
int ctr = p->pe[n]->trace;
if (!p->pe[n]->attr.comp)
printf(PAD(" Pe %s %s chan %d:\n"),icmPeSig2str(p->pe[n]->etype),
p->pe[n]->attr.inv ? "bwd" : "fwd", n);
p->pe[n]->trace = p->trace + 1;
rv |= p->pe[n]->lookup_fwd(p->pe[n], &out[n], &in[n]);
p->pe[n]->trace = ctr;
} else {
/* Implement a NOP */
out[n] = in[n];
rv |= icmPe_lurv_imp;
}
}
printf(PAD(" Output %s\n"),icmPdv(p->outputChan, out));
return rv;
}
static icmPe_lurv icmPeCurveSet_trace_lookup_bwd(icmPeCurveSet *p, double *out, double *in) {
unsigned int n;
icmPe_lurv rv = icmPe_lurv_OK;
int pad = p->trace > 0 ? p->trace -1 : 0;
printf(PAD("PeCurveSet bwd, noChan %d\n"),p->inputChan);
if (p->trace <= 1)
printf(PAD(" Input %s\n"),icmPdv(p->inputChan, in));
for (n = 0; n < p->inputChan; n++) {
if (p->pe[n] != NULL && p->pe[n]->lookup_bwd != NULL) {
int ctr = p->pe[n]->trace;
if (!p->pe[n]->attr.comp)
printf(PAD(" Pe %s %s chan %d:\n"),icmPeSig2str(p->pe[n]->etype),
p->pe[n]->attr.inv ? "fwd" : "bwd", n);
p->pe[n]->trace = p->trace + 1;
rv |= p->pe[n]->lookup_bwd(p->pe[n], &out[n], &in[n]);
p->pe[n]->trace = ctr;
} else {
/* Implement a NOP */
out[n] = in[n];
rv |= icmPe_lurv_imp;
}
}
printf(PAD(" Output %s\n"),icmPdv(p->outputChan, out));
return rv;
}
/* ---------------------------------------------------------- */
/* icmPeCurve: A linear/gamma/table curve */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Support for reverse interpolation of 1D lookup tables */
/* Create a reverse curve lookup acceleration table */
/* return non-zero on error, 2 = malloc error. */
static int icmTable_setup_bwd(
icc *icp, /* Base icc object */
icmRevTable *rt, /* Reverse table data to setup */
unsigned int size, /* Size of fwd table */
double *data /* Table */
) {
unsigned int i;
if (rt->inited) {
return 0;
}
rt->count = size; /* Stash pointers to these away */
rt->data = data;
/* Find range of output values */
rt->rmin = 1e300;
rt->rmax = -1e300;
for (i = 0; i < rt->count; i++) {
if (rt->data[i] > rt->rmax)
rt->rmax = rt->data[i];
if (rt->data[i] < rt->rmin)
rt->rmin = rt->data[i];
}
/* Decide on reverse granularity */
rt->rsize = sat_add(rt->count,2)/2;
rt->qscale = (double)rt->rsize/(rt->rmax - rt->rmin); /* Scale factor to quantize to */
if (ovr_mul(rt->count, sizeof(unsigned int *))) {
return 2;
goto fail;
}
/* Initialize the reverse lookup structures, and get overall min/max */
if ((rt->rlists = (unsigned int **) icp->al->calloc(icp->al, rt->rsize, sizeof(unsigned int *))) == NULL) {
goto fail;
}
/* Assign each output value range bucket lists it intersects */
for (i = 0; rt->count > 1 && i < (rt->count-1); i++) {
unsigned int s, e, j; /* Start and end indexes (inclusive) */
s = (unsigned int)((rt->data[i] - rt->rmin) * rt->qscale);
e = (unsigned int)((rt->data[i+1] - rt->rmin) * rt->qscale);
if (s >= rt->rsize)
s = rt->rsize-1;
if (e >= rt->rsize)
e = rt->rsize-1;
if (s > e) { /* swap */
unsigned int t;
t = s; s = e; e = t;
}
/* For all buckets that may contain this output range, add index of this output */
for (j = s; j <= e; j++) {
unsigned int as; /* Allocation size */
unsigned int nf; /* Next free slot */
if (rt->rlists[j] == NULL) { /* No allocation */
as = 5; /* Start with space for 5 */
if ((rt->rlists[j] = (unsigned int *) icp->al->calloc(icp->al, as, sizeof(unsigned int))) == NULL) {
goto fail;
}
rt->rlists[j][0] = as;
nf = rt->rlists[j][1] = 2;
} else {
as = rt->rlists[j][0]; /* Allocate space for this list */
nf = rt->rlists[j][1]; /* Next free location in list */
if (nf >= as) { /* need to expand space */
if ((as = sat_mul(as, 2)) == UINT_MAX
|| ovr_mul(as, sizeof(unsigned int))) {
goto fail;
}
rt->rlists[j] = (unsigned int *) icp->al->realloc(icp->al,rt->rlists[j], as * sizeof(unsigned int));
if (rt->rlists[j] == NULL) {
goto fail;
}
rt->rlists[j][0] = as;
}
}
rt->rlists[j][nf++] = i;
rt->rlists[j][1] = nf;
}
}
rt->inited = 1;
return 0;
fail:;
return 2;
}
/* Free up any data */
static void icmTable_delete_bwd(
icc *icp, /* Base icc */
icmRevTable *rt /* Reverse table data to setup */
) {
if (rt->inited != 0) {
while (rt->rsize > 0)
icp->al->free(icp->al, rt->rlists[--rt->rsize]);
icp->al->free(icp->al, rt->rlists);
rt->count = 0; /* Don't keep these */
rt->data = NULL;
}
}
/* Do a reverse lookup through the curve */
/* Return 0 on success, 1 if clipping occured */
static int icmTable_lookup_bwd(
icmRevTable *rt,
double *out,
double *in
) {
int rv = 0;
unsigned int ix, k, i;
double oval, ival = *in, val;
double rsize_1;
/* Find appropriate reverse list */
rsize_1 = (double)(rt->rsize-1);
val = ((ival - rt->rmin) * rt->qscale);
if (val < 0.0)
val = 0.0;
else if (val > rsize_1)
val = rsize_1;
ix = (unsigned int)floor(val); /* Coordinate */
if (ix > (rt->count-2))
ix = (rt->count-2);
if (rt->rlists[ix] != NULL) { /* There is a list of fwd candidates */
/* For each candidate forward range */
for (i = 2; i < rt->rlists[ix][1]; i++) { /* For all fwd indexes */
double lv,hv;
k = rt->rlists[ix][i]; /* Base index */
lv = rt->data[k];
hv = rt->data[k+1];
if ((ival >= lv && ival <= hv) /* If this slot contains output value */
|| (ival >= hv && ival <= lv)) {
/* Reverse linear interpolation */
if (hv == lv) { /* Technically non-monotonic - due to quantization ? */
oval = (k + 0.5)/(rt->count-1.0);
} else
oval = (k + ((ival - lv)/(hv - lv)))/(rt->count-1.0);
/* If we kept looking, we would find multiple */
/* solution for non-monotonic curve */
*out = oval;
return rv;
}
}
}
/* We have failed to find an exact value, so return the nearest value */
/* (This is slow !) */
val = fabs(ival - rt->data[0]);
for (k = 0, i = 1; i < rt->count; i++) {
double er;
er = fabs(ival - rt->data[i]);
if (er < val) { /* new best */
val = er;
k = i;
}
}
*out = k/(rt->count-1.0);
rv |= 1;
return rv;
}
/* - - - - - - - - - - - - - - */
/* Sample curve transform code */
static int icmPeCurve_init(icmPeCurve *p) {
if (p->inited) {
return ICM_ERR_OK;
}
p->rt.inited = 0; /* If icmPeCurve not-inited, then rt is not inited */
/* Set attr. */
p->attr.op = icmPeOp_perch;
if (p->ctype == icmCurveLin) {
p->attr.op = icmPeOp_NOP;
} else if (p->ctype == icmCurveGamma && p->count == 1
&& p->data[0] == 1.0) {
p->attr.op = icmPeOp_NOP;
} else if ((p->ttype == icSigCurveType || p->ttype == icmSig816Curve)
&& p->ctype == icmCurveSpec && p->count == 2
&& p->data[0] == 0.0 && p->data[1] == 1.0) {
p->attr.op = icmPeOp_NOP;
}
/* Setup reverse lookup info */
if (p->ctype == icmCurveSpec) {
if (icmTable_setup_bwd(p->icp, &p->rt, p->count, p->data)) {
return 1; // ~8
}
p->inited = 1;
}
return ICM_ERR_OK;
}
static int icmPeCurve_deinit(icmPeCurve *p) {
if (p->rt.inited) {
if (p->ctype == icmCurveSpec)
icmTable_delete_bwd(p->icp, &p->rt);
}
return 0;
}
/* Do a forward lookup through the curve */
/* Return 0 on success, 1 if clipping occured, 2 on other error */
static icmPe_lurv icmPeCurve_lookup_fwd(
icmPeCurve *p,
double *out,
double *in
) {
icmPe_lurv rv = icmPe_lurv_OK;
if (p->ctype == icmCurveLin) {
*out = *in;
} else if (p->ctype == icmCurveGamma) {
double val = *in;
if (val <= 0.0)
*out = 0.0;
else
*out = pow(val, p->data[0]);
} else if (p->count == 0) { /* Table of 0 size */
*out = *in;
} else { /* Use linear interpolation */
unsigned int ix;
double val, w;
double inputEnt_1 = (double)(p->count-1);
val = *in * inputEnt_1;
if (val < 0.0) {
val = 0.0;
rv |= icmPe_lurv_clip;
} else if (val > inputEnt_1) {
val = inputEnt_1;
rv |= icmPe_lurv_clip;
}
ix = (unsigned int)floor(val); /* Coordinate */
if (ix > (p->count-2))
ix = (p->count-2);
w = val - (double)ix; /* weight */
val = p->data[ix];
*out = val + w * (p->data[ix+1] - val);
}
return rv;
}
/* Do a reverse lookup through the curve */
/* Return 0 on success, 1 if clipping occured, 2 on other error */
/* (Note that clipping means mathematical clipping, and is not */
/* set just because a device value is out of gamut. */
static icmPe_lurv icmPeCurve_lookup_bwd(
icmPeCurve *p,
double *out,
double *in
) {
icc *icp = p->icp;
icmPe_lurv rv = icmPe_lurv_OK;
if (p->ctype == icmCurveLin) {
*out = *in;
} else if (p->ctype == icmCurveGamma) {
double val = *in;
if (val <= 0.0)
*out = 0.0;
else
*out = pow(val, 1.0/p->data[0]);
} else { /* Use linear interpolation */
if (p->inited == 0) {
if (icmPeCurve_init(p)) {
return icmPe_lurv_imp;
}
}
if (icmTable_lookup_bwd(&p->rt, out, in))
rv |= icmPe_lurv_clip;
}
return rv;
}
/* ---------------------------------------------------------- */
/* icmPeMatrix: An N x M + F matrix */
static void mul_matrix(
double out[MAX_CHAN][MAX_CHAN], /* Assume no aliases... */
double in1[MAX_CHAN][MAX_CHAN],
double in2[MAX_CHAN][MAX_CHAN],
int n
) {
int i, j, k;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
out[i][j] = 0.0;
for (k = 0; k < n; k++) {
out[i][j] += in1[i][k] * in2[k][j];
}
}
}
}
/* Matrix inversion using Gauss-Jordan elimination with full pivoting. */
/* Return nz on error. */
static
int invmatrix(
double out[MAX_CHAN][MAX_CHAN],
double in[MAX_CHAN][MAX_CHAN],
int n
) {
int colixs[MAX_CHAN], rowixs[MAX_CHAN], pivs[MAX_CHAN] = { 0 };
int i, j, k, kk;
int cix, rix;
double lval, ival;
if (n <= 0)
return 1;
if (out != in) {
/* Copy input to output */
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
out[i][j] = in[i][j];
}
/* For each column */
for (i = 0; i < n; i++) {
/* Look for the pivot element */
for (lval = 0.0, j = 0; j < n; j++) {
if (pivs[j] != 1) {
for (k = 0; k < n; k++) {
if (pivs[k] == 0) {
double aa = fabs(out[j][k]);
if (aa >= lval) {
lval = aa;
rix = j;
cix = k;
}
}
}
}
}
pivs[cix] = 1;
#define DSWAP(a,b) { double tt = (a); (a) = (b); (b) = tt; }
/* Swap rows to put pivot on the diagonal */
if (rix != cix) {
for (k = 0; k < n; k++)
DSWAP(out[rix][k], out[cix][k])
}
/* Divide the pivot row by the pivot element */
rowixs[i] = rix;
colixs[i] = cix;
if (fabs(out[cix][cix]) < ICM_SMALL_NUMBER) {
return 1;
}
ival = 1.0/out[cix][cix];
out[cix][cix] = 1.0;
for (k = 0; k < n; k++)
out[cix][k] *= ival;
/* Reduce the rows */
for (kk = 0; kk < n; kk++) {
if (kk != cix) {
double val = out[kk][cix];
out[kk][cix] = 0.0;
for (k = 0; k < n; k++)
out[kk][k] -= val * out[cix][k];
}
}
}
/* Unscramble columns */
for (kk = n-1; kk >= 0; kk--) {
if (rowixs[kk] != colixs[kk])
for (k = 0; k < n; k++)
DSWAP(out[k][rowixs[kk]], out[k][colixs[kk]])
}
#undef DSWAP
/* Polish the inverse slightly */
{
double t1[MAX_CHAN][MAX_CHAN];
double t2[MAX_CHAN][MAX_CHAN];
for (kk = 0; kk < 2; kk++) {
mul_matrix(t1, in, out, n);
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
t2[i][j] = out[i][j];
if (i == j)
t1[i][j] = 2.0 - t1[i][j];
else
t1[i][j] = 0.0 - t1[i][j];
}
}
mul_matrix(out, t2, t1, n);
}
}
return 0;
}
/* Initialise matrix flags and inverse */
static int icmPeMatrix_init(icmPeMatrix *p) {
if (!p->inited) {
if (!p->inited) {
int i, j;
p->mx_unity = 0;
p->ct_zero = 0;
if (p->inputChan == p->outputChan
&& invmatrix(p->imx, p->mx, p->inputChan) == 0) {
p->ivalid = 1;
p->attr.bwd = 1;
} else {
p->ivalid = 0;
p->attr.bwd = 0;
}
/* Is the matrix unity */
if (p->outputChan == p->inputChan) {
for (i = 0; i < p->outputChan; i++) {
for (j = 0; j < p->inputChan; j++) {
if (i == j) {
if (fabs(p->mx[i][j] - 1.0) > ICM_SMALL_NUMBER)
break;
} else {
if (fabs(p->mx[i][j]) > ICM_SMALL_NUMBER)
break;
}
}
if (j < p->inputChan) {
break;
}
}
if (i >= p->outputChan)
p->mx_unity = 1;
}
/* Is the constant zero */
for (i = 0; i < p->outputChan; i++) {
if (fabs(p->ct[i]) > ICM_SMALL_NUMBER)
break;
}
if (i >= p->outputChan)
p->ct_zero = 1;
/* Set attr. */
if (p->mx_unity && p->ct_zero)
p->attr.op = icmPeOp_NOP;
else
p->attr.op = icmPeOp_matrix;
p->inited = 1;
}
}
return ICM_ERR_OK;
}
static icmPe_lurv icmPeMatrix_lookup_fwd(
icmPeMatrix *p, /* This */
double *out, /* Vector of output values */
double *in /* Vector of input values */
) {
icc *icp = p->icp;
icmPe_lurv rv = icmPe_lurv_OK;
unsigned int i, j;
double tout[MAX_CHAN];
if (!p->inited) {
if (icmPeMatrix_init(p))
return icmPe_lurv_imp;
}
for (i = 0; i < p->outputChan; i++) {
tout[i] = 0.0;
for (j = 0; j < p->inputChan; j++)
tout[i] += p->mx[i][j] * in[j];
tout[i] += p->ct[i];
}
for (i = 0; i < p->outputChan; i++)
out[i] = tout[i];
return rv;
}
/* Inverse matrix lookup */
static icmPe_lurv icmPeMatrix_lookup_bwd(
icmPeMatrix *p, /* This */
double *out, /* Vector of output values */
double *in /* Vector of input values */
) {
icc *icp = p->icp;
icmPe_lurv rv = icmPe_lurv_OK;
if (!p->inited) {
if (icmPeMatrix_init(p)) {
return icmPe_lurv_imp;
}
}
if (p->ivalid) {
unsigned int i, j;
double tin[MAX_CHAN];
for (j = 0; j < p->outputChan; j++)
tin[j] = in[j] - p->ct[j];
for (i = 0; i < p->inputChan; i++) {
out[i] = 0.0;
for (j = 0; j < p->outputChan; j++)
out[i] += p->imx[i][j] * tin[j];
}
} else {
rv |= icmPe_lurv_imp;
}
return rv;
}
/* ---------------------------------------------------------- */
/* icmPeClut: An N x M cLUT */
/* Initialise dinc and attr. */
static int icmPeClut_init(icmPeClut *p) {
if (!p->inited) {
if (!p->inited) {
int i, j, g;
/* Compute dimensional increment though clut in doubles. */
/* Note that first channel varies least rapidly. */
if (p->inputChan > 0) {
i = p->inputChan-1;
p->dinc[i--] = p->outputChan;
for (; i >= 0; i--)
p->dinc[i] = p->dinc[i+1] * p->clutPoints[i];
}
/* Compute offsets from base of cube to other corners in doubles. */
for (p->dcube[0] = 0, g = 1, j = 0; j < p->inputChan; j++) {
for (i = 0; i < g; i++)
p->dcube[g+i] = p->dcube[i] + p->dinc[j];
g *= 2;
}
/* Setup attr. */
p->attr.op = icmPeOp_cLUT;
/* See if the cLUT is a NOP */
if (p->outputChan == p->inputChan
&& (p->ttype == icmSig816CLUT
)) {
/* Check that all dims are res == 2 */
for (i = 0; i < p->inputChan; i++) {
if (p->clutPoints[i] != 2)
break;
}
if (i >= p->inputChan) { /* Yes */
/* Check that table values are 0.0 and 1.0 */
for (i = 0; i < (1 << p->inputChan); i++) {
double *out = p->clutTable + p->dcube[i];
for (j = 0; j < p->outputChan; j++) {
if (out[j] != ((1 << j) & i) ? 1.0 : 0.0)
break; /* nope */
}
if (j < p->outputChan)
break; /* nope */
}
if (i >= (1 << p->inputChan)) { /* yes, cLUT is a NOP */
p->attr.op = icmPeOp_NOP;
}
}
}
p->inited = 1;
}
}
return ICM_ERR_OK;
}
#ifndef NEVER /* ifdef for DIAGNOSTIC cLut lookup versions */
/* Convert normalized numbers though this Luts multi-dimensional table. */
/* using multi-linear interpolation. */
static icmPe_lurv icmPeClut_lookup_clut_nl(
/* Return 0 on success, 1 if clipping occured, 2 on other error */
icmPeClut *p, /* Pointer to Lut object */
double *out, /* Output array[inputChan] */
double *in /* Input array[outputChan] */
) {
icc *icp = p->icp;
icmPe_lurv rv = icmPe_lurv_OK;
double *gp; /* Pointer to grid cube base */
double co[MAX_CHAN]; /* Coordinate offset with the grid cell */
double *gw, GW[1 << 8]; /* weight for each grid cube corner */
if (p->inputChan <= 8) {
gw = GW; /* Use stack allocation */
} else {
if ((gw = (double *) icp->al->malloc(icp->al, sat_mul((1 << p->inputChan), sizeof(double)))) == NULL) {
return icm_err(icp, 2,"icmPeClut_lookup_clut: malloc() failed");
}
}
/* We are using an multi-linear (ie. Trilinear for 3D input) interpolation. */
/* The implementation here uses more multiplies that some other schemes, */
/* (for instance, see "Tri-Linear Interpolation" by Steve Hill, */
/* Graphics Gems IV, page 521), but has less involved bookeeping, */
/* needs less local storage for intermediate output values, does fewer */
/* output and intermediate value reads, and fp multiplies are fast on */
/* todays processors! */
/* Compute base index into grid and coordinate offsets */
{
unsigned int e;
gp = p->clutTable; /* Base of grid array */
for (e = 0; e < p->inputChan; e++) {
double clutPoints_1 = (double)(p->clutPoints[e]-1);
int clutPoints_2 = p->clutPoints[e]-2;
unsigned int x;
double val;
val = in[e] * clutPoints_1;
if (val < 0.0) {
val = 0.0;
rv |= icmPe_lurv_clip;
} else if (val > clutPoints_1) {
val = clutPoints_1;
rv |= icmPe_lurv_clip;
}
x = (unsigned int)floor(val); /* Grid coordinate */
if (x > clutPoints_2)
x = clutPoints_2;
co[e] = val - (double)x; /* 1.0 - weight */
gp += x * p->dinc[e]; /* Add index offset for base of cube */
}
}
/* Compute corner weights needed for interpolation */
{
unsigned int e;
int i, g = 1;
gw[0] = 1.0;
for (e = 0; e < p->inputChan; e++) {
for (i = 0; i < g; i++) {
gw[g+i] = gw[i] * co[e];
gw[i] *= (1.0 - co[e]);
}
g *= 2;
}
}
/* Now compute the output values */
if (p->_clutsize > 0) {
int i;
unsigned int f;
double w = gw[0];
double *d = gp + p->dcube[0];
for (f = 0; f < p->outputChan; f++) /* Base of cube */
out[f] = w * d[f];
for (i = 1; i < (1 << p->inputChan); i++) { /* For all other corners of cube */
w = gw[i]; /* Strength reduce */
d = gp + p->dcube[i];
for (f = 0; f < p->outputChan; f++)
out[f] += w * d[f];
}
}
if (gw != GW)
icp->al->free(icp->al, (void *)gw);
return rv;
}
/* Convert normalized numbers though this Luts multi-dimensional table */
/* using simplex interpolation. */
/* Return 0 on success, 1 if clipping occured, 2 on other error */
static icmPe_lurv icmPeClut_lookup_clut_sx(
icmPeClut *p, /* Pointer to Lut object */
double *out, /* Output array[inputChan] */
double *in /* Input array[outputChan] */
) {
icmPe_lurv rv = icmPe_lurv_OK;
double *gp; /* Pointer to grid cube base */
double co[MAX_CHAN]; /* Coordinate offset with the grid cell */
int si[MAX_CHAN]; /* co[] Sort index, [0] = smallest */
/* We are using a simplex (ie. tetrahedral for 3D input) interpolation. */
/* This method is more appropriate for XYZ/RGB/CMYK input spaces, */
/* and uses much fewer node accesses and multiplies than multi-linear interpolation */
/* as the dimensionality increases. */
/* Compute base index into grid and coordinate offsets */
{
unsigned int e;
gp = p->clutTable; /* Base of grid array */
for (e = 0; e < p->inputChan; e++) {
double clutPoints_1 = (double)(p->clutPoints[e]-1);
int clutPoints_2 = p->clutPoints[e]-2;
unsigned int x;
double val;
val = in[e] * clutPoints_1;
if (val < 0.0) {
val = 0.0;
rv |= icmPe_lurv_clip;
} else if (val > clutPoints_1) {
val = clutPoints_1;
rv |= icmPe_lurv_clip;
}
x = (unsigned int)floor(val); /* Grid coordinate */
if (x > clutPoints_2)
x = clutPoints_2;
co[e] = val - (double)x; /* 1.0 - weight */
gp += x * p->dinc[e]; /* Add index offset for base of cube */
}
}
/* Do insertion sort on coordinates, smallest to largest. */
/* (Selection sort is generally slower) */
{
int f, vf;
unsigned int e;
double v;
for (e = 0; e < p->inputChan; e++)
si[e] = e; /* Initial unsorted indexes */
for (e = 1; e < p->inputChan; e++) {
f = e;
v = co[si[f]];
vf = f;
while (f > 0 && co[si[f-1]] > v) {
si[f] = si[f-1];
f--;
}
si[f] = vf;
}
}
/* Now compute the weightings, simplex vertices and output values */
if (p->_clutsize > 0) {
unsigned int e, f;
double w; /* Current vertex weight */
w = 1.0 - co[si[p->inputChan-1]]; /* Vertex at base of cell */
for (f = 0; f < p->outputChan; f++)
out[f] = w * gp[f];
for (e = p->inputChan; e-- > 1;) { /* Middle vertices */
w = co[si[e]] - co[si[e-1]];
gp += p->dinc[si[e]]; /* Move to top of cell in next largest dimension */
for (f = 0; f < p->outputChan; f++)
out[f] += w * gp[f];
}
w = co[si[0]];
gp += p->dinc[si[0]]; /* Far corner from base of cell */
for (f = 0; f < p->outputChan; f++)
out[f] += w * gp[f];
}
return rv;
}
#else
/* DIAGNOSTIC VERSION */
/* Convert normalized numbers though this Luts multi-dimensional table. */
/* using multi-linear interpolation. */
static icmPe_lurv icmPeClut_lookup_clut_nl(
/* Return 0 on success, 1 if clipping occured, 2 on other error */
icmPeClut *p, /* Pointer to Lut object */
double *out, /* Output array[inputChan] */
double *in /* Input array[outputChan] */
) {
icc *icp = p->icp;
icmPe_lurv rv = icmPe_lurv_OK;
double *gp; /* Pointer to grid cube base */
double co[MAX_CHAN]; /* Coordinate offset with the grid cell */
double *gw, GW[1 << 8]; /* weight for each grid cube corner */
if (p->inputChan <= 8) {
gw = GW; /* Use stack allocation */
} else {
if ((gw = (double *) icp->al->malloc(icp->al, sat_mul((1 << p->inputChan), sizeof(double)))) == NULL) {
return icm_err(icp, 2,"icmPeClut_lookup_clut: malloc() failed");
}
}
printf("lookup_clut_nl input = %s\n",icmPdv(p->inputChan, in));
/* We are using an multi-linear (ie. Trilinear for 3D input) interpolation. */
/* The implementation here uses more multiplies that some other schemes, */
/* (for instance, see "Tri-Linear Interpolation" by Steve Hill, */
/* Graphics Gems IV, page 521), but has less involved bookeeping, */
/* needs less local storage for intermediate output values, does fewer */
/* output and intermediate value reads, and fp multiplies are fast on */
/* todays processors! */
/* Compute base index into grid and coordinate offsets */
{
unsigned int x[MAX_CHAN];
unsigned int e;
gp = p->clutTable; /* Base of grid array */
for (e = 0; e < p->inputChan; e++) {
double clutPoints_1 = (double)(p->clutPoints[e]-1);
int clutPoints_2 = p->clutPoints[e]-2;
double val;
val = in[e] * clutPoints_1;
if (val < 0.0) {
val = 0.0;
rv |= icmPe_lurv_clip;
} else if (val > clutPoints_1) {
val = clutPoints_1;
rv |= icmPe_lurv_clip;
}
x[e] = (unsigned int)floor(val); /* Grid coordinate */
if (x[e] > clutPoints_2)
x[e] = clutPoints_2;
co[e] = val - (double)x[e]; /* 1.0 - weight */
gp += x[e] * p->dinc[e]; /* Add index offset for base of cube */
}
printf(" ix = %s\n",icmPiv(p->inputChan, x));
}
printf(" co = %s\n",icmPdv(p->inputChan, in));
printf(" gp = %ld\n",(long int)(gp - p->clutTable));
/* Compute corner weights needed for interpolation */
{
unsigned int e;
int i, g = 1;
gw[0] = 1.0;
for (e = 0; e < p->inputChan; e++) {
for (i = 0; i < g; i++) {
gw[g+i] = gw[i] * co[e];
gw[i] *= (1.0 - co[e]);
}
g *= 2;
}
}
printf(" gw = %s\n",icmPdv(1 << p->inputChan, gw));
/* Now compute the output values */
{
int i;
unsigned int f;
double w = gw[0];
double *d = gp + p->dcube[0];
for (f = 0; f < p->outputChan; f++) { /* Base of cube */
out[f] = w * d[f];
printf(" out[%d] = %f * %f = %f\n",f,w,d[f],out[f]);
}
for (i = 1; i < (1 << p->inputChan); i++) { /* For all other corners of cube */
w = gw[i]; /* Strength reduce */
d = gp + p->dcube[i];
for (f = 0; f < p->outputChan; f++) {
printf(" out[%d] = %f + %f * %f = %f\n",f,out[f],w,d[f],out[f] + w * d[f]);
out[f] += w * d[f];
}
}
}
if (gw != GW)
icp->al->free(icp->al, (void *)gw);
return rv;
}
/* DIAGNOSTIC VERSION */
/* Convert normalized numbers though this Luts multi-dimensional table */
/* using simplex interpolation. */
/* Return 0 on success, 1 if clipping occured, 2 on other error */
static icmPe_lurv icmPeClut_lookup_clut_sx(
icmPeClut *p, /* Pointer to Lut object */
double *out, /* Output array[inputChan] */
double *in /* Input array[outputChan] */
) {
icmPe_lurv rv = icmPe_lurv_OK;
double *gp; /* Pointer to grid cube base */
double co[MAX_CHAN]; /* Coordinate offset with the grid cell */
int si[MAX_CHAN]; /* co[] Sort index, [0] = smallest */
printf("lookup_clut_sx input = %s\n",icmPdv(p->inputChan, in));
/* We are using a simplex (ie. tetrahedral for 3D input) interpolation. */
/* This method is more appropriate for XYZ/RGB/CMYK input spaces, */
/* and uses much fewer node accesses and multiplies than multi-linear interpolation */
/* as the dimensionality increases. */
/* Compute base index into grid and coordinate offsets */
{
unsigned int x[MAX_CHAN];
unsigned int e;
gp = p->clutTable; /* Base of grid array */
for (e = 0; e < p->inputChan; e++) {
double clutPoints_1 = (double)(p->clutPoints[e]-1);
int clutPoints_2 = p->clutPoints[e]-2;
double val;
val = in[e] * clutPoints_1;
if (val < 0.0) {
val = 0.0;
rv |= icmPe_lurv_clip;
} else if (val > clutPoints_1) {
val = clutPoints_1;
rv |= icmPe_lurv_clip;
}
x[e] = (unsigned int)floor(val); /* Grid coordinate */
if (x[e] > clutPoints_2)
x[e] = clutPoints_2;
co[e] = val - (double)x[e]; /* 1.0 - weight */
gp += x[e] * p->dinc[e]; /* Add index offset for base of cube */
}
printf(" ix = %s\n",icmPiv(p->inputChan, x));
}
printf(" co = %s\n",icmPdv(p->inputChan, in));
printf(" gp = %ld\n",(long int)(gp - p->clutTable));
/* Do insertion sort on coordinates, smallest to largest. */
/* (Selection sort is generally slower) */
{
int f, vf;
unsigned int e;
double v;
for (e = 0; e < p->inputChan; e++)
si[e] = e; /* Initial unsorted indexes */
for (e = 1; e < p->inputChan; e++) {
f = e;
v = co[si[f]];
vf = f;
while (f > 0 && co[si[f-1]] > v) {
si[f] = si[f-1];
f--;
}
si[f] = vf;
}
}
printf(" si = %s\n",icmPiv(3, si));
/* Now compute the weightings, simplex vertices and output values */
{
unsigned int e, f;
double w; /* Current vertex weight */
w = 1.0 - co[si[p->inputChan-1]]; /* Vertex at base of cell */
for (f = 0; f < p->outputChan; f++) {
out[f] = w * gp[f];
printf(" out[%d] = %f * %f = %f\n",f,w,gp[f],out[f]);
}
for (e = p->inputChan; e-- > 1;) { /* Middle vertices */
w = co[si[e]] - co[si[e-1]];
gp += p->dinc[si[e]]; /* Move to top of cell in next largest dimension */
for (f = 0; f < p->outputChan; f++) {
printf(" out[%d] = %f + %f * %f = %f\n",f,out[f],w,gp[f],out[f] + w * gp[f]);
out[f] += w * gp[f];
}
}
w = co[si[0]];
gp += p->dinc[si[0]]; /* Far corner from base of cell */
for (f = 0; f < p->outputChan; f++) {
printf(" out[%d] = %f + %f * %f = %f\n",f,out[f],w,gp[f],out[f] + w * gp[f]);
out[f] += w * gp[f];
}
}
printf("lookup_clut_sx output = %s\n",icmPdv(p->outputChan, out));
return rv;
}
#endif
/* Clut lookup */
static icmPe_lurv icmPeClut_lookup_fwd(
icmPeClut *p, /* This */
double *out, /* Vector of output values */
double *in /* Vector of input values */
) {
icc *icp = p->icp;
if (!p->inited) {
if (icmPeClut_init(p))
return icmPe_lurv_imp;
}
if (p->use_sx)
return icmPeClut_lookup_clut_sx(p, out, in);
else
return icmPeClut_lookup_clut_nl(p, out, in);
}
/* Inverse clut lookup */
static icmPe_lurv icmPeClut_lookup_bwd(
icmPeClut *p, /* This */
double *out, /* Vector of output values */
double *in /* Vector of input values */
) {
/* No can do... */
return icmPe_lurv_imp;
}
/* return the locations of the minimum and */
/* maximum values of the given channel, in the clut */
static void icmPeClut_min_max(
icmPeClut *p, /* Pointer to Lut object */
double *minp, /* Return position of min/max */
double *maxp,
int chan /* Channel, -1 for average of all */
) {
double *tp;
double minv, maxv; /* Values */
unsigned int e, ee, f;
int gc[MAX_CHAN]; /* Grid coordinate */
minv = 1e6;
maxv = -1e6;
for (e = 0; e < p->inputChan; e++)
gc[e] = 0; /* init coords */
/* Search the whole table */
for (tp = p->clutTable, e = 0; e < p->inputChan; tp += p->outputChan) {
double v;
if (chan == -1) {
for (v = 0.0, f = 0; f < p->outputChan; f++)
v += tp[f];
} else {
v = tp[chan];
}
if (v < minv) {
minv = v;
for (ee = 0; ee < p->inputChan; ee++)
minp[ee] = gc[ee]/(p->clutPoints[ee]-1.0);
}
if (v > maxv) {
maxv = v;
for (ee = 0; ee < p->inputChan; ee++)
maxp[ee] = gc[ee]/(p->clutPoints[ee]-1.0);
}
/* Increment coord */
for (e = 0; e < p->inputChan; e++) {
if (++gc[e] < p->clutPoints[ee])
break; /* No carry */
gc[e] = 0;
}
}
}
/* Determine appropriate clut lookup algorithm */
static void icmPeClut_choose_alg(icmPeClut *p, icmLu4Space *lu) {
int use_sx; /* -1 = undecided, 0 = N-linear, 1 = Simplex lookup */
icmCSInfo ini, outi; /* In and out Lut color spaces */
lu->native_spaces(lu, &ini, &outi, NULL);
/* Determine if the input space is "Device" like, */
/* ie. luminance will be expected to vary most strongly */
/* with the diagonal change in input coordinates. */
switch(ini.sig) {
/* Luminence is carried by the sum of all the output channels, */
/* so output luminence will dominantly be in diagonal direction. */
case icSigXYZData: /* This seems to be appropriate ? */
case icSigRgbData:
case icSigGrayData:
case icSigCmykData:
case icSigCmyData:
case icSigMch6Data:
use_sx = 1; /* Simplex interpolation is appropriate */
break;
/* A single channel carries the luminence information */
case icSigLabData:
case icSigLuvData:
case icSigYCbCrData:
case icSigYxyData:
case icSigHlsData:
case icSigHsvData:
use_sx = 0; /* N-linear interpolation is appropriate */
break;
default:
use_sx = -1; /* undecided */
break;
}
/* If we couldn't figure it out from the input space, */
/* check output luminance variation with a diagonal input */
/* change. */
if (use_sx == -1) {
int lc; /* Luminance channel */
/* Determine where the luminence is carried in the output */
switch(outi.sig) {
/* Luminence is carried by the sum of all the output channels */
case icSigRgbData:
case icSigGrayData:
case icSigCmykData:
case icSigCmyData:
case icSigMch6Data:
lc = -1; /* Average all channels */
break;
/* A single channel carries the luminence information */
case icSigLabData:
case icSigLuvData:
case icSigYCbCrData:
case icSigYxyData:
lc = 0;
break;
case icSigXYZData:
case icSigHlsData:
lc = 1;
break;
case icSigHsvData:
lc = 2;
break;
/* default means give up and use N-linear type lookup */
default:
lc = -2;
break;
}
/* If we know how luminance is represented in output space */
if (lc != -2) {
double tout1[MAX_CHAN]; /* Test output values */
double tout2[MAX_CHAN];
double tt, diag;
int n;
/* Determine input space location of min and max of */
/* given output channel (chan = -1 means average of all) */
p->min_max(p, tout1, tout2, lc);
/* Convert to vector and then calculate normalized */
/* dot product with diagonal vector (1,1,1...) */
for (tt = 0.0, n = 0; n < ini.nch; n++) {
tout1[n] = tout2[n] - tout1[n];
tt += tout1[n] * tout1[n];
}
if (tt > 0.0)
tt = sqrt(tt); /* normalizing factor for maximum delta */
else
tt = 1.0; /* Hmm. */
tt *= sqrt((double)ini.nch); /* Normalizing factor for diagonal vector */
for (diag = 0.0, n = 0; n < outi.nch; n++)
diag += tout1[n] / tt;
diag = fabs(diag);
/* I'm not really convinced that this is a reliable */
/* indicator of whether simplex interpolation should be used ... */
/* It does seem to do the right thing with YCC space though. */
if (diag > 0.8) /* Diagonal is dominant ? */
use_sx = 1;
/* If we couldn't figure it out, use N-linear interpolation */
if (use_sx == -1)
use_sx = 0;
}
}
p->use_sx = use_sx;
}
/* Return total ink limit and channel maximums. */
static double icmPeClut_get_tac(
struct _icmPeClut *p,
double *chmax, /* device return channel sums. May be NULL */
icmPe *tail, /* lu tail transform from clut to device output */
void (*calfunc)(void *cntx, double *out, double *in), /* Optional calibration func. */
void *cntx
) {
double tac = 0.0;
double max[MAX_CHAN]; /* Channel maximums */
int outn;
int f;
double *tp; /* Pointer to grid cube base */
if (tail)
outn = tail->outputChan;
else
outn = p->outputChan;
/* Search the lut for the largest values */
for (f = 0; f < outn; f++)
max[f] = 0.0;
for (tp = p->clutTable; tp < (p->clutTable + p->_clutsize); tp += p->outputChan) {
double tot, vv[MAX_CHAN];
icmCpyN(vv, tp, p->outputChan);
if (tail != NULL)
tail->lookup_fwd(tail, vv, tp); /* Lookup though tail of transform */
if (calfunc != NULL)
calfunc(cntx, vv, vv); /* Apply any device calibration */
for (tot = 0.0, f = 0; f < outn; f++) {
tot += vv[f];
if (vv[f] > max[f])
max[f] = vv[f];
}
if (tot > tac) {
tac = tot;
}
}
if (chmax != NULL) {
for (f = 0; f < outn; f++)
chmax[f] = max[f];
}
return tac;
}
/* =========================================================================================== */
/* ICC profile lookup support */
/* =========================================================================================== */
/* Methods common to all non-named transforms (icmLu4Base) : */
/* Given a normalized colorspace sig, return a full range colorspace sig if any. */
static icColorSpaceSignature icmNorm2Sig(
icc *icp,
icColorSpaceSignature sig /* Normalized range Colorspace sig */
) {
switch ((int)sig) {
case icmSigXYZ8Data:
case icmSigXYZ16Data:
return icSigXYZData;
case icmSigLab8Data:
case icmSigLabV2Data:
return icSigLabData;
case icmSigLuv16Data:
return icSigLuvData;
case icmSigYCbCr16Data:
return icSigYCbCrData;
case icmSigYxy16Data:
return icSigYxyData;
}
/* Unknown sig, already full range sig or no normalised sig needed */
return sig;
}
/* Given an full range colorspace sig, return the normalized colorspace sig if any. */
static icColorSpaceSignature icmSig2NormSig(
icc *icp,
icColorSpaceSignature sig, /* Full range Colorspace sig */
icmEncLabVer ver, /* 0 = V2, 2 same as profile */
icmEncBytes bytes /* 1 = 8 bit int, 2 = 16 bit int, else 32 bit float */
) {
if (ver == icmEncProf) {
ver = icmEncV2;
}
if (sig == icSigXYZData) {
if (bytes == icmEnc8bit)
return icmSigXYZ8Data;
else if (bytes == icmEnc16bit)
return icmSigXYZ16Data;
} else if (sig == icSigLabData) {
if (bytes == icmEnc8bit)
return icmSigLab8Data;
else if (bytes == icmEnc16bit) {
return icmSigLabV2Data;
}
}
if (sig == icSigLuvData)
return icmSigLuv16Data;
else if (sig == icSigYCbCrData)
return icmSigYCbCr16Data;
else if (sig == icSigYxyData)
return icmSigYxy16Data;
/* Unknown sig, already normalized sig or no normalised sig needed */
return sig;
}
/* Given a normalized colorspace sig, return a new normalizing conversion. */
/* Return NULL and icm->e.c set on error. */
static icmPe *new_icmNSig2NormPe(
icc *icp,
icColorSpaceSignature *tofrom, /* Return output space or if invert input space */
icColorSpaceSignature nsig, /* Normalized colorspace sig */
int invert, /* nz to return norm to full transform */
int nullnop /* On NOP return null rather than icmPeNOP */
) {
/* Full scale PCS target needs no normalisation */
if (nsig == icSigXYZData
|| nsig == icSigLabData) {
if (tofrom != NULL)
*tofrom = nsig;
if (!nullnop)
return new_icmPeNOP(icp, 3);
return NULL;
}
/* PCS normalised target */
if (nsig == icmSigXYZ8Data) {
if (tofrom != NULL)
*tofrom = icSigXYZData;
return new_icmPeXYZ2XYZ8(icp, invert);
} else if (nsig == icmSigXYZ16Data) {
if (tofrom != NULL)
*tofrom = icSigXYZData;
return new_icmPeXYZ2XYZ16(icp, invert);
} else if (nsig == icmSigLab8Data) {
if (tofrom != NULL)
*tofrom = icSigLabData;
return new_icmPeLab2Lab8(icp, invert);
} else if (nsig == icmSigLabV2Data) {
if (tofrom != NULL)
*tofrom = icSigLabData;
return new_icmPeLab2LabV2(icp, invert);
}
/* Undefined device spaces */
/* These aren't actually specified by ICC, so we punt. */
if (nsig == icmSigLuv16Data) {
double min[3] = { 0.0, -128.0, -128.0 };
double max[3] = { 100.0, 127.0 + 255.0/256.0, 127.0 + 255.0/256.0 };
if (tofrom != NULL)
*tofrom = icSigLuvData;
return new_icmPeGeneric2Norm(icp, 3, min, max, "Luv2Norm", invert);
} else if (nsig == icmSigYCbCr16Data) {
double min[3] = { 0.0, -0.5, -0.5 };
double max[3] = { 1.0, 0.5, 0.5 };
if (tofrom != NULL)
*tofrom = icSigYCbCrData;
return new_icmPeGeneric2Norm(icp, 3, min, max, "YCbCr2Norm", invert);
} else if (nsig == icmSigYxy16Data) {
double min[3] = { 0.0, 0.0, 0.0 };
double max[3] = { 1.0, 1.0, 1.0 };
if (tofrom != NULL)
*tofrom = icSigYxyData;
return new_icmPeGeneric2Norm(icp, 3, min, max, "Yxy2Norm", invert);
}
/* If a device space not otherwise handled above, */
/* then range is 0..1 so return a NOP */
if (icmCSSig2type(nsig) & CSSigType_DEV) {
if (tofrom != NULL)
*tofrom = nsig;
if (!nullnop)
return new_icmPeNOP(icp, icmCSSig2nchan(nsig));
return NULL;
}
icm_err(icp, ICM_ERR_UNKNOWN_NORMSIG,"new_icmNSig2NormPe: unhandled sig '%s'",icmColorSpaceSig2str(nsig));
return NULL;
}
/* Given an full range colorspace sig, return a normalizing conversion. */
/* Return NULL and icm->e.c set on error. */
static icmPe *new_icmSig2NormPe(
icc *icp,
icmSnPrim *pt, /* Return matching serializatio enum */
icColorSpaceSignature sig, /* Full range Colorspace sig */
icmEncLabVer ver, /* 0 = V2, 2 same as profile */
icmEncBytes bytes /* 1 = 8 bit int, 2 = 16 bit int, else 32 bit float */
) {
icColorSpaceSignature nsig;
if (pt != NULL) {
if (bytes == icmEnc8bit)
*pt = icmSnPrim_d_NFix8;
else if (bytes == icmEnc16bit)
*pt = icmSnPrim_d_NFix16;
else
*pt = icmSnPrim_d_Float32;
}
nsig = icmSig2NormSig(icp, sig, ver, bytes);
return new_icmNSig2NormPe(icp, NULL, nsig, 0, 0);
}
/* Given a normalization icmPe and a icmSnPrim enum, */
/* serialize the given value. */
static void icmSn_d_n_prim(icmFBuf *b, icmPe *npe, icmSnPrim pt, double *p) {
unsigned int i;
double tt[MAX_CHAN];
if (b->op & icmSnSerialise) {
if (b->op == icmSnWrite)
npe->lookup_fwd(npe, tt, p);
for (i = 0; i < npe->inputChan; i++) {
icmSn_primitive(b, (void *)&tt[i], pt, 0);
}
if (b->op == icmSnRead)
npe->lookup_bwd(npe, p, tt);
}
}
/* Given a full range colorspace sig, return a default value ranges. */
/* (This is not for normalized ranges) */
/* Return NZ if range is not 0.0 - 1.0 */
int icmGetDefaultFullRange(
icc *icp,
double *min, double *max, /* Return range */
icColorSpaceSignature sig /* Full range colorspace sig */
) {
int nch, i;
if (sig == icSigXYZData) {
min[0] = 0.0; max[0] = 1.0 + 32767.0/32768;
min[1] = 0.0; max[1] = 1.0 + 32767.0/32768;
min[2] = 0.0; max[2] = 1.0 + 32767.0/32768;
return 1;
}
if (sig == icSigLabData) {
min[0] = 0.0; max[0] = 100.0;
min[1] = -128.0; max[1] = 128.0;
min[2] = -128.0; max[2] = 128.0;
return 1;
}
if (sig == icSigLuvData) {
min[0] = 0.0; max[0] = 100.0;
min[1] = -128.0; max[1] = 128.0;
min[2] = -128.0; max[2] = 128.0;
return 1;
} else if (sig == icSigYCbCrData) {
min[0] = 0.0; max[0] = 10.0;
min[1] = -0.5; max[1] = 0.5;
min[2] = -0.5; max[2] = 0.5;
return 1;
} else if (sig == icSigYxyData) {
min[0] = 0.0; max[0] = 1.0;
min[1] = 0.0; max[1] = 1.0;
min[2] = 0.0; max[2] = 1.0;
return 0;
}
nch = icmCSSig2nchan(sig);
for (i = 0; i < nch; i++)
min[i] = 0.0, max[i] = 1.0;
return 0;
}
/* Clip the given PCS value to be within the 16 bit encodable range */
/* Return nz if clipped */
static int icmClipPCS16(
icc *icp,
double out[3],
double in[3],
icColorSpaceSignature encSpace,
icmEncLabVer ver /* 0 = V2, 2 same as profile */
) {
int i, clip = 0;
double min[3] = { 0 }, max[3] = { 0 };
out[0] = in[0];
out[1] = in[1];
out[2] = in[2];
if (encSpace == icSigXYZData) {
min[0] = 0.0; max[0] = 1.0 + 32767.0/32768;
min[1] = 0.0; max[1] = 1.0 + 32767.0/32768;
min[2] = 0.0; max[2] = 1.0 + 32767.0/32768;
} else if (encSpace == icSigLabData) {
/* icmEncV2 */
min[0] = 0.0; max[0] = 100.0 + 25500.0/65280.0;
min[1] = -128.0; max[1] = 127.0 + 255.0/256.0;
min[2] = -128.0; max[2] = 127.0 + 255.0/256.0;
}
for (i = 0; i < 3; i++) {
if (out[i] < min[i]) {
out[i] = min[i];
clip = 1;
}
if (out[i] > max[i]) {
out[i] = max[i];
clip = 1;
}
}
return clip;
}
/* Given a normalized or full range colorspace sig, return a default value ranges of */
/* the equivalent full range after de-normalization */
/* Return NZ if range is not 0.0 - 1.0 */
static int icmGetDefaultRange(
icc *icp,
double *min, double *max, /* Return range */
icColorSpaceSignature sig /* Noramlize or full range colorspace sig */
) {
icmPe *nfunc;
int i, nch;
nfunc = new_icmNSig2NormPe(icp, NULL, sig, 0, 1);
if (nfunc == NULL)
return icmGetDefaultFullRange(icp, min, max, sig);
nch = icmCSSig2nchan(sig);
for (i = 0; i < nch; i++)
min[i] = 0.0, max[i] = 1.0;
nfunc->lookup_bwd(nfunc, min, min);
nfunc->lookup_bwd(nfunc, max, max);
nfunc->del(nfunc);
return 1;
}
/* - - - - - - - - - */
/* Create a PCS format conversion sequence. */
/* Nothing will be added if there is no conversion needed. */
/* Return nz on error */
static int create_fmt(
icmPeContainer *p, /* Pe to append conversion to */
icmLu4Space *lu, /* Lu this is part of */
icColorSpaceSignature dstsp, /* destination space */
int dsta, /* destination anbsolute colorimetric flag */
icColorSpaceSignature srcsp, /* source space */
int srca /* source anbsolute colorimetric flag */
) {
icc *icp = p->icp;
icmPe *pe;
#ifdef DEBUG_GET_LU
printf("\ncreate_fmt Entry: srcsp = %s srca = %d, dstsp = %s dsta = %d\n",icmColorSpaceSig2str(srcsp), srca, icmColorSpaceSig2str(dstsp), dsta);
#endif
for (;;) {
#ifdef DEBUG_GET_LU
printf(" srcsp = %s srca = %d, dstsp = %s dsta = %d\n",icmColorSpaceSig2str(srcsp), srca, icmColorSpaceSig2str(dstsp), dsta);
#endif
if (srcsp == dstsp && srca == dsta) {
#ifdef DEBUG_GET_LU
printf(" No conversion needed - all done\n");
#endif
p->init(p);
return ICM_ERR_OK;
}
/* If we have a non-PCS, full to norm conversion needed */
if (!(icmCSSig2type(srcsp) & CSSigType_gPCS)
&& !(icmCSSig2type(dstsp) & CSSigType_gPCS)
&& (icmCSSig2type(dstsp) & CSSigType_NORM)) {
icColorSpaceSignature tofrom;
if ((pe = new_icmNSig2NormPe(icp, &tofrom, dstsp, 0, 1)) != NULL) {
if (p->append(p, pe))
return icp->e.c;
pe->del(pe); /* Container has taken a reference */
} else if (icp->e.c != ICM_ERR_OK)
return icp->e.c;
if (srcsp != tofrom) /* Ooops. Shouldn't happen though... */
return icm_err(p->icp, ICM_ERR_PE_FMT_UNH_DEV, "create_fmt: can't deal with src"
" '%s' abs %d & dst '%s' abs %d", icmColorSpaceSig2str(srcsp), srca,
icmColorSpaceSig2str(dstsp), dsta);
srcsp = dstsp;
#ifdef DEBUG_GET_LU
printf(" Added non-PCS full to norm\n");
#endif
continue;
}
/* If we have a non-PCS, norm to full conversion needed */
if (!(icmCSSig2type(srcsp) & CSSigType_gPCS)
&& (icmCSSig2type(srcsp) & CSSigType_NORM)
&& !(icmCSSig2type(dstsp) & CSSigType_gPCS)) {
icColorSpaceSignature tofrom;
if ((pe = new_icmNSig2NormPe(icp, &tofrom, srcsp, 1, 1)) != NULL) {
if (p->append(p, pe))
return icp->e.c;
pe->del(pe); /* Container has taken a reference */
} else if (icp->e.c != ICM_ERR_OK)
return icp->e.c;
srcsp = tofrom;
#ifdef DEBUG_GET_LU
printf(" Added non-PCS norm to full\n");
#endif
continue;
}
/* If we're not dealing with a PCS conversion now, it's an error */
if (!(icmCSSig2type(srcsp) & CSSigType_gPCS)
|| !(icmCSSig2type(dstsp) & CSSigType_gPCS)) {
return icm_err(p->icp, ICM_ERR_PE_FMT_UNH_DEV, "create_fmt: can't deal with src"
" '%s' abs %d & dst '%s' abs %d", icmColorSpaceSig2str(srcsp), srca,
icmColorSpaceSig2str(dstsp), dsta);
}
/* If there needs to be an intent change */
if (srca != dsta) {
if (srcsp != icSigXYZData) {
/* Recurse: Create a conversion from the source to XYZ */
#ifdef DEBUG_GET_LU
printf(" Recursing\n");
#endif
if (create_fmt(p, lu, icSigXYZData, 0, srcsp, 0))
return icp->e.c;
srcsp = icSigXYZData;
#ifdef DEBUG_GET_LU
printf(" Returned\n");
#endif
}
/* Add XYZ intent change */
if ((pe = new_icmPeAbs2Rel(icp, lu, dsta)) == NULL)
return icp->e.c;
if (p->append(p, pe))
return icp->e.c;
pe->del(pe); /* Container has taken a reference */
/* Continue from where we are now */
dsta = srca;
#ifdef DEBUG_GET_LU
printf(" Added intent change\n");
#endif
continue;
}
/* Intent is now correct. */
/* If source is a normalised PCS */
if (icmCSSig2type(srcsp) & CSSigType_nPCS) {
icColorSpaceSignature tofrom;
if ((pe = new_icmNSig2NormPe(icp, &tofrom, srcsp, 1, 1)) != NULL) {
if (p->append(p, pe))
return icp->e.c;
pe->del(pe); /* Contain has taken a reference */
} else if (icp->e.c != ICM_ERR_OK)
return icp->e.c;
srcsp = tofrom;
#ifdef DEBUG_GET_LU
printf(" Added PCS norm to full\n");
#endif
continue;
}
/* Source is now full range. */
/* If src and destination are XYZ,Lab */
if (srcsp == icSigXYZData && (icmCSSig2type(dstsp) & CSSigType_gLab)) {
if ((pe = new_icmPeXYZ2Lab(icp, lu, 0)) == NULL)
return icp->e.c;
if (p->append(p, pe))
return icp->e.c;
pe->del(pe); /* Contain has taken a reference */
srcsp = icSigLabData;
#ifdef DEBUG_GET_LU
printf(" Added XYZ to Lab\n");
#endif
continue;
}
/* If src and destination are Lab,XYZ */
if (srcsp == icSigLabData && (icmCSSig2type(dstsp) & CSSigType_gXYZ)) {
if ((pe = new_icmPeXYZ2Lab(icp, lu, 1)) == NULL)
return icp->e.c;
if (p->append(p, pe))
return icp->e.c;
pe->del(pe); /* Contain has taken a reference */
srcsp = icSigXYZData;
#ifdef DEBUG_GET_LU
printf(" Added Lab to XYZ\n");
#endif
continue;
}
/* src is now full range same PCS type as destination */
/* If destination is a normalised PCS */
if (icmCSSig2type(dstsp) & CSSigType_nPCS) {
icColorSpaceSignature tofrom;
if ((pe = new_icmNSig2NormPe(icp, &tofrom, dstsp, 0, 1)) != NULL) {
if (p->append(p, pe))
return icp->e.c;
pe->del(pe); /* Contain has taken a reference */
} else if (icp->e.c != ICM_ERR_OK)
return icp->e.c;
srcsp = dstsp;
#ifdef DEBUG_GET_LU
printf(" Added PCS full to norm\n");
#endif
continue;
}
/* Shouldn't get here ? */
return icm_err(p->icp, ICM_ERR_PE_FMT_UNEXP_CASE, "create_fmt: unexpected case src '%s' "
"& dst '%s'", icmColorSpaceSig2str(srcsp), icmColorSpaceSig2str(dstsp));
}
p->init(p);
return ICM_ERR_OK;
}
/* - - - - - - - - - */
#define LU4INIT_WH_BK(TTYPE) (int (*)(TTYPE *))
/* Initialise the LU pcswht, white and black points from the ICC tags, */
/* and the corresponding absolute<->relative conversion matrices */
/* return nz on error */
static int icmLu4Init_Wh_bk(
icmLu4Base *lup
) {
icc *p = lup->icp;
lup->pcswht = p->header->illuminant;
return p->get_wb_points(p, NULL, &lup->whitePoint, &lup->blackisassumed, &lup->blackPoint,
lup->toAbs, lup->fromAbs);
}
#define LU4NATIVESPACES(TTYPE) (void (*)(TTYPE *, icmCSInfo *, icmCSInfo *, icmCSInfo *))
/* Return information about the native lut in/out/pcs colorspaces. */
/* Any pointer may be NULL if value is not to be returned */
static void
icmLu4NativeSpaces(
icmLu4Base *p, /* This */
icmCSInfo *ini,
icmCSInfo *outi,
icmCSInfo *pcsi
) {
if (ini != NULL) {
*ini = p->ini;
}
if (outi != NULL) {
*outi = p->outi;
}
if (pcsi != NULL) {
*pcsi = p->pcsi;
}
}
#define LU4SPACES(TTYPE) (void (*)(TTYPE *, \
icmCSInfo *, icmCSInfo *, icmCSInfo *, \
icTagSignature *, icRenderingIntent *, \
icmLookupFunc *, icmLookupOrder *, \
icmPeOp *op, int *can_bwd))
/* Return information about the effective lookup in/out colorspaces, */
/* including allowance for PCS override. */
/* Any pointer may be NULL if value is not to be returned */
static void
icmLu4Spaces(
icmLu4Base *p,
icmCSInfo *ini, icmCSInfo *outi, icmCSInfo *pcsi,
icTagSignature *srctag, icRenderingIntent *intt,
icmLookupFunc *fnc, icmLookupOrder *ord,
icmPeOp *op, int *can_bwd
) {
if (ini != NULL) {
*ini = p->e_ini;
}
if (outi != NULL) {
*outi = p->e_outi;
}
if (pcsi != NULL) {
*pcsi = p->e_pcsi;
}
if (srctag != NULL)
*srctag = p->sourceTag;
if (intt != NULL)
*intt = p->intent;
if (fnc != NULL)
*fnc = p->function;
if (ord != NULL)
*ord = p->order;
if (op != NULL)
*op = p->op;
if (can_bwd != NULL)
*can_bwd = p->can_bwd;
}
/* Fake up alg from icmLu4Space attributes */
icmLuAlgType icmSynthAlgType(icmLuBase *pp) {
icmLuSpace *p = (icmLuSpace *)pp;
icmCSInfo ini, outi;
icmLookupFunc fnc;
int can_bwd;
icmLuAlgType alg;
if (p->ttype != icmSpaceLu4Type) { /* Must be named color ? */
return icmNamedType;
}
p->spaces(p, &ini, &outi, NULL, NULL, NULL, &fnc, NULL, NULL, &can_bwd);
alg = icmLutType;
if (can_bwd) {
if (fnc == icmFwd
&& p->output_pch->attr.op == icmPeOp_NOP) {
if (ini.sig == icSigGrayData)
alg = icmMonoFwdType;
else if (ini.sig == icSigRgbData
|| ini.sig == icSigCmyData)
alg = icmMatrixFwdType;
} else if (p->input_pch->attr.op == icmPeOp_NOP) {
if (outi.sig == icSigGrayData)
alg = icmMonoBwdType;
else if (outi.sig == icSigRgbData
|| outi.sig == icSigCmyData)
alg = icmMatrixBwdType;
}
}
return alg;
}
#define LU4XYZ_REL2ABS(TTYPE) (void (*)(TTYPE *, double *, double *))
/* Relative to Absolute for this WP in XYZ */
static void icmLu4XYZ_Rel2Abs(icmLu4Base *p, double *out, double *in) {
icmMulBy3x3(out, p->toAbs, in);
}
#define LU4XYZ_ABS2REL(TTYPE) (void (*)(TTYPE *, double *, double *))
/* Absolute to Relative for this WP in XYZ */
static void icmLu4XYZ_Abs2Rel(icmLu4Base *p, double *out, double *in) {
icmMulBy3x3(out, p->fromAbs, in);
}
/* - - - - - - - - - */
#define LU4WH_BK_POINTS(TTYPE) (int (*)(TTYPE *, double *, double *, double *))
/* Return the pcs, media white and black points in absolute XYZ space. */
/* Note that if not in the icc, the black point will be returned as 0, 0, 0, */
/* and the function will return nz. */
/* Any pointer may be NULL if value is not to be returned */
static int icmLu4Wh_bk_points(
icmLu4Base *p,
double *pcs,
double *wht,
double *blk
) {
if (pcs != NULL) {
icmXYZ2Ary(pcs, p->pcswht);
}
if (wht != NULL) {
icmXYZ2Ary(wht, p->whitePoint);
}
if (blk != NULL) {
icmXYZ2Ary(blk, p->blackPoint);
}
if (p->blackisassumed)
return 1;
return 0;
}
#define LU4LU_WH_BK_POINTS(TTYPE) (int (*)(TTYPE *, double *, double *, double *))
/* Get the LU white and black points in LU PCS space, converted to XYZ. */
/* (ie. white and black will be relative if LU is relative intent etc.) */
/* Return nz if the black point is being assumed to be 0,0,0 rather */
/* than being from the tag. */ \
static int icmLu4Lu_wh_bk_points(
icmLu4Base *p,
double *pcs,
double *wht,
double *blk
) {
if (pcs != NULL) {
icmXYZ2Ary(wht,p->pcswht);
}
if (wht != NULL) {
icmXYZ2Ary(wht,p->whitePoint);
}
if (blk != NULL) {
icmXYZ2Ary(blk,p->blackPoint);
}
if (p->intent != icAbsoluteColorimetric
&& p->intent != icmAbsolutePerceptual
&& p->intent != icmAbsoluteSaturation) {
if (pcs != NULL)
icmMulBy3x3(pcs, p->fromAbs, pcs);
if (wht != NULL)
icmMulBy3x3(wht, p->fromAbs, wht);
if (blk != NULL)
icmMulBy3x3(blk, p->fromAbs, blk);
}
if (p->blackisassumed)
return 1;
return 0;
}
/* - - - - - - - - - */
/* Transform methods */
/* Overall transforms */
static icmPe_lurv icmLu4_lookup_fwd (icmLu4Space *p, double *out, double *in) {
#ifndef CHECK_LOOKUP_PARTS
return p->lookup->lookup_fwd(p->lookup, out, in);
#else
# pragma message("######### icmLu4_lookup_fwd CHECK_LOOKUP_PARTS enabled ########")
double cin[MAX_CHAN], cout3[MAX_CHAN], cout5[MAX_CHAN];
double c31[MAX_CHAN], c32[MAX_CHAN];
double c51[MAX_CHAN], c52[MAX_CHAN];
icmPe_lurv rv, rv1, rv2, rv3, rv5;
double err;
int trace = 0, ltrace = p->lookup->trace;
int pad = 0;
if (ltrace) trace = 3;
p->lookup->trace = trace;
pad = trace-1;
if (trace) printf("### single part fwd ##\n");
if (trace) printf(PAD("Input %s\n"),icmPdv(p->lookup->inputChan, in));
icmCpyN(cin, in, p->lookup->inputChan);
rv = p->lookup->lookup_fwd(p->lookup, out, in);
/* 3 part: */
if (trace) printf("### 3 part fwd ##\n");
if (trace) printf(PAD("Input %s\n"),icmPdv(p->input->inputChan, cin));
p->input->trace = p->core3->trace = p->output->trace = trace;
if (trace) printf(" ## input\n");
p->input->lookup_fwd(p->input, c31, cin);
if (trace) printf(" ## core3\n");
p->core3->lookup_fwd(p->core3, c32, c31);
if (trace) printf(" ## output\n");
p->output->lookup_fwd(p->output, cout3, c32);
p->input->trace = p->core3->trace = p->output->trace = 0;
if ((err = icmDiffN(cout3, out, p->output->outputChan)) > 1e-6) {
printf("!!!!! 3 part fwd mismatch 1 part by %f\n",err);
}
/* 5 part: */
if (trace) printf("### 5 part fwd ##\n");
if (trace) printf(PAD("Input %s\n"),icmPdv(p->input_fmt->inputChan, cin));
p->input_fmt->trace = p->input_pch->trace = p->core5->trace
= p->output_pch->trace = p->output_fmt->trace = trace;
if (trace) printf(" ## input_fmt\n");
p->input_fmt->lookup_fwd(p->input_fmt, cout5, cin);
if (trace) printf(" ## input_pch\n");
p->input_pch->lookup_fwd(p->input_pch, c51, cout5);
if (trace) printf(" ## core5\n");
p->core5->lookup_fwd(p->core5, c52, c51);
if (trace) printf(" ## output_pch\n");
p->output_pch->lookup_fwd(p->output_pch, cout5, c52);
if (trace) printf(" ## output_fmt\n");
p->output_fmt->lookup_fwd(p->output_fmt, cout5, cout5);
p->input_fmt->trace = p->input_pch->trace = p->core5->trace
= p->output_pch->trace = p->output_fmt->trace = 0;
// Not a true expectation because core3 can include a format change...
// if ((err = icmDiffN(c31, c51, p->output->outputChan)) > 1e-6)
// printf("!!!!! 3 part inter 1 fwd mismatch 5 part inter 1 fwd by %f\n",err);
// if ((err = icmDiffN(c32, c52, p->output->outputChan)) > 1e-6)
// printf("!!!!! 3 part inter 2 fwd mismatch 5 part inter 2 fwd by %f\n",err);
if ((err = icmDiffN(cout5, out, p->output_fmt->outputChan)) > 1e-6)
printf("!!!!! 5 part fwd mismatch 1 part fwd by %f\n",err);
p->lookup->trace = ltrace;
return rv;
#endif
}
static icmPe_lurv icmLu4_lookup_bwd (icmLu4Space *p, double *out, double *in) {
#ifndef CHECK_LOOKUP_PARTS
return p->lookup->lookup_bwd(p->lookup, out, in);
#else
# pragma message("######### icmLu4_lookup_bwd CHECK_LOOKUP_PARTS enabled ########")
double cin[MAX_CHAN], cout3[MAX_CHAN], cout5[MAX_CHAN];
double c31[MAX_CHAN], c32[MAX_CHAN];
double c51[MAX_CHAN], c52[MAX_CHAN];
icmPe_lurv rv, rv1, rv2, rv3, rv5;
double err;
int trace, ltrace = p->lookup->trace;
if (ltrace) trace = 3;
p->lookup->trace = trace;
if (trace) printf("## single part bwd ##\n");
icmCpyN(cin, in, p->lookup->inputChan);
rv = p->lookup->lookup_bwd(p->lookup, out, in);
/* 3 part: */
if (trace) printf("### 3 part bwd ##\n");
p->input->trace = p->core3->trace = p->output->trace = trace;
if (trace) printf(" ## output\n");
p->output->lookup_bwd(p->output, c31, cin);
if (trace) printf(" ## core3\n");
p->core3->lookup_bwd(p->core3, c32, c31);
if (trace) printf(" ## input\n");
p->input->lookup_bwd(p->input, cout3, c32);
p->input->trace = p->core3->trace = p->output->trace = 0;
if ((err = icmDiffN(cout3, out, p->output->outputChan)) > 1e-6) {
printf("!!!!! 3 part bwd mismatch 1 part by %f\n",err);
}
/* 5 part: */
if (trace) printf("### 5 part bwd ##\n");
p->input_fmt->trace = p->input_pch->trace = p->core5->trace
= p->output_pch->trace = p->output_fmt->trace = trace;
if (trace) printf(" ## output_fmt\n");
p->output_fmt->lookup_bwd(p->output_fmt, cout5, cin);
if (trace) printf(" ## output_pch\n");
p->output_pch->lookup_bwd(p->output_pch, c51, cout5);
if (trace) printf(" ## core5\n");
p->core5->lookup_bwd(p->core5, c52, c51);
if (trace) printf(" ## input_pch\n");
p->input_pch->lookup_bwd(p->input_pch, cout5, c52);
if (trace) printf(" ## input_fmt\n");
p->input_fmt->lookup_bwd(p->input_fmt, cout5, cout5);
p->input_fmt->trace = p->input_pch->trace = p->core5->trace
= p->output_pch->trace = p->output_fmt->trace = 0;
// Not a true expectation because core3 can include a format change...
// if ((err = icmDiffN(c31, c51, p->output->outputChan)) > 1e-6)
// printf("!!!!! 3 part inter 1 bwd mismatch 5 part inter 1 bwd by %f\n",err);
// if ((err = icmDiffN(c32, c52, p->output->outputChan)) > 1e-6)
// printf("!!!!! 3 part inter 2 bwd mismatch 5 part inter 2 bwd by %f\n",err);
if ((err = icmDiffN(cout5, out, p->output_fmt->outputChan)) > 1e-6)
printf("!!!!! 5 part bwd mismatch 1 part bwd by %f\n",err);
p->lookup->trace = ltrace;
return rv;
#endif
}
/* 3 stage components of fwd lookup, in order. */
static icmPe_lurv icmLu4_input_fwd (icmLu4Space *p, double *out, double *in) {
return p->input->lookup_fwd(p->input, out, in);
}
static icmPe_lurv icmLu4_core3_fwd (icmLu4Space *p, double *out, double *in) {
return p->core3->lookup_fwd(p->core3, out, in);
}
static icmPe_lurv icmLu4_output_fwd (icmLu4Space *p, double *out, double *in) {
return p->output->lookup_fwd(p->output, out, in);
}
/* 3 stange components of bwd lookup, in order. */
static icmPe_lurv icmLu4_output_bwd (icmLu4Space *p, double *out, double *in) {
return p->output->lookup_bwd(p->output, out, in);
}
static icmPe_lurv icmLu4_core3_bwd (icmLu4Space *p, double *out, double *in) {
return p->core3->lookup_bwd(p->core3, out, in);
}
static icmPe_lurv icmLu4_input_bwd (icmLu4Space *p, double *out, double *in) {
return p->input->lookup_bwd(p->input, out, in);
}
/* 5 stage components of fwd lookup, in order. */
static icmPe_lurv icmLu4_input_fmt_fwd (icmLu4Space *p, double *out, double *in) {
return p->input_fmt->lookup_fwd(p->input_fmt, out, in);
}
static icmPe_lurv icmLu4_input_pch_fwd (icmLu4Space *p, double *out, double *in) {
return p->input_pch->lookup_fwd(p->input_pch, out, in);
}
static icmPe_lurv icmLu4_core5_fwd (icmLu4Space *p, double *out, double *in) {
return p->core5->lookup_fwd(p->core5, out, in);
}
static icmPe_lurv icmLu4_output_pch_fwd (icmLu4Space *p, double *out, double *in) {
return p->output_pch->lookup_fwd(p->output_pch, out, in);
}
static icmPe_lurv icmLu4_output_fmt_fwd (icmLu4Space *p, double *out, double *in) {
return p->output_fmt->lookup_fwd(p->output_fmt, out, in);
}
/* 5 stange components of bwd lookup, in order. */
static icmPe_lurv icmLu4_output_fmt_bwd (icmLu4Space *p, double *out, double *in) {
return p->output_fmt->lookup_bwd(p->output_fmt, out, in);
}
static icmPe_lurv icmLu4_output_pch_bwd (icmLu4Space *p, double *out, double *in) {
return p->output_pch->lookup_bwd(p->output_pch, out, in);
}
static icmPe_lurv icmLu4_core5_bwd (icmLu4Space *p, double *out, double *in) {
return p->core5->lookup_bwd(p->core5, out, in);
}
static icmPe_lurv icmLu4_input_pch_bwd (icmLu4Space *p, double *out, double *in) {
return p->input_pch->lookup_bwd(p->input_pch, out, in);
}
static icmPe_lurv icmLu4_input_fmt_bwd (icmLu4Space *p, double *out, double *in) {
return p->input_fmt->lookup_bwd(p->input_fmt, out, in);
}
static unsigned int icmLu4_max_in_res(icmLu4Space *p, int res[MAX_CHAN]) {
return p->lookup->max_in_res(p->lookup, res);
}
static unsigned int icmLu4_max_clut_res(icmLu4Space *p, int res[MAX_CHAN]) {
return p->lookup->max_clut_res(p->lookup, res);
}
static unsigned int icmLu4_max_out_res(icmLu4Space *p, int res[MAX_CHAN]) {
return p->lookup->max_out_res(p->lookup, res);
}
static int icmLu4_linear_light_inout(icmLu4Space *p, int dir) {
if (p->pcsi.sig != icSigXYZData)
return 0;
return p->lookup->linear_light_inout(p->lookup, dir);
}
static icmPeClut *icmLu4_get_lut(struct _icmLu4Space *p, icmPeContainer **ptail) {
return p->lookup->get_lut(p->lookup, ptail);
}
static double icmLu4_get_tac(icmLu4Space *p, double *chmax,
void (*calfunc)(void *cntx, double *out, double *in), void *cntx) {
return p->lookup->get_tac(p->lookup, chmax, calfunc, cntx);
}
static void icmLu4_del(icmLu4Space *p) {
if (p != NULL) {
/* Single */
if (p->lookup != NULL)
p->lookup->del(p->lookup);
/* 3 stage */
if (p->input != NULL)
p->input->del(p->input);
if (p->core3 != NULL)
p->core3->del(p->core3);
if (p->output != NULL)
p->output->del(p->output);
/* 5 stage */
if (p->input_fmt != NULL)
p->input_fmt->del(p->input_fmt);
if (p->input_pch != NULL)
p->input_pch->del(p->input_pch);
if (p->core5 != NULL)
p->core5->del(p->core5);
if (p->output_pch != NULL)
p->output_pch->del(p->output_pch);
if (p->output_fmt != NULL)
p->output_fmt->del(p->output_fmt);
p->icp->al->free(p->icp->al, p);
}
}
/* Return an appropriate lookup object */
/* Return NULL on error, and detailed error in icc */
static icmLu4Base *icc_get_lu4obj (
icc *icp, /* ICC */
icmLookupFunc func, /* Conversion functionality */
icRenderingIntent intent, /* Rendering intent, including icmAbsoluteColorimetricXYZ */
icColorSpaceSignature pcsor,/* PCS override (0 = def) */
icmLookupOrder order /* Conversion representation search Order */
) {
icmLu4Space *p = NULL; /* Lookup object to return */
icmPeSeq *tt = NULL; /* TagType transform */
icColorSpaceSignature pcs, e_pcs; /* Native PCS and effective PCS */
icColorSpaceSignature e_ins, e_outs;/* Effective (i.e. target) in/out spaces */
int e_ina = 0, e_outa = 0; /* Effective (i.e. target) in/out abs intent */
int inch, outch; /* Number of input and output channels */
struct {
icTagSignature sig; /* Tag to look for. */
icColorSpaceSignature in; /* (possibly normalized) Input space */
icColorSpaceSignature out; /* (possibly normalized) Output space */
int ina; /* Input abs intent flag */
int outa; /* Output abs intent flag */
} sch[10] = { 0 }; /* Tags to search for, in order */
int ix = 0, nix, ixi; /* Index into search table */
icColorSpaceSignature tdev, tpcs; /* Default transform Device and PCS encoding */
/* Figure out the native and effective PCS */
e_pcs = pcs = icp->header->pcs;
if (pcsor != icmSigDefaultData)
e_pcs = pcsor; /* Override */
/* Default in & out encoding for cLUT */
tdev = icp->header->colorSpace;
if (tdev == icSigLabData)
tdev = icmSigLabV2Data;
else if (tdev == icSigXYZData)
tdev = icmSigXYZ16Data ;
tpcs = icp->header->pcs;
if (tpcs == icSigLabData)
tpcs = icmSigLabV2Data;
else if (tpcs == icSigXYZData)
tpcs = icmSigXYZ16Data ;
#ifdef DEBUG_GET_LU
printf("icc_get_lu4obj:\n");
printf(" LookupFunc = %s\n",icm2str(icmTransformLookupFunc, func));
printf(" RenderingIntent = %s\n",icm2str(icmRenderingIntent, intent));
printf(" PCS override = %s\n",icm2str(icmColorSpaceSig, pcsor));
printf(" LookupOrder = %s\n",icm2str(icmTransformLookupOrder, order));
#endif
/* How we expect to execute the request depends firstly on the type of profile */
switch (icp->header->deviceClass) {
case icSigInputClass:
case icSigDisplayClass:
case icSigColorSpaceClass:
/* Look for Intent based AToBX profile + optional BToAX reverse */
/* or for AToB0 based profile + optional BToA0 reverse */
/* or three component matrix profile (reversable) */
/* or momochrome table profile (reversable) */
/* No Lut intent for ICC < V2.4, but possible for >= V2.4, */
/* so fall back if we can't find the chosen Lut intent. */
/* Device <-> PCS */
switch (func) {
case icmFwd: /* Device to PCS */
e_ins = icp->header->colorSpace;
e_outs = e_pcs;
if (intent == icmDefaultIntent)
intent = icp->header->renderingIntent;
switch ((int)intent) {
case icAbsoluteColorimetric:
e_outa = 1;
sch[ix].sig = icSigAToB1Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
case icRelativeColorimetric:
sch[ix].sig = icSigAToB1Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
case icmAbsolutePerceptual: /* Special icclib intent */
e_outa = 1; /* (fall though) */
case icPerceptual:
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
case icmAbsoluteSaturation: /* Special icclib intent */
e_outa = 1; /* (fall though) */
case icSaturation:
sch[ix].sig = icSigAToB2Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
default:
icm_err(icp, 1,"icc_get_luobj: Unknown intent");
return NULL;
}
break;
case icmBwd: /* PCS to Device */
e_ins = e_pcs;
e_outs = icp->header->colorSpace;
if (intent == icmDefaultIntent)
intent = icp->header->renderingIntent;
switch ((int)intent) {
case icAbsoluteColorimetric:
e_ina = 1;
sch[ix].sig = icSigBToA1Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
case icRelativeColorimetric:
sch[ix].sig = icSigBToA1Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
case icmAbsolutePerceptual: /* Special icclib intent */
e_ina = 1; /* (fall though) */
case icPerceptual:
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
case icmAbsoluteSaturation: /* Special icclib intent */
e_ina = 1; /* (fall though) */
case icSaturation:
sch[ix].sig = icSigBToA2Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
default:
icm_err(icp, 1,"icc_get_luobj: Unknown intent");
return NULL;
}
break;
default:
icm_err(icp, 1,"icc_get_luobj: Inaproptiate function requested");
return NULL;
}
/* Setup for forward and this wll be reverses on creating Pe */
sch[ix].sig = icmSigShaperMatrix;
sch[ix].in = icp->header->colorSpace;;
sch[ix++].out = icSigXYZData;
sch[ix].sig = icmSigShaperMono;
sch[ix].in = icp->header->colorSpace;
sch[ix++].out = icp->header->pcs;
break;
case icSigOutputClass:
/* Expect BToA Lut and optional AToB Lut, All intents, expect gamut */
/* or momochrome table profile (reversable) */
/* Device <-> PCS */
/* Gamut Lut - no intent, assume Relative Colorimetric */
/* Optional preview links PCS <-> PCS */
/* Determine the algorithm and set its parameters */
switch (func) {
icTagSignature ttag;
case icmFwd: /* Device to PCS */
e_ins = icp->header->colorSpace;
e_outs = e_pcs;
if (intent == icmDefaultIntent)
intent = icp->header->renderingIntent;
switch ((int)intent) {
case icAbsoluteColorimetric:
e_outa = 1;
sch[ix].sig = icSigAToB1Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
case icRelativeColorimetric:
sch[ix].sig = icSigAToB1Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
case icmAbsolutePerceptual: /* Special icclib intent */
e_outa = 1; /* (fall though) */
case icPerceptual:
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
case icmAbsoluteSaturation: /* Special icclib intent */
e_outa = 1; /* (fall though) */
case icSaturation:
sch[ix].sig = icSigAToB2Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
default:
icm_err(icp, 1,"icc_get_luobj: Unknown intent");
return NULL;
}
/* Setup for forward and this will be reverses on creating Pe */
sch[ix].sig = icmSigShaperMono;
sch[ix].in = icp->header->colorSpace;
sch[ix++].out = icp->header->pcs;
break;
case icmBwd: /* PCS to Device */
e_ins = e_pcs;
e_outs = icp->header->colorSpace;
if (intent == icmDefaultIntent)
intent = icp->header->renderingIntent;
switch ((int)intent) {
case icAbsoluteColorimetric:
e_ina = 1;
sch[ix].sig = icSigBToA1Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
case icRelativeColorimetric:
sch[ix].sig = icSigBToA1Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
case icmAbsolutePerceptual: /* Special icclib intent */
e_ina = 1; /* (fall though) */
case icPerceptual:
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
case icmAbsoluteSaturation: /* Special icclib intent */
e_ina = 1; /* (fall though) */
case icSaturation:
sch[ix].sig = icSigBToA2Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
default:
icm_err(icp, 1,"icc_get_luobj: Unknown intent");
return NULL;
}
/* Setup for forward and this will be reverses on creating Pe */
sch[ix].sig = icmSigShaperMono;
sch[ix].in = icp->header->colorSpace;
sch[ix++].out = icp->header->pcs;
break;
case icmGamut: /* PCS to 1D */
e_ins = e_pcs;
e_outs = icSigGrayData;
if ((icp->cflags & icmCFlagAllowQuirks) == 0
&& (icp->cflags & icmCFlagAllowExtensions) == 0) {
/* Allow only default and absolute */
if (intent != icmDefaultIntent
&& intent != icAbsoluteColorimetric) {
icm_err(icp, ICM_ERR_GAMUT_INTENT,"icc_get_luobj: Intent (0x%x)is unexpected for Gamut table",intent);
return NULL;
}
} else {
int warn = 0;
/* Be more forgiving */
switch ((int)intent) {
case icAbsoluteColorimetric:
e_ina = 1;
break;
case icmAbsolutePerceptual: /* Special icclib intent */
case icmAbsoluteSaturation: /* Special icclib intent */
warn = 1;
break;
case icmDefaultIntent:
break;
case icRelativeColorimetric:
case icPerceptual:
case icSaturation:
intent = icmDefaultIntent; /* Make all other look like default */
warn = 1;
break;
default:
icm_err(icp, ICM_ERR_GAMUT_INTENT,"icc_get_luobj: Unknown intent (0x%x)",intent);
return NULL;
}
if (warn) {
/* (icmQuirkWarning noset doesn't care about icp->op direction) */
icmQuirkWarning(icp, ICM_ERR_GAMUT_INTENT, 1, "icc_get_luobj: Intent (0x%x) is unexpected for Gamut table",intent);
}
}
sch[ix].sig = icSigGamutTag;
sch[ix].in = tpcs;
sch[ix++].out = icSigGrayData;
break;
case icmPreview: /* PCS to PCS */
e_ins = e_pcs;
e_outs = e_pcs;
switch ((int)intent) {
case icRelativeColorimetric:
sch[ix].sig = icSigBToA1Tag;
sch[ix].in = tpcs;
sch[ix++].out = tpcs;
break;
case icPerceptual:
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tpcs;
break;
case icSaturation:
sch[ix].sig = icSigBToA2Tag;
sch[ix].in = tpcs;
sch[ix++].out = tpcs;
break;
case icAbsoluteColorimetric:
case icmAbsolutePerceptual: /* Special icclib intent */
case icmAbsoluteSaturation: /* Special icclib intent */
icm_err(icp, 1,"icc_get_luobj: Intent is inappropriate for preview table");
return NULL;
default:
icm_err(icp, 1,"icc_get_luobj: Unknown intent");
return NULL;
}
break;
default:
icm_err(icp, 1,"icc_get_luobj: Inaproptiate function requested");
return NULL;
}
break;
case icSigLinkClass:
/* Expect AToB0 Lut and optional BToA0 Lut, One intent in header */
/* Device <-> Device */
if (intent != icp->header->renderingIntent
&& intent != icmDefaultIntent) {
icm_err(icp, 1,"icc_get_luobj: Intent is inappropriate for Link profile");
return NULL;
}
intent = icp->header->renderingIntent;
/* Determine the algorithm and set its parameters */
switch (func) {
case icmFwd: /* Device to PCS (== Device) */
e_ins = icp->header->colorSpace;
e_outs = icp->header->pcs;
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
case icmBwd: /* PCS (== Device) to Device */
e_ins = icp->header->pcs;
e_outs = icp->header->colorSpace;
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
default:
icm_err(icp, 1,"icc_get_luobj: Inaproptiate function requested");
return NULL;
}
break;
case icSigAbstractClass:
/* Expect AToB0 Lut and option BToA0 Lut, with either relative or absolute intent. */
/* PCS <-> PCS */
/* Determine the algorithm and set its parameters */
if (intent != icmDefaultIntent
&& intent != icRelativeColorimetric
&& intent != icAbsoluteColorimetric) {
icm_err(icp, 1,"icc_get_luobj: Intent is inappropriate for Abstract profile");
return NULL;
}
switch (func) {
case icmFwd: /* PCS (== Device) to PCS */
e_ins = e_pcs;
e_outs = e_pcs;
if (intent == icAbsoluteColorimetric) {
e_ina = 1;
e_outa = 1;
}
sch[ix].sig = icSigAToB0Tag;
sch[ix].in = tdev;
sch[ix++].out = tpcs;
break;
case icmBwd: /* PCS to PCS (== Device) */
e_ins = e_pcs;
e_outs = e_pcs;
if (intent == icAbsoluteColorimetric) {
e_ina = 1;
e_outa = 1;
}
sch[ix].sig = icSigBToA0Tag;
sch[ix].in = tpcs;
sch[ix++].out = tdev;
break;
default:
icm_err(icp, 1,"icc_get_luobj: Inaproptiate function requested");
return NULL;
}
break;
case icSigNamedColorClass:
/* Expect Name -> Device, Optional PCS */
/* and a reverse lookup would be useful */
/* (ie. PCS or Device coords to closest named color). */
/* ~~88 to be implemented ~~ */
/* ~~ Absolute intent is valid for processing of */
/* PCS from named Colors. Also allow for e_pcs */
if (intent != icmDefaultIntent
&& intent != icRelativeColorimetric
&& intent != icAbsoluteColorimetric) {
icm_err(icp, 1,"icc_get_luobj: Intent is inappropriate for Named Color profile");
return NULL;
}
e_ins = icmSigNamedData;
e_outs = e_pcs;
if (intent == icAbsoluteColorimetric)
e_ina = 1;
icm_err(icp, 1,"icc_get_luobj: Named Colors not implemented");
return NULL;
default:
icm_err(icp, 1,"icc_get_luobj: Unknown profile class '%s'",icm2str(icmProfileClassSig,icp->header->deviceClass));
return NULL;
}
/* Set search order */
nix = ix;
if (order == icmLuOrdRev) {
ix = nix-1;
ixi = -1;
} else {
ix = 0;
ixi = 1;
}
/* Go through the search list and see if we can create a color conversion */
for (; ix >= 0 && ix < nix; ix += ixi) {
#ifdef DEBUG_GET_LU
printf(" Checking sch[%d] sig %s\n",ix,icmLuSrcType2str(sch[ix].sig));
#endif
/* Mono and Matrix are special case Pe's made from several tags. */
if (sch[ix].sig == icmSigShaperMono || sch[ix].sig == icmSigShaperMatrix) {
int invert = 0;
if (func == icmBwd)
invert = 1;
if (sch[ix].sig == icmSigShaperMono)
tt = new_icmShaperMono(icp, invert);
else
tt = new_icmShaperMatrix(icp, invert);
if (tt != NULL && invert) {
icColorSpaceSignature t1;
int t2;
t1 = sch[ix].in; sch[ix].in = sch[ix].out; sch[ix].out = t1;
t2 = sch[ix].ina; sch[ix].ina = sch[ix].outa; sch[ix].outa = t2;
}
} else {
/* We know that all tags that are color transforms are icmPeSeq */
tt = (icmPeSeq *)icp->read_tag(icp, sch[ix].sig);
if (tt != NULL
&& (tt->ttype == icmSigUnknownType
|| tt->etype == icmSigPeNone))
tt = NULL; /* We can't use this... */
if (tt != NULL)
tt->reference(tt); /* We'll delete this at the end of the function... */
}
if (tt != NULL) {
#ifdef DEBUG_GET_LU
printf(" Choosing sch[%d] sig %s\n",ix,icmLuSrcType2str(sch[ix].sig));
#endif
break;
}
}
if (tt == NULL) {
icm_err(icp, 1,"icc_get_luobj: Unable to locate usable conversion");
return NULL;
}
tt->init(tt); /* Make sure attr is computed */
/* Correct cLut encoding for Tag Type used */
if (tt->etype == icmSigPeLut816) {
icmLut1 *ttt = (icmLut1 *)tt;
if (ttt->bpv == 1) {
if (sch[ix].in == icmSigLabV2Data)
sch[ix].in = icmSigLab8Data;
else if (sch[ix].in == icmSigXYZ16Data)
sch[ix].in = icmSigXYZ8Data;
if (sch[ix].out == icmSigLabV2Data)
sch[ix].out = icmSigLab8Data;
else if (sch[ix].out == icmSigXYZ16Data)
sch[ix].out = icmSigXYZ8Data;
}
}
/* For any non-PCS spaces that need normalisation, set cLut encoded ColorSpace */
if (tt->etype == icmSigPeLut816) {
sch[ix].in = icmSig2NormSig(icp, sch[ix].in, icmEncV2, icmEnc16bit);
sch[ix].out = icmSig2NormSig(icp, sch[ix].out, icmEncV2, icmEnc16bit);
}
/* Create and fill in the luobj */
if ((p = (icmLu4Space *) icp->al->calloc(icp->al, 1, sizeof(icmLu4Space))) == NULL) {
icm_err(icp, ICM_ERR_MALLOC, "Allocating icmLu failed");
return NULL;
}
p->ttype = icmSpaceLu4Type;
p->icp = icp;
p->sourceTag = sch[ix].sig;
p->intent = intent;
p->function = func;
p->order = order;
/* pcswht .. fromAbs are set by ->init_wh_bk() below */
p->ini.sig = icmNorm2Sig(icp, sch[ix].in);
p->ini.nch = icmCSSig2nchan(p->ini.sig);
p->outi.sig = icmNorm2Sig(icp, sch[ix].out);
p->outi.nch = icmCSSig2nchan(p->outi.sig);
p->pcsi.sig = pcs;
p->pcsi.nch = icmCSSig2nchan(p->pcsi.sig);
p->e_ini.sig = e_ins;
p->e_ini.nch = icmCSSig2nchan(p->e_ini.sig);
p->e_outi.sig = e_outs;
p->e_outi.nch = icmCSSig2nchan(p->e_outi.sig);
p->e_pcsi.sig = e_pcs;
p->e_pcsi.nch = icmCSSig2nchan(p->e_pcsi.sig);
/* We set all the icmCSInfo range info after determining normalizations.. */
p->del = icmLu4_del;
p->init_wh_bk = LU4INIT_WH_BK(icmLu4Space) icmLu4Init_Wh_bk;
p->native_spaces = LU4NATIVESPACES(icmLu4Space) icmLu4NativeSpaces;
p->spaces = LU4SPACES(icmLu4Space) icmLu4Spaces;
p->XYZ_Rel2Abs = LU4XYZ_REL2ABS(icmLu4Space) icmLu4XYZ_Rel2Abs;
p->XYZ_Abs2Rel = LU4XYZ_ABS2REL(icmLu4Space) icmLu4XYZ_Abs2Rel;
p->wh_bk_points = LU4WH_BK_POINTS(icmLu4Space) icmLu4Wh_bk_points;
p->lu_wh_bk_points = LU4LU_WH_BK_POINTS(icmLu4Space) icmLu4Lu_wh_bk_points;
p->lookup_fwd = icmLu4_lookup_fwd;
p->lookup_bwd = icmLu4_lookup_bwd;
p->input_fwd = icmLu4_input_fwd;
p->core3_fwd = icmLu4_core3_fwd;
p->output_fwd = icmLu4_output_fwd;
p->output_bwd = icmLu4_output_bwd;
p->core3_bwd = icmLu4_core3_bwd;
p->input_bwd = icmLu4_input_bwd;
p->input_fmt_fwd = icmLu4_input_fmt_fwd;
p->input_pch_fwd = icmLu4_input_pch_fwd;
p->core5_fwd = icmLu4_core5_fwd;
p->output_pch_fwd = icmLu4_output_pch_fwd;
p->output_fmt_fwd = icmLu4_output_fmt_fwd;
p->output_fmt_bwd = icmLu4_output_fmt_bwd;
p->output_pch_bwd = icmLu4_output_pch_bwd;
p->core5_bwd = icmLu4_core5_bwd;
p->input_pch_bwd = icmLu4_input_pch_bwd;
p->input_fmt_bwd = icmLu4_input_fmt_bwd;
p->max_in_res = icmLu4_max_in_res;
p->max_clut_res = icmLu4_max_clut_res;
p->max_out_res = icmLu4_max_out_res;
p->linear_light_inout = icmLu4_linear_light_inout;
p->get_lut = icmLu4_get_lut;
p->get_tac = icmLu4_get_tac;
inch = tt->inputChan;
outch = tt->outputChan;
/* Create the Pe's for all the transforms */
if ((p->lookup = new_icmPeContainer(icp, inch, outch)) == NULL
|| (p->input = new_icmPeContainer(icp, inch, inch)) == NULL
|| (p->core3 = new_icmPeContainer(icp, inch, outch)) == NULL
|| (p->output = new_icmPeContainer(icp, outch, outch)) == NULL
|| (p->input_fmt = new_icmPeContainer(icp, inch, inch)) == NULL
|| (p->input_pch = new_icmPeContainer(icp, inch, inch)) == NULL
|| (p->core5 = new_icmPeContainer(icp, inch, outch)) == NULL
|| (p->output_pch = new_icmPeContainer(icp, outch, outch)) == NULL
|| (p->output_fmt = new_icmPeContainer(icp, outch, outch)) == NULL) {
p->del(p);
return NULL;
}
/* -------------------------- */
/* Create the overall lookup: */
/* Add any PCS format conversions to the tagtype transform */
/* Setup any input PCS format converters */
if (create_fmt(p->lookup, p, sch[ix].in, sch[ix].ina, e_ins, e_ina)) {
p->del(p);
return NULL;
}
/* Append the tagtype transform */
if (p->lookup->append_pes(p->lookup, tt, 0, tt->count)) {
p->del(p);
return NULL;
}
/* Setup any output PCS format converters */
if (create_fmt(p->lookup, p, e_outs, e_outa, sch[ix].out, sch[ix].outa)) {
p->del(p);
return NULL;
}
p->lookup->init(p->lookup);
/* -------------------------- */
/* Split the overall up into the 3 component lookup: */
/* Setup components based on scanning the contents of lookup transform. */
/* NULL or NOP pe's are skipped. */
/* The in_curve is assumed to end on the first matrix, cLUT or complex Pe, */
/* as long as there is at least one non-norm per-channel op. */
/* If there is no matrix, cLUT or complex Pe, the whole transform */
/* is placed in the core transform. */
{
int nnpch = 0;
int ix1, ix2;
/* Locate the start of the core transform */
for (ix1 = 0; ix1 < p->lookup->count; ix1++) {
if (p->lookup->pe[ix1] == NULL
|| p->lookup->pe[ix1]->attr.op == icmPeOp_NOP
|| p->lookup->pe[ix1]->attr.op == icmPeOp_perch) {
if (p->lookup->pe[ix1] != NULL
&& p->lookup->pe[ix1]->attr.op == icmPeOp_perch
&& p->lookup->pe[ix1]->attr.norm == 0)
nnpch = 1;
continue;
}
break;
}
/* If we didn't find an input non-norm per-channel op, then keep all the */
/* NOP's and normalizing per-channel as part of the core. */
if (nnpch == 0)
ix1 = 0;
/* Whole transform is per channel */
if (ix1 >= p->lookup->count) {
if (p->core3->append_pes(p->core3, (icmPeSeq *)p->lookup, 0, p->lookup->count)) {
p->del(p);
return NULL;
}
/* There are components */
} else {
/* Locate the end of the core transform */
nnpch = 0;
for (ix2 = p->lookup->count-1; ix2 > ix1; ix2--) {
if (p->lookup->pe[ix2] == NULL
|| p->lookup->pe[ix2]->attr.op == icmPeOp_NOP
|| p->lookup->pe[ix2]->attr.op == icmPeOp_perch) {
if (p->lookup->pe[ix2] != NULL
&& p->lookup->pe[ix2]->attr.op == icmPeOp_perch
&& p->lookup->pe[ix2]->attr.norm == 0)
nnpch = 1;
continue;
}
break;
}
ix2++;
/* If we didn't find an output non-norm per-channel op, then keep all the */
/* NOP's and normalizing per-channel as part of the core. */
if (nnpch == 0)
ix2 = p->lookup->count;
/* Add the components we've found */
if (p->input->append_pes(p->input, (icmPeSeq *)p->lookup, 0, ix1)
|| p->core3->append_pes(p->core3, (icmPeSeq *)p->lookup, ix1, ix2)
|| p->output->append_pes(p->output, (icmPeSeq *)p->lookup, ix2, p->lookup->count)) {
p->del(p);
return NULL;
}
}
}
p->input->init(p->input);
p->core3->init(p->core3);
p->output->init(p->output);
/* Add intermediate de-norm/normalization functions to make */
/* all i/o be full range. */
/* (Note that the effective and native PCS types will be the same if */
/* the per channel transform is not a NOP) */
{
/* If native input space is a normalized space */
if (p->input->attr.op != icmPeOp_NOP && (icmCSSig2type(sch[ix].in) & CSSigType_NORM)) {
icmPe *toNorm, *fromNorm;
/* Create normalizing/de-normalizing transform */
if ((toNorm = new_icmNSig2NormPe(icp, NULL, sch[ix].in, 0, 1)) == NULL) {
p->del(p);
return NULL;
}
if ((fromNorm = new_icmNSig2NormPe(icp, NULL, sch[ix].in, 1, 1)) == NULL) {
toNorm->del(toNorm);
p->del(p);
return NULL;
}
/* add these in the appropriate places */
if (p->input->append(p->input, fromNorm)
|| p->core3->prepend(p->core3, toNorm)) {
p->del(p);
return NULL;
}
toNorm->del(toNorm); /* Container has taken a reference */
fromNorm->del(fromNorm);
}
/* If native output space is a normalized space */
if (p->output->attr.op != icmPeOp_NOP && (icmCSSig2type(sch[ix].out) & CSSigType_NORM)) {
icmPe *toNorm, *fromNorm;
/* Create normalizing/de-normalizing transform */
if ((toNorm = new_icmNSig2NormPe(icp, NULL, sch[ix].out, 0, 1)) == NULL) {
p->del(p);
return NULL;
}
if ((fromNorm = new_icmNSig2NormPe(icp, NULL, sch[ix].out, 1, 1)) == NULL) {
toNorm->del(toNorm);
p->del(p);
return NULL;
}
/* add these in the appropriate places */
if (p->core3->append(p->core3, fromNorm)
|| p->output->prepend(p->output, toNorm)) {
p->del(p);
return NULL;
}
toNorm->del(toNorm); /* Container has taken a reference */
fromNorm->del(fromNorm);
}
}
p->input->init(p->input);
p->core3->init(p->core3);
p->output->init(p->output);
/* -------------------------- */
/* Split the overall up into the 5 component lookup: */
/* This is similar to above except we split the transform before adding */
/* format conversion. */
/* Setup components based on scanning the contents of tt transform. */
/* NULL or NOP pe's are skipped. */
/* The in_curve is assumed to end on the first matrix, cLUT or complex Pe. */
/* If there is no matrix, cLUT or complex Pe, the whole transform */
/* is placed in the core transform. */
/* (We don't have to worry about not splitting a norm only per channel, */
/* because we haven't added any.) */
{
int ix1, ix2;
/* Locate the start of the core transform */
for (ix1 = 0; ix1 < tt->count; ix1++) {
if (tt->pe[ix1] == NULL
|| tt->pe[ix1]->attr.op == icmPeOp_NOP
|| tt->pe[ix1]->attr.op == icmPeOp_perch) {
continue;
}
break;
}
/* Whole transform is per channel */
if (ix1 >= tt->count) {
if (p->core5->append_pes(p->core5, (icmPeSeq *)tt, 0, tt->count)) {
p->del(p);
return NULL;
}
/* There are components */
} else {
/* Locate the end of the core transform */
for (ix2 = tt->count-1; ix2 > ix1; ix2--) {
if (tt->pe[ix2] == NULL
|| tt->pe[ix2]->attr.op == icmPeOp_NOP
|| tt->pe[ix2]->attr.op == icmPeOp_perch) {
continue;
}
break;
}
ix2++;
/* Add the components we've found */
if (p->input_pch->append_pes(p->input_pch, (icmPeSeq *)tt, 0, ix1)
|| p->core5->append_pes(p->core5, (icmPeSeq *)tt, ix1, ix2)
|| p->output_pch->append_pes(p->output_pch, (icmPeSeq *)tt, ix2, tt->count)) {
p->del(p);
return NULL;
}
}
}
p->input_pch->init(p->input_pch);
p->core5->init(p->core5);
p->output_pch->init(p->output_pch);
/* Add the final 2 parts of the 5 part conversion: */
/* Because we want each part to have full range values, make sure the */
/* format conversion doesn't do the normalization. */
/* Create any input PCS format converters */
if (create_fmt(p->input_fmt, p, p->ini.sig, sch[ix].ina, e_ins, e_ina)) {
p->del(p);
return NULL;
}
/* Create any output PCS format converters */
if (create_fmt(p->output_fmt, p, e_outs, e_outa, p->outi.sig, sch[ix].outa)) {
p->del(p);
return NULL;
}
/* Add intermediate de-norm/normalization functions to make */
/* all i/o be full range. */
{
/* If input space is a normalized space */
if (icmCSSig2type(sch[ix].in) & CSSigType_NORM) {
icmPe *toNorm, *fromNorm;
/* Create normalizing/de-normalizing transform */
if ((toNorm = new_icmNSig2NormPe(icp, NULL, sch[ix].in, 0, 1)) == NULL) {
p->del(p);
return NULL;
}
if ((fromNorm = new_icmNSig2NormPe(icp, NULL, sch[ix].in, 1, 1)) == NULL) {
toNorm->del(toNorm);
p->del(p);
return NULL;
}
/* add these in the appropriate places */
if (p->input_pch->attr.op != icmPeOp_NOP) {
if (p->input_pch->prepend(p->input_pch, toNorm)
|| p->input_pch->append(p->input_pch, fromNorm)) {
p->del(p);
return NULL;
}
}
if (p->core5->prepend(p->core5, toNorm)) {
p->del(p);
return NULL;
}
toNorm->del(toNorm); /* Containers have taken a reference */
fromNorm->del(fromNorm);
}
/* If output space is a normalized space */
if (icmCSSig2type(sch[ix].out) & CSSigType_NORM) {
icmPe *toNorm, *fromNorm;
/* Create normalizing/de-normalizing transform */
if ((toNorm = new_icmNSig2NormPe(icp, NULL, sch[ix].out, 0, 1)) == NULL) {
p->del(p);
return NULL;
}
if ((fromNorm = new_icmNSig2NormPe(icp, NULL, sch[ix].out, 1, 1)) == NULL) {
toNorm->del(toNorm);
p->del(p);
return NULL;
}
/* add these in the appropriate places */
if (p->core5->append(p->core5, fromNorm)) {
p->del(p);
return NULL;
}
if (p->output_pch->attr.op != icmPeOp_NOP) {
if (p->output_pch->prepend(p->output_pch, toNorm)
|| p->output_pch->append(p->output_pch, fromNorm)) {
p->del(p);
return NULL;
}
}
toNorm->del(toNorm); /* Container has taken a reference */
fromNorm->del(fromNorm);
}
}
/* Make sure all the attributes are computed */
p->input_fmt->init(p->input_fmt);
p->input_pch->init(p->input_pch);
p->core5->init(p->core5);
p->output_pch->init(p->output_pch);
p->output_fmt->init(p->output_fmt);
p->op = p->lookup->attr.op;
p->can_bwd = p->lookup->attr.bwd;
/* set the ranges */
if (p->ini.min[0] == 0.0 && p->ini.max[0] == 0.0 )
icmGetDefaultRange(icp, p->ini.min, p->ini.max, sch[ix].in);
if (p->outi.min[0] == 0.0 && p->outi.max[0] == 0.0 )
icmGetDefaultRange(icp, p->outi.min, p->outi.max, sch[ix].out);
if (p->ini.sig == pcs) {
icmCpyN(p->pcsi.min, p->ini.min, p->ini.nch);
icmCpyN(p->pcsi.max, p->ini.max, p->ini.nch);
} else if (p->outi.sig == pcs) {
icmCpyN(p->pcsi.min, p->outi.min, p->outi.nch);
icmCpyN(p->pcsi.max, p->outi.max, p->outi.nch);
} else
icmGetDefaultRange(icp, p->pcsi.min, p->pcsi.max, pcs);
if (e_ins == p->ini.sig) {
icmCpyN(p->e_ini.min, p->ini.min, p->ini.nch);
icmCpyN(p->e_ini.max, p->ini.max, p->ini.nch);
} else
icmGetDefaultRange(icp, p->e_ini.min, p->e_ini.max, e_ins);
if (e_outs == p->outi.sig) {
icmCpyN(p->e_outi.min, p->outi.min, p->outi.nch);
icmCpyN(p->e_outi.max, p->outi.max, p->outi.nch);
} else
icmGetDefaultRange(icp, p->e_outi.min, p->e_outi.max, e_outs);
if (p->e_ini.sig == e_pcs) {
icmCpyN(p->e_pcsi.min, p->e_ini.min, p->e_ini.nch);
icmCpyN(p->e_pcsi.max, p->e_ini.max, p->e_ini.nch);
} else if (p->e_outi.sig == e_pcs) {
icmCpyN(p->e_pcsi.min, p->e_outi.min, p->e_outi.nch);
icmCpyN(p->e_pcsi.max, p->e_outi.max, p->e_outi.nch);
} else
icmGetDefaultRange(icp, p->e_pcsi.min, p->e_pcsi.max, e_pcs);
if (p->init_wh_bk(p)) {
p->del(p);
return NULL;
}
/* See if there is a cLut that needs its interpolation algorithm determining */
/* (We're relying on append_pes() having flattened any hierarchy of icmPeSeq's) */
for (ix = 0; ix < p->lookup->count; ix++) {
if (p->lookup->pe[ix] == NULL)
continue;
if (p->lookup->pe[ix]->isPeSeq) {
icm_err(icp, ICM_ERR_SEARCH_PESEQ_INTERNAL, "icc_get_lu4obj found unexpected icmPeSeq inside icmPeContainer");
p->del(p);
return NULL;
/* Is this likely ? Does it make any sense if it existed ?? */
} else if (p->lookup->pe[ix]->etype == icmSigPeInverter) {
icmPeInverter *iv = (icmPeInverter *)p->lookup->pe[ix];
if (iv->pe->etype == icmSigPeClut) {
icmPeClut *cl = (icmPeClut *)iv->pe;
cl->choose_alg(cl, p);
break;
}
} else if (p->lookup->pe[ix]->etype == icmSigPeClut) {
icmPeClut *cl = (icmPeClut *)p->lookup->pe[ix];
cl->choose_alg(cl, p);
break;
}
}
/* We're done with source transform */
tt->del(tt);
#ifdef ALWAYSLUTRACE
# pragma message("######### icmLu ALWAYSLUTRACE enabled ########")
p->lookup->trace = 1;
p->input->trace = 1;
p->core3->trace = 1;
p->output->trace = 1;
p->input_fmt->trace = 1;
p->input_pch->trace = 1;
p->core5->trace = 1;
p->output_pch->trace = 1;
p->output_fmt->trace = 1;
#endif
#ifdef DEBUG_GET_LU
printf("done icc_get_lu4obj\n");
#endif
return (icmLu4Base *)p;
}
/* =========================================================================================== */
/* ICC profile transform creation support */
/* =========================================================================================== */
#define CLIP_MARGIN 0.005 /* Margine to allow before reporting clipping = 0.5% */
/* Helper function to set multiple Monochrome tags simultaneously. */
/* Note that these tables and matrix value all have to be */
/* compatible in having the same configuration and resolutions. */
/* Set errc and return error number in underlying icc */
/* Note that clutfunc in[] value has "index under". */
/* Returns ec */
int icc_create_mono_xforms(
struct _icc *icp,
int flags, /* Setting flags */
void *cbctx, /* Opaque callback context pointer value */
int ntables, /* Number of tables to be set, 1..n */
icmXformSigs *sigs, /* signatures and tag types for each table */
/* ICCV2 Matrix/gamma/shaper uses icmSigShaperMatrix */
/* Valid combinations are:
icmSigShaperMono + icmSigShaperMatrixType
icSigAToB0Tag + icSigLut16Type
(icSigBToA0Tag + icSigLutB16Type automatically added) */
unsigned int inputEnt, /* Num of in-table entries */
unsigned int inv_inputEnt, /* Num of inverse in-table entries */
icColorSpaceSignature insig, /* Input color space */
icColorSpaceSignature outsig, /* Output color space */
void (*infunc)(void *cbctx, double *out, double *in, int tn)
/* Input transfer function, inspace->inspace' (NULL = default) */
/* Will be called ntables times each input grid value */
) {
/* Pointers to elements to set */
struct {
icmBase *wo_fwd; /* TagType */
icmPeCurve *in_fwd; /* Input curve */
icmPeMatrix *matrix_fwd; /* Either matrix or clut set */
icmPeClut *clut_fwd;
icmPeCurve *out_fwd[3]; /* Output curve */
icmBase *wo_bwd; /* bwd TagType */
icmPeCurve *in_bwd[3]; /* bwd Input curve */
icmPeMatrix *matrix_bwd; /* Either inverse matrix or bwd clut set */
icmPeClut *clut_bwd;
icmPeCurve *out_bwd; /* bwd output curve */
icmPe *pcs_nf; /* PCS normalization function */
int clip_ent; /* nz to clip entry values of tables */
} *tables = NULL;
int inputChan, outputChan;
int ii[MAX_CHAN]; /* Index value */
double _iv[2 * MAX_CHAN], *iv = &_iv[MAX_CHAN], *ivn; /* Real index value/table value */
int tn, i, e, f;
int clip = 0;
inputChan = icmCSSig2nchan(insig);
outputChan = icmCSSig2nchan(outsig);
if (inputChan != 1 || outputChan != 3)
return icm_err(icp, ICM_ERR_CRM_XFORMS_BADNCHAN, "icc_create_mono_xforms got unexpected no. inChan %d and outChan %d",inputChan, outputChan);
/* Allocate the per table info */
if ((tables = icp->al->calloc(icp->al, ntables, sizeof(*tables))) == NULL) {
return icm_err(icp, ICM_ERR_MALLOC, "Allocating icc_create_mono_xforms tables failed");
}
/* Create tags and tagtypes for each table. */
for (tn = 0; tn < ntables; tn++) {
if (sigs[tn].sig == icmSigShaperMono
&& sigs[tn].ttype == icmSigShaperMatrixType) {
/* ---------------------------------------------------------- */
icmCurve *wo;
tables[tn].clip_ent = 1;
/* Delete any existing tag */
if (icp->delete_tag_quiet(icp, icSigGrayTRCTag) != ICM_ERR_OK)
return icp->e.c;
if ((tables[tn].wo_fwd = icp->add_tag(icp, sigs[tn].sig, sigs[tn].ttype)) == NULL)
return icp->e.c;
wo = (icmCurve *)tables[tn].wo_fwd;
tables[tn].in_fwd = (icmPeCurve *)wo;
/* Allocate the fwd curve contents */
wo->outputChan = wo->inputChan = 1;
wo->ctype = icmCurveSpec;
wo->count = inputEnt;
if (wo->allocate(wo))
return icp->e.c;
}
else if (sigs[tn].sig == icSigAToB0Tag) {
if (sigs[tn].ttype == icSigLut16Type) {
/* ---------------------------------------------------------- */
icColorSpaceSignature pcsnsig;
icmLut1 *wo;
tables[tn].clip_ent = 1;
/* Setup normalization Pe */
pcsnsig = icmSig2NormSig(icp, outsig, icmEncV2, icmEnc16bit);
tables[tn].pcs_nf = new_icmNSig2NormPe(icp, NULL, pcsnsig, 0, 1);
/* - - - - - - fwd transform - - - - - - */
/* Delete any existing tag and add one */
if (icp->delete_tag_quiet(icp, sigs[tn].sig) != ICM_ERR_OK)
return icp->e.c;
if ((tables[tn].wo_fwd = icp->add_tag(icp, sigs[tn].sig, sigs[tn].ttype)) == NULL)
return icp->e.c;
wo = (icmLut1 *)tables[tn].wo_fwd;
/* Allocate space in tags */
wo->inputChan = 1;
wo->outputChan = 3;
wo->inputEnt = inputEnt;
wo->clutPoints = 2;
wo->outputEnt = 2;
if (wo->allocate(wo))
return icp->e.c;
/* Get pointers to tables to be filled */
tables[tn].in_fwd = wo->pe_ic[0];
tables[tn].clut_fwd = wo->pe_cl;
tables[tn].out_fwd[0] = wo->pe_oc[0];
tables[tn].out_fwd[1] = wo->pe_oc[1];
tables[tn].out_fwd[2] = wo->pe_oc[2];
if (icp->e.c != ICM_ERR_OK)
return icp->e.c;
/* - - - - - - bwd transform - - - - - - */
/* Delete any existing tag and add one */
if (icp->delete_tag_quiet(icp, icSigBToA0Tag) != ICM_ERR_OK)
return icp->e.c;
if ((tables[tn].wo_bwd = icp->add_tag(icp, icSigBToA0Tag, icSigLut16Type)) == NULL)
return icp->e.c;
wo = (icmLut1 *)tables[tn].wo_bwd;
/* Allocate space in tags */
wo->inputChan = 3;
wo->outputChan = 1;
wo->inputEnt = 1024;
wo->clutPoints = 2;
wo->outputEnt = inv_inputEnt;
if (wo->allocate(wo))
return icp->e.c;
/* Get pointers to tables to be filled */
tables[tn].in_bwd[0] = wo->pe_ic[0];
tables[tn].in_bwd[1] = wo->pe_ic[1];
tables[tn].in_bwd[2] = wo->pe_ic[2];
tables[tn].clut_bwd = wo->pe_cl;
tables[tn].out_bwd = wo->pe_oc[0];
if (icp->e.c != ICM_ERR_OK)
return icp->e.c;
}
else {
return icm_err(icp, ICM_ERR_CRM_XFORMS_BADTTYPE, "icc_create_mono_xforms got unexpected tag type '%s'", icmTypeSig2str(sigs[tn].ttype));
}
}
else {
return icm_err(icp, ICM_ERR_CRM_XFORMS_BADSIG, "icc_create_mono_xforms got unexpected sig '%s'", icmTagSig2str(sigs[tn].sig));
}
} /* Next table */
/* Fill in the transform values */
for (tn = 0; tn < ntables; tn++) {
double nwp[3]; /* (possibly) Normalized white point */
double nbp[3]; /* (possibly) Normalized black point */
int wplix; /* wp luminance index */
/* Compute the normalized PCS white */
if (outsig == icSigLabData) {
nwp[0] = 100.0, nwp[1] = 0.0, nwp[2] = 0.0;
nbp[0] = 0.0, nbp[1] = 0.0, nbp[2] = 0.0;
wplix = 0;
} else {
icmXYZ2Ary(nwp, icp->header->illuminant);
nbp[0] = 0.0, nbp[1] = 0.0, nbp[2] = 0.0;
wplix = 1;
}
if (tables[tn].pcs_nf != NULL) {
tables[tn].pcs_nf->lookup_fwd(tables[tn].pcs_nf, nwp, nwp);
tables[tn].pcs_nf->lookup_fwd(tables[tn].pcs_nf, nbp, nbp);
}
/* - - - - - fwd transform - - - - - - - */
/* Fill in the input table */
if (tables[tn].in_fwd != NULL) {
/* Create the input table entry values */
for (i = 0; i < tables[tn].in_fwd->count; i++) {
double fv = i/(tables[tn].in_fwd->count-1.0);
*((int *)&iv[-((int)0)-1]) = i; /* Trick to supply grid index in iv[] */
icmSetN(iv, fv, inputChan);
if (infunc != NULL)
infunc(cbctx, iv, iv, tn); /* inspace -> input function -> inspace' */
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, inputChan) > CLIP_MARGIN)
clip |= 1;
}
tables[tn].in_fwd->data[i] = iv[0];
}
}
/* Create a matrix mono->PCS */
if (tables[tn].matrix_fwd != NULL) {
for (i = 0; i < 3; i++)
tables[tn].matrix_fwd->mx[i][0] = nwp[i];
}
/* or create a cLut mono->PCS */
if (tables[tn].clut_fwd != NULL) {
/* Just two entries to interp between 0 and wp */
icmCpy3(tables[tn].clut_fwd->clutTable, nbp);
icmCpy3(tables[tn].clut_fwd->clutTable + 3, nwp);
}
/* Create a NOP output table */
if (tables[tn].out_fwd[0] != 0) {
for (e = 0; e < 3; e++) {
tables[tn].out_fwd[e]->data[0] = 0.0;
tables[tn].out_fwd[e]->data[1] = 1.0;
}
}
/* - - - - - bwd transform - - - - - - - */
/* Create a NOP/divide by wp inverse matrix mono->PCS */
if (tables[tn].in_bwd[0] != NULL) {
/* Create the input table entry values */
for (i = 0; i < tables[tn].in_bwd[0]->count; i++) {
double fv = i/(tables[tn].in_bwd[0]->count-1.0);
if (outsig == icSigLabData) {
iv[0] = fv/nwp[0];
iv[1] = fv;
iv[2] = fv;
} else {
for (f = 0; f < 3; f++)
iv[f] = fv/nwp[f];
}
icmClip3(iv, iv);
for (f = 0; f < 3; f++)
tables[tn].in_bwd[f]->data[i] = iv[f];
}
}
/* Create an inverse matrix */
if (tables[tn].matrix_bwd != NULL) {
for (i = 0; i < 3; i++) {
if (i == wplix)
tables[tn].matrix_bwd->mx[0][i] = 1.0/nwp[i];
else
tables[tn].matrix_bwd->mx[0][i] = 0.0;
}
}
/* else create PCS->mono cLut */
if (tables[tn].clut_bwd != NULL) {
for (e = 0; e < 8 ; e++) { /* Cube index */
if ((e & (4 >> wplix)) != 0) /* Luminance coord max */
tables[tn].clut_bwd->clutTable[e] = 1.0;
else
tables[tn].clut_bwd->clutTable[e] = 0.0;
}
}
/* Create output table */
if (tables[tn].out_bwd != NULL) {
if (tables[tn].in_fwd == NULL)
return icm_err(icp, ICM_ERR_CRM_XFORMS_NOFWD, "icc_create_mono_xforms got no fwd table to create bwd");
for (i = 0; i < tables[tn].out_bwd->count; i++) {
double fv = i/(tables[tn].out_bwd->count-1.0);
iv[0] = fv;
tables[tn].in_fwd->lookup_bwd(tables[tn].in_fwd, iv, iv);
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, inputChan) > CLIP_MARGIN)
clip |= 1;
}
tables[tn].out_bwd->data[i] = iv[0];
}
}
/* Free up transforms */
if (tables[tn].pcs_nf != NULL)
tables[tn].pcs_nf->del(tables[tn].pcs_nf);
}
icp->al->free(icp->al, tables);
return ICM_ERR_OK;
}
/* ============================================================ */
/* Helper function to set multiple Matrix tags simultaneously. */
/* Note that these tables and matrix value all have to be */
/* compatible in having the same configuration and resolutions. */
/* Set errc and return error number in underlying icc */
/* Note that clutfunc in[] value has "index under". */
/* Returns ec */
int icc_create_matrix_xforms(
struct _icc *icp,
int flags, /* Setting flags */
void *cbctx, /* Opaque callback context pointer value */
int ntables, /* Number of tables to be set, 1..n */
icmXformSigs *sigs, /* signatures and tag types for each table */
/* ICCV2 Matrix/gamma/shaper uses icmSigShaperMatrix */
/* Valid combinations are:
icmSigShaperMatrix + icmSigShaperMatrixType */
unsigned int inputEnt, /* Num of in-table entries */
unsigned int inv_inputEnt, /* Num of inverse in-table entries */
icColorSpaceSignature insig, /* Input color space */
icColorSpaceSignature outsig, /* Output color space */
void (*infunc)(void *cbctx, double *out, double *in, int tn),
/* Input transfer function, inspace->inspace' (NULL = default) */
/* Will be called ntables times each input grid value */
double mx[3][3], /* [outn][inn] Matrix values */
double ct[3], /* [outn] Constant values added after mx[][] multiply */
/* Must be NULL or 0.0 if a table uses icmSigShaperMatrix */
int isShTRC, /* NZ if shared TRCs */
double *gamma, /* != NULL if gamma rather than shaper. Only one gamma if isShTRC */
int isLinear /* NZ if pure linear, gamma = 1.0 */
) {
/* Pointers to elements to set */
struct {
icmBase *wo_fwd; /* TagType */
icmPeCurve *in_fwd[3]; /* Input curve */
icmXYZArray *chmx_fwd[3]; /* Matrix as RGB channels */
icmPeMatrix *matrix_fwd; /* Matrix */
icmBase *wo_bwd; /* bwd TagType */
icmPeMatrix *matrix_bwd; /* Inverse matrix */
icmPeCurve *out_bwd[3]; /* bwd output curve */
icmPe *pcs_nf; /* PCS normalization function */
int clip_ent; /* nz to clip entry values of tables */
} *tables = NULL;
int inputChan, outputChan;
int ii[MAX_CHAN]; /* Index value */
double _iv[2 * MAX_CHAN], *iv = &_iv[MAX_CHAN], *ivn; /* Real index value/table value */
int tn, i, e, f;
int clip = 0;
if (isLinear) /* isLinear takes priority */
gamma = NULL;
inputChan = icmCSSig2nchan(insig);
outputChan = icmCSSig2nchan(outsig);
if (inputChan != 3 || outputChan != 3 || outsig != icSigXYZData)
return icm_err(icp, ICM_ERR_CRX_XFORMS_BADNCHAN, "icc_create_matrix_xforms got unexpected no. inChan %d and outChan %d or output space %s",inputChan, outputChan,icmColorSpaceSig2str(outsig));
/* Allocate the per table info */
if ((tables = icp->al->calloc(icp->al, ntables, sizeof(*tables))) == NULL) {
return icm_err(icp, ICM_ERR_MALLOC, "Allocating icc_create_matrix_xforms tables failed");
}
/* Create tags and tagtypes for each table. */
for (tn = 0; tn < ntables; tn++) {
if (sigs[tn].sig == icmSigShaperMatrix
&& sigs[tn].ttype == icmSigShaperMatrixType) {
/* ---------------------------------------------------------- */
icmCurve *wcr, *wcg, *wcb;
icmXYZArray *wmr, *wmg, *wmb;
tables[tn].clip_ent = 1;
if (ct != NULL) {
for (i = 0; i < 3; i++) {
if (ct[i] != 0.0)
break;
}
if (i < 3)
return icm_err(icp, ICM_ERR_CRX_XFORMS_NZCONST, "icc_create_matrix_xforms got non-zero constant %s",icmPdv(3,ct));
}
/* Delete any existing input curve tags */
if (icp->delete_tag_quiet(icp, icSigRedTRCTag) != ICM_ERR_OK
|| icp->delete_tag_quiet(icp, icSigGreenTRCTag) != ICM_ERR_OK
|| icp->delete_tag_quiet(icp, icSigBlueTRCTag) != ICM_ERR_OK)
return icp->e.c;
if (isShTRC) { /* Make TRCs shared and the same value(s) */
if ((wcr = (icmCurve *)icp->add_tag(icp, icSigRedTRCTag, icSigCurveType)) == NULL
|| (wcg = (icmCurve *)icp->link_tag(icp, icSigGreenTRCTag, icSigRedTRCTag))
== NULL
|| (wcb = (icmCurve *)icp->link_tag(icp, icSigBlueTRCTag, icSigRedTRCTag))
== NULL)
return icp->e.c;
} else {
if ((wcr = (icmCurve *)icp->add_tag(icp, icSigRedTRCTag, icSigCurveType)) == NULL
|| (wcg = (icmCurve *)icp->add_tag(icp, icSigGreenTRCTag, icSigCurveType)) == NULL
|| (wcb = (icmCurve *)icp->add_tag(icp, icSigBlueTRCTag, icSigCurveType)) == NULL)
return icp->e.c;
}
/* Set gamma or linear */
if (gamma != NULL) {
if (isShTRC) {
wcr->ctype = icmCurveGamma;
if (wcr->allocate(wcr))
return icp->e.c;
wcr->data[0] = gamma[0];
} else {
wcr->ctype = wcg->ctype = wcb->ctype = icmCurveGamma;
if (wcr->allocate(wcr) || wcg->allocate(wcg) || wcb->allocate(wcb))
return icp->e.c;
wcr->data[0] = gamma[0];
wcg->data[0] = gamma[1];
wcb->data[0] = gamma[2];
}
} else if (isLinear) {
if (isShTRC) {
wcr->ctype = icmCurveLin;
if (wcr->allocate(wcr))
return icp->e.c;
} else {
wcr->ctype = wcg->ctype = wcb->ctype = icmCurveLin;
if (wcr->allocate(wcr) || wcg->allocate(wcg) || wcb->allocate(wcb))
return icp->e.c;
}
/* Prep table for callback */
} else {
wcr->ctype = wcg->ctype = wcb->ctype = icmCurveSpec;
wcr->count = wcg->count = wcb->count = inputEnt;
if (wcr->allocate(wcr) || wcg->allocate(wcg) || wcb->allocate(wcb))
return icp->e.c;
tables[tn].in_fwd[0] = wcr;
tables[tn].in_fwd[1] = wcg;
tables[tn].in_fwd[2] = wcb;
}
/* Delete any existing colorant tags */
if (icp->delete_tag_quiet(icp, icSigRedColorantTag) != ICM_ERR_OK
|| icp->delete_tag_quiet(icp, icSigGreenColorantTag) != ICM_ERR_OK
|| icp->delete_tag_quiet(icp, icSigBlueColorantTag) != ICM_ERR_OK)
return icp->e.c;
if ((wmr = (icmXYZArray *)icp->add_tag(icp, icSigRedColorantTag, icSigXYZArrayType))
== NULL
|| (wmg = (icmXYZArray *)icp->add_tag(icp, icSigGreenColorantTag, icSigXYZArrayType))
== NULL
|| (wmb = (icmXYZArray *)icp->add_tag(icp, icSigBlueColorantTag, icSigXYZArrayType))
== NULL)
return icp->e.c;
wmr->count = wmg->count = wmb->count = 1;
if (wmr->allocate(wmr) || wmg->allocate(wmg) || wmb->allocate(wmb))
return icp->e.c;
tables[tn].chmx_fwd[0] = wmr;
tables[tn].chmx_fwd[1] = wmg;
tables[tn].chmx_fwd[2] = wmb;
}
else {
return icm_err(icp, ICM_ERR_CRX_XFORMS_BADSIG, "icc_create_matrix_xforms got unexpected sig '%s' or type '%s'", icmTagSig2str(sigs[tn].sig), icmTypeSig2str(sigs[tn].ttype));
}
} /* Next table */
/* Fill in the transform values */
for (tn = 0; tn < ntables; tn++) {
double tmx[3][3];
double tct[3];
/* - - - - - fwd transform - - - - - - - */
/* Fill in the input table */
if (tables[tn].in_fwd[0] != NULL) {
/* Create the input table entry values */
for (i = 0; i < inputEnt; i++) {
double fv = i/(inputEnt-1.0);
*((int *)&iv[-((int)0)-1]) = i; /* Trick to supply grid index in iv[] */
icmSetN(iv, fv, inputChan);
if (infunc != NULL)
infunc(cbctx, iv, iv, tn); /* inspace -> input function -> inspace' */
if (isShTRC) {
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, 1) > CLIP_MARGIN)
clip |= 1;
}
for (e = 0; e < inputChan; e++)
tables[tn].in_fwd[e]->data[i] = iv[0];
} else {
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, inputChan) > CLIP_MARGIN)
clip |= 1;
}
for (e = 0; e < inputChan; e++)
tables[tn].in_fwd[e]->data[i] = iv[e];
}
}
}
if (tables[tn].chmx_fwd[0] != NULL) {
tables[tn].chmx_fwd[0]->data[0].X = mx[0][0];
tables[tn].chmx_fwd[0]->data[0].Y = mx[1][0];
tables[tn].chmx_fwd[0]->data[0].Z = mx[2][0];
tables[tn].chmx_fwd[1]->data[0].X = mx[0][1];
tables[tn].chmx_fwd[1]->data[0].Y = mx[1][1];
tables[tn].chmx_fwd[1]->data[0].Z = mx[2][1];
tables[tn].chmx_fwd[2]->data[0].X = mx[0][2];
tables[tn].chmx_fwd[2]->data[0].Y = mx[1][2];
tables[tn].chmx_fwd[2]->data[0].Z = mx[2][2];
}
/* Copy and scale matrix */
if (tables[tn].matrix_fwd != NULL) {
icmCpy3x3(tmx, mx);
if (ct != NULL)
icmCpy3(tct, ct);
else
icmSet3(tct, 0.0);
if (tables[tn].pcs_nf != NULL) {
icmTranspose3x3(tmx, tmx);
for (f = 0; f < 3; f++) {
tables[tn].pcs_nf->lookup_fwd(tables[tn].pcs_nf, tmx[f], tmx[f]);
}
tables[tn].pcs_nf->lookup_fwd(tables[tn].pcs_nf, tct, tct);
icmTranspose3x3(tmx, tmx);
}
for (f = 0; f < 3; f++) {
for (e = 0; e < 3; e++)
tables[tn].matrix_fwd->mx[f][e] = tmx[f][e];
tables[tn].matrix_fwd->ct[f] = tct[f];
}
}
/* - - - - - bwd transform - - - - - - - */
/* Create an inverse matrix */
if (tables[tn].matrix_bwd != NULL) {
double imx[3][3];
double ict[3];
if (icmInverse3x3(imx, tmx))
return icm_err(icp, ICM_ERR_CRX_XFORMS_NOIMX, "icc_create_matrix_xforms unable to invert fwd matrix");
icmMulBy3x3(ict, imx, tct);
icmScale3(ict, ict, -1.0);
for (f = 0; f < 3; f++) {
for (e = 0; e < 3; e++)
tables[tn].matrix_bwd->mx[f][e] = imx[f][e];
tables[tn].matrix_bwd->ct[f] = ict[f];
}
}
/* Create output table */
if (tables[tn].out_bwd[0] != NULL) {
if (tables[tn].in_fwd[0] == NULL)
return icm_err(icp, ICM_ERR_CRX_XFORMS_NOFWD, "icc_create_matrix_xforms got no fwd table to create bwd");
for (i = 0; i < inv_inputEnt; i++) {
double fv = i/(inv_inputEnt-1.0);
for (f = 0; f < 3; f++) {
iv[0] = fv;
tables[tn].in_fwd[f]->lookup_bwd(tables[tn].in_fwd[f], iv, iv);
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, 1) > CLIP_MARGIN)
clip |= 1;
}
tables[tn].out_bwd[f]->data[i] = iv[0];
}
}
}
/* Free up transforms */
if (tables[tn].pcs_nf != NULL)
tables[tn].pcs_nf->del(tables[tn].pcs_nf);
}
icp->al->free(icp->al, tables);
return ICM_ERR_OK;
}
/* ============================================================ */
/* Function to set multiple Lut tables simultaneously. */
/* Note that these tables all have to be compatible in */
/* having the same configuration and resolutions, and the */
/* same per channel input and output curves. */
/* Set errc and return error number in underlying icc */
/* Returns ec */
int icc_create_lut_xforms(
icc *icp,
int flags, /* Setting flags */
void *cbctx, /* Opaque callback context pointer value */
int ntables, /* Number of tables to be set, 1..n */
icmXformSigs *sigs, /* signatures and tag types for each table */
unsigned int bpv, /* Bytes per value of AToB or BToA CLUT, 1 or 2 */
unsigned int inputEnt, /* Num of in-table entries (must be 256 for Lut8 */
unsigned int clutPoints[MAX_CHAN], /* Num of grid points (must be same for Lut8/16) */
unsigned int outputEnt, /* Num of out-table entries (must be 256 for Lut8) */
icColorSpaceSignature insig, /* Input color space */
icColorSpaceSignature outsig, /* Output color space */
double *inmin, double *inmax, /* Maximum range of inspace values (Not used) */
/* (NULL = default) */
void (*infunc)(void *cbctx, double *out, double *in, int tn),
/* Input transfer function, inspace->inspace' (NULL = default) */
/* Will be called ntables times each input grid value */
double *indmin, double *indmax, /* Maximum range of inspace' values */
/* (NULL = same as inmin/max or default) */
void (*clutfunc)(void *cbntx, double *out, double *in, int tn),
/* inspace' -> outspace[ntables]' transfer function */
/* will be called once for each input' grid value, and ntable times */
/* Note that in[] value has "index under". */
double *clutmin, double *clutmax, /* Maximum range of outspace' values */
/* (NULL = default) */
void (*outfunc)(void *cbntx, double *out, double *in, int tn),
/* Output transfer function, outspace'->outspace (NULL = deflt) */
/* Will be called ntables times on each output value */
int *apxls_gmin, int *apxls_gmax/* If not NULL, the grid indexes not to be affected */
/* by ICM_CLUT_SET_APXLS, defaulting to 0..>clutPoints-1 */
) {
/* Pointers to elements to set */
struct {
icmBase *wo; /* TagType */
icmPeCurve *in[MAX_CHAN]; /* Associated Pe's */
icmPeClut *clut;
icmPeCurve *out[MAX_CHAN];
icmPe *in_nf; /* input normalization transform (NULL if NOP) */
icmPe *ind_nf; /* input' normalization transform (NULL if NOP) */
icmPe *outd_nf; /* output' normalization transform (NULL if NOP) */
icmPe *out_nf; /* output normalization transform (NULL if NOP) */
int clip_ent; /* nz to clip entry values of tables */
} *tables = NULL;
int inputChan, outputChan;
int ii[MAX_CHAN]; /* Index value */
psh counter; /* Pseudo-Hilbert counter */
double _iv[2 * MAX_CHAN], *iv = &_iv[MAX_CHAN], *ivn; /* Real index value/table value */
double ivc[MAX_CHAN]; /* Copy of iv */
double **clutTable2 = NULL; /* Cell center values for ICM_CLUT_SET_APXLS */
int def_apxls_gmin[MAX_CHAN], def_apxls_gmax[MAX_CHAN];
int tn, i, e, f;
int clip = 0;
inputChan = icmCSSig2nchan(insig);
outputChan = icmCSSig2nchan(outsig);
/* Allocate the per table info */
if ((tables = icp->al->calloc(icp->al, ntables, sizeof(*tables))) == NULL) {
return icm_err(icp, ICM_ERR_MALLOC, "Allocating icc_create_lut_xforms tables failed");
}
/* Create tags and tagtypes for each table. */
for (tn = 0; tn < ntables; tn++) {
if (sigs[tn].sig == icSigAToB0Tag
|| sigs[tn].sig == icSigAToB1Tag
|| sigs[tn].sig == icSigAToB2Tag
|| sigs[tn].sig == icSigBToA0Tag
|| sigs[tn].sig == icSigBToA1Tag
|| sigs[tn].sig == icSigBToA2Tag
|| sigs[tn].sig == icSigGamutTag) {
if (sigs[tn].ttype == icSigLut16Type
|| sigs[tn].ttype == icSigLut8Type) {
/* ---------------------------------------------------------- */
icColorSpaceSignature ninsig; /* Normalized input space */
icColorSpaceSignature noutsig; /* Normalized output space */
icmLut1 *wo;
ninsig = icmSig2NormSig(icp, insig, icmEncV2,
sigs[tn].ttype == icSigLut16Type ? icmEnc16bit : icmEnc8bit);
noutsig = icmSig2NormSig(icp, outsig, icmEncV2,
sigs[tn].ttype == icSigLut16Type ? icmEnc16bit : icmEnc8bit);
tables[tn].clip_ent = 1;
/* Delete any existing tag */
if (icp->delete_tag_quiet(icp, sigs[tn].sig) != ICM_ERR_OK)
return icp->e.c;
if ((tables[tn].wo = icp->add_tag(icp, sigs[tn].sig, sigs[tn].ttype)) == NULL)
return icp->e.c;
wo = (icmLut1 *)tables[tn].wo;
/* Allocate space in tags */
wo->inputChan = inputChan;
wo->outputChan = outputChan;
if (sigs[tn].ttype == icSigLut8Type)
wo->outputEnt = wo->inputEnt = 256;
else {
wo->inputEnt = inputEnt;
wo->outputEnt = outputEnt;
}
/* Check all clutPoints[] are the same */
for (i = 1; i < inputChan; i++) {
if (clutPoints[i] != clutPoints[0])
return icm_err(icp, ICM_ERR_CRL_XFORMS_BAD_CLUTRES, "icc_create_lut_xforms got non-equal clutPoints[%d] %d != clutPoints[0] %d",i,clutPoints[i],clutPoints[0]);
}
wo->clutPoints = clutPoints[0];
if (wo->allocate(wo))
return icp->e.c;
/* Get pointers to tables to be filled */
for (i = 0; i < inputChan; i++)
tables[tn].in[i] = wo->pe_ic[i];
tables[tn].clut = wo->pe_cl;
for (i = 0; i < outputChan; i++)
tables[tn].out[i] = wo->pe_oc[i];
/* Setup basic normalization Pe's */
tables[tn].in_nf = new_icmNSig2NormPe(icp, NULL, ninsig, 0, 1);
if (indmin == NULL || indmax == NULL) {
tables[tn].ind_nf = new_icmNSig2NormPe(icp, NULL, ninsig, 0, 1);
} else {
tables[tn].ind_nf = new_icmPeGeneric2Norm(icp, inputChan, indmin, indmax, "cluti", 0);
}
if (clutmin == NULL || clutmax == NULL) {
tables[tn].outd_nf = new_icmNSig2NormPe(icp, NULL, noutsig, 0, 1);
} else {
tables[tn].outd_nf = new_icmPeGeneric2Norm(icp, outputChan, clutmin, clutmax, "cluto", 0);
}
tables[tn].out_nf = new_icmNSig2NormPe(icp, NULL, noutsig, 0, 1);
if (icp->e.c != ICM_ERR_OK)
return icp->e.c;
}
else {
return icm_err(icp, ICM_ERR_CRL_XFORMS_BADTTYPE, "icc_create_lut_xforms got unexpected tag type '%s'", icmTypeSig2str(sigs[tn].ttype));
}
}
else {
return icm_err(icp, ICM_ERR_CRL_XFORMS_BADSIG, "icc_create_lut_xforms got unexpected sig '%s'", icmTagSig2str(sigs[tn].sig));
}
#ifdef PUT_PCS_WHITE_ON_GRIDPOINT
/* If Lab or XYZ input, map the PCS white to a cLUT grid */
/* Only do this if input table res. is sufficient to represent break accurately. */
if ((icmCSSig2type(insig) & CSSigType_PCS)
&& inputEnt >= 256
&& sigs[tn].ttype != icSigLut8Type) {
double wp[3];
icmPe *ga;
if (insig == icSigLabData)
wp[0] = 100.0, wp[1] = 0.0, wp[2] = 0.0;
else
icmXYZ2Ary(wp, icp->header->illuminant);
//printf("~1 grid align res = %s\n",icmPiv(3,clutPoints));
//printf("~1 grid point align input src = %s\n",icmPdv(3,wp));
/* Transform white point through input table function */
if (infunc != NULL) {
infunc(cbctx, wp, wp, tn); /* inspace -> input function -> inspace' */
}
//printf("~1 grid point align src' = %s\n",icmPdv(3,wp));
/* And in' normalization function */
if (tables[tn].ind_nf != NULL) /* Normalized to full range */
tables[tn].ind_nf->lookup_fwd(tables[tn].ind_nf, wp, wp);
//printf("~1 grid point align norm src' = %s\n",icmPdv(3,wp));
/* Create grid point adjustment */
if ((ga = new_icmPeGridAlign(icp, inputChan, wp, clutPoints, 0)) == NULL)
return icp->e.c;
//printf("~1 grid point align dst = %s\n",icmPdv(3,((icmPeGridAlign *)ga)->dst));
/* Append the ga after any current ind_nf */
if (tables[tn].ind_nf == NULL) {
//printf("~1 added as ind_nf\n");
tables[tn].ind_nf = ga;
} else {
icmPeContainer *seq;
if ((seq = new_icmPeContainer(icp, inputChan, inputChan)) == NULL)
return icp->e.c;
seq->append(seq, tables[tn].ind_nf);
tables[tn].ind_nf->del(tables[tn].ind_nf); /* Container has taken ref. */
seq->append(seq, ga);
ga->del(ga); /* Container has taken ref. */
seq->init(seq);
tables[tn].ind_nf = (icmPe *)seq;
//printf("~1 appended to ind_nf\n");
}
}
#endif /* PUT_PCS_WHITE_ON_GRIDPOINT */
} /* Next tn */
/* Allocate space for cell center value lookup */
if (flags & ICM_CLUT_SET_APXLS) {
int clutsize = 1;
for (i = 0; i < inputChan; i++)
clutsize = sat_mul(clutPoints[i], clutsize);
clutsize = sat_mul(outputChan, clutsize);
if (apxls_gmin == NULL) {
apxls_gmin = def_apxls_gmin;
for (e = 0; e < inputChan; e++)
apxls_gmin[e] = 0;
}
if (apxls_gmax == NULL) {
apxls_gmax = def_apxls_gmax;
for (e = 0; e < inputChan; e++)
apxls_gmax[e] = clutPoints[e]-1;
}
if ((clutTable2 = (double **) icp->al->calloc(icp->al,sizeof(double *), ntables)) == NULL) {
icp->al->free(icp->al, _iv);
return icm_err(icp, 1,"icmLut_set_tables malloc of cube center array failed");
}
for (tn = 0; tn < ntables; tn++) {
if ((clutTable2[tn] = (double *) icp->al->calloc(icp->al,sizeof(double),
clutsize)) == NULL) {
for (--tn; tn >= 0; tn--)
icp->al->free(icp->al, clutTable2[tn]);
icp->al->free(icp->al, _iv);
icp->al->free(icp->al, clutTable2);
return icm_err(icp, ICM_ERR_CRL_XFORMS_C2MALLOC,"icmLut_set_tables malloc of cube center array failed");
}
}
}
/* Now fill in the table values: */
/* Create the input table entry values */
for (tn = 0; tn < ntables; tn++) {
for (i = 0; i < inputEnt; i++) {
double fv = i/(inputEnt-1.0);
*((int *)&iv[-((int)0)-1]) = i; /* Trick to supply grid index in iv[] */
icmSetN(iv, fv, inputChan);
/* Normalized to full range */
if (tables[tn].in_nf != NULL)
tables[tn].in_nf->lookup_bwd(tables[tn].in_nf, iv, iv);
if (infunc != NULL)
infunc(cbctx, iv, iv, tn); /* inspace -> input function -> inspace' */
/* Full range to normalized */
if (tables[tn].ind_nf != NULL)
tables[tn].ind_nf->lookup_fwd(tables[tn].ind_nf, iv, iv);
/* Note that if the range is reduced and clipping occurs, */
/* then the resolution within the input table should be made */
/* large enough to be able to represent the sharp edges of the */
/* clipping. */
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, inputChan) > CLIP_MARGIN)
clip |= 1;
}
for (e = 0; e < inputChan; e++)
tables[tn].in[e]->data[i] = iv[e];
}
}
/* Create the multi-dimensional lookup table values */
/* To make this clut function cache friendly, we use the pseudo-hilbert */
/* count sequence. This keeps each point close to the last in the */
/* multi-dimensional space. This is the point of setting multiple Luts at */
/* once too - the assumption is that these tables are all related (different */
/* gamut compressions for instance), and hence calling the clutfunc() with */
/* close values will maximise reverse lookup cache hit rate. */
#ifdef NEVER
# pragma message("######### set CMYK B2A clut index to single point ########")
printf("### set CMYK B2A clut index to single point ###\n");
if (inputChan == 3 && outputChan == 4) {
printf("Triggered single point\n");
ii[0] = 12;
ii[1] = 9;
ii[2] = 8;
psh_init_diag(&counter, inputChan, ii);
} else {
psh_initN(&counter, inputChan, clutPoints, ii); /* Initialise counter */
}
#else
psh_initN(&counter, inputChan, clutPoints, ii); /* Initialise counter */
#endif
/* Itterate through all vertices in the grid */
for (;;) {
int ti; /* Table index */
/* (Note that we assume all the cLuts have the same res[] and dinc) */
for (ti = e = 0; e < inputChan; e++) { /* Input tables */
ti += ii[e] * tables[0].clut->dinc[e]; /* Clut index */
ivc[e] = iv[e] = ii[e]/(clutPoints[e]-1.0); /* Vertex value */
*((int *)&iv[-((int)e)-1]) = ii[e]; /* Trick to supply grid index in iv[] */
}
//printf("~1 clut coord %s\n",icmPiv(inputChan, ii));
//printf("~1 clut index %d\n",ti);
//printf("~1 clut in value %s\n",icmPdv(inputChan, iv));
for (tn = 0; tn < ntables; tn++) {
if (tn > 0)
for (e = 0; e < inputChan; e++) /* Restore iv */
iv[e] = ivc[e];
/* Normalized to full range */
if (tables[tn].ind_nf != NULL)
tables[tn].ind_nf->lookup_bwd(tables[tn].ind_nf, iv, iv);
//printf("~1 tn %d clut normed in value %s\n",tn,icmPdv(inputChan, iv));
/* Lookup our cLut function */
if (clutfunc != NULL)
clutfunc(cbctx, iv, iv, tn);
//printf("~1 tn %d clut normed out value %s\n",tn,icmPdv(inputChan, iv));
/* Full range to normalized */
if (tables[tn].outd_nf != NULL)
tables[tn].outd_nf->lookup_fwd(tables[tn].outd_nf, iv, iv);
//printf("~1 tn %d clut out value %s\n",tn,icmPdv(inputChan, iv));
/* Clip */
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, outputChan) > CLIP_MARGIN)
clip |= 2;
}
//printf("~1 tn %d clut clipped value %s\n",tn,icmPdv(inputChan, iv));
for (f = 0; f < outputChan; f++)
tables[tn].clut->clutTable[ti + f] = iv[f];
/* Lookup cell center value if ICM_CLUT_SET_APXLS */
if (clutTable2 != NULL) {
for (e = 0; e < inputChan; e++) {
if (ii[e] < apxls_gmin[e]
|| ii[e] >= apxls_gmax[e])
break; /* Don't lookup outside least squares area */
iv[e] = (ii[e] + 0.5)/(clutPoints[e]-1.0); /* Vertex coordinates + 0.5 */
*((int *)&iv[-((int)e)-1]) = -ii[e]-1; /* Trick to supply -ve grid index in iv[] */
/* (Note this is only the base for +0.5 center) */
}
if (e >= inputChan) { /* We're not on the last row */
/* Normalized to full range */
if (tables[tn].ind_nf != NULL)
tables[tn].ind_nf->lookup_bwd(tables[tn].ind_nf, iv, iv);
/* Lookup our cLut function */
if (clutfunc != NULL)
clutfunc(cbctx, iv, iv, tn);
/* Full range to normalized */
if (tables[tn].outd_nf != NULL)
tables[tn].outd_nf->lookup_fwd(tables[tn].outd_nf, iv, iv);
/* Clip */
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, outputChan) > CLIP_MARGIN)
clip |= 4;
}
for (f = 0; f < outputChan; f++) /* Output chans */
clutTable2[tn][ti + f] = iv[f];
}
}
}
/* Increment index within block (Reverse index significancd) */
if (psh_inc(&counter, ii))
break;
}
#define APXLS_WHT 0.5
#define APXLS_DIFF_THRHESH 0.2
/* Deal with cell center value, aproximate least squares adjustment. */
/* Subtract some of the mean of the surrounding center values from each grid value. */
/* Skip the range edges so that things like the white point or Video sync are not changed. */
/* Avoid modifying the value if the difference between the */
/* interpolated value and the current value is too great, */
/* and there is the possibility of different color aliases. */
if (clutTable2 != NULL) {
int ti; /* cube vertex table index */
int ti2; /* cube center table2 index */
int ee;
double cw = 1.0/(double)(1 << inputChan); /* Weight for each cube corner */
icmPeClut *clut = tables[0].clut; /* Use first cLut offset info */
/* For each cell center point except last row because we access ii[e]+1 */
for (e = 0; e < inputChan; e++)
ii[e] = apxls_gmin[e]; /* init coords */
/* Compute linear interpolated value from center values */
for (ee = 0; ee < inputChan;) {
/* Compute base index for table2 */
for (ti2 = e = 0; e < inputChan; e++) /* Input tables */
ti2 += ii[e] * clut->dinc[e]; /* Clut index */
ti = ti2 + clut->dcube[(1 << inputChan)-1]; /* +1 to each coord for vertex index */
for (tn = 0; tn < ntables; tn++) {
double mval[MAX_CHAN], vv;
double maxd = 0.0;
clut = tables[tn].clut;
/* Compute mean of center values */
for (f = 0; f < outputChan; f++) { /* Output chans */
mval[f] = 0.0;
for (i = 0; i < (1 << inputChan); i++) { /* For surrounding center values */
mval[f] += clutTable2[tn][ti2 + clut->dcube[i] + f];
}
mval[f] = clut->clutTable[ti + f] - mval[f] * cw; /* Diff to mean */
vv = fabs(mval[f]);
if (vv > maxd)
maxd = vv;
}
if (outputChan <= 3 || maxd < APXLS_DIFF_THRHESH) {
for (f = 0; f < outputChan; f++) { /* Output chans */
vv = clut->clutTable[ti + f] + APXLS_WHT * mval[f];
if (tables[tn].clip_ent) {
/* Hmm. This is a bit crude. How do we know valid range is 0-1 ? */
/* What about an ink limit ? */
if (vv < 0.0) {
vv = 0.0;
} else if (vv > 1.0) {
vv = 1.0;
}
}
// printf("~1 delta[%d] = %e\n",f,fabs(clut->clutTable[ti + f] - vv));
clut->clutTable[ti + f] = vv;
}
// printf("~1 nix %s apxls ov %s\n",icmPiv(inputChan, ii), icmPdv(outputChan, clut->clutTable + ti));
}
}
/* Increment coord */
for (ee = 0; ee < inputChan; ee++) {
if (++ii[ee] < (apxls_gmax[ee]-1)) /* Stop short of upper row of clutTable2 */
break; /* No carry */
ii[ee] = apxls_gmin[ee];
}
}
/* Done with center values */
for (tn = 0; tn < ntables; tn++)
icp->al->free(icp->al, clutTable2[tn]);
icp->al->free(icp->al, clutTable2);
}
/* Create the output table entry values */
for (tn = 0; tn < ntables; tn++) {
for (i = 0; i < outputEnt; i++) {
double fv = i/(outputEnt-1.0);
*((int *)&iv[-((int)0)-1]) = i; /* Trick to supply grid index in iv[] */
icmSetN(iv, fv, outputChan);
/* Normalized to full range */
if (tables[tn].outd_nf != NULL)
tables[tn].outd_nf->lookup_bwd(tables[tn].outd_nf, iv, iv);
if (outfunc != NULL)
outfunc(cbctx, iv, iv, tn); /* outspace' -> output function -> outspace */
/* Full range to normalized */
if (tables[tn].out_nf != NULL)
tables[tn].out_nf->lookup_fwd(tables[tn].out_nf, iv, iv);
/* Clip */
if (tables[tn].clip_ent) {
if (icmClipNmarg(iv, iv, inputChan) > CLIP_MARGIN)
clip |= 8;
}
for (f = 0; f < outputChan; f++)
tables[tn].out[f]->data[i] = iv[f];
}
}
/* Free up transforms */
for (tn = 0; tn < ntables; tn++) {
if (tables[tn].in_nf != NULL)
tables[tn].in_nf->del(tables[tn].in_nf);
if (tables[tn].ind_nf != NULL)
tables[tn].ind_nf->del(tables[tn].ind_nf);
if (tables[tn].outd_nf != NULL)
tables[tn].outd_nf->del(tables[tn].outd_nf);
if (tables[tn].out_nf != NULL)
tables[tn].out_nf->del(tables[tn].out_nf);
}
icp->al->free(icp->al, tables);
return ICM_ERR_OK;
}