Files
argyllcms/xicc/mpp.c
2026-08-20 20:28:40 +01:00

5101 lines
132 KiB
C

/*
* Argyll Color Management System
* Model Printer Profile object.
*
* Author: Graeme W. Gill
* Date: 24/2/2002
*
* Copyright 2003 Graeme W. Gill
* All rights reserved.
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
* see the License.txt file for licencing details.
*/
/* This version (based on mpp_x1) has n * 2^(n-1) shape params, */
/* used for linear interpolation of the shaping correction. */
/*
* This program takes in the scattered test chart
* points, and creates a model based forward printer profile
* (Device -> CIE + spectral), based on heuristic extensions to
* the Neugenbauer model.
* It is designed to handle an arbitrary number of colorants,
* and in the future, (optionaly) create an aproximate ink overlap/mixing model
* to allow synthesis of a forward model for a hyperthetical
* similar printing process with additional inks.
*
* The model is as follows:
*
* A per input channel transfer curve, that models
* per channel dot gain, transfer curve processing etc.
*
* A per input channel shape modification model.
* Each transfer curve adjusted value is further
* adjusted in a way that depends on the combination
* of value of all the other input channels.
* This allows for ink interaction effects in a way
* that should allow good conformance to all combinations
* of input values at 50% values.
*
* An n-linear interpolation between all combination of
* the 0 and 100% colorant primary combinations (Neugenbauer).
*
* The model making can be rather slow, particularly if high
* quality, large number of colorants, large number of sample
* points.
*
* This code is based on profile.c, sprof.c and xlut.c
*
*/
/*
* TTBD:
*
* !!!! Should change device transfer model to include offset & scale,
* to better match display & other devices !!!!
*
* Should add Jab pcs mode, so that Jab gamuts can be
* written.
*
* Remove #ifndef DEBUG & replace with verbose progress bars.
*
* Add support for extra profile details to create() to support
* profxinf like stuff.
*
* Add ink order and overlay modeling stuff back in, with
* new ink overlay model (see mpprof0.c).
*
* Rather than computing XYZ based versios of the print model
* and ink mixing models, should compute spectrally sharpened
* equivalents to XYZ ?? (The spectral CIE base values don't
* seem much more accurate than XYZ, so perhaps remove SHARPEN code ?).
*
* Need to cleanup error handling.
*/
#define VERSION "1.0"
#undef DEBUG
#undef TESTDFUNC /* Check delta functions */
#undef NODDV /* Use (very slow) non d/dv powell */
#undef DOPLOT /* Plot the device curves */
#undef NOPROCESS /* Define to skip all fitting */
#define MULTIPASS /* Fit passes by parts (normal mode) */
#undef BIGBANG /* Define to fit all parameters at once */
#undef ISHAPE /* define to try SVD init of shape parameters */
#undef SHARPEN /* use sharpened XYZ values for modeling */
/* (Wrecks derivative lookup at the moment, and makes */
/* accuracy worse!) */
/* Transfer curve parameter (wiggle) minimisation weight */
//#define TRANS_BASE 0.2 /* 0 & 1 harmonic parameter weight */
//#define TRANS_HBASE 0.8 /* 2nd harmonic and above base parameter weight */
#define TRANS_HW01 0.01 /* 0 & 1 harmonic weights */
#define TRANS_HBREAK 3 /* Harmonic that has HWBR */
//#define TRANS_HWBR 0.5 /* Base weight of harmonics HBREAK up */
//#define TRANS_HWINC 0.5 /* Increase in weight for each harmonic above HWBR */
#define TRANS_HWBR 0.5 /* Base weight of harmonics HBREAK up */
#define TRANS_HWINC 0.5 /* Increase in weight for each harmonic above HWBR */
//#define SHAPE_PMW 0.2 /* Shape parameter (wiggle) minimisation weight */
#define SHAPE_PMW 10.0 /* Shape parameter (wiggle) minimisation weight */
#define COMB_PMW 0.008 /* Primary combination anchor point distance weight */
#define verbo stdout
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include "aconfig.h"
#include "numlib.h"
#include "cgats.h"
#include "icc.h"
#include "conv.h"
#include "xicc.h" /* Spectral support */
#include "xspect.h" /* Spectral support */
#include "xcolorants.h" /* Known colorants support */
#include "insttypes.h"
#include "gamut.h"
#include "mpp.h"
#ifdef DOPLOT
# include "plot.h"
# include "ui.h"
#endif
/* Forward declarations */
static double bandval(mpp *p, int band, double *dev);
static double dbandval(mpp *p, double *dv, int band, double *dev);
static void forward(mpp *p, double *spec, double *Lab, double *XYZ, double *dev);
static int create(mpp *p, int verb, int quality, int display, double limit, inkmask devmask,
int spec_n, double spec_wl_short, double spec_wl_long,
double norm, instType itype, int nodp, mppcol *points);
static void compute_wb(mpp *p);
static void init_shape(mpp *p);
/* Utilities */
#ifdef SHARPEN
/* Convert from XYZ to spectrally sharpened response */
/* (Can this generate -ve values for real colors ??) */
static void XYZ2sharp(double *a, double *b, double *c) {
double xyz[3], rgb[3];
xyz[0] = *a;
xyz[1] = *b;
xyz[2] = *c;
rgb[0] = 0.8562 * xyz[0] + 0.3372 * xyz[1] - 0.1934 * xyz[2];
rgb[1] = -0.8360 * xyz[0] + 1.8327 * xyz[1] + 0.0033 * xyz[2];
rgb[2] = 0.0357 * xyz[0] - 0.0469 * xyz[1] + 1.0112 * xyz[2];
*a = rgb[0];
*b = rgb[1];
*c = rgb[2];
}
/* Convert from spectrally sharpened response to XYZ */
static void sharp2XYZ(double *a, double *b, double *c) {
double xyz[3], rgb[3];
rgb[0] = *a;
rgb[1] = *b;
rgb[2] = *c;
xyz[0] = 0.9873999149199270 * rgb[0]
- 0.1768250198556842 * rgb[1]
+ 0.1894251049357572 * rgb[2];
xyz[1] = 0.4504351090445316 * rgb[0]
+ 0.4649328977527109 * rgb[1]
+ 0.0846319932027575 * rgb[2];
xyz[2] = -0.0139683251072516 * rgb[0]
+ 0.0278065725014340 * rgb[1]
+ 0.9861617526058175 * rgb[2];
*a = xyz[0];
*b = xyz[1];
*c = xyz[2];
}
#endif /* SHARPEN */
/* Method implimentations */
/* Write out the mpp to a CGATS format .mpp file */
/* Return nz on error */
static int write_mpp(
mpp *p, /* This */
char *outname, /* Filename to write to */
int dolab /* If NZ, write Lab values rather than XYZ */
) {
int i, j, n;
time_t clk = time(0);
struct tm *tsp = localtime(&clk);
char *atm = asctime(tsp); /* Ascii time */
cgats *ocg; /* CGATS structure */
char *ident = icx_inkmask2char(p->imask, 1);
int nsetel = 0;
cgats_set_elem *setel; /* Array of set value elements */
char buf[100];
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
/* Setup output cgats file */
ocg = new_cgats(); /* Create a CGATS structure */
ocg->add_other(ocg, "MPP"); /* our special type is Model Printer Profile */
ocg->add_table(ocg, tt_other, 0); /* Start the first table */
ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Model Printer Profile, Colorant linearisation",NULL);
ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll mpp", NULL);
ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
if (p->display)
ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL); /* What sort of device this is */
else {
ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL); /* What sort of device this is */
/* Note what instrument the chart was read with, in case we */
/* want to apply FWA when converting spectral data to CIE */
ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(p->itype) , NULL);
sprintf(buf,"%5.1f",p->limit * 100.0);
ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT", buf, NULL);
}
ocg->add_kword(ocg, 0, "COLOR_REP", ident, NULL);
/* Record how many factors are used in device channel transfer curve */
sprintf(buf,"%d",p->cord);
ocg->add_kword(ocg, 0, "TRANSFER_ORDERS", buf, NULL);
/* Record if shaper parameters are being used */
if (p->useshape) {
ocg->add_kword(ocg, 0, "USE_SHAPER", "YES", NULL);
} else {
ocg->add_kword(ocg, 0, "USE_SHAPER", "NO", NULL);
}
/* Setup the table, which holds all the model parameters. */
/* There is always a parameter per X Y Z or spectral band */
ocg->add_field(ocg, 0, "PARAMETER", nqcs_t);
if (dolab) {
ocg->add_field(ocg, 0, "LAB_L", r_t);
ocg->add_field(ocg, 0, "LAB_A", r_t);
ocg->add_field(ocg, 0, "LAB_B", r_t);
} else {
ocg->add_field(ocg, 0, "XYZ_X", r_t);
ocg->add_field(ocg, 0, "XYZ_Y", r_t);
ocg->add_field(ocg, 0, "XYZ_Z", r_t);
}
nsetel = 1 + 3;
/* Add fields for spectral values */
if (p->spec_n > 0) {
nsetel += p->spec_n; /* Spectral values */
sprintf(buf,"%d", p->spec_n);
ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
sprintf(buf,"%f", p->spec_wl_short);
ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
sprintf(buf,"%f", p->spec_wl_long);
ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
sprintf(buf,"%f", p->norm * 100.0);
ocg->add_kword(ocg, 0, "SPECTRAL_NORM",buf, NULL);
/* Generate fields for spectral values */
for (i = 0; i < p->spec_n; i++) {
int nm;
/* Compute nearest integer wavelength */
nm = (int)(p->spec_wl_short + ((double)i/(p->spec_n-1.0))
* (p->spec_wl_long - p->spec_wl_short) + 0.5);
sprintf(buf,"SPEC_%03d",nm);
ocg->add_field(ocg, 0, buf, r_t);
}
}
if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL) {
free(ident);
sprintf(p->e.m,"write_mpp: malloc of setel failed");
return 1;
}
/* Write out the transfer curve values */
for (i = 0; i < p->n; i++) { /* each colorant */
for (j = 0; j < p->cord; j++) { /* curve order values */
sprintf(buf,"t_%d_%d",i,j);
setel[0].c = buf; /* Parameter identifier */
for (n = 0; n < (3+p->spec_n); n++)
setel[1+n].d = p->tc[i][n][j];
ocg->add_setarr(ocg, 0, setel);
}
}
if (p->useshape) {
/* Write out the shaper values */
for (i = 0; i < p->nnn2; i++) { /* For all shaper values */
int m = p->c2f[i].ink;
int k = p->c2f[i].comb;
sprintf(buf,"s_%d_%d",m, k);
setel[0].c = buf; /* Parameter identifier */
for (n = 0; n < (3+p->spec_n); n++)
setel[1+n].d = p->shape[m][k][n];
ocg->add_setarr(ocg, 0, setel);
}
}
/* Write out the colorant combination values */
for (i = 0; i < p->nn; i++) {
sprintf(buf,"c_%d",i);
setel[0].c = buf; /* Parameter identifier */
for (n = 0; n < (3+p->spec_n); n++)
setel[1+n].d = p->pc[i][n];
#ifdef SHARPEN
sharp2XYZ(&setel[1+0].d, &setel[1+1].d, &setel[1+2].d);
#endif
if (dolab) {
double ttt[3];
ttt[0] = setel[1+0].d, ttt[1] = setel[1+1].d, ttt[2] = setel[1+2].d;
icmXYZ2Lab(&icmD50, ttt, ttt);
setel[1+0].d = ttt[0], setel[1+1].d = ttt[1], setel[1+2].d = ttt[2];
}
ocg->add_setarr(ocg, 0, setel);
}
free(setel);
free(ident);
/* Write it */
if (ocg->write_name(ocg, outname)) {
strcpy(p->e.m, ocg->e.m);
return 1;
}
ocg->del(ocg); /* Clean up */
return 0;
}
/* Read in the mpp CGATS .mpp file */
/* Return nz on error */
static int read_mpp(
mpp *p, /* This */
char *inname /* Filename to read from */
) {
int i, j, n, ix;
cgats *icg; /* input cgats structure */
int ti; /* Temporary CGATs index */
int islab = 0; /* nz if Lab parameters */
/* Open and look at the .mpp model printer profile */
if ((icg = new_cgats()) == NULL) { /* Create a CGATS structure */
sprintf(p->e.m, "read_mpp: new_cgats() failed");
return 2;
}
icg->add_other(icg, "MPP"); /* our special type is Model Printer Profile */
if (icg->read_name(icg, inname)) {
strcpy(p->e.m, icg->e.m);
icg->del(icg);
return 1;
}
if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0) {
sprintf(p->e.m, "read_mpp: Input file '%s' isn't a MPP format file",inname);
icg->del(icg);
return 1;
}
if (icg->ntables != 1) {
sprintf(p->e.m, "Input file '%s' doesn't contain exactly one table",inname);
icg->del(icg);
return 1;
}
if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0) {
sprintf(p->e.m, "read_mpp: Input file '%s' doesn't contain keyword COLOR_REP",inname);
icg->del(icg);
return 1;
}
p->imask = icx_char2inkmask(icg->t[0].kdata[ti]);
p->n = icx_noofinks(p->imask);
p->nn = 1 << p->n;
p->nnn2 = p->n * p->nn/2;
if (p->n == 0) {
sprintf(p->e.m, "read_mpp: COLOR_REP '%s' invalid from file '%s' (No matching devmask)",
icg->t[0].kdata[ti], inname);
icg->del(icg);
return 1;
}
/* See if it is the expected device class */
if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0) {
sprintf(p->e.m, "read_mpp: Input file '%s' doesn't contain keyword DEVICE_CLASS",inname);
icg->del(icg);
return 1;
}
if (strcmp(icg->t[0].kdata[ti],"OUTPUT") == 0) {
if ((ti = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) {
double imax;
imax = atof(icg->t[0].kdata[ti]);
p->limit = imax/100.0;
} else {
p->limit = 0.0; /* Don't use ink limit */
}
if ((ti = icg->find_kword(icg, 0, "TARGET_INSTRUMENT")) < 0) {
sprintf(p->e.m, "read_mpp: Can't find keyword TARGET_INSTRUMENT in file '%s'", inname);
icg->del(icg);
return 1;
}
if ((p->itype = inst_enum(icg->t[0].kdata[ti])) == instUnknown
&& icg->find_kword(icg, 0, "SPECTRAL_BANDS") >= 0) {
sprintf(p->e.m, "read_mpp: Unrecognised target instrument '%s' in file '%s'",
icg->t[0].kdata[ti], inname);
icg->del(icg);
return 1;
}
p->display = 0;
} else if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) {
p->display = 1;
p->itype = instUnknown;
p->limit = p->n;
} else {
/* Don't know anything else at the moment */
sprintf(p->e.m, "read_mpp: Input file '%s' has unknown DEVICE_CLASS '%s'",
inname, icg->t[0].kdata[ti]);
icg->del(icg);
return 1;
}
/* Read the number of device linearisation orders */
if ((ti = icg->find_kword(icg, 0, "TRANSFER_ORDERS")) < 0) {
sprintf(p->e.m, "read_mpp: Input file '%s' doesn't contain keyword TRANSFER_ORDERS",
inname);
icg->del(icg);
return 1;
}
p->cord = atoi(icg->t[0].kdata[ti]);
if (p->cord < 1 || p->cord > MPP_MXTCORD) {
sprintf(p->e.m, "read_mpp: Input file '%s' has out of range TRANSFER_ORDERS %d",
inname, p->cord);
icg->del(icg);
return 1;
}
/* See if shaper parameters are used */
p->useshape = 0;
if ((ti = icg->find_kword(icg, 0, "USE_SHAPER")) >= 0) {
if(strcmp(icg->t[0].kdata[ti], "YES") == 0)
p->useshape = 1;
}
/* Read the model parameters */
{
int ci; /* Parameter dentified index */
int spi[3+MPP_MXBANDS]; /* CGATS indexes for each band */
char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" };
char buf[100];
/* See if we have spectral information available */
if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0) {
p->spec_n = 0; /* None */
} else {
if ((ti = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
error ("Input file doesn't contain keyword SPECTRAL_BANDS");
p->spec_n = atoi(icg->t[0].kdata[ti]);
if ((ti = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
error ("Input file doesn't contain keyword SPECTRAL_START_NM");
p->spec_wl_short = atof(icg->t[0].kdata[ti]);
if ((ti = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
error ("Input file doesn't contain keyword SPECTRAL_END_NM");
p->spec_wl_long = atof(icg->t[0].kdata[ti]);
if ((ti = icg->find_kword(icg, 0, "SPECTRAL_NORM")) < 0)
error ("Input file doesn't contain keyword SPECTRAL_NORM");
p->norm = atof(icg->t[0].kdata[ti])/100.0;
}
if ((new_mppcol(&p->white, p->n, p->spec_n)) != 0) {
error("Malloc failed!");
}
if ((new_mppcol(&p->black, p->n, p->spec_n)) != 0) {
error("Malloc failed!");
}
if ((new_mppcol(&p->kblack, p->n, p->spec_n)) != 0) {
error("Malloc failed!");
}
init_shape(p); /* Allocate and init shape related parameter space */
/* Get the field indexes */
if ((ci = icg->find_field(icg, 0, "PARAMETER")) < 0) {
sprintf(p->e.m, "read_mpp: Input file '%s' doesn't contain field PARAMETER",
inname);
icg->del(icg);
return 1;
}
if (icg->t[0].ftype[ci] != nqcs_t) {
sprintf(p->e.m, "read_mpp: Input file '%s' field PARAMETER is wrong type",
inname);
icg->del(icg);
return 1;
}
for (i = 0; i < 3; i++) { /* XYZ fields */
if ((spi[i] = icg->find_field(icg, 0, xyzfname[i])) < 0) {
break;
}
if (icg->t[0].ftype[spi[i]] != r_t) {
sprintf(p->e.m, "read_mpp: Input file '%s' field %s is wrong type",
inname, buf);
icg->del(icg);
return 1;
}
}
if (i < 3) {
islab = 1;
for (i = 0; i < 3; i++) { /* XYZ fields */
if ((spi[i] = icg->find_field(icg, 0, labfname[i])) < 0) {
sprintf(p->e.m, "read_mpp: Input file '%s' doesn't contain field %s or %s",
inname, xyzfname[i], labfname[i]);
icg->del(icg);
return 1;
}
if (icg->t[0].ftype[spi[i]] != r_t) {
sprintf(p->e.m, "read_mpp: Input file '%s' field %s is wrong type",
inname, buf);
icg->del(icg);
return 1;
}
}
}
/* Find the fields for spectral values */
if (p->spec_n > 0) {
for (j = 0; j < p->spec_n; j++) {
int nm;
/* Compute nearest integer wavelength */
nm = (int)(p->spec_wl_short + ((double)j/(p->spec_n-1.0))
* (p->spec_wl_long - p->spec_wl_short) + 0.5);
sprintf(buf,"SPEC_%03d",nm);
if ((spi[3+j] = icg->find_field(icg, 0, buf)) < 0) {
sprintf(p->e.m, "read_mpp: Input file '%s' doesn't contain field %s",
buf,inname);
icg->del(icg);
return 1;
}
if (icg->t[0].ftype[spi[3+j]] != r_t) {
sprintf(p->e.m, "read_mpp: Input file '%s' field %s is wrong type",
inname, buf);
icg->del(icg);
return 1;
}
}
}
/* Read the transfer curve values */
for (i = 0; i < p->n; i++) { /* each colorant */
for (j = 0; j < p->cord; j++) { /* curve order values */
sprintf(buf,"t_%d_%d",i,j);
/* Find the right parameter */
for (ix = 0; ix < icg->t[0].nsets; ix++) {
if (strcmp((char *)icg->t[0].fdata[ix][ci], buf) == 0) {
for (n = 0; n < (3+p->spec_n); n++)
p->tc[i][n][j] = *((double *)icg->t[0].fdata[ix][spi[n]]);
break;
}
}
}
}
if (p->useshape) {
/* Read the shaper values */
for (i = 0; i < p->nnn2; i++) { /* For all shaper values */
int m = p->c2f[i].ink;
int k = p->c2f[i].comb;
sprintf(buf,"s_%d_%d",m, k);
/* Find the right parameter */
for (ix = 0; ix < icg->t[0].nsets; ix++) {
if (strcmp((char *)icg->t[0].fdata[ix][ci], buf) == 0) {
for (n = 0; n < (3+p->spec_n); n++)
p->shape[m][k][n] = *((double *)icg->t[0].fdata[ix][spi[n]]);
break;
}
}
}
}
/* Read the combination values */
for (i = 0; i < p->nn; i++) {
sprintf(buf,"c_%d",i);
/* Find the right parameter */
for (ix = 0; ix < icg->t[0].nsets; ix++) {
if (strcmp((char *)icg->t[0].fdata[ix][ci], buf) == 0) {
for (n = 0; n < (3+p->spec_n); n++)
p->pc[i][n] = *((double *)icg->t[0].fdata[ix][spi[n]]);
if (islab) {
double tt[3];
tt[0] = p->pc[i][0], tt[1] = p->pc[i][1], tt[2] = p->pc[i][2];
icmLab2XYZ(&icmD50, tt, tt);
p->pc[i][0] = tt[0], p->pc[i][1] = tt[1], p->pc[i][2] = tt[2];
}
#ifdef SHARPEN
XYZ2sharp(&p->pc[i][0], &p->pc[i][1], &p->pc[i][2]);
#endif
break;
}
}
}
}
icg->del(icg); /* Clean up */
/* Figure out the white and black points */
compute_wb(p);
return 0;
}
/* Get various types of information about the mpp */
static void get_info(
mpp *p, /* This */
inkmask *imask, /* Inkmask, describing device colorspace */
int *nodchan, /* Number of device channels */
double *limit, /* Total ink limit (0.0 .. devchan) */
int *spec_n, /* Number of spectral bands, 0 if none */
double *spec_wl_short, /* First reading wavelength in nm (shortest) */
double *spec_wl_long, /* Last reading wavelength in nm (longest) */
instType *itype, /* Instrument type */
int *display /* Return nz if display type */
) {
if (imask != NULL)
*imask = p->imask;
if (nodchan != NULL)
*nodchan = p->n;
if (limit != NULL)
*limit = p->limit;
if (spec_n != NULL)
*spec_n = p->spec_n;
if (spec_wl_short != NULL)
*spec_wl_short = p->spec_wl_short;
if (spec_wl_long != NULL)
*spec_wl_long = p->spec_wl_long;
if (itype != NULL)
*itype = p->itype;
if (display != NULL)
*display = p->display;
}
/* Set an illuminant and observer to use spectral model */
/* for CIE lookup with optional FWA. Set both to default for XYZ mpp model. */
/* return 0 on OK, 1 on spectral not supported, 2 on other error */
/* If the model is for a display, the illuminant will be ignored. */
static int set_ilob(
mpp *p,
icxIllumeType ilType, /* Illuminant type (icxIT_default for none) */
xspect *custIllum, /* Custom illuminant (NULL for none) */
icxObserverType obType, /* Observer type (icxOT_default for none) */
xspect custObserver[3], /* Custom observer (NULL for none) */
icColorSpaceSignature rcs, /* Return color space, icSigXYZData or icSigLabData */
int use_fwa /* NZ to involke FWA. */
) {
/* Get rid of any existing conversion object */
if (p->spc != NULL) {
p->spc->del(p->spc);
p->spc = NULL;
}
p->pcs = rcs;
if (ilType == icxIT_default && obType == icxOT_default && use_fwa == 0)
return 0;
if (p->spec_n == 0) {
p->e.c = 1;
sprintf(p->e.m,"No Spectral Data in MPP");
return 1;
}
if (p->display) {
ilType = icxIT_none;
custIllum = NULL;
}
if ((p->spc = new_xsp2cie(ilType, 0.0, custIllum, obType, custObserver, rcs, 1)) == NULL)
error("mpp->set_ilob, new_xsp2cie failed");
if (use_fwa) {
int j;
xspect white, inst;
white.norm = p->norm;
white.spec_n = p->spec_n;
white.spec_wl_short = p->spec_wl_short;
white.spec_wl_long = p->spec_wl_long;
for (j = 0; j < p->spec_n; j++)
white.spec[j] = p->white.band[3+j];
if (inst_illuminant(&inst, p->itype) != 0)
error ("mpp->set_ilob, instrument doesn't have an FWA illuminent");
if (p->spc->set_fwa(p->spc, &inst, NULL, &white))
error ("mpp->set_ilob, set_fwa failed");
}
return 0;
}
/* Lookup an XYZ or Lab color */
static void lookup(
mpp *p, /* This */
double *out, /* Returned XYZ or Lab */
double *in /* Input device values */
) {
if (p->spc == NULL) {
double *Lab;
double *XYZ;
if (p->pcs == icSigLabData) {
Lab = out;
XYZ = NULL;
} else {
Lab = NULL;
XYZ = out;
}
forward(p, NULL, Lab, XYZ, in);
return;
} else {
xspect tspec;
p->lookup_spec(p, &tspec, in);
p->spc->convert(p->spc, out, &tspec);
}
}
/* Lookup an XYZ or Lab color, plus the partial derivative. */
/* This is useful if the lookup is being used within */
/* a minimisation routine */
static void dlookup(
mpp *p,
double *out, /* Return the XYZ or Lab */
double **dout, /* Return the partial derivative dout[3][n] */
double *dev) {
int i, j, k;
#ifdef SHARPEN
/* Can't handle this without converting from sharpened derivative */
/* to XYZ derivative. ~~~~~~ */
#endif
/* We can't handle derivative using FWA, so */
/* always compute from the XYZ model values. */
for (j = 0; j < 3; j++) { /* Compute each bands value */
out[j] = dbandval(p, dout[j], j, dev);
}
if (p->pcs == icSigLabData) {
double dlab[3][3];
icxdXYZ2Lab(&icmD50, out, dlab, out);
/* Apply Lab deriv to dout */
for (i = 0; i < p->n; i++) {
double tt[3];
for (k = 0; k < 3; k++)
tt[k] = dout[k][i];
for (j = 0; j < 3; j++) {
dout[j][i] = 0.0;
for (k = 0; k < 3; k++) {
dout[j][i] += dlab[j][k] * tt[k];
}
}
}
}
}
/* Get the white and black points */
static void get_wb(
mpp *p, /* This */
double *white,
double *black,
double *kblack /* K only black */
) {
if (white != NULL) {
if (p->spc == NULL) {
white[0] = p->white.band[0];
white[1] = p->white.band[1];
white[2] = p->white.band[2];
#ifdef SHARPEN
sharp2XYZ(&white[0], &white[1], &white[2]);
#endif
if (p->pcs == icSigLabData)
icmXYZ2Lab(&icmD50, white, white);
} else {
int j;
xspect ispect;
ispect.norm = p->norm;
ispect.spec_n = p->spec_n;
ispect.spec_wl_short = p->spec_wl_short;
ispect.spec_wl_long = p->spec_wl_long;
for (j = 0; j < p->spec_n; j++)
ispect.spec[j] = p->white.band[3+j];
p->spc->convert(p->spc, white, &ispect);
}
}
if (black != NULL) {
if (p->spc == NULL) {
black[0] = p->black.band[0];
black[1] = p->black.band[1];
black[2] = p->black.band[2];
#ifdef SHARPEN
sharp2XYZ(&black[0], &black[1], &black[2]);
#endif
if (p->pcs == icSigLabData)
icmXYZ2Lab(&icmD50, black, black);
} else {
int j;
xspect ispect;
ispect.norm = p->norm;
ispect.spec_n = p->spec_n;
ispect.spec_wl_short = p->spec_wl_short;
ispect.spec_wl_long = p->spec_wl_long;
for (j = 0; j < p->spec_n; j++)
ispect.spec[j] = p->black.band[3+j];
p->spc->convert(p->spc, black, &ispect);
}
}
if (kblack != NULL) {
if (p->spc == NULL) {
kblack[0] = p->kblack.band[0];
kblack[1] = p->kblack.band[1];
kblack[2] = p->kblack.band[2];
#ifdef SHARPEN
sharp2XYZ(&kblack[0], &kblack[1], &kblack[2]);
#endif
if (p->pcs == icSigLabData)
icmXYZ2Lab(&icmD50, kblack, kblack);
} else {
int j;
xspect ispect;
ispect.norm = p->norm;
ispect.spec_n = p->spec_n;
ispect.spec_wl_short = p->spec_wl_short;
ispect.spec_wl_long = p->spec_wl_long;
for (j = 0; j < p->spec_n; j++)
ispect.spec[j] = p->kblack.band[3+j];
p->spc->convert(p->spc, kblack, &ispect);
}
}
}
/* Lookup an XYZ value. */
/* (Note that this is never FWA compensated) */
static void lookup_xyz(
mpp *p, /* This */
double *out, /* Returned XYZ value */
double *in /* Input device values */
) {
forward(p, NULL, NULL, out, in);
}
/* Lookup a spectral value. */
/* (Note that this is never FWA compensated) */
static void lookup_spec(
mpp *p, /* This */
xspect *out, /* Returned spectral value */
double *in /* Input device values */
) {
int j;
out->norm = p->norm;
out->spec_n = p->spec_n;
out->spec_wl_short = p->spec_wl_short;
out->spec_wl_long = p->spec_wl_long;
forward(p, out->spec, NULL, NULL, in);
for (j = 0; j < p->spec_n; j++)
out->spec[j] *= out->norm;
}
/* Macros for an arbitrary dimensional counter */
/* Declare the counter name nn, dimensions di, & count */
#define DCOUNT(nn, di, start, reset, count) \
int nn[MAX_CHAN]; /* counter value */ \
int nn##_di = (di); /* Number of dimensions */ \
int nn##_stt = (start); /* start count value */ \
int nn##_rst = (reset); /* reset on carry value */ \
int nn##_res = (count); /* last count +1 */ \
int nn##_e /* dimension index */
/* Set the counter value to 0 */
#define DC_INIT(nn) \
{ \
for (nn##_e = 0; nn##_e < nn##_di; nn##_e++) \
nn[nn##_e] = nn##_stt; \
nn##_e = 0; \
}
/* Increment the counter value */
#define DC_INC(nn) \
{ \
for (nn##_e = 0; nn##_e < nn##_di; nn##_e++) { \
nn[nn##_e]++; \
if (nn[nn##_e] < nn##_res) \
break; /* No carry */ \
nn[nn##_e] = nn##_rst; \
} \
}
/* After increment, expression is TRUE if counter is done */
#define DC_DONE(nn) \
(nn##_e >= nn##_di)
/* Create a gamut */
/* (This will be in the current PCS) */
static gamut *mpp_gamut(
mpp *p, /* This */
double detail /* gamut detail level, 0.0 = def */
) {
gamut *gam;
int res; /* Sample point resolution */
double white[3], black[3], kblack[3];
DCOUNT(co, p->n, 0, 0, 2);
if (detail == 0.0)
detail = 10.0;
gam = new_gamut(detail, 0, 0); /* Lab only at the moment */
/* Explore the gamut by itterating through */
/* it with sample points in device space. */
res = (int)(100.0/detail); /* Establish an appropriate sampling density */
if (res < 3)
res = 3;
DC_INIT(co);
/* Itterate over all the 2 faces in the device space. */
/* We're assuming in practice that only colors lying */
/* on such faces contribute to the gamut volume. */
/* The total number of faces explored will be */
/* (dim * (dim-1))/2 * 2 ^ (dim-2) */
/* It seems possible that a number of these faces could */
/* be cheaply culled by examining the PCS corner values */
/* for each 2^(dim-2) combinations, and skiping those. */
/* that cannot possibly expand the gamut ? */
/* ie. that they are clearly contained by other face combinations. */
while(!DC_DONE(co)) {
int e, m1, m2;
double in[MAX_CHAN];
double out[3];
double sum;
/* Check the ink limit */
for (sum = 0.0, e = 0; e < p->n; e++)
sum += (double)co[e];
if (p->limit > 1e-4 && (sum - 1.0) > p->limit) {
DC_INC(co);
continue; /* Skip points really over limit */
}
/* Scan only device surface */
for (m1 = 0; m1 < p->n; m1++) {
if (co[m1] != 0)
continue;
for (m2 = m1 + 1; m2 < p->n; m2++) {
int x, y;
if (co[m2] != 0)
continue;
for (e = 0; e < p->n; e++)
in[e] = (double)co[e]; /* Base value */
for (x = 0; x < res; x++) { /* step over surface */
in[m1] = x/(res - 1.0);
for (y = 0; y < res; y++) {
double ssum, iin[MAX_CHAN];
in[m2] = y/(res - 1.0);
ssum = sum + in[m1] + in[m2];
if (p->limit > 1e-4 && (ssum - 1.0) > p->limit) {
continue;
}
for (e = 0; e < p->n; e++)
iin[e] = in[e]; /* Scalable copy */
/* Apply ink limit by simple scaling */
if (p->limit > 1e-4 && ssum > p->limit) {
for (e = 0; e < p->n; e++)
iin[e] *= p->limit/ssum;
}
forward(p, NULL, out, NULL, iin);
gam->expand(gam, out);
}
}
}
}
/* Increment index within block */
DC_INC(co);
}
/* set the white and black points */
p->get_wb(p, white, black, kblack);
gam->setwb(gam, white, black, kblack);
/* Set the cusp points */
/* Do this by scanning just 0 & 100% colorant combinations */
res = 2;
gam->setcusps(gam, 0, NULL);
DC_INIT(co);
/* Itterate over all the faces in the device space */
while(!DC_DONE(co)) {
int e, m1, m2;
double in[MAX_CHAN];
double out[3];
double sum;
/* Check the ink limit */
for (sum = 0.0, e = 0; e < p->n; e++)
sum += (double)co[e];
if (p->limit > 1e-4 && (sum - 1.0) > p->limit) {
DC_INC(co);
continue; /* Skip points really over limit */
}
/* Scan only device surface */
for (m1 = 0; m1 < p->n; m1++) {
if (co[m1] != 0)
continue;
for (m2 = m1 + 1; m2 < p->n; m2++) {
int x, y;
if (co[m2] != 0)
continue;
for (e = 0; e < p->n; e++)
in[e] = (double)co[e]; /* Base value */
for (x = 0; x < res; x++) { /* step over surface */
in[m1] = x/(res - 1.0);
for (y = 0; y < res; y++) {
double ssum, iin[MAX_CHAN];
in[m2] = y/(res - 1.0);
ssum = sum + in[m1] + in[m2];
if (p->limit > 1e-4 && (ssum - 1.0) > p->limit) {
continue;
}
for (e = 0; e < p->n; e++)
iin[e] = in[e]; /* Scalable copy */
/* Apply ink limit by simple scaling */
if (p->limit > 1e-4 && ssum > p->limit) {
for (e = 0; e < p->n; e++)
iin[e] *= p->limit/ssum;
}
forward(p, NULL, out, NULL, iin);
gam->setcusps(gam, 1, out);
}
}
}
}
/* Increment index within block */
DC_INC(co);
}
gam->setcusps(gam, 2, NULL);
return gam;
}
/* Delete it */
static void del_mpp(mpp *p) {
if (p != NULL) {
del_mppcol(&p->white, p->n, p->spec_n);
del_mppcol(&p->black, p->n, p->spec_n);
del_mppcol(&p->kblack, p->n, p->spec_n);
del_mppcols(p->cols, p->nodp, p->n, p->spec_n); /* Delete array of target points */
if (p->spc != NULL)
p->spc->del(p->spc);
/* Delete shape parameters */
if (p->shape != NULL) {
int i, j;
for (j = 0; j < p->n; j++) {
if (p->shape[j] != NULL) {
for (i = 0; i < p->nn; i++) {
if (p->shape[j][i] != NULL)
free(p->shape[j][i]);
}
free(p->shape[j]);
}
}
free(p->shape);
}
free(p);
}
}
/* Allocate a new, uninitialised mpp */
/* Note thate black and white points aren't allocated */
mpp *new_mpp(void) {
mpp *p;
if ((p = (mpp *)calloc(1, sizeof(mpp))) == NULL)
return NULL;
p->version = 1;
p->pcs = icSigXYZData;
/* Init method pointers */
p->del = del_mpp;
p->create = create;
p->get_gamut = mpp_gamut;
p->write_mpp = write_mpp;
p->read_mpp = read_mpp;
p->get_info = get_info;
p->set_ilob = set_ilob;
p->get_wb = get_wb;
p->lookup = lookup;
p->dlookup = dlookup;
p->lookup_xyz = lookup_xyz;
p->lookup_spec = lookup_spec;
return p;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Utility functions */
/* Allocate the data portion of an mppcol */
/* Return NZ if malloc error */
int new_mppcol(
mppcol *p, /* mppcol to allocate */
int n, /* Number of inks */
int nb /* Number of spectral bands */
) {
int nn = (1 << n); /* Number of ink combinations */
if ((p->nv = (double *)malloc(sizeof(double) * n)) == NULL) {
del_mppcol(p, n, nb);
return 1;
}
if ((p->band = (double *)malloc(sizeof(double) * (3 + nb))) == NULL) {
del_mppcol(p, n, nb);
return 1;
}
if ((p->lband = (double *)malloc(sizeof(double) * (3 + nb))) == NULL) {
del_mppcol(p, n, nb);
return 1;
}
if ((p->tcnv = (double *)calloc(n, sizeof(double))) == NULL) {
del_mppcol(p, n, nb);
return 1;
}
if ((p->scnv = (double *)calloc(n, sizeof(double))) == NULL) {
del_mppcol(p, n, nb);
return 1;
}
if ((p->pcnv = (double *)malloc(nn * sizeof(double))) == NULL) {
del_mppcol(p, n, nb);
return 1;
}
if ((p->fcnv = (double *)malloc(n * nn/2 * sizeof(double))) == NULL) {
del_mppcol(p, n, nb);
return 1;
}
return 0;
}
/* Copy the contents of one mppcol to another */
void copy_mppcol(
mppcol *d, /* Destination */
mppcol *s, /* Source */
int n, /* Number of inks */
int nb /* Number of spectral bands */
) {
mppcol al;
int nn = (1 << n); /* Number of ink combinations */
int i, j;
al = *d; /* Copy destinations allocations */
*d = *s; /* Copy source contents & pointers */
/* Now fixup allocation addresses */
d->nv = al.nv;
d->band = al.band;
d->lband = al.lband;
d->tcnv = al.tcnv;
d->scnv = al.scnv;
d->pcnv = al.pcnv;
d->fcnv = al.fcnv;
/* Copy contents of allocated data */
for (j = 0; j < n; j++)
d->nv[j] = s->nv[j];
for (j = 0; j < (3+nb); j++)
d->band[j] = s->band[j];
for (j = 0; j < (3+nb); j++)
d->lband[j] = s->lband[j];
for (i = 0; i < n; i++)
d->tcnv[i] = s->tcnv[i];
for (i = 0; i < n; i++)
d->scnv[i] = s->scnv[i];
for (i = 0; i < nn; i++)
d->pcnv[i] = s->pcnv[i];
for (i = 0; i < (n * nn/2); i++)
d->fcnv[i] = s->fcnv[i];
}
/* Free the data allocation of an mppcol */
void del_mppcol(
mppcol *p,
int n, /* Number of inks */
int nb /* Number of spectral bands */
) {
if (p != NULL) {
if (p->nv != NULL)
free (p->nv);
if (p->band != NULL)
free (p->band);
if (p->lband != NULL)
free (p->lband);
if (p->tcnv != NULL)
free (p->tcnv);
if (p->scnv != NULL)
free (p->scnv);
if (p->pcnv != NULL)
free (p->pcnv);
if (p->fcnv != NULL)
free (p->fcnv);
}
}
/* Allocate an array of mppcol */
/* Return NULL if malloc error */
mppcol *new_mppcols(
int no, /* Number in array */
int n, /* Number of inks */
int nb /* Number of spectral bands */
) {
mppcol *p;
int i;
if ((p = (mppcol *)calloc(no, sizeof(mppcol))) == NULL) {
return NULL;
}
for (i = 0; i < no; i++) {
if (new_mppcol(&p[i], n, nb) != 0) {
del_mppcols(p, no, n, nb);
return NULL;
}
}
return p;
}
/* Free an array of mppcol */
void del_mppcols(
mppcol *p,
int no, /* Number in array */
int n, /* Number of inks */
int nb /* Number of spectral bands */
) {
if (p != NULL) {
int i;
for (i = 0; i < no; i++)
del_mppcol(&p[i], n, nb);
free (p);
}
}
/* Allocate and setup the extra shape parameters combinations */
static void init_shape(mpp *p) {
int i, j, k;
int ix[MPP_MXINKS];
/* First allocate the shape parameter array */
if ((p->shape = (double ***)malloc(p->n * sizeof(double **))) == NULL)
error("Malloc failed (mpp shape)!");
for (j = 0; j < p->n; j++) {
if ((p->shape[j] = (double **)malloc(p->nn * sizeof(double *))) == NULL)
error("Malloc failed (mpp shape)!");
for (i = 0; i < p->nn; i++) {
if ((i & (1 << j)) == 0) { /* Valid combo for this ink */
if ((p->shape[j][i] = (double *)malloc((3+p->spec_n) * sizeof(double))) == NULL)
error("Malloc failed (mpp shape)!");
for (k = 0; k < (3+p->spec_n); k++)
p->shape[j][i][k] = 0.0; /* Initial shape value */
} else {
p->shape[j][i] = NULL;
}
}
}
/* Setup sparse to full and back indexing lookup */
for (j = 0; j < p->n; j++)
ix[j] = 0;
for (i = 0; i < p->nn; i++) {
for (j = 0; j < p->n; j++) {
p->f2c[j][i] = j * p->nn/2 + ix[j];
if ((i & (1 << j)) == 0) {
p->c2f[j * p->nn/2 + ix[j]].ink = j;
p->c2f[j * p->nn/2 + ix[j]].comb = i;
ix[j]++;
}
}
}
#ifdef NEVER
/* Print result */
for (j = 0; j < p->n; j++) {
for (i = 0; i < p->nn; i++) {
printf("f2c[%d][%d] = %d\n", j,i,p->f2c[j][i]);
}
for (i = 0; i < p->nn/2; i++) {
printf("c2f[%d].ink,comb = %d, %d\n",
j * p->nn/2 + i,p->c2f[j * p->nn/2 + i].ink,
p->c2f[j * p->nn/2 + i].comb);
}
}
#endif /* NEVER */
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Creation helper functions */
/* Per band error metric */
/* Convert argument to L* like scale */
#define lDE(aa) ((aa) > 0.008856451586 ? 116.0 * pow((aa),1.0/3.0) - 16.0 : (aa) * 903.2962896)
/* Function to approximate visible difference in an XYZ or spectral difference */
static double DEsq(double aa, double bb) {
double dd;
/* Convert input to L* like value */
aa = lDE(aa);
bb = lDE(bb);
dd = aa - bb;
return dd * dd;
}
/* Convert argument to the derivative of the DEsq function at that value */
#define dDE(aa) ((aa) > 0.008856451586 ? 38.666667 * pow((aa), -2.0/3.0) : 903.2962896)
/* Given a device value, return the given bands value */
/* according to the current model. */
static double bandval(mpp *p, int band, double *dev) {
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int m, k;
double ov;
int j = band;
/* Compute the tranfer corrected device values */
for (m = 0; m < p->n; m++) {
tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, dev[m]);
tcnv1[m] = 1.0 - tcnv[m];
}
if (p->useshape) {
for (m = 0; m < p->n; m++)
ww[m] = 0.0;
/* Lookup the shape values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
for (m = 0; m < p->n; m++) {
ww[m] += p->shape[m][k & ~(1<<m)][j] * vv;
/* Apply weighting to shape vertex value */
}
}
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double vv = tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
vv = vv/(gg - gg * vv + 1.0);
} else {
vv = (vv - gg * vv)/(1.0 - gg * vv);
}
tcnv[m] = vv;
tcnv1[m] = 1.0 - vv;
}
}
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double vv = p->pc[k][j];
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
ov += vv;
}
return ov;
}
/* Given a device value, return the given bands value */
/* according to the current model, as well as the partial */
/* derivative wrt the device values. */
static double dbandval(mpp *p, double *dv, int band, double *dev) {
double dtcnv_ddev[MPP_MXINKS]; /* Del in tcnv[m] due to del dev[m] */
double dww_dtcnv[MPP_MXINKS][MPP_MXINKS]; /* Del in ww[m] due to del in tcnv[m] */
double dtcnv_tc[MPP_MXINKS]; /* Del in tcnv'[m] due to del in tcnv[m] */
double dtcnv_ww[MPP_MXINKS]; /* Del in tcnv'[m] due to del in ww[m] */
double dov[MPP_MXINKS]; /* Del of ov due to del in tcnv'[m] */
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int m, k;
double ov;
int j = band;
/* Compute the tranfer corrected device values */
for (m = 0; m < p->n; m++) {
tcnv[m] = icxdiTransFunc(p->tc[m][j], &dtcnv_ddev[m], p->cord, dev[m]);
tcnv1[m] = 1.0 - tcnv[m];
ww[m] = 0.0;
}
/* Lookup the shape values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
for (m = 0; m < p->n; m++) {
ww[m] += p->shape[m][k & ~(1<<m)][j] * vv;
/* Apply weighting to shape vertex value */
}
}
/* Compute del ww[m][m] for del tcnv[m] */
for (m = 0; m < p->n; m++) { /* For each input channel were doing del of */
int mo;
for (mo = 0; mo < p->n; mo++)
dww_dtcnv[mo][m] = 0.0;
for (k = 0; k < p->nn; k++) {
int mm;
double vv = 1.0;
for (mm = 0; mm < p->n; mm++) { /* Compute weight for node k */
if (m == mm)
continue;
if (k & (1 << mm))
vv *= tcnv[mm];
else
vv *= tcnv1[mm];
}
for (mo = 0; mo < p->n; mo++) { /* For each output channel */
double vvv = vv * p->shape[mo][k & ~(1<<mo)][j];
if (k & (1 << m))
dww_dtcnv[mo][m] += vvv;
else
dww_dtcnv[mo][m] -= vvv;
}
}
}
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double tt;
double gg = ww[m]; /* Curve adjustment */
double sv, dsv, vv = tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
tt = gg - gg * vv + 1.0;
sv = vv/tt;
dsv = (gg + 1.0)/(tt * tt);
} else {
tt = 1.0 - gg * vv;
sv = (vv - gg * vv)/tt;
dsv = (1.0 - gg)/(tt * tt);
}
tcnv[m] = sv;
tcnv1[m] = 1.0 - sv;
dtcnv_tc[m] = dsv; /* del in tcnv[m] due to del in tcnv[m] */
dtcnv_ww[m] = (vv * vv - vv)/(tt * tt); /* del in tcnv[m] due to del in ww[m] */
}
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double vv = p->pc[k][j];
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
ov += vv;
}
/* Compute del ov for del tcnv[m] */
for (m = 0; m < p->n; m++) {
for (dov[m] = 0.0, k = 0; k < p->nn; k++) {
int mm;
double vv = p->pc[k][j];
for (mm = 0; mm < p->n; mm++) {
if (m == mm)
continue;
if (k & (1 << mm))
vv *= tcnv[mm];
else
vv *= tcnv1[mm];
}
if (k & (1 << m))
dov[m] += vv;
else
dov[m] -= vv;
}
}
/* Accumulate delta from input value to output */
for (m = 0; m < p->n; m++) {
double ttt;
int mm;
/* delta via dww */
for (ttt = 0.0, mm = 0; mm < p->n; mm++)
ttt += dov[mm] * dtcnv_ww[mm] * dww_dtcnv[mm][m] * dtcnv_ddev[m];
/* delta direct */
dv[m] = ttt + dov[m] * dtcnv_tc[m] * dtcnv_ddev[m];
}
return ov;
}
/* Given a device value, return the XYZ, D50 Lab and spectral */
/* values according to the current model. */
/* Pointer may be NULL if result is not needed */
static void forward(mpp *p, double *spec, double *Lab, double *XYZ, double *dev) {
double tXYZ[3] = { 0 };
int sb = 3, eb = 3; /* Start and end bands to compute */
int j;
if (XYZ != NULL || Lab != NULL)
sb = 0;
if (spec != NULL)
eb = 3 + p->spec_n;
for (j = sb; j < eb; j++) { /* Compute each bands value */
double ov;
ov = bandval(p, j, dev);
if (j < 3)
tXYZ[j] = ov;
else
spec[j-3] = ov;
}
#ifdef SHARPEN
if (sb == 0)
sharp2XYZ(&tXYZ[0], &tXYZ[1], &tXYZ[2]);
#endif
if (XYZ != NULL) {
XYZ[0] = tXYZ[0];
XYZ[1] = tXYZ[1];
XYZ[2] = tXYZ[2];
}
if (Lab != NULL) {
icmXYZ2Lab(&icmD50, Lab, tXYZ); /* Convert D50 Lab */
}
}
/* Return the specified bands current average error */
static void banderr(
mpp *p,
double *avese, /* Return average Error from spectral bands */
double *maxse, /* Return maximum Error from spectral bands */
int band
) {
double serr = 0.0, smax = 0.0;
int i;
/* For each test point */
for (i = 0; i < p->nodp; i++) {
double spv;
mppcol *c = &p->cols[i];
/* Compute models output */
spv = bandval(p, band, c->nv);
spv = sqrt(DEsq(c->band[band], spv));
if (spv > smax)
smax = spv;
serr += spv;
}
if (avese != NULL)
*avese = serr/((double)p->nodp);
if (maxse != NULL)
*maxse = smax;
}
/* Figure out what the current model errors are and return them */
static void deltae(
mpp *p,
double *avede, /* Return average Delta E from XYZ bands */
double *maxde, /* Return maximum Delta E from XYZ bands */
double *avese, /* Return average Error from spectral bands */
double *maxse /* Return maximum Error from spectral bands */
) {
double spec[MPP_MXBANDS]; /* spectral value for each point */
double Lab[3];
double err = 0.0, max = 0.0; /* Delta E */
double serr = 0.0, smax = 0.0; /* Delta S */
int i, j;
/* For each test point */
for (i = 0; i < p->nodp; i++) {
double ee;
mppcol *c = &p->cols[i];
/* Compute models output */
forward(p, p->spec_n > 0 ? spec : NULL, Lab, c->cXYZ, c->nv);
/* Track the Delta E */
c->err = icmLabDEsq(Lab, c->Lab);
ee = sqrt(c->err);
if (ee > max)
max = ee;
err += ee;
/* Track the spectral error */
for (j = 0; j < p->spec_n; j++) {
ee = DEsq(c->band[3+j], spec[j]);
ee = sqrt(ee);
if (ee > smax)
smax = ee;
serr += ee;
}
}
if (avede != NULL)
*avede = err/((double)p->nodp);
if (maxde != NULL)
*maxde = max;
if (p->spec_n > 0 && avese != NULL)
*avese = serr/(((double)p->nodp) * ((double)p->spec_n));
if (maxse != NULL)
*maxse = smax;
}
/* - - - - - - - - - - - - - - - */
/* Powell optimisation callbacks */
/* Progress reporter for mpp powell/conjgrad */
static void mppprog(void *pdata, int perc) {
mpp *p = (mpp *)pdata;
if (p->verb) {
printf("%c% 3d%%",cr_char,perc);
if (perc == 100)
printf("\n");
fflush(stdout);
}
}
#ifdef NEVER // Skip efunc1 passes for now.
/* Setup test point data ready for efunc1 on the given och and oba */
static void sfunc1(mpp *p) {
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
int k = p->och; /* Channel being optimised */
int j = p->oba; /* Band being optimised */
int i, m;
/* Pre-compute combination weighting and colorant combination values */
/* for this band, without this device channels contribution. */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
int kk;
/* Compute the tranfer corrected device values */
for (m = 0; m < p->n; m++) {
tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, c->nv[m]);
tcnv1[m] = 1.0 - tcnv[m];
}
/* Compute the test point primary combination values */
c->tpcnv = c->tpcnv1 = 0.0;
for (kk = 0; kk < p->nn; kk++) { /* Combination value */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) {
if (m == k)
continue; /* Multiply this in efunc1 */
if (kk & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
if (kk & (1 << k))
c->tpcnv += vv * p->pc[kk][j];
else
c->tpcnv1 += vv * p->pc[kk][j];
}
}
}
#endif /* NEVER */
#ifdef NEVER // Skip efunc1 passes for now.
/* Optimise a device transfer curve to minimise a particular bands error */
/* (Assume no shape parameters) */
static double efunc1(void *adata, double pv[]) {
mpp *p = (mpp *)adata;
double smv, rv = 0.0;
double tcnv; /* Transfer curve corrected device values */
int k = p->och; /* Channel being optimised */
int j = p->oba; /* Band being optimised */
int pc, m, i;
/* For each test point */
for (pc = m = 0; m < p->nodp; m++) {
mppcol *c = &p->cols[m];
double vv;
if (c->w < 1e-6)
continue; /* Skip zero weighted points */
/* Compute the tranfer corrected device values */
tcnv = icxTransFunc(pv, p->cord, c->nv[k]);
/* Compute band value */
vv = tcnv * c->tpcnv + (1.0 - tcnv) * c->tpcnv1;
/* Compute the band value error */
vv = lDE(vv) - c->lband[j];
rv += c->w * vv * vv;
pc++;
}
rv /= (double)pc;
/* Compute average magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
for (smv = 0.0, m = 0; m < p->cord; m++) {
smv += pv[m] * pv[m];
}
smv /= (double)(p->cord);
rv += 0.01 * smv;
#ifdef DEBUG
printf("efunc1 itt %d/%d chan %d band %d k0 %f returning %f\n",p->oit,p->ott,k,j,pv[0],rv);
#endif
return rv;
}
#endif /* NEVER */
/* Optimise all transfer curves simultaniously to minimise a particular bands error */
static double efunc2(void *adata, double pv[]) {
mpp *p = (mpp *)adata;
double smv, rv = 0.0;
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int j = p->oba; /* Band being optimised */
int i, m, k;
/* For each test point */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double ov;
/* Compute the tranfer corrected device values */
for (m = 0; m < p->n; m++) {
tcnv[m] = icxTransFunc(&pv[m * p->cord], p->cord, c->nv[m]);
tcnv1[m] = 1.0 - tcnv[m];
}
if (p->useshape) {
for (m = 0; m < p->n; m++)
ww[m] = 0.0;
/* Lookup the shape values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
for (m = 0; m < p->n; m++) {
ww[m] += p->shape[m][k & ~(1<<m)][j] * vv;
/* Apply weighting to shape vertex value */
}
}
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double vv = tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
vv = vv/(gg - gg * vv + 1.0);
} else {
vv = (vv - gg * vv)/(1.0 - gg * vv);
}
tcnv[m] = vv;
tcnv1[m] = 1.0 - vv;
}
}
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double vv = p->pc[k][j];
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
ov += vv;
}
/* Compute the band value error */
ov = lDE(ov) - c->lband[j];
rv += ov * ov;
}
rv /= (double)p->nodp;
/* Compute weighted magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
for (smv = 0.0, m = 0; m < p->n; m++) {
double w;
for (k = 0; k < p->cord; k++) {
i = m * p->cord + k;
#ifdef NEVER
w = (k < 2) ? TRANS_BASE : k * TRANS_HBASE; /* Increase weight with harmonics */
#else
/* Weigh to suppress ripples */
if (k <= 1) { /* Use TRANS_HW01 */
w = TRANS_HW01;
} else if (k <= TRANS_HBREAK) { /* Blend from TRANS_HW01 to TRANS_HWBR */
double bl = (k - 1.0)/(TRANS_HBREAK - 1.0);
w = (1.0 - bl) * TRANS_HW01 + bl * TRANS_HWBR;
} else { /* Use TRANS_HWBR */
w = TRANS_HWBR + (k-TRANS_HBREAK) * TRANS_HWINC;
}
#endif
smv += w * pv[i] * pv[i];
}
}
smv /= (double)(p->n);
rv += smv;
#ifdef DEBUG
printf("efunc2 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv);
#endif
return rv;
}
/* Return the gradient of the minimisation function at the given location, */
/* as well as the function value at this location. */
static double dfunc2(void *adata, double dv[], double pv[]) {
mpp *p = (mpp *)adata;
double smv, tt, rv = 0.0;
double dtcnv_dpv[MPP_MXINKS][MPP_MXTCORD]; /* Del in tcnv[m] due to del in parameter */
double dww_dtcnv[MPP_MXINKS][MPP_MXINKS]; /* Del in ww[m] due to del in tcnv[m] */
double dtcnv_tc[MPP_MXINKS]; /* Del in tcnv'[m] due to del in tcnv[m] */
double dtcnv_ww[MPP_MXINKS]; /* Del in tcnv'[m] due to del in ww[m] */
double dov[MPP_MXINKS]; /* Del of ov due to del in tcnv'[m] */
double ddov; /* Del in final ov due to del in raw ov */
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int j = p->oba; /* Band being optimised */
int i, m, k;
for (k = 0; k < (p->n * p->cord); k++)
dv[k] = 0.0;
/* For each test point */
for (i = 0; i < p->nodp; i++) {
int mo;
mppcol *c = &p->cols[i];
double ov;
/* Compute the tranfer corrected device values */
/* and del in these values due to del in input parameters */
for (m = 0; m < p->n; m++) {
tcnv[m] = icxdpTransFunc(&pv[m * p->cord], dtcnv_dpv[m], p->cord, c->nv[m]);
tcnv1[m] = 1.0 - tcnv[m];
}
#ifdef NEVER
{
for (m = 0; m < p->n; m++) {
int ii;
double ttt;
for (ii = 0; ii < p->cord; ii++) {
pv[m * p->cord + ii] += 1e-6;
ttt = (icxTransFunc(&pv[m * p->cord], p->cord, c->nv[m]) - tcnv[m])/1e-6;
pv[m * p->cord + ii] -= 1e-6;
printf("~1 chan %d cord %d is %f ref %f\n",m,ii,dtcnv_dpv[m][ii],ttt);
}
}
}
#endif
for (m = 0; m < p->n; m++)
ww[m] = 0.0;
/* Lookup the shape values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
for (m = 0; m < p->n; m++) {
ww[m] += p->shape[m][k & ~(1<<m)][j] * vv;
/* Apply weighting to shape vertex value */
}
}
/* Compute del ww[m][m] for del tcnv[m] */
for (m = 0; m < p->n; m++) { /* For each input channel were doing del of */
for (mo = 0; mo < p->n; mo++)
dww_dtcnv[mo][m] = 0.0;
for (k = 0; k < p->nn; k++) {
int mm;
double vv = 1.0;
for (mm = 0; mm < p->n; mm++) { /* Compute weight for node k */
if (m == mm)
continue;
if (k & (1 << mm))
vv *= tcnv[mm];
else
vv *= tcnv1[mm];
}
for (mo = 0; mo < p->n; mo++) { /* For each output channel */
double vvv = vv * p->shape[mo][k & ~(1<<mo)][j];
if (k & (1 << m))
dww_dtcnv[mo][m] += vvv;
else
dww_dtcnv[mo][m] -= vvv;
}
}
}
#ifdef NEVER
{
int mm;
double tww[MPP_MXINKS][MPP_MXINKS];
for (mm = 0; mm < p->n; mm++) { /* For del in each input value */
tcnv[mm] += 1e-6;
for (m = 0; m < p->n; m++)
tww[m][mm] = 0.0;
/* Lookup the shape values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= (1.0 - tcnv[m]);
}
for (m = 0; m < p->n; m++) {
tww[m][mm] += p->shape[m][k & ~(1<<m)][j] * vv;
/* Apply weighting to shape vertex value */
}
}
tcnv[mm] -= 1e-6;
for (m = 0; m < p->n; m++) {
tww[m][mm] = (tww[m][mm] - ww[m])/1e-6;
}
}
for (mm = 0; mm < p->n; mm++) {
for (m = 0; m < p->n; m++) {
printf("~1 ww[%d][%d] %f should be %f\n",mm, m, dww_dtcnv[mm][m],tww[mm][m]);
}
}
}
#endif /* NEVER */
#ifdef NEVER
{
double itc[MPP_MXINKS];
double ttc[MPP_MXINKS];
double tww[MPP_MXINKS];
for (m = 0; m < p->n; m++) {
itc[m] = tcnv[m];
}
#endif /* NEVER */
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double sv, dsv, vv = tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
tt = gg - gg * vv + 1.0;
sv = vv/tt;
dsv = (gg + 1.0)/(tt * tt);
} else {
tt = 1.0 - gg * vv;
sv = (vv - gg * vv)/tt;
dsv = (1.0 - gg)/(tt * tt);
}
tcnv[m] = sv;
tcnv1[m] = 1.0 - sv;
dtcnv_tc[m] = dsv; /* del in tcnv[m] due to del in tcnv[m] */
dtcnv_ww[m] = (vv * vv - vv)/(tt * tt); /* del in tcnv[m] due to del in ww[m] */
}
#ifdef NEVER
/* Check derivatives */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double vv = itc[m]; /* Input value to be tweaked */
vv += 1e-6;
if (gg >= 0.0) {
vv = vv/(gg - gg * vv + 1.0);
} else {
vv = (vv - gg * vv)/(1.0 - gg * vv);
}
ttc[m] = (vv - tcnv[m])/1e-6;
}
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double vv = itc[m]; /* Input value to be tweaked */
gg += 1e-6;
if (gg >= 0.0) {
vv = vv/(gg - gg * vv + 1.0);
} else {
vv = (vv - gg * vv)/(1.0 - gg * vv);
}
tww[m] = (vv - tcnv[m])/1e-6;
}
for (m = 0; m < p->n; m++) {
printf("~1 dtcnv_tc[%d] is %f should be %f\n",m, dtcnv_tc[m], ttc[m]);
}
for (m = 0; m < p->n; m++) {
printf("~1 dtcnv_ww[%d] is %f should be %f\n",m, dtcnv_ww[m], tww[m]);
}
}
#endif /* NEVER */
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double vv = p->pc[k][j];
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
ov += vv;
}
/* Compute del ov for del tcnv[m] */
for (m = 0; m < p->n; m++) {
for (dov[m] = 0.0, k = 0; k < p->nn; k++) {
int mm;
double vv = p->pc[k][j];
for (mm = 0; mm < p->n; mm++) {
if (m == mm)
continue;
if (k & (1 << mm))
vv *= tcnv[mm];
else
vv *= tcnv1[mm];
}
if (k & (1 << m))
dov[m] += vv;
else
dov[m] -= vv;
}
}
#ifdef NEVER
{
int mm;
double tdov[MPP_MXINKS];
for (mm = 0; mm < p->n; mm++) {
tcnv[mm] += 1e-6;
for (tdov[mm] = 0.0, k = 0; k < p->nn; k++) {
double vv = p->pc[k][j];
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= (1.0 - tcnv[m]);
}
tdov[mm] += vv;
}
tcnv[mm] -= 1e-6;
tdov[mm] = (tdov[mm] - ov)/1e-6;
}
for (m = 0; m < p->n; m++) {
printf("~1 dov[%d] is %f should be %f\n",m, tdov[m], dov[m]);
}
}
#endif /* NEVER */
/* Compute the band value error */
ddov = dDE(ov);
ov = lDE(ov) - c->lband[j];
ddov *= 2.0 * ov; /* del in final ov due to del in raw ov */
rv += ov * ov;
/* Accumulate delta from input params to output */
for (m = 0; m < p->n; m++) {
for (k = 0; k < p->cord; k++) {
double ttt;
int mm;
/* delta via dww */
for (ttt = 0.0, mm = 0; mm < p->n; mm++)
ttt += dov[mm] * dtcnv_ww[mm] * dww_dtcnv[mm][m] * dtcnv_dpv[m][k];
/* delta direct */
dv[m * p->cord + k] += ddov * (ttt + dov[m] * dtcnv_tc[m] * dtcnv_dpv[m][k]);
}
}
}
rv /= (double)p->nodp;
for (k = 0; k < (p->n * p->cord); k++)
dv[k] /= (double)p->nodp;
/* Compute weighted magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
tt = 2.0/(double)(p->n); /* Common factor */
for (smv = 0.0, m = 0; m < p->n; m++) {
double w;
for (k = 0; k < p->cord; k++) {
i = m * p->cord + k;
#ifdef NEVER
w = (k < 2) ? TRANS_BASE : k * TRANS_HBASE; /* Increase weight with harmonics */
#else
/* Weigh to suppress ripples */
if (k <= 1) { /* Use TRANS_HW01 */
w = TRANS_HW01;
} else if (k <= TRANS_HBREAK) { /* Blend from TRANS_HW01 to TRANS_HWBR */
double bl = (k - 1.0)/(TRANS_HBREAK - 1.0);
w = (1.0 - bl) * TRANS_HW01 + bl * TRANS_HWBR;
} else { /* Use TRANS_HWBR */
w = TRANS_HWBR + (k-TRANS_HBREAK) * TRANS_HWINC;
}
#endif
dv[i] += w * tt * pv[i]; /* Del in rv due to del in pv */
smv += w * pv[i] * pv[i];
}
}
smv /= (double)(p->n);
rv += smv;
#ifdef DEBUG
printf("dfunc2 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv);
#endif
return rv;
}
#ifdef TESTDFUNC
/* Check that dfunc2 returns the right values */
static void test_dfunc2(
mpp *p,
int nparms,
double *pv
) {
int i, j, k;
double refov;
double refdv[MPP_MXINKS * MPP_MXTCORD];
double ov;
double dv[MPP_MXINKS * MPP_MXTCORD];
/* Create reference dvs */
refov = efunc2((void *)p, pv);
for (i = 0; i < nparms; i++) {
pv[i] += 1e-6;
refdv[i] = (efunc2((void *)p, pv) - refov)/1e-6;
pv[i] -= 1e-6;
}
/* Check dfunc2 */
ov = dfunc2((void *)p, dv, pv);
printf("~#############################################\n");
printf("~! Check dfunc2, ov %f, refov %f\n",ov, refov);
for (i = 0; i < nparms; i++) {
printf("~1 Parm %d val %f, ref %f\n",i,dv[i],refdv[i]);
}
}
#endif /* TESTDFUNC */
/* Setup test point data ready for efunc3 on the given oba */
static void sfunc3(mpp *p) {
double pcnv[MPP_MXCCOMB]; /* Interpolation combination values */
int j = p->oba; /* Band being optimised */
int i, k, m;
/* Setup the correct per patch value fcnv values */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
/* Compute the tranfer corrected device values */
for (m = 0; m < p->n; m++)
c->tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, c->nv[m]);
/* Compute combination values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= c->tcnv[m];
else
vv *= (1.0 - c->tcnv[m]);
}
pcnv[k] = vv;
}
/* Compute shape weighting values */
for (k = 0; k < p->nnn2; k++) {
int m = p->c2f[k].ink;
int n = p->c2f[k].comb;
/* Compress full interpolation one dimension to */
/* exclude ink value being tweaked by shape */
c->fcnv[k] = pcnv[n & ~(1<<m)] + pcnv[n | (1<<m)];
}
}
}
/* Optimise all shape parameters simultaniously to minimise a particular bands error */
/* Assume test point tcnv and pcnv are setup for pre-shape values */
static double efunc3(void *adata, double pv[]) {
mpp *p = (mpp *)adata;
double smv, rv = 0.0;
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int j = p->oba; /* Band being optimised */
int n1 = p->n - 1;
int i, m, k;
/* For each test point */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double ov;
for (m = 0; m < p->n; m++)
ww[m] = 0.0;
/* Interpolate the per ink shape values for this set of input values */
for (k = 0; k < p->nnn2; k++) { /* For each ink & interp vertex */
m = k >> n1; /* Corresponding ink channel */
ww[m] += pv[k] * c->fcnv[k]; /* Apply weighting to shape vertex value */
}
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double vv = c->tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
vv = vv/(gg - gg * vv + 1.0);
} else {
vv = (vv - gg * vv)/(1.0 - gg * vv);
}
tcnv[m] = vv;
tcnv1[m] = 1.0 - vv;
}
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double vv = p->pc[k][j];
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
ov += vv;
}
/* Compute the band value error */
ov = lDE(ov) - c->lband[j];
rv += ov * ov;
}
rv /= (double)p->nodp;
/* Compute average magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
for (smv = 0.0, m = 0; m < p->nnn2; m++) {
smv += pv[m] * pv[m];
}
smv /= (double)(p->nnn2);
rv += SHAPE_PMW * smv;
#ifdef DEBUG
printf("efunc3 itt %d/%d band %d (smv %f) returning %f\n",p->oit,p->ott,j,smv,rv);
#endif
return rv;
}
/* Return the gradient of the minimisation function at the given location, */
/* as well as the function value at this location. */
static double dfunc3(void *adata, double dv[], double pv[]) {
mpp *p = (mpp *)adata;
double smv, tt, rv = 0.0;
double dtcnv[MPP_MXINKS]; /* Derivative of transfer curve corrected device values */
double dov[MPP_MXINKS]; /* Derivative of output interpolation device values */
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int j = p->oba; /* Band being optimised */
int n1 = p->n - 1;
int i, m, k;
for (k = 0; k < p->nnn2; k++)
dv[k] = 0.0; /* Start with 0 delta */
/* For each test point */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double ov, ddov;
for (m = 0; m < p->n; m++)
ww[m] = 0.0;
/* Interpolate the per ink shape values for this set of input values */
for (k = 0; k < p->nnn2; k++) {
m = k >> n1; /* Corresponding ink channel */
ww[m] += pv[k] * c->fcnv[k]; /* Apply weighting to shape vertex value */
}
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double sv, vv = c->tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
tt = gg - gg * vv + 1.0;
sv = vv/tt;
} else {
tt = 1.0 - gg * vv;
sv = (vv - gg * vv)/tt;
}
tcnv[m] = sv;
tcnv1[m] = 1.0 - sv;
dtcnv[m] = (vv * vv - vv)/(tt * tt); /* del in tcnv[m] due to del in ww[m] */
}
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double vv = p->pc[k][j];
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
ov += vv;
}
/* Compute del ov[m] for del tcnv[m] */
for (m = 0; m < p->n; m++) {
for (dov[m] = 0.0, k = 0; k < p->nn; k++) {
int mm;
double vv = p->pc[k][j];
for (mm = 0; mm < p->n; mm++) {
if (m == mm)
continue;
if (k & (1 << mm))
vv *= tcnv[mm];
else
vv *= tcnv1[mm];
}
if (k & (1 << m))
dov[m] += vv;
else
dov[m] -= vv;
}
dov[m] *= dtcnv[m]; /* del ov[m] due to del in ww[m] */
}
/* Compute the band value error */
ddov = dDE(ov);
ov = lDE(ov) - c->lband[j];
ddov *= 2.0 * ov; /* del in final ov due to del in raw ov */
rv += ov * ov;
for (k = 0; k < p->nnn2; k++) {
m = k >> n1; /* Corresponding ink channel */
dv[k] += ddov * dov[m] * c->fcnv[k];
}
}
rv /= ((double)p->nodp);
for (k = 0; k < p->nnn2; k++)
dv[k] /= ((double)p->nodp);
/* Compute average magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
tt = SHAPE_PMW * 2.0/(double)(p->nnn2); /* Common factor */
for (smv = 0.0, k = 0; k < p->nnn2; k++) {
dv[k] += tt * pv[k]; /* del rv due to del pv[k] */
smv += pv[k] * pv[k];
}
smv /= (double)(p->nnn2);
rv += SHAPE_PMW * smv;
#ifdef DEBUG
printf("dfunc3 itt %d/%d band %d (smv %f) returning %f\n",p->oit,p->ott,j,smv,rv);
#endif
return rv;
}
#ifdef TESTDFUNC
/* Check that dfunc3 returns the right values */
static void test_dfunc3(
mpp *p,
int nparms,
double *pv
) {
int i, j, k;
double refov;
double refdv[MPP_MXINKS * MPP_MXCCOMB/2];
double ov;
double dv[MPP_MXINKS * MPP_MXCCOMB/2];
/* Create reference dvs */
refov = efunc3((void *)p, pv);
for (k = 0; k < nparms; k++) {
pv[k] += 1e-6;
dv[k] = (efunc3((void *)p, pv) - refov)/1e-6;
pv[k] -= 1e-6;
}
/* Check dfunc3 */
ov = dfunc3((void *)p, dv, pv);
printf("~#############################################\n");
printf("~! Check dfunc3, ov %f, refov %f\n",ov, refov);
for (i = 0; i < nparms; i++) {
printf("~1 Parm %d val %f, ref %f\n",i,dv[i],refdv[i]);
}
}
#endif /* TESTDFUNC */
/* Setup test point data ready for efunc4 on the given oba */
static void sfunc4(mpp *p) {
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int j = p->oba; /* Band being optimised */
int i, k, m;
/* Setup the correct per patch value fcnv values */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
/* Compute the tranfer corrected device values */
for (m = 0; m < p->n; m++) {
tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, c->nv[m]);
tcnv1[m] = 1.0 - tcnv[m];
}
for (m = 0; m < p->n; m++)
ww[m] = 0.0;
/* Lookup the interpolated shape values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
for (m = 0; m < p->n; m++) {
ww[m] += p->shape[m][k & ~(1<<m)][j] * vv;
/* Apply weighting to shape vertex value */
}
}
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double vv = tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
vv = vv/(gg - gg * vv + 1.0);
} else {
vv = (vv - gg * vv)/(1.0 - gg * vv);
}
tcnv[m] = vv;
tcnv1[m] = 1.0 - vv;
}
/* Compute the shape adjusted primary combination values */
for (k = 0; k < p->nn; k++) {
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
c->pcnv[k] = vv;
}
}
}
/* Optimise all vertex values simultaniously to minimise a particular bands error */
/* Assume test point tcnv and pcnv are setup for post-shape values */
static double efunc4(void *adata, double pv[]) {
mpp *p = (mpp *)adata;
double smv = 0.0, rv = 0.0;
int j = p->oba; /* Band being optimised */
int i, k;
/* For each test point */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double ov = 0.0;
for (k = 0; k < p->nn; k++) {
double pp = pv[k];
if (pp < 0.0) /* Stop non-real values */
rv += -5000.0 * pp;
ov += c->pcnv[k] * pp;
}
/* Compute the band value error */
ov = lDE(ov) - c->lband[j];
rv += ov * ov;
}
rv /= p->nodp;
/* Compute anchor point error */
for (smv = 0.0, i = 0; i < p->nn; i++) {
double tt = lDE(pv[i]) - p->lpca[i][j];
smv += tt * tt;
}
smv /= (double)p->nn;
//printf("~1 anchor error = %f\n",smv);
rv += COMB_PMW * smv;
#ifdef DEBUG
printf("efunc4 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv);
#endif
return rv;
}
/* Return the gradient of the minimisation function at the given location, */
/* as well as the function value at this location. */
static double dfunc4(void *adata, double dv[], double pv[]) {
mpp *p = (mpp *)adata;
double smv = 0.0, rv = 0.0;
double drv[MPP_MXCCOMB]; /* Delta in rv */
int j = p->oba; /* Band being optimised */
int i, k;
for (k = 0; k < p->nn; k++) {
dv[k] = 0.0;
drv[k] = 0.0;
}
/* For each test point */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double ov = 0.0, ddov;
for (k = 0; k < p->nn; k++) {
double pp = pv[k];
if (pp < 0.0) { /* Stop non-real values */
rv += -5000.0 * pp;
drv[k] += -5000.0;
}
ov += c->pcnv[k] * pp;
}
/* Compute the band value error */
ddov = dDE(ov);
ov = lDE(ov) - c->lband[j];
ddov *= 2.0 * ov; /* del in final ov due to del in raw ov */
rv += ov * ov;
for (k = 0; k < p->nn; k++) {
dv[k] += ddov * c->pcnv[k];
}
}
rv /= p->nodp;
for (k = 0; k < p->nn; k++)
dv[k] /= ((double)p->nodp);
/* Compute anchor point error */
for (smv = 0.0, k = 0; k < p->nn; k++) {
double tt, ddov;
ddov = dDE(pv[k]);
tt = lDE(pv[k]) - p->lpca[k][j];
ddov *= COMB_PMW * 2.0 * tt/(double)p->nn;
dv[k] += ddov;
smv += tt * tt;
}
smv /= (double)p->nn;
//printf("~1 anchor error = %f\n",smv);
rv += COMB_PMW * smv;
for (k = 0; k < p->nn; k++) /* Add any < 0 contribution */
dv[k] += drv[k];
#ifdef DEBUG
printf("dfunc4 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv);
#endif
return rv;
}
#ifdef TESTDFUNC
/* Check that dfunc4 returns the right values */
static void test_dfunc4(
mpp *p,
int nparms,
double *pv
) {
int i, j, k;
double refov;
double refdv[MPP_MXCCOMB];
double ov;
double dv[MPP_MXCCOMB];
/* Create reference dvs */
refov = efunc4((void *)p, pv);
for (i = 0; i < nparms; i++) {
pv[i] += 1e-9;
refdv[i] = (efunc4((void *)p, pv) - refov)/1e-9;
pv[i] -= 1e-9;
}
/* Check dfunc4 */
ov = dfunc4((void *)p, dv, pv);
printf("~#############################################\n");
printf("~! Check dfunc4, ov %f, refov %f\n",ov, refov);
for (i = 0; i < nparms; i++) {
printf("~1 Parm %d val %f, ref %f\n",i,dv[i],refdv[i]);
}
}
#endif /* TESTDFUNC */
/* ---------------------------------------------------- */
/* Optimise whole model in one go */
#ifdef BIGBANG
/* Optimise all parameters simultaniously to minimise a particular bands error */
static double efunc0(void *adata, double pv[]) {
mpp *p = (mpp *)adata;
double *pv2, *pv3, *pv4; /* Pointers to each group of parameters */
double smv, rv = 0.0;
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int j = p->oba; /* Band being optimised */
int i, m, k;
pv2 = pv;
pv3 = pv + (p->n * p->cord);
pv4 = pv + (p->n * p->cord) + p->nnn2;
/* For each test point */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double ov;
/* Compute the tranfer corrected device values */
for (m = 0; m < p->n; m++) {
tcnv[m] = icxTransFunc(&pv2[m * p->cord], p->cord, c->nv[m]);
tcnv1[m] = 1.0 - tcnv[m];
}
for (m = 0; m < p->n; m++)
ww[m] = 0.0;
/* Lookup the shape values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
for (m = 0; m < p->n; m++) {
ww[m] += pv3[p->f2c[m][k & ~(1<<m)]] * vv;
/* Apply weighting to shape vertex value */
}
}
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double vv = tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
vv = vv/(gg - gg * vv + 1.0);
} else {
vv = (vv - gg * vv)/(1.0 - gg * vv);
}
tcnv[m] = vv;
tcnv1[m] = 1.0 - vv;
}
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double vv = pv4[k];
if (vv < 0.0) /* Stop non-real values */
rv += -5000.0 * vv;
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
ov += vv;
}
/* Compute the band value error */
ov = lDE(ov) - c->lband[j];
rv += ov * ov;
}
rv /= (double)p->nodp;
/* Compute weighted magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
for (smv = 0.0, m = 0; m < p->n; m++) {
double w;
for (k = 0; k < p->cord; k++) {
i = m * p->cord + k;
w = (k < 2) ? TRANS_BASE : k * TRANS_HBASE; /* Increase weight with harmonics */
smv += w * pv2[i] * pv2[i];
}
}
smv /= (double)(p->n);
rv += smv;
/* Compute average magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
for (smv = 0.0, m = 0; m < p->nnn2; m++) {
smv += pv3[m] * pv3[m];
}
smv /= (double)(p->nnn2);
rv += SHAPE_PMW * smv;
/* Compute anchor point error */
for (smv = 0.0, i = 0; i < p->nn; i++) {
double tt = lDE(pv4[i]) - p->lpca[i][j];
smv += tt * tt;
}
smv /= (double)p->nn;
rv += COMB_PMW * smv;
#ifdef DEBUG
printf("efunc0 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv);
#endif
return rv;
}
/* Return the gradient of the minimisation function at the given location, */
/* as well as the function value at this location. */
static double dfunc0(void *adata, double dv[], double pv[]) {
mpp *p = (mpp *)adata;
double *pv2, *pv3, *pv4; /* Pointers to each group of parameters */
double *dv2, *dv3, *dv4; /* Pointers to each group of derivatives */
double smv, tt, rv = 0.0;
double dtcnv_dpv2[MPP_MXINKS][MPP_MXTCORD]; /* Del in tcnv[m] due to del in pv2 */
double dww_dpv3[MPP_MXINKS][MPP_MXINKS * MPP_MXCCOMB/2]; /* Del in ww[m] due to del in pv3 */
double dov_dpv4[MPP_MXCCOMB]; /* Delta in ov due to delta in pv4 */
double drv4[MPP_MXCCOMB]; /* Delta in rv due to error in pv4 */
double dww_dtcnv[MPP_MXINKS][MPP_MXINKS]; /* Del in ww[m] due to del in tcnv[m] */
double dtcnv_tc[MPP_MXINKS]; /* Del in tcnv'[m] due to del in tcnv[m] */
double dtcnv_ww[MPP_MXINKS]; /* Del in tcnv'[m] due to del in ww[m] */
double dov_dcnv[MPP_MXINKS]; /* Del of ov due to del in tcnv'[m] */
double ddov; /* Del in final ov due to del in raw ov */
double tcnv[MPP_MXINKS]; /* Transfer curve corrected device values */
double tcnv1[MPP_MXINKS]; /* 1.0 - Transfer curve corrected device values */
double ww[MPP_MXINKS]; /* Interpolated tweak params for each channel */
int j = p->oba; /* Band being optimised */
int i, m, k;
pv2 = pv;
pv3 = pv + (p->n * p->cord);
pv4 = pv + (p->n * p->cord) + p->nnn2;
dv2 = dv;
dv3 = dv + (p->n * p->cord);
dv4 = dv + (p->n * p->cord) + p->nnn2;
for (k = 0; k < (p->n * p->cord); k++)
dv2[k] = 0.0;
for (k = 0; k < p->nnn2; k++)
dv3[k] = 0.0;
for (k = 0; k < p->nn; k++)
dv4[k] += 0.0;
/* For each test point */
for (i = 0; i < p->nodp; i++) {
int mo;
mppcol *c = &p->cols[i];
double ov;
/* Compute the tranfer corrected device values */
/* and del in these values due to del in input parameters */
for (m = 0; m < p->n; m++) {
tcnv[m] = icxdpTransFunc(&pv2[m * p->cord], dtcnv_dpv2[m], p->cord, c->nv[m]);
tcnv1[m] = 1.0 - tcnv[m];
}
for (m = 0; m < p->n; m++) {
ww[m] = 0.0;
for (k = 0; k < p->nnn2; k++)
dww_dpv3[m][k] = 0.0;
}
/* Lookup the shape values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
for (m = 0; m < p->n; m++) {
int pix = p->f2c[m][k & ~(1<<m)];
ww[m] += pv3[pix] * vv; /* Apply weighting to shape vertex value */
dww_dpv3[m][pix] += vv; /* Delta to ww[m] from del in pv3[pix] */
}
}
/* Compute del ww[m][m] for del tcnv[m] */
for (m = 0; m < p->n; m++) { /* For each input channel were doing del of */
for (mo = 0; mo < p->n; mo++)
dww_dtcnv[mo][m] = 0.0;
for (k = 0; k < p->nn; k++) {
int mm;
double vv = 1.0;
for (mm = 0; mm < p->n; mm++) { /* Compute weight for node k */
if (m == mm)
continue;
if (k & (1 << mm))
vv *= tcnv[mm];
else
vv *= tcnv1[mm];
}
for (mo = 0; mo < p->n; mo++) { /* For each output channel */
double vvv = vv * pv3[p->f2c[mo][k & ~(1<<mo)]];
if (k & (1 << m))
dww_dtcnv[mo][m] += vvv;
else
dww_dtcnv[mo][m] -= vvv;
}
}
}
/* Apply the shape values to adjust the primaries */
for (m = 0; m < p->n; m++) {
double gg = ww[m]; /* Curve adjustment */
double sv, dsv, vv = tcnv[m]; /* Input value to be tweaked */
if (gg >= 0.0) {
tt = gg - gg * vv + 1.0;
sv = vv/tt;
dsv = (gg + 1.0)/(tt * tt);
} else {
tt = 1.0 - gg * vv;
sv = (vv - gg * vv)/tt;
dsv = (1.0 - gg)/(tt * tt);
}
tcnv[m] = sv;
tcnv1[m] = 1.0 - sv;
dtcnv_tc[m] = dsv; /* del in tcnv[m] due to del in tcnv[m] */
dtcnv_ww[m] = (vv * vv - vv)/(tt * tt); /* del in tcnv[m] due to del in ww[m] */
}
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double pp, vv;
for (vv = 1.0, m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
dov_dpv4[k] = vv;
pp = pv4[k];
if (pp < 0.0) { /* Stop non-real values */
rv += -5000.0 * pp;
drv4[k] += -5000.0;
}
ov += vv * pp;
}
/* Compute del ov for del tcnv[m] */
for (m = 0; m < p->n; m++) {
for (dov_dcnv[m] = 0.0, k = 0; k < p->nn; k++) {
int mm;
double vv = p->pc[k][j];
for (mm = 0; mm < p->n; mm++) {
if (m == mm)
continue;
if (k & (1 << mm))
vv *= tcnv[mm];
else
vv *= tcnv1[mm];
}
if (k & (1 << m))
dov_dcnv[m] += vv;
else
dov_dcnv[m] -= vv;
}
}
/* Compute the band value error */
ddov = dDE(ov);
ov = lDE(ov) - c->lband[j];
ddov *= 2.0 * ov; /* del in final ov due to del in raw ov */
rv += ov * ov;
/* Accumulate delta from input params to output */
for (m = 0; m < p->n; m++) {
for (k = 0; k < p->cord; k++) {
double ttt;
int mm;
/* delta via dww */
for (ttt = 0.0, mm = 0; mm < p->n; mm++)
ttt += dov_dcnv[mm] * dtcnv_ww[mm] * dww_dtcnv[mm][m] * dtcnv_dpv2[m][k];
/* delta direct */
dv2[m * p->cord + k] += ddov * (ttt + dov_dcnv[m] * dtcnv_tc[m] * dtcnv_dpv2[m][k]);
}
}
for (k = 0; k < p->nnn2; k++) {
double ttt;
/* delta via dww */
for (ttt = 0.0, m = 0; m < p->n; m++)
ttt += dov_dcnv[m] * dtcnv_ww[m] * dww_dpv3[m][k];
dv3[k] += ddov * ttt;
}
for (k = 0; k < p->nn; k++) {
dv4[k] += ddov * dov_dpv4[k];
}
}
rv /= (double)p->nodp;
for (k = 0; k < (p->n * p->cord); k++)
dv2[k] /= (double)p->nodp;
for (k = 0; k < p->nnn2; k++)
dv3[k] /= ((double)p->nodp);
for (k = 0; k < p->nn; k++)
dv4[k] /= ((double)p->nodp);
/* Compute weighted magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
tt = 2.0/(double)(p->n); /* Common factor */
for (smv = 0.0, m = 0; m < p->n; m++) {
double w;
for (k = 0; k < p->cord; k++) {
i = m * p->cord + k;
w = (k < 2) ? TRANS_BASE : k * TRANS_HBASE; /* Increase weight with harmonics */
dv2[i] += w * tt * pv2[i]; /* Del in rv due to del in pv */
smv += w * pv2[i] * pv2[i];
}
}
smv /= (double)(p->n);
rv += smv;
/* Compute average magnitude of shaper parameters squared */
/* to minimise unconstrained "wiggles" */
tt = SHAPE_PMW * 2.0/(double)(p->nnn2); /* Common factor */
for (smv = 0.0, k = 0; k < p->nnn2; k++) {
dv3[k] += tt * pv3[k]; /* del rv due to del pv[k] */
smv += pv3[k] * pv3[k];
}
smv /= (double)(p->nnn2);
rv += SHAPE_PMW * smv;
/* Compute anchor point error */
for (smv = 0.0, k = 0; k < p->nn; k++) {
double tt, ddov;
ddov = dDE(pv4[k]);
tt = lDE(pv4[k]) - p->lpca[k][j];
ddov *= COMB_PMW * 2.0 * tt/(double)p->nn;
dv4[k] += ddov;
smv += tt * tt;
}
smv /= (double)p->nn;
rv += COMB_PMW * smv;
for (k = 0; k < p->nn; k++) /* Add any < 0 contribution */
dv4[k] += drv4[k];
#ifdef DEBUG
printf("dfunc0 itt %d/%d band %d returning %f\n",p->oit,p->ott,j,rv);
#endif
return rv;
}
#ifdef TESTDFUNC
/* Check that dfunc0 returns the right values */
static void test_dfunc0(
mpp *p,
int nparms,
double *pv
) {
int i, j, k;
double refov;
double refdv[MPP_MXPARMS];
double ov;
double dv[MPP_MXPARMS];
/* Create reference dvs */
refov = efunc0((void *)p, pv);
for (i = 0; i < nparms; i++) {
pv[i] += 1e-9;
refdv[i] = (efunc0((void *)p, pv) - refov)/1e-9;
pv[i] -= 1e-9;
}
/* Check dfunc0 */
ov = dfunc0((void *)p, dv, pv);
printf("~#############################################\n");
printf("~! Check dfunc0, ov %f, refov %f\n",ov, refov);
for (i = 0; i < nparms; i++) {
printf("~1 Parm %d val %f, ref %f\n",i,dv[i],refdv[i]);
}
}
#endif /* TESTDFUNC */
#endif /* BIGBANG */
#ifdef ISHAPE
/* ----------------------------------------------------- */
/* Create an initial solution for shape parameters */
/* by solving least squares using SVD */
/* Optimise device values to match test points Lab */
/* while minimising the difference between the device */
/* values and the current model device values in tcnv */
static double efuncS(void *adata, double pv[]) {
double tcnv[MPP_MXINKS]; /* Shape tweaked input values */
double tcnv1[MPP_MXINKS];
mpp *p = (mpp *)adata;
double smv, rv = 0.0;
mppcol *c = p->otp;
int j = p->oba;
int k, m;
double ov;
for (k = 0; k < p->n; k++) {
double gg = pv[k]; /* Curve adjustment */
double vv = c->tcnv[k]; /* Input value to be tweaked */
if (gg >= 0.0) {
vv = vv/(gg - gg * vv + 1.0);
} else {
vv = (vv - gg * vv)/(1.0 - gg * vv);
}
tcnv[k] = vv;
tcnv1[k] = 1.0 - vv;
}
/* Compute the primary combination values */
for (ov = 0.0, k = 0; k < p->nn; k++) {
double vv = p->pc[k][j];
for (m = 0; m < p->n; m++) {
if (k & (1 << m))
vv *= tcnv[m];
else
vv *= tcnv1[m];
}
ov += vv;
}
/* Compute the band value error */
ov = lDE(ov) - c->lband[j];
rv += ov * ov;
/* Compute magnitude of shape params */
for (smv = 0.0, k = 0; k < p->n; k++) {
smv += pv[k] * pv[k];
}
rv += SHAPE_PMW * 0.1 * smv/(double)p->n; /* Don't worry about this here - SVD will cope */
//printf("~1 efuncS %f %f %f %f returning %f\n",pv[0],pv[1],pv[2],pv[3],rv);
return rv;
}
static void ishape(
mpp *p,
int j /* Band being initialised */
) {
int i, k, m;
double **a; /* A[0..m-1][0..n-1] input A[][], will return U[][] */
double *b; /* B[0..m-1] Right hand side of equation, return solution */
p->oba = j;
if (p->nodp < (p->nn/2)) /* Nicer to return error ?? */
error ("Not enough data points to solve shaper parameters");
//printf("~1 ishape called for band %d\n",j);
/* First step is to compute the ideal shape values */
/* for each test point. */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double pv[MPP_MXINKS]; /* Parameter values */
double sr[MPP_MXINKS]; /* search radius */
p->otp = c;
//printf("~1 setting up point %d\n",i);
/* Compute the tranfer corrected device values */
for (m = 0; m < p->n; m++)
c->tcnv[m] = icxTransFunc(p->tc[m][j], p->cord, c->nv[m]);
/* Compute pre-shape combination values */
for (k = 0; k < p->nn; k++) { /* For each interp vertex */
double vv;
for (vv = 1.0, m = 0; m < p->n; m++) { /* Compute weighting */
if (k & (1 << m))
vv *= c->tcnv[m];
else
vv *= (1.0 - c->tcnv[m]);
}
c->pcnv[k] = vv;
}
/* Do reverse lookup to find ideal post transfer device */
/* shape values that are minimal, but will give the desired */
/* band value */
for (k = 0; k < p->n; k++) {
pv[k] = 0.0;
sr[k] = 0.5;
}
if (powell(NULL, p->n, pv, sr, 0.001, 300, efuncS, (void *)p, NULL, NULL) != 0) {
//printf("~1 ishape Powell failed at point %d\n",i);
for (k = 0; k < p->n; k++)
pv[k] = 0.0;
}
//printf("~1 got uncorrected device vals %f %f %f %f\n",c->nv[0],c->nv[1],c->nv[2],c->nv[3]);
//printf("~1 got shape target values %f %f %f %f\n",pv[0],pv[1],pv[2],pv[3]);
/* put them leave them in scnv */
for (k = 0; k < p->n; k++) {
c->scnv[k] = pv[k];
}
}
/* The second step is to use SVD to compute the per colorant */
/* interpolation corner values that are the best least squares */
/* solution to providing those shape parameters at each point. */
/* Allocate our SVD matricies */
a = dmatrix(0, p->nodp-1 + p->nn/2, 0, p->nn/2-1);
b = dvector(0, p->nodp-1 + p->nn/2);
for (m = 0; m < p->n; m++) { /* For each channel */
int kk;
/* Setup our matricies for the test point */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double ss; /* Weighting factor */
/* The theoretically correct weighting to get */
/* better alighment with DEsq() measure is */
/* ss = dDE(c->band[j]);, but this doesn't seem */
/* to work well in practice. */
/* I don't quite understand why this weighting */
/* seems to give a good result. */
ss = c->tcnv[m] * (1.0 - c->tcnv[m]); /* Don't understand why this is so good */
for (kk = k = 0; k < p->nn; k++) {
if (k & (1<<m))
continue; /* Invalid comb for this channel */
a[i][kk++] = ss * (c->pcnv[k] + c->pcnv[k | (1<<m)]);
}
b[i] = ss * c->scnv[m];
}
/* Add extra fake "data points" to weight the minimisation of */
/* the shape parameter values */
{
int ii;
double ss;
/* We want the relative weight to be similar to that */
/* aimed for in efunc3 */
// ~~~ 0.1 seems about right for 1150 ???
// 1.0 for roland8 ??
// ss = (double)p->nodp/((double)(p->nn/2)) * 1.0 * SHAPE_PMW;
ss = (double)p->nodp * 0.05 * SHAPE_PMW;
for (ii = 0; ii < p->nn/2; ii++) {
for (kk = k = 0; k < p->nn; k++) {
if (k & (1<<m))
continue; /* Invalid comb for this channel */
if (kk == ii) /* Weights only on diagonals */
a[i+ii][kk] = ss;
else
a[i+ii][kk] = 0.0;
kk++;
}
b[i+ii] = 0.0;
}
}
/* Solve the equation A.x = b using SVD */
if (svdsolve(a, b, p->nodp + p->nn/2, p->nn/2) != 0)
error("ishape: SVD failed");
/* Copy the solution back */
for (kk = k = 0; k < p->nn; k++) {
if (k & (1<<m))
continue; /* Invalid comb for this channel */
//printf("~1 Calculated shape[%d][%d] = %f\n",m,k,b[kk]);
p->shape[m][k][j] = b[kk++];
}
}
free_dvector(b, 0, p->nodp-1 + p->nn/2);
free_dmatrix(a, 0, p->nodp-1 + p->nn/2, 0, p->nn/2-1);
//printf("~1 ishape is done for band %d\n",j);
}
#endif /* ISHAPE */
/* ----------------------------------------------------- */
/* Routine to figure out a suitable black point */
/* Structure to hold optimisation information */
typedef struct {
mpp *p; /* Lookup object */
int n; /* Number of device channels */
double ilimit; /* Ink limit */
double p1[3]; /* white pivot point */
double p2[3]; /* Point on vector towards black */
} bfinds;
/* Optimise device values to minimise L, while remaining */
/* within the ink limit, and staying in line between p1 and p2 */
static double efunc6(void *adata, double pv[]) {
bfinds *b = (bfinds *)adata;
double rv = 0.0;
double dv[MPP_MXINKS];
double Lab[3];
double lr, ta, tb, terr; /* L ratio, target a, target b, target error */
double sum, ovr;
int j;
/* See if over ink limit or outside device range */
for (ovr = sum = 0.0, j = 0; j < b->n; j++) {
dv[j] = pv[j];
if (dv[j] < 0.0) {
if (-dv[j] > ovr)
ovr = -dv[j];
dv[j] = 0.0;
} else if (dv[j] > 1.0) {
if ((dv[j] - 1.0) > ovr)
ovr = dv[j] - 1.0;
dv[j] = 1.0;
}
sum += dv[j];
}
if (b->ilimit > 1e-4) {
sum -= b->ilimit;
if (sum < 0.0)
sum = 0.0;
} else {
sum = 0.0;
}
/* Compute Lab value */
forward(b->p, NULL, Lab, NULL, dv);
#ifdef DEBUG
printf("p1 = %f %f %f, p2 = %f %f %f\n",b->p1[0],b->p1[1],b->p1[2],b->p2[0],b->p2[1],b->p2[2]);
printf("device value %f %f %f %f, Lab = %f %f %f\n",dv[0],dv[1],dv[2],dv[3],Lab[0],Lab[1],Lab[2]);
#endif
/* Primary is to minimise L value */
rv = Lab[0];
/* See how out of line from p1 to p2 we are */
lr = (Lab[0] - b->p1[0])/(b->p2[0] - b->p1[0]); /* Distance towards p2 from p1 */
ta = lr * (b->p2[1] - b->p1[1]) + b->p1[1]; /* Target a value */
tb = lr * (b->p2[2] - b->p1[2]) + b->p1[2]; /* Target b value */
terr = (ta - Lab[1]) * (ta - Lab[1])
+ (tb - Lab[2]) * (tb - Lab[2]);
#ifdef DEBUG
printf("target error %f\n",terr);
#endif
rv += 100.0 * terr;
#ifdef DEBUG
printf("out of range error %f\n",ovr);
printf("over limit error %f\n",sum);
#endif
rv += 200 * (ovr + sum);
#ifdef DEBUG
printf("black find tc ret %f\n",rv);
#endif
return rv;
}
static void compute_wb(
mpp *p
) {
int j;
/* Figure out the white and black points */
if (p->imask & ICX_ADDITIVE) { /* Assume additive doesn't have an ink limit */
/* Simply lookup values from min and max device. */
/* If we have ICX_WHITE, should this be treated the same as */
/* subtractive black ???? */
for (j = 0; j < p->n; j++)
p->white.nv[j] = 1.0;
forward(p, &p->white.band[3], NULL, p->white.band, p->white.nv);
for (j = 0; j < p->n; j++)
p->black.nv[j] = 0.0;
forward(p, &p->black.band[3], NULL, p->black.band, p->black.nv);
for (j = 0; j < p->n; j++)
p->kblack.nv[j] = 0.0;
forward(p, &p->kblack.band[3], NULL, p->kblack.band, p->kblack.nv);
} else { /* Subtractive */
bfinds bfs;
double sr[MPP_MXINKS]; /* search radius */
double tt[MPP_MXINKS]; /* temporary */
int trial;
double rv, brv;
int kbset = 0;
/* Lookup white directly */
for (j = 0; j < p->n; j++)
p->white.nv[j] = 0.0;
forward(p, &p->white.band[3], bfs.p1, p->white.band, p->white.nv);
/* Choose a black direction */
if (p->imask & ICX_BLACK) {
int bix;
bix = icx_ink2index(p->imask, ICX_BLACK);
for (j = 0; j < p->n; j++) {
if (j == bix)
p->kblack.nv[j] = 1.0;
else
p->kblack.nv[j] = 0.0;
}
forward(p, &p->kblack.band[3], bfs.p2, p->kblack.band, p->kblack.nv);
kbset = 1;
} else if ((p->imask & (ICX_CYAN | ICX_MAGENTA | ICX_YELLOW))
== (ICX_CYAN | ICX_MAGENTA | ICX_YELLOW)) {
int cix, mix, yix;
cix = icx_ink2index(p->imask, ICX_CYAN);
mix = icx_ink2index(p->imask, ICX_MAGENTA);
yix = icx_ink2index(p->imask, ICX_YELLOW);
for (j = 0; j < p->n; j++) {
if (j == cix)
p->kblack.nv[j] = 1.0;
else if (j == mix)
p->kblack.nv[j] = 1.0;
else if (j == yix)
p->kblack.nv[j] = 1.0;
else
p->kblack.nv[j] = 0.0;
}
forward(p, NULL, bfs.p2, NULL, p->kblack.nv);
} else { /* Make direction parallel to L axis */
bfs.p2[0] = 0.0;
bfs.p2[1] = bfs.p1[1];
bfs.p2[2] = bfs.p1[2];
}
bfs.p = p;
bfs.n = p->n;
bfs.ilimit = p->limit;
/* Find the black point */
/* Do several trials to avoid local minima */
for (j = 0; j < p->n; j++) {
tt[j] = p->black.nv[j] = 0.5; /* Starting point */
sr[j] = 0.1;
}
brv = 1e38;
for (trial = 0; trial < 20; trial++) {
if (powell(&rv, p->n, tt, sr, 0.00001, 500, efunc6, (void *)&bfs, NULL, NULL) == 0) {
if (rv < brv) {
brv = rv;
for (j = 0; j < p->n; j++)
p->black.nv[j] = tt[j];
}
}
for (j = 0; j < p->n; j++) {
tt[j] = p->black.nv[j] + d_rand(-0.3, 0.3);
if (tt[j] < 0.0)
tt[j] = 0.0;
else if (tt[j] > 1.0)
tt[j] = 1.0;
}
}
if (brv > 1000.0)
error ("mpp: Black point powell failed");
for (j = 0; j < p->n; j++) { /* Make sure device values are in range */
if (p->black.nv[j] < 0.0)
p->black.nv[j] = 0.0;
else if (p->black.nv[j] > 1.0)
p->black.nv[j] = 1.0;
}
/* Set black value */
forward(p, &p->black.band[3], NULL, p->black.band, p->black.nv);
/* Set K only if device doesn't have a K channel */
if (kbset == 0) {
for (j = 0; j < p->n; j++)
p->kblack.nv[j] = p->black.nv[j];
forward(p, &p->kblack.band[3], NULL, p->kblack.band, p->kblack.nv);
}
if (p->verb) {
double Lab[3];
icmXYZ2Lab(&icmD50, Lab, p->white.band);
printf("White point %f %f %f [Lab %f %f %f]\n",
p->white.band[0],p->white.band[1],p->white.band[2],
Lab[0], Lab[1], Lab[2]);
icmXYZ2Lab(&icmD50, Lab, p->black.band);
printf("Black point %f %f %f [Lab %f %f %f]\n",
p->black.band[0],p->black.band[1],p->black.band[2],
Lab[0], Lab[1], Lab[2]);
icmXYZ2Lab(&icmD50, Lab, p->kblack.band);
printf("K only Black point %f %f %f [Lab %f %f %f]\n",
p->kblack.band[0],p->kblack.band[1],p->kblack.band[2],
Lab[0], Lab[1], Lab[2]);
}
}
}
/* ===================================== */
/* Create the mpp from scattered data points */
/* Returns nz on error */
static int create(
mpp *p, /* This */
int verb, /* Vebosity level, 0 = none */
int quality, /* Profile quality, 0..3 */
int display, /* non-zero if display device */
double limit, /* Total ink limit (if not display) */
inkmask devmask, /* Inkmask describing device colorspace */
int spec_n, /* Number of spectral bands, 0 if not valid */
double spec_wl_short, /* First reading wavelength in nm (shortest) */
double spec_wl_long, /* Last reading wavelength in nm (longest) */
double norm, /* Normalising scale value for spectral values */
instType itype, /* Spectral instrument type (if not display) */
int nodp, /* Number of points */
mppcol *points /* Array of input points */
) {
int it, i, j, k;
double de, mxde; /* Average Delta E and maximum Delta E */
double sde, mxsde; /* Average Spectral error and maximum spectral error */
int mxtcord; /* maximum transfer curve order */
int maxit; /* Maximum number of tuning itterations */
double thr; /* Powell threshold multiplier at each tuning pass */
int useshape; /* Make use of shaping parameters */
int mode; /* Band scanning mode */
/* Convert quality into operation counts */
switch (quality) {
case 0: /* Low */
useshape = 0;
mxtcord = 8;
maxit = 3;
break;
case 1:
default: /* Medium */
useshape = 1;
mxtcord = 10;
maxit = 4;
break;
case 2: /* High */
useshape = 1;
mxtcord = 14;
maxit = 5;
break;
case 3: /* Ultra high */
useshape = 1;
mxtcord = 20; /* Is more actually better ? */
maxit = 10;
break;
case 99: /* Special, simple model */
useshape = 0;
mxtcord = 1;
maxit = 4;
break;
}
/* Setup the basic mpp information */
p->verb = verb;
p->imask = devmask;
p->n = icx_noofinks(devmask);
p->nn = 1 << p->n;
p->nnn2 = p->n * p->nn/2;
if (display) {
p->display = 1;
p->limit = p->n;
p->itype = instUnknown;
} else {
p->display = 0;
p->limit = limit; /* Record it here */
p->itype = itype;
}
p->spec_n = spec_n;
p->spec_wl_short = spec_wl_short;
p->spec_wl_long = spec_wl_long;
p->nodp = nodp;
/* MPP limit is less than XICC */
if (p->n > MPP_MXINKS) {
p->e.c = 1;
sprintf(p->e.m,"MPP Can't handle %d colorants",p->n);
return 1;
}
/* MPP limit is less than XSPECT */
if (spec_n > MPP_MXBANDS) {
p->e.c = 1;
sprintf(p->e.m,"MPP Can't handle %d spectral bands",spec_n);
return 1;
}
/* Take a copy of the data points */
if ((p->cols = new_mppcols(p->nodp, p->n, p->spec_n)) == NULL) {
error("Malloc failed!");
}
if ((new_mppcol(&p->white, p->n, p->spec_n)) != 0) {
error("Malloc failed!");
}
if ((new_mppcol(&p->black, p->n, p->spec_n)) != 0) {
error("Malloc failed!");
}
if ((new_mppcol(&p->kblack, p->n, p->spec_n)) != 0) {
error("Malloc failed!");
}
p->spmax = -1e6;
for (i = 0; i < p->nodp; i++) {
copy_mppcol(&p->cols[i], &points[i], p->n, p->spec_n); /* Copy structure */
/* Create Lab version */
icmXYZ2Lab(&icmD50, p->cols[i].Lab, p->cols[i].band);
#ifdef SHARPEN
XYZ2sharp(&p->cols[i].band[0], &p->cols[i].band[1], &p->cols[i].band[2]);
#endif
/* Normalise spectral values */
for (j = 0; j < p->spec_n; j++) {
p->cols[i].band[3+j] /= norm; /* Normalise spectral value to range 0..1 */
if (p->cols[i].band[3+j] > p->spmax) /* Track maximum value */
p->spmax = p->cols[i].band[3+j];
}
/* Compute L* type band target values */
for (j = 0; j < (3+p->spec_n); j++) {
p->cols[i].lband[j] = lDE(p->cols[i].band[j]);
}
}
p->norm = 1.0; /* Internal norm is 1.0 */
/* Compute L* type band target values */
for (i = 0; i < p->nodp; i++) {
for (j = 0; j < (3+p->spec_n); j++) {
p->cols[i].lband[j] = lDE(p->cols[i].band[j]);
}
}
/* Init transfer curve parameters of model */
for (k = 0; k < p->n; k++) { /* For each ink */
for (j = 0; j < (p->spec_n+3); j++) { /* For each band */
for (i = 0; i < mxtcord; i++) { /* For each curve order */
if (i == 0)
p->tc[k][j][i] = -1.6; /* Typical starting value */
else
p->tc[k][j][i] = 0.0; /* Straight transfer curve */
}
}
}
/* Initialise the primary colorant values */
for (i = -1; i < p->n; i++) {
int ii, bk = 0;
double bdif = 1e6;
if (i < 0)
ii = 0;
else
ii = 1 << i;
/* Search the patch list to find the one closest to this colorant combination */
for (k = 0; k < p->nodp; k++) {
double dif = 0.0;
for (j = 0; j < p->n; j++) {
double tt;
if (i == j)
tt = 1.0 - p->cols[k].nv[j];
else
tt = p->cols[k].nv[j];
dif += tt * tt;
}
if (dif < bdif) { /* best so far */
bdif = dif;
bk = k;
if (dif < 0.001)
break; /* Don't bother looking further */
}
}
/* Put that sample patch in place as initial value */
for (j = 0; j < (3+p->spec_n); j++)
p->pc[ii][j] = p->cols[bk].band[j];
#ifdef DEBUG
printf("comb 0x%x XYZ is %f %f %f\n", ii, p->pc[ii][0], p->pc[ii][1], p->pc[ii][2]);
#endif
}
/* Estimate primary combination values from primary values */
{
double sm[3+MPP_MXBANDS]; /* Smallest reflection sample in this band */
double ink[3+MPP_MXBANDS];
/* Search the patch list to find the smallest values for each band */
/* we'll use this as a guide to the freznell reflection from the surface */
for (j = 0; j < (3+p->spec_n); j++) {
sm[j] = 1e6;
for (k = 0; k < p->nodp; k++) {
if (sm[j] > p->cols[k].band[j]) {
int m;
sm[j] = p->cols[k].band[j];
ink[j] = 0.0;
for (m = 0; m < p->n; m++)
ink[j] += p->cols[k].nv[m];
}
}
/* Adjust for error in freznell was estimated from */
/* a low ink coverage */
if (ink[j] < 4.0) {
sm[j] *= pow(0.775, 4.0 - ink[j]);
}
#ifdef DEBUG
printf("smallest value in band %d = %f from total ink %f\n",j,sm[j],ink[j]);
#endif
}
for (i = 3; i < p->nn; i++) {
if ((i & (i-1)) == 0)
continue; /* Skip primaries */
for (j = 0; j < (3+p->spec_n); j++) {
int k;
double wh = p->pc[0][j]; /* white */
double tr = 1.0; /* Trapping coefficient */
if (wh < 0.01)
wh = 0.01; /* Guard against silliness */
p->pc[i][j] = wh; /* Start with white */
for (k = 0; k < p->n; k++) {
if (i & (1<<k)) {
double co = p->pc[1 << k][j]; /* colorant + paper reflectance */
co = (co - sm[j])/wh; /* Colorant reflectance */
co /= tr; /* Trapping reduction */
if (co > 1.0)
co = 1.0;
else if (co < 0.0)
co = 0.0;
p->pc[i][j] *= co; /* Estimated combined reflectivity */
tr *= 0.90; /* Next inks effectiveness due to trapping */
}
}
p->pc[i][j] = p->pc[i][j];
p->pc[i][j] += sm[j]; /* Add freznell back in */
}
#ifdef DEBUG
printf("comb 0x%x estimated XYZ = %f %f %f\n",i, p->pc[i][0], p->pc[i][1], p->pc[i][2]);
#endif
}
}
/* Override the estimated primary combination values, if actual readings are available */
for (i = 3; i < p->nn; i++) {
int k, bk = 0;
double bdif = 1e6;
if ((i & (i-1)) == 0)
continue; /* Skip primaries */
/* Search the patch list to find the one closest to this colorant combination */
for (k = 0; k < p->nodp; k++) {
double dif = 0.0;
for (j = 0; j < p->n; j++) {
double tt;
if (i & (1<<j))
tt = 1.0 - p->cols[k].nv[j];
else
tt = p->cols[k].nv[j];
dif += tt * tt;
}
if (dif < bdif) { /* best so far */
bdif = dif;
bk = k;
if (dif < 0.001)
break; /* Don't bother looking further */
}
}
if (bdif < 0.02) {
/* Override the estimated combination values with the real values */
for (j = 0; j < (3+p->spec_n); j++) {
#ifdef DEBUG
printf("comb 0x%x band %d was %f ",i, j, p->pc[i][j]);
#endif
p->pc[i][j] = p->cols[bk].band[j];
#ifdef DEBUG
printf("best %d now %f\n",bk, p->pc[i][j]);
#endif
}
}
}
/* These initial primary combination values become the anchors during optimisation */
for (i = 0; i < p->nn; i++) {
for (j = 0; j < (3+p->spec_n); j++)
p->lpca[i][j] = lDE(p->pc[i][j]);
}
/* Allocate and init shape related parameter space */
init_shape(p);
p->cord = 1; /* Start with only 1 order */
#ifndef DEBUG
if (p->verb)
#endif /* !DEBUG */
{
deltae(p, &de, &mxde, &sde, &mxsde);
printf("Before optimising model have average dE of %f, max %f\n", de, mxde);
if (p->spec_n > 0) printf("and average spectral E of %f, max %f\n",sde, mxsde);
}
/* - - - - - - - - - - - - - - - - - */
#ifndef NOPROCESS
#ifdef NEVER // Skip efunc1 passes for now.
/* Do initial fast pass of optimisations */
/* using only first transfer curve order */
for (it = 0, p->ott = 3; it < p->ott; it++) {
double resid;
p->oit = it+1;
/* First optimise each input channels transfer curve */
for (k = 0; k < p->n; k++) { /* For each input channel */
double tw = 0.0; /* Total weight */
p->och = k; /* device channel being optimised */
/* Setup the appropriate weights */
for (i = 0; i < p->nodp; i++) {
mppcol *c = &p->cols[i];
double ww;
int kk;
for (ww = 0.0, kk = 0; kk < p->n; kk++) {
if (kk == k)
continue; /* Channel of interest can have any value */
ww += c->nv[kk] * c->nv[kk];
}
c->w = 1.0 - ww;
if (c->w < 0.0)
c->w = 0.0;
tw += c->w;
//printf("~1 chan %d point %d weight %f\n",k,i,c->w);
}
if (tw < 3.0)
error ("MPP - not enough weighted point");
for (j = 0; j < (3+p->spec_n); j++) { /* For all bands */
double pv[MPP_MXTCORD]; /* Parameter values */
double sr[MPP_MXTCORD]; /* search radius */
p->oba = j; /* Band being optimised */
if (p->verb) {
printf("Optimising device transfer curves channel %d band %d:\n",k,j);
}
/* Get the current values */
for (i = 0; i < p->cord; i++) {
pv[i] = p->tc[k][j][i];
sr[i] = 0.05;
}
sfunc1(p); /* Setup test point values for this chan and band */
if (powell(&resid, p->cord, pv, sr, 0.001, 100, efunc1, (void *)p, mppprog, (void *)p) != 0)
error ("Powell failed");
/* Put results back into place */
for (i = 0; i < p->cord; i++) {
p->tc[k][j][i] = pv[i];
}
}
#ifndef DEBUG
if (p->verb)
#endif /* !DEBUG */
{
deltae(p, &de, &mxde, &sde, &mxsde);
printf("\nNow got avg dE of %f, max %f\n",de, mxde);
if (p->spec_n > 0)
printf("and avgerage spectral E of %f, max %f\n",sde, mxsde);
}
}
}
#endif /* NEVER */
p->cord = mxtcord;
/* - - - - - - - - - - - - - - - - - */
/* Fine tune all parameters in the model */
for (mode = 0;;) {
double pv[MPP_MXPARMS]; /* Parameter values */
double sr[MPP_MXPARMS]; /* search radius */
int lj, yj = 0; /* Last band, peak Y band */
/* Decide which band to do next */
if (mode == 0) { /* Start at the beginning */
lj = -1;
if (p->spec_n == 0) {
mode = 3; /* Switch to doing Y values */
j = 1;
} else {
/* Start at the peak Y bandwidth */
j = (int)(p->spec_n * (555.0 - p->spec_wl_short)
/(p->spec_wl_long - p->spec_wl_short) + 0.5);
if (j < 0)
j = 0;
else if (j >= p->spec_n)
j = p->spec_n-1;
j += 3;
yj = j;
mode = 1; /* Switch to incrementing j */
}
} else if (mode == 1) { /* Increment j */
lj = j;
j++;
if (j >= (3+p->spec_n)) { /* We're finished moving up */
lj = yj;
j = yj-1;
mode = 2;
if (j < 3) { /* Just in case */
lj = yj;
j = 1;
mode = 3;
}
}
} else if (mode == 2) { /* Decrement j */
lj = j;
j--;
if (j < 3) {
lj = yj; /* We know that spect is valid */
j = 1;
mode = 3; /* Switch to Y mode */
}
} else if (mode == 3) { /* Doing Y mode */
if (spec_n > 0) {
/* Use spec at the peak X bandwidth */
lj = (int)(p->spec_n * (600.0 - p->spec_wl_short)
/(p->spec_wl_long - p->spec_wl_short) + 0.5);
if (lj < 0)
lj = 0;
else if (lj >= p->spec_n)
lj = p->spec_n-1;
lj += 3;
} else {
lj = 1;
}
j = 0; /* Doing X mode */
mode = 4;
} else if (mode == 4) { /* Doing X mode */
if (spec_n > 0) {
/* Use spec at the peak Z bandwidth */
lj = (int)(p->spec_n * (445.0 - p->spec_wl_short)
/(p->spec_wl_long - p->spec_wl_short) + 0.5);
if (lj < 0)
lj = 0;
else if (lj >= p->spec_n)
lj = p->spec_n-1;
lj += 3;
} else {
lj = 1;
}
j = 2; /* Doing Z mode */
mode = 5;
} else {
break; /* we're now done */
}
p->oba = j; /* Band being optimised */
if (p->verb)
printf("Doing band %d, last band %d\n",j,lj);
/* See if the last bands values are a good place to start */
if (lj >= 0) {
double cval, pval, p0val;
banderr(p, &cval, NULL, j); /* Current error */
/* Copy previous band transfer and shape into current band */
for (k = 0; k < p->n; k++)
for (i = 0; i < p->cord; i++) {
pv[k * p->cord + i] = p->tc[k][j][i]; /* Save current for restore */
p->tc[k][j][i] = p->tc[k][lj][i];
}
for (i = 0; i < p->nnn2; i++) {
int m = p->c2f[i].ink;
int n = p->c2f[i].comb;
sr[i] = p->shape[m][n][j];
p->shape[m][n][j] = p->shape[m][n][lj];
}
banderr(p, &pval, NULL, j);
/* Try out the urrent order 0 transfer values with rest of transfer and shape */
for (k = 0; k < p->n; k++)
p->tc[k][j][0] = pv[k * p->cord];
banderr(p, &p0val, NULL, j);
/* See which was best out of the three */
if (pval >= cval && p0val >= pval) { /* Original was the best */
for (k = 0; k < p->n; k++)
for (i = 0; i < p->cord; i++)
p->tc[k][j][i] = pv[k * p->cord + i]; /* Restore previous values */
for (i = 0; i < p->nnn2; i++) {
int m = p->c2f[i].ink;
int n = p->c2f[i].comb;
p->shape[m][n][j] = sr[i]; /* Restore previous value */
}
//printf("~1 Starting values were best (%f && %f > %f)\n",pval,p0val,cval);
} else if (p0val >= pval) { /* Copying all was best */
for (k = 0; k < p->n; k++)
p->tc[k][j][0] = p->tc[k][lj][0]; /* Back to order 0 values */
//printf("~1 copying all previous bands values was best (%f < %f && %f)\n",pval,cval,p0val);
} else {
//printf("~1 copying except order 0 values was best (%f < %f && %f)\n",p0val,cval,pval);
}
}
#ifdef MULTIPASS /* Multipass in parts */
for (it = 0, p->ott = maxit, thr = 1.0; it < maxit; it++, thr *= 0.2) {
double sde, mxsde;
double resid;
p->oit = it+1;
/* Optimise main transfer curve to minimise each bands error */
/* Initially using only first transfer curve order */
if (p->verb)
printf("Fine tuning device transfer curves itteration %d\n",it);
/* Get the current values */
for (k = 0; k < p->n; k++) {
for (i = 0; i < p->cord; i++) {
pv[k * p->cord + i] = p->tc[k][j][i];
sr[k * p->cord + i] = 0.05;
}
}
#ifdef TESTDFUNC
test_dfunc2(p, p->n * p->cord, pv);
#endif /* TESTDFUNC */
#ifdef NODDV
if (powell(&resid, p->n * p->cord, pv, sr, thr * 0.01, 200,
efunc2, (void *)p, mppprog, (void *)p) != 0)
error ("Powell failed");
#else /* !NODDV */
if (conjgrad(&resid, p->n * p->cord, pv, sr, thr * 0.01, 200,
efunc2, dfunc2, (void *)p, mppprog, (void *)p)!= 0)
error ("ConjGrad failed");
#endif /* !NODDV */
/* Put results back into place */
for (k = 0; k < p->n; k++) {
for (i = 0; i < p->cord; i++)
p->tc[k][j][i] = pv[k * p->cord + i];
}
#ifndef DEBUG
if (p->verb)
#endif /* !DEBUG */
{
banderr(p, &sde, &mxsde, j); /* Current error */
printf("\nNow got avg E of %f, max %f for band %d\n",sde, mxsde, j);
}
p->cord = mxtcord; /* maximum transfer curve order after very first run */
/* Tune the shaping parameters */
if (useshape) {
if (p->verb)
printf("Tuning detailed shaping parameters itteration %d\n",it);
sfunc3(p); /* Setup test point values for this band */
/* Get the current values */
for (i = 0; i < p->nnn2; i++) {
int m = p->c2f[i].ink;
int n = p->c2f[i].comb;
pv[i] = p->shape[m][n][j];
sr[i] = 0.01;
}
#ifdef TESTDFUNC
test_dfunc3(p, p->nnn2, pv);
#endif /* TESTDFUNC */
#ifdef NODDV
if (powell(&resid, p->nnn2, pv, sr, thr * 0.05, 2000,
efunc3, (void *)p, mppprog, (void *)p) != 0)
error ("Powell failed");
#else /* !NODDV */
if (conjgrad(&resid, p->nnn2, pv, sr, thr * 0.05, 2000,
efunc3, dfunc3, (void *)p, mppprog, (void *)p) != 0.0)
error ("ConjGrad failed");
#endif /* !NODDV */
/* Put results back into place */
for (i = 0; i < p->nnn2; i++) {
int m = p->c2f[i].ink;
int n = p->c2f[i].comb;
p->shape[m][n][j] = pv[i];
//printf("~1 shape[%d][%d] = %f\n",m,n,pv[i]);
}
#ifndef DEBUG
if (p->verb)
#endif /* !DEBUG */
{
banderr(p, &sde, &mxsde, j); /* Current error */
printf("\nNow got avg E of %f, max %f for band %d\n",sde, mxsde, j);
}
#ifdef DEBUG
dump_shape(p, 0, "After efunc3:");
#endif /* DEBUG */
p->useshape = 1; /* Would be nice to flag this on a per band basis */
}
/* Tune the vertex parameters */
if (p->verb)
printf("Optimising device combination values itteration %d\n",it);
sfunc4(p); /* Setup test point values for this band */
/* Get the current values */
for (k = 0; k < p->nn; k++) {
pv[k] = p->pc[k][j];
sr[k] = 0.01;
}
#ifdef TESTDFUNC
test_dfunc4(p, p->nn, pv);
#endif /* TESTDFUNC */
#ifdef NODDV
if (powell(&resid, p->nn, pv, sr, thr * 0.01, 500,
efunc4, (void *)p, mppprog, (void *)p) != 0)
error ("Powell failed");
#else /* !NODDV */
if (conjgrad(&resid, p->nn, pv, sr, thr * 0.01, 500,
efunc4, dfunc4, (void *)p, mppprog, (void *)p) != 0)
error ("ConjGrad failed");
#endif /* !NODDV */
/* Put results back into place */
for (k = 0; k < p->nn; k++) {
double pp = pv[k];
if (pp < 0.0)
pp = 0.0;
p->pc[k][j] = pp;
}
#ifndef DEBUG
if (p->verb)
#endif /* !DEBUG */
{
banderr(p, &sde, &mxsde, j); /* Current error */
printf("\nNow got avg E of %f, max %f for band %d\n",sde, mxsde, j);
}
}
#endif /* MULTIPASS */
#ifdef BIGBANG
/* Do optimisation with one big bang */
{
double resid;
double *pv2, *pv3, *pv4; /* Pointers to each group of parameters */
double *sr2, *sr3, *sr4; /* Pointers to each group of search radius */
int tparms = p->n * p->cord + p->nnn2 + p->nn;
pv2 = pv;
pv3 = pv + (p->n * p->cord);
pv4 = pv + (p->n * p->cord) + p->nnn2;
sr2 = sr;
sr3 = sr + (p->n * p->cord);
sr4 = sr + (p->n * p->cord) + p->nnn2;
/* Get the current transfer values */
for (k = 0; k < p->n; k++) {
for (i = 0; i < p->cord; i++) {
pv2[k * p->cord + i] = p->tc[k][j][i];
sr2[k * p->cord + i] = 0.005;
}
}
/* Get the current shaper values */
for (i = 0; i < p->nnn2; i++) {
int m = p->c2f[i].ink;
int n = p->c2f[i].comb;
pv3[i] = p->shape[m][n][j];
sr3[i] = 0.005;
}
/* Get the current device combination values */
for (k = 0; k < p->nn; k++) {
pv4[k] = p->pc[k][j];
sr4[k] = 0.005;
}
#ifdef TESTDFUNC
test_dfunc0(p, tparms, pv);
#endif /* TESTDFUNC */
if (conjgrad(&resid, tparms, pv, sr, 0.001, 4000, efunc0, dfunc0, (void *)p,
mppprog, (void *)p) != 0)
error ("ConjGrad failed");
/* Put results back into place */
for (k = 0; k < p->n; k++) {
for (i = 0; i < p->cord; i++)
p->tc[k][j][i] = pv2[k * p->cord + i];
}
for (i = 0; i < p->nnn2; i++) {
int m = p->c2f[i].ink;
int n = p->c2f[i].comb;
p->shape[m][n][j] = pv3[i];
}
for (k = 0; k < p->nn; k++) {
double pp = pv4[k];
if (pp < 0.0)
pp = 0.0;
p->pc[k][j] = pp;
}
#ifndef DEBUG
if (p->verb)
#endif /* !DEBUG */
{
banderr(p, &sde, &mxsde, j); /* Current error */
printf("\nNow got avg E of %f, max %f for band %d\n",sde, mxsde, j);
}
}
#endif /* BIGBANG */
}
#endif /* NOPROCESS */
#ifdef DEBUG
if (p->verb)
#endif /* !DEBUG */
{
double de, mxde;
deltae(p, &de, &mxde, &sde, &mxsde);
printf("\nNow got avg dE of %f, max %f\n",de, mxde);
if (p->spec_n > 0)
printf("and avgerage spectral E of %f, max %f\n",sde, mxsde);
}
#ifdef DOPLOT /* Plot the device curves */
{
#define XRES 100
double xx[XRES];
double y1[XRES];
for (i = 0; i < (3+p->spec_n); i++) {
printf("Band %d:\n",i);
for (j = 0; j < p->n; j++) {
printf("Ink %d:\n",j);
for (k = 0; k < XRES; k++) {
double x;
x = k/(double)(XRES-1);
xx[k] = x;
y1[k] = icxTransFunc(p->tc[j][i], p->cord, x);
}
do_plot(xx,y1,NULL,NULL,XRES);
}
}
}
#endif /* DOPLOT */
/* Figure out the white and black points */
compute_wb(p);
/* Done with our copy of the input points */
free (p->cols);
p->nodp = 0;
p->cols = NULL;
return 0;
}
#ifdef DEBUG
/* Dump current shape params to a file */
static void dump_shape(mpp *p, int first, char *title) {
int i,j,k;
FILE *df;
/* Some debug code */
if (first) {
if ((df = fopen("debug.txt","w")) == NULL)
error ("Failed to open debug.txt");
} else {
if ((df = fopen("debug.txt","a")) == NULL)
error ("Failed to open debug.txt");
}
fprintf(df,"%s\n",title);
for (i = 0; i < (3+p->spec_n); i++) {
fprintf(df,"Band %d:\n",i);
for (j = 0; j < p->n; j++) {
fprintf(df,"Ink %d:\n",j);
for (k = 0; k < p->nn; k++) {
if ((k & (1<<j)) == 0) {
int m, n;
double val = 0.0;
fprintf(df,"Comb %d = %f\n", k, p->shape[j][k][i]);
}
}
fprintf(df,"\n");
}
fprintf(df,"\n");
}
fclose(df);
}
/* Combine two first order shapers coefficients together */
static double comb(double n1, double n2) {
double nn;
if (n1 > 0.0)
n1 = (n1+1.0);
else
n1 = 1.0/(1.0-n1);
if (n2 > 0.0)
n2 = (n2+1.0);
else
n2 = 1.0/(1.0-n2);
nn = n1 * n2;
if (nn >= 1.0)
nn -= 1.0;
else
nn = 1.0-(1.0/nn);
return nn;
}
#endif /* DEBUG */
/* - - - - - - - - - - - - - - - - - - - */
#ifdef COMMPLUS /* Commercial+ */
#ifdef NEVER // ~~~~8888
#undef ADD_NEW_SECONDARY
/* Create a modified mpp */
mpp *mpp_modify(
mppchch *chl, /* List of changes */
int nil, /* Number in list */
mpp *p /* mpp to use as base */
) {
int i, j, k;
inkmask imask1 = p->imask;
inkmask imask2 = 0;
/* Source of destination channels index by channel no. */
struct _isource {
int nn; /* nz if new channel from chl, z if existing from base */
int ix; /* Index into chl or base */
} *isrc; /* [p2->n] */
mpp *p2 = NULL;
/* Dump out what is to be changed */
if (p->verb) {
for (i = 0; i < nil; i++) {
if (!chl[i].add) {
printf("%d: deleting '%s'\n",i,icx_inkmask2char(chl[i].im, 0));
} else {
printf("%d: adding '%s':\n",i,icx_inkmask2char(chl[i].im, 0));
if (chl[i].Lab[0] > 0.0) {
printf(" Lab = %s\n",debPdv(3,chl[i].Lab));
} else if (chl[i].sp.spec_n > 0) {
xspect_dump(&chl[i].sp);
} else {
printf(" mix = %s\n",debPdv(p->n,chl[i].mix));
}
}
}
}
/* Create the new ink mask and sanity check */
for (i = 0; i < nil; i++) {
if (!chl[i].add) {
if ((p->imask & chl[i].im) == 0)
error("Base profile doesn't contain '%s'\n",icx_inkmask2char(chl[i].im, 0));
imask1 &= ~chl[i].im;
}
}
for (i = 0; i < nil; i++) {
if (chl[i].add) {
if ((imask1 & chl[i].im) != 0)
error("Clashing added ink '%s'\n",icx_inkmask2char(chl[i].im, 0));
if ((imask2 & chl[i].im) != 0)
error("Duplicate added ink '%s'\n",icx_inkmask2char(chl[i].im, 0));
imask2 |= chl[i].im;
}
}
/* Create the new profile and fill in basic information */
if ((p2 = new_mpp()) == NULL)
error("new_mpp() failed");
p2->verb = p->verb;
p2->plot = p->plot;
p2->imask = imask1 | imask2;
if ((p2->ii = new_ncm(p2->imask)) == NULL)
error("mpp_mod: new_ncm on imask %s failed",icx_inkmask2char(p2->imask,1));
p2->n = p2->ii->n;
p2->np = p2->ii->np;
p2->nn = p2->ii->nn;
if (p2->n == 0)
error("mpp_mod: invalid inkmask %s", icx_inkmask2char(p2->imask,1));
p2->limit = 0.0;
p2->spec_n = p2->t_spec_n = p->spec_n;
p2->spec_wl_short = p->spec_wl_short;
p2->spec_wl_long = p->spec_wl_long;
p2->norm = p->norm;
/* Compute best ccomb of colorants that approximates spectra */
for (i = 0; i < nil; i++) {
if (!chl[i].add)
continue;
if (chl[i].Lab[0] > 0.0) {
fit_lab(p, chl[i].mix, chl[i].Lab);
} else if (chl[i].sp.spec_n > 0) {
xspect src;
/* First resample to the base */
src = chl[i].sp;
chl[i].sp.spec_n = p->spec_n;
chl[i].sp.spec_wl_short = p->spec_wl_short;
chl[i].sp.spec_wl_long = p->spec_wl_long;
chl[i].sp.norm = p->norm;
poi_resample_xspect(&chl[i].sp, &src);
/* Sanitize it */
for (j = 0; j < p->spec_n; j++) {
if (chl[i].sp.spec[j] > (p->m.media.mrefl[j] - 0.02))
chl[i].sp.spec[j] = (p->m.media.mrefl[j] - 0.02);
if (chl[i].sp.spec[j] < 0.02)
chl[i].sp.spec[j] = 0.02;
}
/* Then compute combo */
fit_spectra(p, chl[i].mix, chl[i].sp.spec);
} else {
// ~~~8888
}
}
/* Create map from base & add to p2 channels */
if ((isrc = (struct _isource *)alloca(p2->n * sizeof(struct _isource))) == NULL)
error("mpp_mod: alloca of isrc failed\n");
for (i = 0; i < p2->n; i++) {
inkmask im;
if ((im = icx_index2ink(p2->imask, i)) == 0)
error("mpp_mod: didn't find ink %d",i);
if (im & imask1) {
isrc[i].nn = 0;
isrc[i].ix = icx_ink2index(p->imask, im);
} else if (im & imask2) {
isrc[i].nn = 1;
for (j = 0; j < nil; j++) {
if (!chl[j].add)
continue;
if (im == chl[j].im) {
isrc[i].ix = j;
break;
}
}
if (j >= nil)
error("mpp_mod: didn't find ink %s in add list",icx_inkmask2char(im,1));
} else
error("mpp_mod: didn't find ink %s",icx_inkmask2char(im,1));
}
/* Allocate model */
if (alloc_model(p2, &p2->m) != 0)
error("mpp_mod: alloc_model failed with '%s'",p->e.m);
/* Copy media values */
p2->m.ilType = p->m.ilType;
p2->m.obType = p->m.obType;
p2->m.cse = p->m.cse;
p2->m.phase = 0;
p2->m.scale = p->m.scale;
p2->m.yn = p->m.yn;
p2->m.iyn = p->m.iyn;
vect_cpy(p2->m.media.t_mrefl, p->m.media.t_mrefl, p->t_spec_n);
vect_cpy(p2->m.media.mrefl, p->m.media.mrefl, p->spec_n);
vect_cpy(p2->m.media.n_mrefl, p->m.media.n_mrefl, p->spec_n);
vect_cpy(p2->m.media.in_mrefl, p->m.media.in_mrefl, p->spec_n);
p2->m.is_ordered = p->m.is_ordered;
/* Translate the ink order. */
/* First the base inks in their order: */
for (j = i = 0; i < p->n; i++) {
inkmask im = icx_index2ink(p->imask, p->m.order[i]);
if (imask1 & im)
p2->m.order[j++] = icx_ink2index(p2->imask, im);
}
/* Then the added inks in the order they are given */
for (i = 0; i < nil; i++) {
if (chl[i].add) {
p2->m.order[j++] = icx_ink2index(p2->imask, chl[i].im);
}
}
set_pos(p2, &p2->m);
set_precombs(p2, &p2->m);
p2->m.n_area_params = p->m.n_area_params;
p2->m.n_thick_params = p->m.n_thick_params;
p2->m.n_warpnl_params = p->m.n_warpnl_params;
/* Set the per ink parameters */
{
double *wghts; /* [p->n] */
double **xpms; /* [p->n] */
double *sr;
double *dev;
double *spec;
rspl **rtor;
co *d;
/* To handle spectral additions, creation reflectance to oref lookups */
if ((rtor = (rspl **)alloca(p->n * sizeof(rspl *))) == NULL)
error("mpp_mod: alloca of rspl* array failed\n");
for (i = 0; i < p->n; i++) {
if ((rtor[i] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL)
error("mpp_mod: new_rspl failed\n");
}
if ((d = (co *)alloca(p->spec_n * sizeof(co))) == NULL)
error("mpp_mod: alloca of rspl co array failed\n");
if ((dev = (double *)alloca(p->n * sizeof(double))) == NULL)
error("mpp_mod: alloca of dev array failed\n");
if ((spec = (double *)alloca(p->spec_n * sizeof(double))) == NULL)
error("mpp_mod: alloca of dev array failed\n");
vect_set(dev, 0.0, p->n);
for (i = 0; i < p->n; i++) {
int gres[1] = { 50 };
dev[i] = 1.0;
comp_Nink(p, spec, dev); /* Ink reflectance */
dev[i] = 0.0;
for (j = 0; j < p->spec_n; j++) {
d[j].p[0] = spec[j];
d[j].v[0] = p->m.inks[i].oref[j]; /* Kubelka-Munk reflectance coef */
}
rtor[i]->fit_rspl(rtor[i], RSPL_NOFLAGS, d, p->spec_n,
NULL, NULL, gres, NULL, NULL, 10.0, NULL, NULL);
#ifdef NEVER
#define XRES 50
{
double xx[XRES];
double y1[XRES];
int k;
for (k = 0; k < XRES; k++) {
double x = k/(double)(XRES-1);
xx[k] = x;
y1[k] = rtor[i]->interp1(rtor[i], x);
}
printf("~1 ref -> oref for chan %d\n",i);
do_plot(xx,y1,NULL,NULL,XRES);
}
#undef XRES
#endif /* NEVER */
}
/* Allocate space for curve fitting */
if ((wghts = (double *)alloca(p->n * sizeof(double))) == NULL)
error("mpp_mod: alloca of wghts failed\n");
if ((xpms = (double **)alloca(p->n * sizeof(double *))) == NULL)
error("mpp_mod: alloca of xpms failed\n");
i = p2->m.n_lin_params;
if (p2->m.n_area_params > i)
i = p2->m.n_area_params;
if (p2->m.n_thick_params > i)
i = p2->m.n_thick_params;
if (p2->m.n_warpnl_params > i)
i = p2->m.n_warpnl_params;
if ((sr = (double *)alloca(i * sizeof(double))) == NULL)
error("mpp_mod: alloca of sr failed\n");
/* Set each channels parameters */
for (i = 0; i < p2->n; i++) {
if (isrc[i].nn) { /* New channel */
int ix = isrc[i].ix;
/* First deal with otran and oref: */
/* We have a spectraly defined new channel */
if (chl[ix].sp.spec_n > 0) {
/* Compute oref from new ink R */
vect_set(p2->m.inks[i].oref, 0.0, p->t_spec_n);
for (j = 0; j < p->n; j++) {
for (k = 0; k < p->spec_n; k++) {
double oref, R = chl[ix].sp.spec[k];
oref = rtor[j]->interp1(rtor[j], R);
p2->m.inks[i].oref[k] += chl[ix].mix[j] * oref;
}
}
/* Sanitize the resulting oref values */
for (k = 0; k < p->spec_n; k++) {
double oref = p2->m.inks[i].oref[k];
if (oref < 0.0)
oref = 0.0;
p2->m.inks[i].oref[k] = oref;
}
/* Convert overal reflectance and oref to otrans */
for (k = 0; k < p->spec_n; k++) {
double oref, otran;
oref = p2->m.inks[i].oref[k];
otran = inv_refl_model(chl[ix].sp.spec[k], oref, 1.0, p2->m.media.mrefl[k]);
p2->m.inks[i].otran[k] = otran;
}
/* We have a new channel defined by a blend of base channels */
} else {
/* Blend existing ink values */
vect_set(p2->m.inks[i].otran, 0.0, p->t_spec_n);
vect_set(p2->m.inks[i].oref, 0.0, p->t_spec_n);
for (j = 0; j < p->n; j++) {
for (k = 0; k < p->spec_n; k++) {
double otran = p->m.inks[j].otran[k], oabs;
if (otran < 1e-6)
otran = 1e-6;
otran = log(otran) + 1.0; /* Power tran to linear tran */
oabs = 1.0 - otran; /* Convert tran to absorbtion */
p2->m.inks[i].otran[k] += chl[ix].mix[j] * oabs;
p2->m.inks[i].oref[k] += chl[ix].mix[j] * p->m.inks[j].oref[k];
}
}
/* Sanitize the resulting values */
for (k = 0; k < p->spec_n; k++) {
double oabs = p2->m.inks[i].otran[k], otran;
double oref;
otran = 1.0 - oabs;
otran = exp(otran - 1.0);
if (otran < KM_LIMIT)
otran = KM_LIMIT;
else if (otran > 1.0)
otran = 1.0;
p2->m.inks[i].otran[k] = otran;
oref = p2->m.inks[i].oref[k];
if (oref < 0.0)
oref = 0.0;
p2->m.inks[i].oref[k] = oref;
}
}
/* Do the rest of the per ink values */
vect_set(p2->m.inks[i].tran_aj, 0.0, p->spec_n);
vect_set(p2->m.inks[i].ref_aj, 0.0, p->spec_n);
for (j = 0; j < p->n; j++) {
vect_scaleadd(p2->m.inks[i].tran_aj, p->m.inks[j].tran_aj, chl[ix].mix[j], p->spec_n);
vect_scaleadd(p2->m.inks[i].ref_aj, p->m.inks[j].ref_aj, chl[ix].mix[j], p->spec_n);
}
/* Do curves using curve fitting */
{
double (*curvf)(double *pms, int n_pms, double vv);
double (*parm_weight)(double *pms, int n_pms, double smooth);
double (*parm_constr)(double *out, double *in, int n_pms);
int n_pms;
double *pms;
int cix;
char *cname;
#define NSAMPS 200
double samps[NSAMPS];
int nsamps = NSAMPS;
mod_cfit_ctx ctx;
double wghttot = 0.0;
/* Normalize weighting */
for (j = 0; j < p->n; j++)
wghttot += wghts[j] = chl[ix].mix[j];
for (j = 0; j < p->n; j++)
wghts[j] /= wghttot;
for (cix = 0; cix < 4; cix++) {
if (cix == 0) {
cname = "lin";
curvf = lin_curve;
parm_weight = lin_parm_weight;
parm_constr = NULL;
n_pms = p2->m.n_lin_params;
for (j = 0; j < p->n; j++)
xpms[j] = p->m.inks[j].lin;
pms = p2->m.inks[i].lin;
} else if (cix == 1) {
cname = "area";
curvf = area_curve;
parm_weight = area_parm_weight;
parm_constr = area_parm_constr;
n_pms = p2->m.n_area_params;
for (j = 0; j < p->n; j++)
xpms[j] = p->m.inks[j].area;
pms = p2->m.inks[i].area;
} else if (cix == 2) {
cname = "thick";
curvf = thick_curve;
parm_weight = thick_parm_weight;
parm_constr = thick_parm_constr;
n_pms = p2->m.n_thick_params;
for (j = 0; j < p->n; j++)
xpms[j] = p->m.inks[j].thick;
pms = p2->m.inks[i].thick;
} else {
cname = "warp";
curvf = warpnl_curve;
parm_weight = warpnl_parm_weight;
parm_constr = NULL;
n_pms = p2->m.n_warpnl_params;
for (j = 0; j < p->n; j++)
xpms[j] = p->m.inks[j].warpnl;
pms = p2->m.inks[i].warpnl;
}
/* Create the target samples */
for (k = 0; k < nsamps; k++) {
double iv = k/(nsamps-1.0);
double ov = 0.0;
for (j = 0; j < p->n; j++) {
double vv;
vv = curvf(xpms[j], n_pms, iv);
ov += wghts[j] * vv;
}
/* Keep target sane */
if (ov < 0.0)
ov = 0.0;
if (ov > 1.0)
ov = 1.0;
samps[k] = ov;
}
/* Fit the curve to the samples */
ctx.curvf = curvf;
ctx.parm_weight = parm_weight;
ctx.parm_constr = parm_constr;
ctx.n_pms = n_pms;
ctx.samps = samps;
ctx.nsamps = nsamps;
vect_set(sr, 0.1, n_pms);
vect_set(pms, 0.0, n_pms);
powell(NULL, n_pms, pms, sr, 1e-6, 1000, cb_mod_cfit, (void *)&ctx, NULL, NULL);
#ifdef NEVER
{
double xx[NSAMPS];
double y2[NSAMPS];
for (k = 0; k < NSAMPS; k++) {
double x;
x = k/(double)(NSAMPS-1);
xx[k] = x;
y2[k] = curvf(pms, n_pms, x);
}
printf("~1 target vs sample for ink %s, curve %s\n",p2->ii->inks[i].irep, cname);
do_plot(xx,samps,y2,NULL,NSAMPS);
}
#endif // NEVER
} /* Next cix */
#undef NSAMPS
}
} else { /* Existing channel, simply copy params. */
int ix = isrc[i].ix; /* Base channel */
vect_cpy(p2->m.inks[i].otran, p->m.inks[ix].otran, p->spec_n);
vect_cpy(p2->m.inks[i].oref, p->m.inks[ix].oref, p->spec_n);
vect_cpy(p2->m.inks[i].tran_aj, p->m.inks[ix].tran_aj, p->spec_n);
vect_cpy(p2->m.inks[i].ref_aj, p->m.inks[ix].ref_aj, p->spec_n);
vect_cpy(p2->m.inks[i].lin, p->m.inks[ix].lin, p->m.n_lin_params);
vect_cpy(p2->m.inks[i].area, p->m.inks[ix].area, p->m.n_area_params);
vect_cpy(p2->m.inks[i].thick, p->m.inks[ix].thick, p->m.n_thick_params);
vect_cpy(p2->m.inks[i].warpnl, p->m.inks[ix].warpnl, p->m.n_warpnl_params);
}
}
} /* End of setting per ink parameters */
/* Set the per pair parameters */
{
int paix2;
/* For each pair of p2 */
for (paix2 = 0; paix2 < p2->np; paix2++) {
int i0, i1; /* p2 ink indexes of pair */
int nn0, nn1; /* New or base flag */
int ix0, ix1; /* Index into chl or base */
i0 = p2->ii->pairs[paix2].iix[0];
i1 = p2->ii->pairs[paix2].iix[1];
nn0 = isrc[i0].nn;
nn1 = isrc[i1].nn;
ix0 = isrc[i0].ix;
ix1 = isrc[i1].ix;
if (nn0 && !nn1) { /* If just one is new channel, swap to make first a base channel */
int tt;
tt = i0; i0 = i1; i1 = tt;
tt = nn0; nn0 = nn1; nn1 = tt;
tt = ix0; ix0 = ix1; ix1 = tt;
}
/* Both are base channels */
if (!nn0 && !nn1) {
/* Simply copy the base pair parameters */
int paix = p->ii->pix[ix0][ix1]; /* Base pair index */
p2->m.sep[paix2] = p->m.sep[paix];
p2->m.itrap[paix2] = p->m.itrap[paix];
p2->m.dadj[paix2] = p->m.dadj[paix];
}
#ifdef ADD_NEW_SECONDARY
/* First is base channel, second is new. */
/* Blend pair params of base and mix components of new. */
else if (!nn0 && nn1) {
p2->m.sep[paix2] = 0.0;
p2->m.itrap[paix2] = 0.0;
p2->m.dadj[paix2] = 0.0;
for (j = 0; j < p->n; j++) {
int paix = p->ii->pix[ix0][j];
if (paix < 0)
continue;
p2->m.sep[paix2] += chl[ix1].mix[j] * p->m.sep[paix];
p2->m.itrap[paix2] += chl[ix1].mix[j] * p->m.itrap[paix];
p2->m.dadj[paix2] += chl[ix1].mix[j] * p->m.dadj[paix];
}
/* both are new */
} else {
p2->m.sep[paix2] = 0.0;
p2->m.itrap[paix2] = 0.0;
p2->m.dadj[paix2] = 0.0;
for (j = 0; j < p->n; j++) {
for (k = 0; k < p->n; k++) {
int paix = p->ii->pix[j][k];
if (paix < 0)
continue;
p2->m.sep[paix2] += chl[ix0].mix[j] * chl[ix1].mix[k] * p->m.sep[paix];
p2->m.itrap[paix2] += chl[ix0].mix[j] * chl[ix1].mix[k] * p->m.itrap[paix];
p2->m.dadj[paix2] += chl[ix0].mix[j] * chl[ix1].mix[k] * p->m.dadj[paix];
}
}
}
#endif /* ADD_NEW_SECONDARY */
}
set_limits(p2, &p2->m); /* In case we every do more fitting... */
} /* End of setting pair parameters */
#ifdef ADD_NEW_SECONDARY
// ~~~ hmm. should maintain warp for existing channels....
/* Set combination parameters (warp) */
{
int *nn; /* nz if new channel */
int *ix; /* Index into chl or base */
int cc2;
if ((nn = (int *)alloca(p2->n * sizeof(int))) == NULL)
error("mpp_mod: alloca of nn failed\n");
if ((ix = (int *)alloca(p2->n * sizeof(int))) == NULL)
error("mpp_mod: alloca of ix failed\n");
/* For all combinations in p2 */
for (cc2 = 0; cc2 < p2->nn; cc2++) {
FCOUNT(co, MPP_MXINKS, 0);
int ii, no;
/* Get sources of p2 channels */
no = p2->ii->icombs[cc2].no;
for (j = 0; j < no; j++) {
ii = p2->ii->icombs[cc2].iix[j];
nn[j] = isrc[ii].nn;
ix[j] = isrc[ii].ix;
}
/* Setup the counter that goes through corresponding source combo's */
FSETDI(co, no);
for (j = 0; j < no; j++) {
if (nn[j]) {
FSETSTARTEND(co, j, 0, p->n); /* Weighted comb. of all existing chan */
} else {
FSETSTARTEND(co, j, ix[j], ix[j]+1); /* Constant for existing channel */
}
}
/* Get ready to accumulate weighted source values */
for (k = 0; k < p2->spec_n; k++)
p2->m.warp[k][cc2] = 0.0;
/* Go through warp sources for this desination */
FC_INIT(co);
while (!FC_DONE(co)) {
int mm, cc1;
double w;
/* Create source imask from combination and weighting of it */
mm = 0;
w = 1.0;
for (j = 0; j < no; j++) {
mm |= 1 << co[j];
if (nn[j])
w *= chl[ix[j]].mix[co[j]];
}
cc1 = p->ii->ibits2comb[mm]->cix; /* icombs index of ibits mask */
for (k = 0; k < p2->spec_n; k++)
p2->m.warp[k][cc2] += w * p->m.warp[k][cc1];
FC_INC(co);
}
}
}
#endif /* ADD_NEW_SECONDARY */
return p2;
}
#endif /* NEVER */
#endif /* COMMPLUS Commercial+ */