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

2916 lines
83 KiB
C

/*
* Argyll Color Management System
* Print Device calibration curve generator.
*
* Author: Graeme W. Gill
* Date: 2008/3/3
*
* Copyright 1996-2008 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 program takes in the colorent wedge test chart
* points, and creates a set of per channel correction curves.
*/
/*
* TTBD:
* Allow auto max threshold to be scaled on command line ?
* ie. -m# set % to go below the default optimal maximum.
*/
/*
Additive spaces are handled by inverting the device values internally.
(Such a space should probably have ICX_INVERTED set as well, indicating
that the underlying device is actually subtractive.)
*/
#undef DEBUG
#define verbo stdout
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include "copyright.h"
#include "aconfig.h"
#include "cgats.h"
#include "numlib.h"
#include "sort.h"
#include "rspl.h"
#include "conv.h"
#include "xicc.h"
#include "plot.h"
#include "vrml.h"
#include "ui.h"
#define RSPLFLAGS (0 /* | RSPL_2PASSSMTH | RSPL_EXTRAFIT2 */)
#define RSPLSMOOTH 1.0 /* RSPL Smoothness factor use on measured device points */
#define TCURVESMOOTH 1.0 /* RSPL smoothness factor for target aim points */
#define GRES 2048 /* Rspl grid resolution */
#define SLOPE_NORM 70.0 /* Normalized delta E for below thresholds */
#define MIN_SLOPE_A 8.0 /* Criteria for Auto max, DE/dDev at max */
#define MIN_SLOPE_O 3.0 /* Criteria for Auto max, min DE/dDev below max */
#define MAX_CAL_RES 2048 /* Maximum resolution saved to .cal file */
#define DEF_CAL_RES 256 /* Default resolution saved to .cal file */
#define PRES 256 /* Plotting & Reporting resolution */
void usage(char *diag, ...) {
int i;
fprintf(stderr,"Create printer calibration, Version %s\n",ARGYLL_VERSION_STR);
fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
if (diag != NULL) {
va_list args;
fprintf(stderr," Diagnostic: ");
va_start(args, diag);
vfprintf(stderr, diag, args);
va_end(args);
fprintf(stderr,"\n");
}
fprintf(stderr,"usage: %s [-options] [prevcal] inoutfile\n",error_program);
fprintf(stderr," -v verbosity Verbose mode\n");
fprintf(stderr," -p Plot graphs.\n");
fprintf(stderr," -w Save colorant 3D plot to inoutname_r%s and inoutname_c%s\n",vrml_ext(),vrml_ext());
fprintf(stderr," -i Initial calibration, set targets, create .cal\n");
fprintf(stderr," -r Re-calibrate against previous .cal and create new .cal\n");
fprintf(stderr," -e Verify against previous .cal\n");
fprintf(stderr," -I Create imitation target from .ti3 and null calibration\n");
fprintf(stderr," -d Go through the motions but don't write any files\n");
fprintf(stderr," -z res Resolution of created calibration curves (default %d)\n",DEF_CAL_RES);
fprintf(stderr," -s smoothing Extra curve smoothing (default 1.0)\n");
fprintf(stderr," -A manufacturer Set the manufacturer description string\n");
fprintf(stderr," -M model Set the model description string\n");
fprintf(stderr," -D description Set the profile Description string\n");
fprintf(stderr," -C copyright Set the copyright string\n");
fprintf(stderr," -x# percent Set initial maximum device %% target (override auto)\n");
fprintf(stderr," -m# percent Set initial dev target to %% of auto maximum\n");
fprintf(stderr," -n# deltaE Set initial white minimum deltaE target\n");
fprintf(stderr," -t# percent Set initial 50%% transfer curve percentage target\n");
fprintf(stderr," # = c, r, 0 First channel\n");
fprintf(stderr," m, g, 1 Second channel\n");
fprintf(stderr," y, b, 2 Third channel\n");
fprintf(stderr," k, 3 Fourth channel, etc.\n");
fprintf(stderr," -a Create an Adobe Photoshop .AMP file as well as a .cal\n");
fprintf(stderr," prevcal Base name of previous .cal file for recal or verify.\n");
fprintf(stderr," inoutname Base name of input .ti3 file, output .cal file\n");
exit(1);
}
/* - - - - - - - - - - - - - - - - - - - - - - - */
typedef struct {
double loc; /* Location up the curve 0.0 - 1.0 */
double val[MAX_CHAN]; /* Value at that location 0.0 - 1.0 */
} trans_point;
/* Class to hold a print calibration target */
struct _pcaltarg {
inkmask devmask; /* ICX ink mask of device space */
/* Note that with all of these, a value < 0.0 */
/* indicates no value set. */
int devmaxset; /* Flag - nz if the devmax is set */
double devmax[MAX_CHAN]; /* Device value maximum 0.0 - 1.0 */
int ademaxset; /* Flag - nz if the ademax is set */
double ademax[MAX_CHAN]; /* abs DE maximum for each channel */
int ademinset; /* Flag - nz if the ademin is set */
double ademin[MAX_CHAN]; /* abs DE minimum for each channel */
int no_tpoints; /* Number of transfer curve points */
trans_point *tpoints; /* Array of transfer curve points */
char err[500]; /* Error message from diagnostics */
/* Methods */
void (*del)(struct _pcaltarg *p);
/* Save/restore to a CGATS file */
int (*write)(struct _pcaltarg *p, cgats *cg, int tab); /* return nz on error */
int (*read)(struct _pcaltarg *p, cgats *cg, int tab); /* return nz on error */
/* Set values in the target */
void (*update_devmax)(struct _pcaltarg *p, int chan, double val);
void (*update_ademax)(struct _pcaltarg *p, int chan, double val);
void (*update_ademin)(struct _pcaltarg *p, int chan, double val);
void (*update_tcurve)(struct _pcaltarg *p, int chan, double loc, double val);
/* Reurn nz if the target has been set */
int (*is_set)(struct _pcaltarg *p);
/* Update settings or from one from another */
void (*update)(struct _pcaltarg *p, struct _pcaltarg *s);
}; typedef struct _pcaltarg pcaltarg;
static void pcaltarg_del(pcaltarg *p) {
if (p != NULL) {
free(p);
}
}
/* Write the cal target to a given cgats table */
static int pcaltarg_write(pcaltarg *p, cgats *cg, int tab) {
int i, j;
time_t clk = time(0);
struct tm *tsp = localtime(&clk);
char *atm = asctime(tsp); /* Ascii time */
char *ident = icx_inkmask2char(p->devmask, 1);
char *bident = icx_inkmask2char(p->devmask, 0);
int devchan = icx_noofinks(p->devmask);
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 */
cg->add_table(cg, tt_other, 0); /* Add a table for Calibration TarGet values */
cg->add_kword(cg, tab, "DESCRIPTOR", "Argyll Calibration Aim Target Definition File",NULL);
cg->add_kword(cg, tab, "ORIGINATOR", "Argyll printcal", NULL);
cg->add_kword(cg, tab, "CREATED",atm, NULL);
cg->add_kword(cg, tab, "COLOR_REP", ident, NULL);
/* Setup the table, which holds all the model parameters. */
/* There is always a parameter per X Y Z or spectral band */
cg->add_field(cg, tab, "PARAMTYPE", nqcs_t);
nsetel++;
sprintf(buf, "%s_I",bident);
cg->add_field(cg, tab, buf, r_t);
nsetel++;
for (i = 0; i < devchan; i++) {
inkmask imask = icx_index2ink(p->devmask, i);
sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
cg->add_field(cg, tab, buf, r_t);
nsetel++;
}
if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL) {
free(ident);
free(bident);
sprintf(p->err,"ctg_write: malloc of setel failed");
return 1;
}
/* Write out the values */
if (p->devmaxset) {
/* This is informational only */
setel[0].c = "DEVMAX_USED";
setel[1].d = 0.0; /* Not used */
if (p->devmask & ICX_ADDITIVE) {
for (i = 0; i < devchan; i++)
setel[2+i].d = 1.0 - p->devmax[i];
} else {
for (i = 0; i < devchan; i++)
setel[2+i].d = p->devmax[i];
}
cg->add_setarr(cg, tab, setel);
}
if (p->ademaxset) {
setel[0].c = "DELMAX_AIM";
setel[1].d = 0.0; /* Not used */
for (i = 0; i < devchan; i++)
setel[2+i].d = p->ademax[i];
cg->add_setarr(cg, tab, setel);
}
if (p->ademinset) {
setel[0].c = "DELMIN_AIM";
setel[1].d = 0.0; /* Not used */
for (i = 0; i < devchan; i++)
setel[2+i].d = p->ademin[i];
cg->add_setarr(cg, tab, setel);
}
for (j = 0; j < p->no_tpoints; j++) {
setel[0].c = "TRANS_PNT";
setel[1].d = p->tpoints[j].loc;
for (i = 0; i < devchan; i++)
setel[2+i].d = p->tpoints[j].val[i];
cg->add_setarr(cg, tab, setel);
}
free(setel);
free(ident);
free(bident);
return 0;
}
/* Read the cal target from a given cgats table */
static int pcaltarg_read(pcaltarg *p, cgats *cg, int tab) {
char *bident;
int devchan;
int i, j, ix;
int ti; /* Temporary CGATs index */
int spi[2+MAX_CHAN]; /* CGATS indexes for each field */
char buf[100];
if ((ti = cg->find_kword(cg, tab, "COLOR_REP")) < 0) {
sprintf(p->err, "ctg_read: Can't fint COLOR_REP");
return 1;
}
if ((p->devmask = icx_char2inkmask(cg->t[tab].kdata[ti]) ) == 0) {
sprintf(p->err, "ctg_read: unrecognized COLOR_REP '%s'",cg->t[tab].kdata[ti]);
return 1;
}
devchan = icx_noofinks(p->devmask);
bident = icx_inkmask2char(p->devmask, 0);
/* Figure out the indexes of all the fields */
if ((spi[0] = cg->find_field(cg, tab, "PARAMTYPE")) < 0) {
sprintf(p->err, "ctg_read: Can't find field PARAMTYPE");
free(bident);
return 1;
}
sprintf(buf, "%s_I",bident);
if ((spi[1] = cg->find_field(cg, tab, buf)) < 0) {
sprintf(p->err, "ctg_read: Can't find field %s",buf);
free(bident);
return 1;
}
for (i = 0; i < devchan; i++) {
inkmask imask = icx_index2ink(p->devmask, i);
sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
if ((spi[2+i] = cg->find_field(cg, tab, buf)) < 0) {
sprintf(p->err, "ctg_read: Can't find field %s",buf);
free(bident);
return 1;
}
}
/* Go through all the entries in the table, putting them in the right place */
for (ix = 0; ix < cg->t[tab].nsets; ix++) {
if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "DELMAX_AIM") == 0) {
for (i = 0; i < devchan; i++)
p->ademax[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
p->ademaxset = 1;
} else if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "DELMIN_AIM") == 0) {
for (i = 0; i < devchan; i++)
p->ademin[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
p->ademinset = 1;
} else if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "TRANS_PNT") == 0) {
if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point)
* (p->no_tpoints+1))) == NULL)
error("Realloc of tpoints");
p->tpoints[p->no_tpoints].loc = *((double *)cg->t[tab].fdata[ix][spi[1]]);
for (i = 0; i < devchan; i++)
p->tpoints[p->no_tpoints].val[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
p->no_tpoints++;
}
}
free(bident);
return 0;
}
/* Update an individual setting. Use chan < 0 to set all to default */
void pcaltarg_update_devmax(struct _pcaltarg *p, int chan, double val) {
int i;
if (p->devmaxset == 0) {
for (i = 0; i < MAX_CHAN; i++)
p->devmax[i] = -1.0;
p->devmaxset = 1;
}
if (chan >= 0)
p->devmax[chan] = val;
}
void pcaltarg_update_ademax(struct _pcaltarg *p, int chan, double val) {
int i;
if (p->ademaxset == 0) {
for (i = 0; i < MAX_CHAN; i++)
p->ademax[i] = -1.0;
p->ademaxset = 1;
}
if (chan >= 0)
p->ademax[chan] = val;
}
void pcaltarg_update_ademin(struct _pcaltarg *p, int chan, double val) {
int i;
if (p->ademinset == 0) {
for (i = 0; i < MAX_CHAN; i++)
p->ademin[i] = -1.0;
p->ademinset = 1;
}
if (chan >= 0)
p->ademin[chan] = val;
}
void pcaltarg_update_tcurve(struct _pcaltarg *p, int chan, double loc, double val) {
int i, j;
/* See if a transfer curve point already exists */
for (j = 0; j < p->no_tpoints; j++) {
if (p->tpoints[j].loc == loc)
break;
}
/* If not, allocate a new one */
if (j >= p->no_tpoints) {
p->no_tpoints++;
if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point) * p->no_tpoints)) == NULL)
error("Realloc of tpoints");
p->tpoints[j].loc = loc;
for (i = 0; i < MAX_CHAN; i++)
p->tpoints[j].val[i] = -1.0;
}
p->tpoints[j].val[chan] = val;
if (p->no_tpoints > 0) {
/* Sort the transfer points into loc order */
#define HEAP_COMPARE(A,B) ((A).loc < (B).loc)
HEAPSORT(trans_point, p->tpoints, p->no_tpoints);
#undef HEAP_COMPARE
}
}
/* Reurn nz if the target has been set */
static int pcaltarg_is_set(pcaltarg *p) {
if (p->devmaxset != 0
|| p->ademaxset != 0
|| p->ademinset != 0
|| p->no_tpoints > 0)
return 1;
return 0;
}
/* Update one from another */
static void pcaltarg_update(pcaltarg *p, pcaltarg *s) {
int i, j, k;
if (s->devmaxset) {
if (p->devmaxset == 0) {
for (i = 0; i < MAX_CHAN; i++)
p->devmax[i] = -1.0;
p->devmaxset = 1;
}
for (i = 0; i < MAX_CHAN; i++) {
if (s->devmax[i] >= 0.0)
p->devmax[i] = s->devmax[i];
}
}
if (s->ademaxset) {
if (p->ademaxset == 0) {
for (i = 0; i < MAX_CHAN; i++)
p->ademax[i] = -1.0;
p->ademaxset = 1;
}
for (i = 0; i < MAX_CHAN; i++) {
if (s->ademax[i] >= 0.0)
p->ademax[i] = s->ademax[i];
}
}
if (s->ademinset) {
if (p->ademinset == 0) {
for (i = 0; i < MAX_CHAN; i++)
p->ademin[i] = -1.0;
p->ademinset = 1;
}
for (i = 0; i < MAX_CHAN; i++) {
if (s->ademin[i] >= 0.0)
p->ademin[i] = s->ademin[i];
}
}
/* For each source transfer curve point */
for (k = 0; k < s->no_tpoints; k++) {
/* See if a transfer curve point already exists */
for (j = 0; j < p->no_tpoints; j++) {
if (p->tpoints[j].loc == s->tpoints[k].loc)
break;
}
/* If not, allocate a new one */
if (j >= p->no_tpoints) {
p->no_tpoints++;
if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point) * p->no_tpoints)) == NULL)
error("Realloc of tpoints");
p->tpoints[j].loc = s->tpoints[k].loc;
for (i = 0; i < MAX_CHAN; i++)
p->tpoints[j].val[i] = -1.0;
}
for (i = 0; i < MAX_CHAN; i++) {
if (s->tpoints[k].val[i] >= 0.0)
p->tpoints[j].val[i] = s->tpoints[k].val[i];
}
}
if (s->no_tpoints > 0) {
/* Sort the transfer points into loc order */
#define HEAP_COMPARE(A,B) ((A).loc < (B).loc)
HEAPSORT(trans_point, p->tpoints, p->no_tpoints);
#undef HEAP_COMPARE
}
}
/* Create a new, empty pcaltarget */
/* Return NULL on error */
pcaltarg *new_pcaltarg() {
pcaltarg *p;
if ((p = (pcaltarg *)calloc(1, sizeof(pcaltarg))) == NULL) {
return NULL;
}
/* Set method pointers */
p->del = pcaltarg_del;
p->write = pcaltarg_write;
p->read = pcaltarg_read;
p->update_devmax = pcaltarg_update_devmax;
p->update_ademax = pcaltarg_update_ademax;
p->update_ademin = pcaltarg_update_ademin;
p->update_tcurve = pcaltarg_update_tcurve;
p->is_set = pcaltarg_is_set;
p->update = pcaltarg_update;
return p;
}
/* - - - - - - - - - - - - - - - - - - - - - - - */
/* Colors to use with do_plot_gen() */
float cal_plot_colors[16][3] = {
{ 0/255.0, 10/255.0, 255/255.0}, /* 0 Blue */
{ 210/255.0, 30/255.0, 0/255.0}, /* 1 Red */
{ 200/255.0, 200/255.0, 0/255.0}, /* 2 Yellow */
{ 0/255.0, 0/255.0, 0/255.0}, /* 3 Black */
{ 0/255.0, 200/255.0, 90/255.0}, /* 4 Green */
{ 220/255.0, 0/255.0, 255/255.0}, /* 5 Purple */
{ 136/255.0, 86/255.0, 68/255.0}, /* 6 Brown */
{ 248/255.0, 95/255.0, 0/255.0}, /* 7 Orange */
{ 160/255.0, 160/255.0, 160/255.0}, /* 8 Grey */
{ 220/255.0, 30/255.0, 220/255.0}, /* 9 Magenta */
{ 112/255.0, 255/255.0, 161/255.0}, /* 10 Lime */
{ 255/255.0, 191/255.0, 80/255.0}, /* 11 Pink */
{ 0/255.0, 100/255.0, 100/255.0}, /* 12 Full Cyan */
{ 100/255.0, 0/255.0, 100/255.0}, /* 13 Full Magenta */
{ 100/255.0, 100/255.0, 0/255.0}, /* 14 Full Yellow */
{ 100/255.0, 0/255.0, 0/255.0} /* 15 Full Red */
};
/* - - - - - - - - - - - - - - - - - - - - - - - */
/* A wedge sample value */
typedef struct {
double inv; /* Input value (cal table, linear with index) */
double dev; /* Device value (cal table = calibrated output) */
double XYZ[3]; /* XYZ value */
double Lab[3]; /* Lab value */
} wval;
#define MAX_INVSOLN 10 /* Rspl maximum reverse solutions */
/* rspl setting functions */
static void rsplset1(void *cbntx, double *out, double *in) {
co *dpoints = (co *)cbntx;
int ix;
ix = *((int*)&in[-0-1]); /* Get grid index being looked up */
out[0] = dpoints[ix].v[0];
}
/* Do an inverse lookup of an rspl. Return -1.0 on error. */
/* dir is value to favour if there are multiple solutions. */
static double rspl_ilookup(rspl *r, double dir, double in) {
int nsoln; /* Number of solutions found */
co pp[MAX_INVSOLN]; /* Room for all the solutions found */
int k; /* Chosen solution */
pp[0].v[0] = in;
nsoln = r->rev_interp (
r, /* this */
RSPL_NEARCLIP, /* Clip to nearest (faster than vector) */
MAX_INVSOLN, /* Maximum number of solutions allowed for */
NULL, /* No auxiliary input targets */
NULL, /* Clip vector direction and length */
pp); /* Input and output values */
nsoln &= RSPL_NOSOLNS; /* Get number of solutions */
if (nsoln == 1) { /* Exactly one solution */
k = 0;
} else if (nsoln == 0) { /* Zero solutions. This is unexpected. */
return -1.0;
} else { /* Multiple solutions */
double bdist = 1e300;
int bsoln = 0;
// warning("Multiple solutions for curve %d for DE %f",j,pp[0].v[0]);
for (k = 0; k < nsoln; k++) {
double tt;
tt = pp[k].p[0] - dir;
tt *= tt;
if (tt < bdist) { /* Better solution */
bdist = tt;
bsoln = k;
}
}
k = bsoln;
}
return pp[k].p[0];
}
/* Given a raw device value, lookup the calibrated device value value */
static double interp_cal(wval *cvals, int n_cvals, double ival) {
int mi, n_cvals_1 = n_cvals-1;
double t, w, oval;
t = (n_cvals-1.0) * ival;
mi = (int)floor(t); /* Grid coordinate */
if (mi < 0) /* Limit to valid index range */
mi = 0;
else if (mi >= n_cvals_1)
mi = n_cvals_1-1;
w = t - (double)mi; /* 1.0 - weight */
oval = (1.0 - w) * cvals[mi].dev + w * cvals[mi+1].dev;
return oval;
}
int main(int argc, char *argv[]) {
int fa,nfa,mfa; /* current argument we're looking at */
int verb = 0;
int doplot = 0; /* nz to plot curves, 2 to plot more details */
int do3dplot = 0; /* nz to save 3D plot of raw & calibrated colorant loci */
int initial = 0; /* Do initial creation of cal target and calibration */
int recal = 0; /* Do recalibrate/use cal target. */
int verify = 0; /* Do verification */
int imitate = 0; /* Do target directly from input */
int dowrite = 1; /* Write to files */
int doamp = 0; /* Write Adobe Photoshop .AMP file */
int verified = 1; /* Set to 0 if verification failed */
profxinf xpi; /* Extra profile/calibration information */
pcaltarg *upct = NULL; /* User settings of print calibration target */
pcaltarg *pct = NULL; /* Settings of print calibration target */
int cal_res = DEF_CAL_RES; /* Calibration file resolution */
double smooth = RSPLSMOOTH; /* RSPL Smoothness factor */
double xsmooth = 1.0; /* Smoothing multiplier */
double ver_maxde = 2.0; /* Verify maximum Delta E (1.0 for smooth == 1.0) */
int spec = 0; /* Use spectral data flag */
icxIllumeType illum = icxIT_D50; /* Spectral defaults */
xspect cust_illum; /* Custom illumination spectrum */
icxObserverType observ = icxOT_CIE_1931_2; /* The classic observer */
static char baname[MAXNAMEL+1] = ""; /* Input & Output base name */
static char inname[MAXNAMEL+1] = ""; /* new .ti3 input file name */
static char calname[MAXNAMEL+1] = ""; /* previous .cal input file name */
static char outname[MAXNAMEL+1] = ""; /* new .cal output file name */
static char ampname[MAXNAMEL+1] = ""; /* new .amp output file name */
double maxscale[MAX_CHAN]; /* Scale auto device maximum to % */
cgats *icg = NULL; /* .ti3 input cgats structure */
int ti; /* Temporary CGATs index */
inkmask devmask; /* ICX ink mask of device space */
int devchan; /* Number of chanels in device space */
int isLab = 0; /* Flag indicating whether PCS is XYZ or Lab */
int n_pvals[MAX_CHAN]; /* Number of measurement values */
wval *pvals[MAX_CHAN]; /* Patch measurement values */
wval white; /* Average white XYZ and L*a*b* value */
int n_white = 0; /* Number of values to average */
icmXYZNumber wht; /* White value used for paper relative L*a*b* conversion */
rspl *raw[MAX_CHAN]; /* Raw L*a*b* values fitted to rspl */
rspl *ade[MAX_CHAN]; /* Absolute delta E76 vs device value */
rspl *rde[MAX_CHAN]; /* Relative delta E94 vs device value */
rspl *dE762dE94[MAX_CHAN]; /* Informational conversion from Absolute dE76 to dE94 */
double mxade[MAX_CHAN]; /* Maximum discovvered ade value */
double mxdev[MAX_CHAN] = { 0 } ; /* Correspondin maximum device value (initial only) */
rspl *pcade[MAX_CHAN]; /* Previous calibrated absolute delta E76 */
double idpow[MAX_CHAN] = { -1.0 }; /* Ideal power-like of targen values */
int n_cvals; /* Number of calibration curve values */
wval *cvals[MAX_CHAN]; /* Calibration curve tables */
rspl *tcurves[MAX_CHAN]; /* Target tweak/shape curves */
int i, j;
/* Init pointers to NULL */
for (j = 0; j < MAX_CHAN; j++) {
maxscale[j] = -1.0;
pvals[j] = NULL;
raw[j] = NULL;
ade[j] = NULL;
rde[j] = NULL;
dE762dE94[j] = NULL;
pcade[j] = NULL;
cvals[j] = NULL;
tcurves[j] = NULL;
}
error_program = argv[0];
memset((void *)&xpi, 0, sizeof(profxinf)); /* Init extra profile info to defaults */
if ((upct = new_pcaltarg()) == NULL || (pct = new_pcaltarg()) == NULL)
error("new_caltarg failed");
if (argc < 3)
usage("Too few arguments, got %d expect at least %d",argc-1,2);
#ifdef NEVER
{
double src, dst, pp;
src = 0.5;
dst = 0.25;
pp = icx_powlike_needed(src, dst);
printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
src = 0.25;
dst = 0.5;
pp = icx_powlike_needed(src, dst);
printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
src = 0.5;
dst = 0.707106;
pp = icx_powlike_needed(src, dst);
printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
src = 0.5;
dst = 0.5;
pp = icx_powlike_needed(src, dst);
printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
}
#endif // NEVER
/* Process the arguments */
mfa = 1; /* Minimum final arguments */
for (fa = 1;fa < argc;fa++) {
nfa = fa; /* skip to nfa if next argument is used */
if (argv[fa][0] == '-') { /* Look for any flags */
char *na = NULL; /* next argument after flag, null if none */
if (argv[fa][2] != '\000')
na = &argv[fa][2]; /* next is directly after flag */
else {
if ((fa+1+mfa) < argc) {
if (argv[fa+1][0] != '-') {
nfa = fa + 1;
na = argv[nfa]; /* next is seperate non-flag argument */
}
}
}
if (argv[fa][1] == '?')
usage("Usage requested");
else if (argv[fa][1] == 'v') {
if (na != NULL) {
fa = nfa;
verb = atoi(na);
} else
verb = 1;
}
else if (argv[fa][1] == 'p') {
if (na != NULL) {
fa = nfa;
doplot = atoi(na);
} else
doplot = 1; /* Plot various graphs */
}
else if (argv[fa][1] == 'w') {
do3dplot = 1; /* Plot input in 3D */
}
else if (argv[fa][1] == 'i') {
initial = 1; /* Initial calibration */
recal = 0;
verify = 0;
imitate = 0;
mfa = 1;
}
else if (argv[fa][1] == 'r') {
initial = 0;
recal = 1; /* Recalibrate */
verify = 0;
imitate = 0;
mfa = 2;
}
else if (argv[fa][1] == 'e') {
initial = 0;
recal = 0;
verify = 1; /* Verify */
imitate = 0;
mfa = 2;
}
else if (argv[fa][1] == 'I') {
initial = 0;
recal = 0;
verify = 0;
imitate = 1; /* Imitation target */
mfa = 1;
}
else if (argv[fa][1] == 'd')
dowrite = 0; /* Don't write to files */
else if (argv[fa][1] == 'a')
doamp = 1; /* write AMP file */
/* Calibration file resolution */
else if (argv[fa][1] == 'z') {
if (na == NULL) usage("Expect argument to resolution flag -z");
fa = nfa;
cal_res = atoi(na);
if (cal_res < 16 | cal_res > MAX_CAL_RES)
usage("resolution %d out of range 16..%d",cal_res, MAX_CAL_RES);
}
/* Smoothing modfider */
else if (argv[fa][1] == 's') {
if (na == NULL) usage("Expect argument to smoothing flag -s");
fa = nfa;
xsmooth = atof(na);
}
/* Manufacturer description string */
else if (argv[fa][1] == 'A') {
if (na == NULL) usage("Expect argument to manufacturer description flag -A");
fa = nfa;
xpi.deviceMfgDesc = na;
}
/* Model description string */
else if (argv[fa][1] == 'M') {
if (na == NULL) usage("Expect argument to model description flag -M");
fa = nfa;
xpi.modelDesc = na;
}
/* Profile Description */
else if (argv[fa][1] == 'D') {
if (na == NULL) usage("Expect argument to profile description flag -D");
fa = nfa;
xpi.profDesc = na;
}
/* Copyright string */
else if (argv[fa][1] == 'C') {
if (na == NULL) usage("Expect argument to copyright flag -C");
fa = nfa;
xpi.copyright = na;
}
/* Per channel target modifiers */
else if (argv[fa][1] == 'x'
|| argv[fa][1] == 'm'
|| argv[fa][1] == 'n'
|| argv[fa][1] == 't') {
char fch = argv[fa][1];
int chan = -1;
double val = -1.0;
if (na == NULL)
usage("Expect channel flag after flag -%c",argv[fa][1]);
fa = nfa;
switch (na[0]) {
case 'c': case 'r': case '0':
chan = 0;
break;
case 'm': case 'g': case '1':
chan = 1;
break;
case 'y': case 'b': case '2':
chan = 2;
break;
case 'k': case '3':
chan = 3;
break;
case '4':
chan = 4;
break;
case '5':
chan = 5;
break;
case '6':
chan = 6;
break;
case '7':
chan = 7;
break;
case '8':
chan = 8;
break;
case '9':
chan = 9;
break;
case 'A':
chan = 10;
break;
case 'B':
chan = 11;
break;
case 'C':
chan = 12;
break;
case 'D':
chan = 13;
break;
case 'E':
chan = 14;
break;
case 'F':
chan = 15;
break;
default:
usage("Unknown channel flag '%c' after flag -%c",argv[fa][2],argv[fa][1]);
}
++fa;
if (fa >= argc || argv[fa][0] == '-') usage("Expect argument after flag -%c%c",fch,na[0]);
val = atof(argv[fa]);
if (fch == 'x') {
if (val < 0.0 || val > 100.0)
usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
val /= 100.0;
upct->update_devmax(upct, chan, val);
} else if (fch == 'm') {
if (val < 0.0 || val > 100.0)
usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
val /= 100.0;
maxscale[chan] = val;
} else if (fch == 'n') {
upct->update_ademin(upct, chan, val);
} else if (fch == 't') {
if (val < 0.0 || val > 100.0)
usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
val /= 100.0;
upct->update_tcurve(upct, chan, 0.5, val);
}
}
else
usage("Unknown flag '%c'",argv[fa][1]);
} else
break;
}
smooth *= xsmooth;
if (!( (initial && !recal && !verify && !imitate)
|| (!initial && recal && !verify && !imitate)
|| (!initial && !recal && verify && !imitate)
|| (!initial && !recal && !verify && imitate)))
error("One of -i, -r -e or -I must be set");
/* Get the file name arguments */
if (verify || recal) {
if (fa >= argc || argv[fa][0] == '-') usage("Missing prevoius .cal basename");
strncpy(calname,argv[fa++],MAXNAMEL-4); calname[MAXNAMEL-4] = '\000';
strcat(calname,".cal");
}
if (fa >= argc || argv[fa][0] == '-') usage("Missing .ti3 and new .cal basename");
strncpy(baname,argv[fa++],MAXNAMEL-4); baname[MAXNAMEL-4] = '\000';
strcpy(inname,baname); /* new .ti3 file */
strcat(inname,".ti3");
strcpy(outname,baname); /* New .cal file */
strcat(outname,".cal");
strcpy(ampname,baname); /* New .amp file */
strcat(ampname,".amp");
if (fa < argc) usage("Too many arguments ('%s')",argv[fa]);
/* Open and look at the .ti3 profile patches file */
icg = new_cgats(); /* Create a CGATS structure */
icg->add_other(icg, "CTI3"); /* our special input type is Calibration Target Information 3 */
if (icg->read_name(icg, inname))
error("CGATS file read error : %s",icg->e.m);
if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
error ("Input file isn't a CTI3 format file");
if (icg->ntables < 1)
error ("Input file doesn't contain at least one table");
/* See if CIE is actually available - some sources of .TI3 don't provide it */
if (!spec
&& icg->find_field(icg, 0, "LAB_L") < 0
&& icg->find_field(icg, 0, "XYZ_X") < 0) {
if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
error ("Neither CIE nor spectral data found in file '%s'",inname);
/* Switch to using spectral information */
if (verb)
printf("No CIE data found, switching to spectral with standard observer & D50\n");
spec = 1;
illum = icxIT_D50;
observ = icxOT_CIE_1931_2;
}
/* If we requested spectral, check that it is available */
if (spec) {
if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
error ("Requested spectral interpretation when data not available");
}
/* Get colorspace information from input CGATS file */
{
char *buf;
char *inc, *outc;
if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
error("Input file doesn't contain keyword COLOR_REPS");
if ((buf = strdup(icg->t[0].kdata[ti])) == NULL)
error("Malloc failed - color rep");
/* Split COLOR_REP into device and PCS space */
inc = buf;
if ((outc = strchr(buf, '_')) == NULL)
error("COLOR_REP '%s' invalid", icg->t[0].kdata[ti]);
*outc++ = '\000';
if (strcmp(outc, "XYZ") == 0)
isLab = 0;
else if (strcmp(outc, "LAB") == 0)
isLab = 1;
else
error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", icg->t[0].kdata[ti]);
devmask = icx_char2inkmask(inc);
devchan = icx_noofinks(devmask);
if (devchan == 0)
error("COLOR_REP '%s' invalid (No matching devmask)", icg->t[0].kdata[ti]);
if ((devmask & ICX_ADDITIVE) && !(devmask & ICX_INVERTED))
warning("COLOR_REP '%s' is probably not suitable for print calibration!", icg->t[0].kdata[ti]);
free(buf);
}
if (verify || recal || imitate) {
if (upct->is_set(upct)) {
warning("Command line calibration target paramers ignored on re-calibrate, verify and imitate!");
}
}
/* For recalibrate or verify, load the previous calibration file */
if (verify || recal) {
cgats *tcg; /* Previous .cal file */
tcg = new_cgats(); /* Create a CGATS structure */
tcg->add_other(tcg, "CAL"); /* our special input type is Calibration Target */
tcg->add_other(tcg, "CALDEEQ");
if (tcg->read_name(tcg, calname))
error("No cal target '%s' found for re-calibrate (%s)\n",calname,tcg->e.m);
/* Check that this is an output cal file */
if ((ti = tcg->find_kword(tcg, 0, "DEVICE_CLASS")) < 0)
error ("Calibration file '%s'doesn't contain keyword DEVICE_CLASS",calname);
if (strcmp(tcg->t[0].kdata[ti],"OUTPUT") != 0)
error ("Calibration file '%s' doesn't has DEVICE_CLASS that is not OUTPUT",calname);
if (pct->read(pct, tcg, 1) != 0)
error("Reading cal target '%s' failed",calname);
if (pct->devmask != devmask)
error("Target '%s' colorspace '%s' doesn't match '%s' colorspace '%s'",
calname,icx_inkmask2char(pct->devmask, 1),inname,icx_inkmask2char(devmask, 1));
/* Load the previous expected absolute DE response. */
/* We use this to reproduce a calibrated response, even though absolute DE */
/* is not the parameter that is being linearized. */
/* It will be in the third table with other type "CAL" */
if (tcg->ntables >= 3 && tcg->t[2].tt == tt_other && tcg->t[2].oi == 0) {
int ti;
char *bident;
int spi[1+MAX_CHAN]; /* CGATS indexes for each field */
char buf[100];
bident = icx_inkmask2char(pct->devmask, 0);
if (tcg->t[2].nsets <= 0)
error ("No Calibration Expected DE Response in '%s'",calname);
/* Figure out the indexes of all the fields */
sprintf(buf, "%s_I_DE",bident);
if ((spi[0] = tcg->find_field(tcg, 2, buf)) < 0)
error("Can't find field %s in '%s' table 3",buf,calname);
for (i = 0; i < devchan; i++) {
inkmask imask = icx_index2ink(pct->devmask, i);
sprintf(buf, "%s_%s_DE",bident,icx_ink2char(imask));
if ((spi[1+i] = tcg->find_field(tcg, 2, buf)) < 0)
error("Can't find field %s in '%s' table 3",buf,calname);
}
/* Read in each channels values and put them in a rspl */
for (j = 0; j < devchan; j++) {
datai low,high;
int gres[1];
co *dpoints;
low[0] = 0.0;
high[0] = 1.0;
gres[0] = tcg->t[2].nsets;
if ((pcade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
error("new_rspl() failed");
if ((dpoints = malloc(sizeof(co) * gres[0])) == NULL)
error("malloc dpoints[%d] failed",gres[0]);
/* Copy the points to our array */
if (devmask & ICX_ADDITIVE) {
for (i = 0; i < gres[0]; i++) {
dpoints[i].p[0] = 1.0 - i/(double)(gres[0]-1); /* (ignored) */
dpoints[i].v[0] = *((double *)tcg->t[2].fdata[gres[0]-1-i][spi[1+j]]);
}
} else {
for (i = 0; i < gres[0]; i++) {
dpoints[i].p[0] = i/(double)(gres[0]-1); /* (ignored) */
dpoints[i].v[0] = *((double *)tcg->t[2].fdata[i][spi[1+j]]);
}
}
pcade[j]->set_rspl(pcade[j],
0,
(void *)dpoints, /* Read points */
rsplset1, /* Setting function */
low, high, gres, /* Low, high, resolution of grid */
NULL, NULL /* Default data scale */
);
free(dpoints);
}
free(bident);
}
/* Load the previous adE76 to rdE94 equivalence curves. */
/* We use this to be able to show the equivalent rdE94 response */
/* when we're actually calibrating to a previous or imitation aDE76 target. */
if (tcg->ntables >= 4 && tcg->t[3].tt == tt_other && tcg->t[3].oi == 1) {
int ti;
char *bident;
int spi[1+2*MAX_CHAN]; /* CGATS indexes for each field */
char buf[100];
bident = icx_inkmask2char(pct->devmask, 0);
if (tcg->t[3].nsets <= 0)
error ("No Calibration adE76 to rdE94 equivalence curves in '%s'",calname);
/* Figure out the indexes of all the fields */
if ((spi[0] = tcg->find_field(tcg, 3, "ID")) < 0)
error("Can't find field %s in '%s' table 4","ID",calname);
for (i = 0; i < devchan; i++) {
inkmask imask = icx_index2ink(pct->devmask, i);
sprintf(buf, "%s_%s_ADE",bident,icx_ink2char(imask));
if ((spi[1+2*i+0] = tcg->find_field(tcg, 3, buf)) < 0)
error("Can't find field %s in '%s' table 4",buf,calname);
sprintf(buf, "%s_%s_RDE",bident,icx_ink2char(imask));
if ((spi[1+2*i+1] = tcg->find_field(tcg, 3, buf)) < 0)
error("Can't find field %s in '%s' table 4",buf,calname);
}
/* Read in each channels values and put them in a rspl */
for (j = 0; j < devchan; j++) {
datai low, high;
int gres[1];
co *dpoints;
gres[0] = tcg->t[3].nsets;
if ((dE762dE94[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
error("new_rspl() failed");
if ((dpoints = malloc(sizeof(co) * gres[0])) == NULL)
error("malloc dpoints[%d] failed",gres[0]);
/* Copy the points to our array */
for (i = 0; i < gres[0]; i++) {
dpoints[i].p[0] = *((double *)tcg->t[3].fdata[i][spi[1+2*j+0]]);
dpoints[i].v[0] = *((double *)tcg->t[3].fdata[i][spi[1+2*j+1]]);
}
mxade[j] = dpoints[gres[0]-1].p[0];
low[0] = 0.0;
high[0] = mxade[j];
dE762dE94[j]->set_rspl(dE762dE94[j],
0,
(void *)dpoints, /* Read points */
rsplset1, /* Setting function */
low, high, gres, /* Low, high, resolution of grid */
NULL, NULL /* Default data scale */
);
free(dpoints);
}
free(bident);
}
tcg->del(tcg);
} else { /* Must be an initial or imitation calibration */
pct->devmask = devmask;
/* Set the cal target from any user supplied parameters */
pct->update(pct, upct);
/* No previous absolute de reference */
for (j = 0; j < devchan; j++)
pcade[j] = NULL;
}
/* Common processing: */
/* Read in the patch data */
{
char buf[100];
char *pcsfname[2][3] = { { "XYZ_X", "XYZ_Y", "XYZ_Z" },
{ "LAB_L", "LAB_A", "LAB_B" } };
int dvi[MAX_CHAN]; /* CGATS indexes for each device field */
int pcsix[3]; /* XYZ/Lab chanel indexes */
xsp2cie *sp2cie = NULL; /* Spectral conversion object */
xspect sp;
int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
char *bident = icx_inkmask2char(devmask, 0);
/* Figure out the indexes of all the device fields */
for (j = 0; j < devchan; j++) {
inkmask imask = icx_index2ink(devmask, j);
sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
if ((dvi[j] = icg->find_field(icg, 0, buf)) < 0)
error("Can't find field %s in '%s'",buf,inname);
#ifdef DEBUG
printf("devn chan %d field %s = %d\n",j,buf,dvi[j]);
#endif
}
free(bident);
if (spec) {
int ii;
char buf[100];
if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
error ("Input file doesn't contain keyword SPECTRAL_BANDS");
sp.spec_n = atoi(icg->t[0].kdata[ii]);
if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
error ("Input file doesn't contain keyword SPECTRAL_START_NM");
sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
error ("Input file doesn't contain keyword SPECTRAL_END_NM");
sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
sp.norm = 100.0;
/* Find the fields for spectral values */
for (j = 0; j < sp.spec_n; j++) {
int nm;
/* Compute nearest integer wavelength */
nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
* (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
sprintf(buf,"SPEC_%03d",nm);
if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
error("Input file doesn't contain field %s",buf);
}
/* Create a spectral conversion object to XYZ */
if ((sp2cie = new_xsp2cie(illum, 0.0, &cust_illum, observ, NULL, icSigXYZData, icxClamp)) == NULL)
error("Creation of spectral conversion object failed");
/* To add FWA comp. would have to locate/create spectral white here, */
/* then set the FWA comp. on. */
/* See profout.c */
} else {
/* Figure out the indexes of the PCS fields */
for (j = 0; j < 3; j++) {
if ((i = icg->find_field(icg, 0, pcsfname[isLab][j])) >= 0) {
if (icg->t[0].ftype[i] != r_t)
error ("Field %s is wrong type",pcsfname[isLab][j]);
pcsix[j] = i;
#ifdef DEBUG
printf("PCS chan %d field %s = %d\n",j,pcsfname[isLab][j],pcsix[j]);
#endif
} else {
error ("Failed to find field %s",pcsfname[isLab][j]);
}
}
}
n_cvals = 0;
for (j = 0; j < devchan; j++) {
pvals[j] = NULL;
n_pvals[j] = 0;
cvals[j] = NULL;
}
/* Read all the test patches in */
for (i = 0; i < icg->t[0].nsets; i++) {
double maxv = -1.0;
int maxch;
#ifdef DEBUG
printf("Reading patch %d\n",i);
#endif
/* Locate the maximum device value of any channel */
for (j = 0; j < devchan; j++) {
double val = *((double *)icg->t[0].fdata[i][dvi[j]]) / 100.0;
if (devmask & ICX_ADDITIVE)
val = 1.0 - val;
if (val > maxv) {
maxv = val;
maxch = j;
}
}
#ifdef DEBUG
printf("max %f at chan %d\n",maxv,maxch);
#endif
/* Treat white specially, and take it out of the list */
if (maxv < 1e-6) {
double wxyz[3];
if (n_white == 0) {
white.dev = 0.0;
for (j = 0; j < 3; j++)
white.XYZ[j] = 0.0;
}
if (spec) {
/* Read and convert the spectral value */
for (j = 0; j < sp.spec_n; j++)
sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
sp2cie->convert(sp2cie, wxyz, &sp);
} else {
/* Read the CIE value */
for (j = 0; j < 3; j++)
wxyz[j] = *((double *)icg->t[0].fdata[i][pcsix[j]]);
/* And convert to XYZ 0..1 */
if (isLab) {
icmLab2XYZ(&icmD50, wxyz, wxyz);
} else {
for (j = 0; j < 3; j++)
wxyz[j] /= 100.0;
}
}
white.dev += maxv;
for (j = 0; j < 3; j++)
white.XYZ[j] += wxyz[j];
n_white++;
#ifdef DEBUG
printf(" white: dev %f,XYZ %f %f %f\n",
white.dev,white.XYZ[0]/n_white, white.XYZ[1]/n_white, white.XYZ[2]/n_white);
#endif
} else {
wval *vp;
/* Check that all the non-max value channels are zero */
for (j = 0; j < devchan; j++) {
double val = *((double *)icg->t[0].fdata[i][dvi[j]]) / 100.0;
if (devmask & ICX_ADDITIVE)
val = 1.0 - val;
if (j == maxch)
continue;
if (val > 0.001)
break;
}
if (j < devchan) {
#ifdef DEBUG
printf("Skipping patch\n");
#endif
continue; /* Ignore this patch */
}
if ((pvals[maxch] = (wval *)realloc(pvals[maxch],
sizeof(wval) * (n_pvals[maxch]+1))) == NULL)
error("Realloc of pvals failed");
vp = &pvals[maxch][n_pvals[maxch]];
vp->dev = maxv;
if (spec) {
/* Read and convert the spectral value */
for (j = 0; j < sp.spec_n; j++)
sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
sp2cie->convert(sp2cie, vp->XYZ, &sp);
} else {
/* Read the CIE value */
for (j = 0; j < 3; j++)
vp->XYZ[j] = *((double *)icg->t[0].fdata[i][pcsix[j]]);
/* And convert to XYZ 0..1 */
if (isLab) {
icmLab2XYZ(&icmD50, vp->XYZ, vp->XYZ);
} else {
for (j = 0; j < 3; j++)
vp->XYZ[j] /= 100.0;
}
}
/* Temporary D50 Lab */
icmXYZ2Lab(&icmD50, vp->Lab, vp->XYZ);
#ifdef DEBUG
printf(" patch %d: dev %f,XYZ %f %f %f, D50 Lab %f %f %f\n",
n_pvals[maxch], vp->dev,vp->XYZ[0], vp->XYZ[1], vp->XYZ[2],
vp->Lab[0], vp->Lab[1], vp->Lab[2]);
#endif
n_pvals[maxch]++;
}
}
/* Average the white */
if (n_white == 0)
error("Can't find even one white patch in '%s'",inname);
#ifdef DEBUG
printf("% white patches\n",n_white);
#endif
for (j = 0; j < 3; j++) {
white.dev /= (double)n_white;
white.XYZ[j] /= (double)n_white;
}
icmXYZ2Lab(&icmD50, white.Lab, white.XYZ);
if (verb) {
icmXYZ2Lab(&icmD50, white.Lab, white.XYZ);
printf("Average white = XYZ %f %f %f, D50 Lab %f %f %f\n",
white.XYZ[0], white.XYZ[1], white.XYZ[2], white.Lab[0], white.Lab[1], white.Lab[2]);
}
icmAry2XYZ(wht, white.XYZ); /* Use the average measured white */
for (j = 0; j < devchan; j++) {
wval *wp;
/* Copy averaged white value back into each channel */
if ((pvals[j] = (wval *)realloc(pvals[j],
sizeof(wval) * (n_pvals[j]+1))) == NULL)
error("Realloc (%d) of pvals failed",n_pvals[j]+1);
wp = &pvals[j][n_pvals[j]];
wp->dev = white.dev;
wp->XYZ[0] = white.XYZ[0];
wp->XYZ[1] = white.XYZ[1];
wp->XYZ[2] = white.XYZ[2];
n_pvals[j]++;
/* Convert all the XYZ values to Lab paper relative */
for (i = 0; i < n_pvals[j]; i++) {
wp = &pvals[j][i];
icmXYZ2Lab(&wht, wp->Lab, wp->XYZ);
}
/* Sort the channel according to device value */
/* For a consistent result for identical device values, */
/* secondary sort by inverse CIE value */
//#define HEAP_COMPARE(A,B) ((A).dev < (B).dev)
#define HEAP_COMPARE(A,B) ((A).dev != (B).dev ? ((A).dev < (B).dev) : ((A).Lab[0] > (B).Lab[0]))
HEAPSORT(wval, pvals[j], n_pvals[j]);
#undef HEAP_COMPARE
/* Check the maximum value looks OK */
if (n_pvals[j] < 5)
warning("Channel %d has only %d test patches",n_pvals[j]);
if (pvals[j][n_pvals[j]-1].dev < 0.99)
warning("Channel %d has max test patch value of %f",pvals[j][n_pvals[j]-1]);
if (verb > 2) {
printf("Chan %d has %d measurement values:\n",j,n_pvals[j]);
for (i = 0; i < n_pvals[j]; i++) {
wp = &pvals[j][i];
printf(" %d: dev %f,XYZ %f %f %f, Lab %f %f %f\n",
i,wp->dev,wp->XYZ[0], wp->XYZ[1], wp->XYZ[2],
wp->Lab[0], wp->Lab[1], wp->Lab[2]);
}
}
}
if (sp2cie != NULL)
sp2cie->del(sp2cie);
}
icg->del(icg); /* Clean up */
/* Interpolate Lab using rspl */
for (j = 0; j < devchan; j++) {
datai low,high;
datao olow,ohigh;
int gres[MXDI];
double avgdev[MXDO];
cow *dpoints;
low[0] = 0.0;
high[0] = 1.0;
gres[0] = GRES;
olow[0] = 0.0;
ohigh[0] = 100.0;
olow[1] = olow[2] = -128.0;
ohigh[1] = ohigh[2] = 128.0;
avgdev[0] = 0.0025;
avgdev[1] = 0.005;
avgdev[2] = 0.005;
if ((raw[j] = new_rspl(RSPL_NOFLAGS,1, 3)) == NULL)
error("new_rspl() failed");
if ((dpoints = (cow *)malloc(sizeof(cow) * n_pvals[j])) == NULL)
error("malloc dpoints[%d] failed",n_pvals[j]);
for (i = 0; i < n_pvals[j]; i++) {
dpoints[i].p[0] = pvals[j][i].dev;
dpoints[i].v[0] = pvals[j][i].Lab[0];
dpoints[i].v[1] = pvals[j][i].Lab[1];
dpoints[i].v[2] = pvals[j][i].Lab[2];
if (i == 0)
dpoints[i].w = (double)n_white;
else
dpoints[i].w = 1.0;
}
raw[j]->fit_rspl_w(raw[j],
RSPLFLAGS,
dpoints, /* Test points */
n_pvals[j], /* Number of test points */
low, high, gres, /* Low, high, resolution of grid */
olow, ohigh, /* Default data scale */
smooth, /* Smoothing */
avgdev, /* Average deviation */
NULL); /* iwidth */
/* Compute & show fit quality */
if (verb > 0) {
double avgde = 0.0, maxde = 0.0;
for (i = 0; i < n_pvals[j]; i++) {
co tp; /* Test point */
double de;
tp.p[0] = pvals[j][i].dev;
raw[j]->interp(raw[j], &tp);
de = icmLabDE(pvals[j][i].Lab, tp.v);
avgde += de;
if (de > maxde)
maxde = de;
}
avgde /= (double)n_pvals[j];
printf("Chan %d raw fit avg DE %f, max %f\n",j,avgde,maxde);
}
free(dpoints);
}
/* Plot the raw data and fitted curves */
if (doplot >= 2) {
plot_g gg = { 0 };
for (j = 0; j < devchan; j++) {
int k;
float rgb[3];
for (k = 0; k < 3; k++) { /* for L*m a*, b* */
/* Make targets red, so they show when not covered by expected */
rgb[0] = 1.0;
rgb[1] = 0.0;
rgb[2] = 0.0;
if ((k % MXGPHS) == 1 /* Except redish colors */
|| (k % MXGPHS) == 6
|| (k % MXGPHS) == 7
|| (k % MXGPHS) == 9
|| (k % MXGPHS) == 11
|| (k % MXGPHS) == 13
|| (k % MXGPHS) == 15) {
rgb[0] = 0.0; /* where we make it blue */
rgb[1] = 0.0;
rgb[2] = 1.0;
}
for (i = 0; i < n_pvals[j]; i++) {
if (i == 0)
start_line_g(&gg, pvals[j][i].dev, pvals[j][i].Lab[k], rgb);
else
extend_line_g(&gg, pvals[j][i].dev, pvals[j][i].Lab[k]);
}
for (i = 0; i < PRES; i++) {
co tp;
tp.p[0] = i/(double)(PRES-1);
raw[j]->interp(raw[j], &tp);
if (i == 0)
start_line_g(&gg, tp.p[0], tp.v[k], cal_plot_colors[k % MXGPHS]);
else
extend_line_g(&gg, tp.p[0], tp.v[k]);
}
}
printf("Chan %d raw and fitted L*a*b*:\n",j);
do_plot_g(&gg, 0.0, 0.0, 0.0, 0.0, 1.0, 0, 1);
clear_g(&gg);
}
}
/* Create 3D plot of raw colorant loci */
if (do3dplot) {
char vname[MAXNAMEL+3] = "";
vrml *wrl;
int doaxes = 0;
int devres = 21;
strcpy(vname, baname);
strcat(vname, "_r");
if ((wrl = new_vrml(vname, doaxes, vrml_lab)) == NULL)
error("new_vrml failed for '%s%s'",vname,vrml_ext());
for (j = 0; j < devchan; j++) {
double last[3];
for (i = 0; i < devres; i++) {
co tp; /* Test point */
tp.p[0] = i/(double)(devres-1);
raw[j]->interp(raw[j], &tp);
if (i > 0) {
wrl->add_cone(wrl, last, tp.v, NULL, 20/(devres-1.0));
}
icmCpy3(last, tp.v);
}
}
wrl->del(wrl); /* Write file and delete */
}
/* Create a RSPL of absolute deltaE '76 and relative deltaE '94 */
for (j = 0; j < devchan; j++) {
datai low,high;
int gres[MXDI];
double avgdev[MXDO];
co *dpoints_a;
co *dpoints_r;
double wh[3], prev[3], tot;
low[0] = 0.0;
high[0] = 1.0;
gres[0] = GRES;
avgdev[0] = 0.0;
if ((ade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
error("new_rspl() failed");
if (imitate) { /* Use pcade to hold target to imitate, just like a recal */
if ((pcade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
error("new_rspl() failed");
}
if ((rde[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
error("new_rspl() failed");
if ((dpoints_a = malloc(sizeof(co) * GRES)) == NULL)
error("malloc dpoints[%d] failed",GRES);
if ((dpoints_r = malloc(sizeof(co) * GRES)) == NULL)
error("malloc dpoints[%d] failed",GRES);
//printf("~1 Chan %d:\n",j);
for (i = 0; i < GRES; i++) {
co tp; /* Test point */
tp.p[0] = i/(double)(GRES-1);
raw[j]->interp(raw[j], &tp);
dpoints_a[i].p[0] = tp.p[0];
dpoints_r[i].p[0] = tp.p[0];
if (i == 0) {
//printf("~1 wht = %f %f %f\n",tp.v[0],tp.v[1],tp.v[2]);
tot = 0.0;
prev[0] = wh[0] = tp.v[0];
prev[1] = wh[1] = tp.v[1];
prev[2] = wh[2] = tp.v[2];
dpoints_a[i].v[0] = 0.0;
dpoints_r[i].v[0] = 0.0;
} else {
//printf("~1 samp %d = %f %f %f\n",i,tp.v[0],tp.v[1],tp.v[2]);
/* Use Euclidean for large DE: (CIE94 stuffs up here) */
dpoints_a[i].v[0] = icmLabDE(tp.v, wh);
/* And CIE94 for small: */
tot += icmCIE94(tp.v, prev);
prev[0] = tp.v[0];
prev[1] = tp.v[1];
prev[2] = tp.v[2];
dpoints_r[i].v[0] = tot;
}
//printf("~1 %d: dev %f, ade %f, rde %f\n",i,tp.p[0],dpoints_a[i].v[0],dpoints_r[i].v[0]);
}
ade[j]->set_rspl(ade[j],
0,
(void *)dpoints_a, /* Test points */
rsplset1, /* Setting function */
low, high, gres, /* Low, high, resolution of grid */
NULL, NULL /* Default data scale */
);
if (imitate) {
pcade[j]->set_rspl(pcade[j],
0,
(void *)dpoints_a, /* Test points */
rsplset1, /* Setting function */
low, high, gres, /* Low, high, resolution of grid */
NULL, NULL /* Default data scale */
);
}
rde[j]->set_rspl(rde[j],
0,
(void *)dpoints_r, /* Test points */
rsplset1, /* Setting function */
low, high, gres, /* Low, high, resolution of grid */
NULL, NULL /* Default data scale */
);
free(dpoints_a);
free(dpoints_r);
}
if (initial || imitate) {
/* Find the absolute maximum aDE value for each curve. */
/* This used for setting the max input range to dE762dE94, */
/* and mxdev is used for limiting the device max to the monotic portion of the ade repsonse */
for (j = 0; j < devchan; j++) {
co tp; /* Test point */
mxade[j] = -1e6;
for (i = 0; i < PRES; i++) {
tp.p[0] = i/(double)(PRES-1);
ade[j]->interp(ade[j], &tp);
if (tp.v[0] > mxade[j]) {
mxdev[j] = tp.p[0];
mxade[j] = tp.v[0];
}
}
if (verb > 1)
printf("Chan %d device value limit %f, aDE76 %f:\n",j,mxdev[j], mxade[j]);
}
}
if (initial) {
/* Establish the ademax calibration aim values */
pct->update_devmax(pct, -1, -1.0); /* Make sure there is a value for each */
pct->update_ademax(pct, -1, -1.0);
pct->update_ademin(pct, -1, -1.0);
for (j = 0; j < devchan; j++) {
co tp; /* Test point */
if (pct->devmax[j] < 0.0) { /* Auto */
double maxd, maxde, maxix;
/* Locate the point of maximum aDE */
for (maxde = -1.0, i = 0; i < GRES; i++) {
tp.p[0] = i/(GRES-1.0);
ade[j]->interp(ade[j], &tp);
if (tp.v[0] > maxde) {
maxd = tp.p[0];
maxde = tp.v[0];
maxix = i;
}
}
pct->devmax[j] = maxd;
pct->ademax[j] = maxde; /* Temporary */
//printf("Chan %d, dev %f, max de = %f\n", j, maxd, maxde);
if (maxd < 0.2) {
warning("Chan %d, max DE point %f is below < 0.2 - ignored\n", j, maxd);
maxix = GRES-1;
}
/* Then locate the point below that where the slope */
/* becomes reasonable. */
for (i = maxix; i >= 40; i--) {
double aslope, minslope = 1e6;
double naslope, nminslope;
int k;
/* Compute the minimum over a span of 20/GRES */
for (k = 0; k < 40; k++) {
double dp, dv, slope;
tp.p[0] = (i-k)/(GRES-1.0);
dp = tp.p[0];
ade[j]->interp(ade[j], &tp);
dv = tp.v[0];
tp.p[0] = (i-k-1)/(GRES-1.0);
ade[j]->interp(ade[j], &tp);
slope = (dv - tp.v[0])/(dp - (i-k-1)/(GRES-1.0));
if (k == 0)
aslope = slope;
//printf(" Chan %d, dev %f, dv = %f, slope = %f\n", j, (i-k)/(GRES-1.0),dv - tp.v[0],slope);
if (slope < minslope)
minslope = slope;
}
//printf("Chan %d, dev %f, aslope = %f, min slope = %f\n", j, i/(GRES-1.0),aslope,minslope);
/* Normalize the slopes */
naslope = aslope * SLOPE_NORM/pct->ademax[j];
nminslope = minslope * SLOPE_NORM/pct->ademax[j];
//printf("Chan %d, dev %f, norm aslope = %f, min slope = %f\n", j, i/(GRES-1.0),naslope,nminslope);
if (naslope > MIN_SLOPE_A && nminslope >= MIN_SLOPE_O)
break;
}
pct->devmax[j] = i/(GRES-1.0);
/* Scale auto max device value */
if (maxscale[j] >= 0.0)
pct->devmax[j] *= maxscale[j];
/* Manually set initial dev max */
} else {
if (maxscale[j] >= 0.0)
warning("Chan %d, scale %.1f%% of auto max ignored since max override used\n", j, maxscale[j] * 100.0);
}
/* Sanity check the aim devmax */
if (pct->devmax[j] > mxdev[j]) {
warning("Chan %d, intended device max %f is beyond monotonicity limit of %f, so will be clipped",j,pct->devmax[j],mxdev[j]);
pct->devmax[j] = mxdev[j];
}
/* Lookup devmax to set ademax */
tp.p[0] = pct->devmax[j];
ade[j]->interp(ade[j], &tp);
pct->ademax[j] = tp.v[0];
/* Establish a default ademin value */
if (pct->ademin[j] < 0.0)
pct->ademin[j] = 0.0;
}
}
if (initial || imitate) {
/* Create equivalence curves from absolute dE76 to relative dE94 for */
/* use in information display */
for (j = 0; j < devchan; j++) {
datai low, high;
int gres[1];
co tp, *dpoints;
low[0] = 0.0;
high[0] = mxade[j];
gres[0] = cal_res;
if ((dE762dE94[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
error("new_rspl() failed");
if ((dpoints = malloc(sizeof(co) * gres[0])) == NULL)
error("malloc dpoints[%d] failed",gres[0]);
/* Create points for our array */
for (i = 0; i < gres[0]; i++) {
dpoints[i].p[0] = high[0] * i/(double)(gres[0]-1);
/* adE76 -> device */
if ((tp.p[0] = rspl_ilookup(ade[j], 0.0, dpoints[i].p[0])) < 0.0)
error("Unexpected failure to invert ade curve %d for DE %f",j,dpoints[i].p[0]);
/* Device -> rdE94 */
rde[j]->interp(rde[j], &tp);
dpoints[i].v[0] = tp.v[0];
}
dE762dE94[j]->set_rspl(dE762dE94[j],
0,
(void *)dpoints, /* Read points */
rsplset1, /* Setting function */
low, high, gres, /* Low, high, resolution of grid */
NULL, NULL /* Default data scale */
);
free(dpoints);
}
} else if (recal) {
/* Since the plot markers use devmax, look it up */
for (j = 0; j < devchan; j++) {
if ((pct->devmax[j] = rspl_ilookup(ade[j], 0.5, pct->ademax[j])) < 0.0)
error("Unexpected failure to invert curve %d for ADE %f",j,pct->ademax[j]);
}
}
/* Plot the adE76 to rdE94 curves */
if (doplot >= 2) {
plot_g gg = { 0 };
co tp;
for (j = 0; j < devchan; j++) {
for (i = 0; i < PRES; i++) {
tp.p[0] = mxade[j] * i/(PRES-1.0);
dE762dE94[j]->interp(dE762dE94[j], &tp);
if (i == 0)
start_line_g(&gg, tp.p[0], tp.v[0], cal_plot_colors[j % MXGPHS]);
else
extend_line_g(&gg, tp.p[0], tp.v[0]);
}
}
printf("adE76 to rdE94 equivalence curve plot:\n");
do_plot_g(&gg, 0.0, 0.0, 0.0, 0.0, 1.0, 0, 1);
clear_g(&gg);
}
if ((initial || recal) && pct->devmaxset) {
/* Compute an ideal power-like value for test target */
for (j = 0; j < devchan; j++) {
double hdv; /* Half device value */
double hdvrde; /* Half device rDE value */
double thdvrde; /* Target half device rDE value */
double thdv; /* Target half device value */
double fdvrde; /* Full device rDE value */
co tp; /* Test point */
/* full rDE */
tp.p[0] = pct->devmax[j];
rde[j]->interp(rde[j], &tp);
fdvrde = tp.v[0];
/* Half device value of maximum */
hdv = pct->devmax[j] * 0.5;
/* rDE value half the device value */
tp.p[0] = hdv;
rde[j]->interp(rde[j], &tp);
hdvrde = tp.v[0];
/* rDE value we'd like at half the device value */
thdvrde = 0.5 * fdvrde;
/* Device value to get the rDE value we'd like at half */
if ((thdv = rspl_ilookup(rde[j], 0.5, thdvrde)) < 0.0)
error("Unexpected failure to invert curve %d for ADE %f",j,thdvrde);
//printf("hdv %f, hdvrde %f, thdvrde %f, fdvrde %f, thdv %f\n",hdv,hdvrde,thdvrde, fdvrde,thdv);
/* Power like value needed to get rDE value we'd like at hald device */
idpow[j] = icx_powlike_needed(hdv, thdv);
}
}
if (verb > 2) {
printf("Abs DE values:\n");
for (i = 0; i < PRES; i++) {
co tp; /* Test point */
tp.p[0]= i/(double)(PRES-1);
printf(" dev %f, aDE",tp.p[0]);
for (j = 0; j < 6 && j < devchan; j++) {
ade[j]->interp(ade[j], &tp);
printf(" %f",tp.v[0]);
}
printf("\n");
}
printf("Rel DE values:\n");
for (i = 0; i < PRES; i++) {
co tp; /* Test point */
tp.p[0]= i/(double)(PRES-1);
printf(" dev %f, rdev",tp.p[0]);
for (j = 0; j < 6 && j < devchan; j++) {
rde[j]->interp(rde[j], &tp);
printf(" %f",tp.v[0]);
}
printf("\n");
}
}
if (initial || recal) {
if (verb && pct->is_set(pct)) {
for (j = 0; j < devchan; j++) {
printf("Chan %d Dev max %f, aDE Max %f, aDE Min %f\n",j,pct->devmax[j],pct->ademax[j],pct->ademin[j]);
}
}
if (verb) {
double avgpow = 0.0;
if (idpow[0] >= 0.0) {
for (j = 0; j < devchan; j++) {
printf("Chan %d ideal targen power = %f\n",j,idpow[j]);
avgpow += idpow[j];
}
avgpow /= (double)devchan;
printf("Average ideal targen power = %f\n",avgpow);
} else {
printf("No devmax, so can't compute ideal targen power.\n");
}
}
}
/* Plot both the delta E curves, and markers */
if (doplot) {
co tp; /* Test point */
double xx[PRES];
double yy[MXGPHS][PRES];
double cx[MXGPHS], cy[MXGPHS];
int nmark;
printf("Absolute dE76 plot:\n");
for (i = 0; i < PRES; i++) {
xx[i] = i/(double)(PRES-1);
for (j = 0; j < 10 && j < devchan; j++) {
tp.p[0] = xx[i];
ade[j]->interp(ade[j], &tp);
yy[j][i] = tp.v[0];
}
}
nmark = 0;
if (pct->is_set(pct)) {
/* Add markers for deMax */
for (j = 0; j < 10 && j < devchan; j++) {
cx[j] = pct->devmax[j];
cy[j] = pct->ademax[j];
nmark++;
}
}
do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
devchan > 1 ? yy[1] : NULL,
devchan > 4 ? yy[4] : NULL,
devchan > 0 ? yy[0] : NULL,
devchan > 2 ? yy[2] : NULL,
devchan > 5 ? yy[5] : NULL,
devchan > 6 ? yy[6] : NULL,
devchan > 7 ? yy[7] : NULL,
devchan > 8 ? yy[8] : NULL,
devchan > 9 ? yy[9] : NULL,
PRES,
cx, cy, verify ? 0 : nmark);
printf("Relative dE94 plot:\n");
for (i = 0; i < PRES; i++) {
xx[i] = i/(double)(PRES-1.0);
for (j = 0; j < 10 && j < devchan; j++) {
tp.p[0] = xx[i];
rde[j]->interp(rde[j], &tp);
yy[j][i] = tp.v[0];
}
}
nmark = 0;
if (pct->is_set(pct)) {
/* Add markers for deMax */
for (j = 0; j < 10 && j < devchan; j++) {
cx[j] = pct->devmax[j];
tp.p[0] = cx[j];
rde[j]->interp(rde[j], &tp);
cy[j] = tp.v[0];
nmark++;
}
}
do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
devchan > 1 ? yy[1] : NULL,
devchan > 4 ? yy[4] : NULL,
devchan > 0 ? yy[0] : NULL,
devchan > 2 ? yy[2] : NULL,
devchan > 5 ? yy[5] : NULL,
devchan > 6 ? yy[6] : NULL,
devchan > 7 ? yy[7] : NULL,
devchan > 8 ? yy[8] : NULL,
devchan > 9 ? yy[9] : NULL,
PRES,
cx, cy, verify ? 0 : nmark);
if (doplot >= 2 && idpow[0] > 0.0) {
printf("Relative dE94 plot with ideal targen power applied:\n");
for (i = 0; i < PRES; i++) {
xx[i] = i/(double)(PRES-1.0);
for (j = 0; j < 10 && j < devchan; j++) {
tp.p[0] = icx_powlike(xx[i],idpow[j]);
rde[j]->interp(rde[j], &tp);
yy[j][i] = tp.v[0];
}
}
nmark = 0;
if (pct->is_set(pct)) {
/* Add markers for deMax */
for (j = 0; j < 10 && j < devchan; j++) {
cx[j] = pct->devmax[j];
tp.p[0] = icx_powlike(cx[j],idpow[j]);
rde[j]->interp(rde[j], &tp);
cy[j] = tp.v[0];
nmark++;
}
}
do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
devchan > 1 ? yy[1] : NULL,
devchan > 4 ? yy[4] : NULL,
devchan > 0 ? yy[0] : NULL,
devchan > 2 ? yy[2] : NULL,
devchan > 5 ? yy[5] : NULL,
devchan > 6 ? yy[6] : NULL,
devchan > 7 ? yy[7] : NULL,
devchan > 8 ? yy[8] : NULL,
devchan > 9 ? yy[9] : NULL,
PRES,
cx, cy, verify ? 0 : nmark);
}
}
/* Compare the previous expected aDE against the current one */
if (verify) {
co tp; /* Test point */
double avg[MAX_CHAN];
double max[MAX_CHAN];
double rms[MAX_CHAN];
/* Verify each channel */
for (j = 0; j < devchan; j++) {
co tp;
avg[j] = 0.0;
max[j] = -1.0;
rms[j] = 0.0;
/* Sample it at GRES */
for (i = 0; i < GRES; i++) {
double iv, targ, val, tt;
iv = i/(GRES-1.0);
/* Lookup the ade that we expect */
tp.p[0] = iv;
pcade[j]->interp(pcade[j], &tp);
targ = tp.v[0];
/* Lookup the ade that we have */
tp.p[0] = iv;
ade[j]->interp(ade[j], &tp);
val = tp.v[0];
val += pct->ademin[j];
/* Compute the stats */
tt = fabs(targ - val);
avg[j] += tt;
rms[j] += tt * tt;
if (tt > max[j])
max[j] = tt;
//printf("~1 chan %d, ix %d, iv %f, targ %f, actual %f, err %f\n",j,i,iv,targ,val,tt);
}
avg[j] /= (double)GRES;
rms[j] /= (double)GRES;
rms[j] = sqrt(rms[j]);
if (max[j] > ver_maxde)
verified = 0;
}
if (verb) {
for (j = 0; j < devchan; j++) {
printf("Verify results:\n");
printf("Channel %d has DE avg %.1f, rms %.1f, max %.1f\n",j,avg[j],rms[j],max[j]);
}
if (verified)
printf("Verified OK\n");
else
printf("Verification FAILED\n");
}
/* Plot the verification curves */
if (doplot) {
double xx[PRES];
double yy[MXGPHS][PRES];
printf("Verification match plot:\n");
for (j = 0; j < 10 && j < devchan; j++) {
co tp; /* Test point */
double max;
/* Establish the scale */
tp.p[0] = 1.0;
pcade[j]->interp(pcade[j], &tp);
max = tp.v[0] - pct->ademin[j];;
for (i = 0; i < PRES; i++) {
xx[i] = i/(double)(PRES-1);
/* Convert ade target to original device */
tp.v[0] = max * xx[i] + pct->ademin[j];
if ((tp.p[0] = rspl_ilookup(pcade[j], 1.0, tp.v[0])) < 0.0)
error("Unexpected failure to invert curve %d for pcADE %f",j,tp.v[0]);
/* Convert device to current device ade */
ade[j]->interp(ade[j], &tp);
if (fabs(max) > 0.1)
yy[j][i] = tp.v[0]/max;
else
yy[j][i] = 0.0;
}
}
do_plot10(xx, devchan > 3 ? yy[3] : NULL,
devchan > 1 ? yy[1] : NULL,
devchan > 4 ? yy[4] : NULL,
devchan > 0 ? yy[0] : NULL,
devchan > 2 ? yy[2] : NULL,
devchan > 5 ? yy[5] : NULL,
devchan > 6 ? yy[6] : NULL,
devchan > 7 ? yy[7] : NULL,
devchan > 8 ? yy[8] : NULL,
devchan > 9 ? yy[9] : NULL,
PRES, 0);
}
/* End verify */
} else if (initial) {
/* Convert any transfer curve target points into a smooth curve */
if (pct->no_tpoints > 0) {
int gres[MXDI] = { GRES };
co *pnts;
if ((pnts = (co *)calloc(pct->no_tpoints + 2, sizeof(co))) == NULL)
error ("Malloc of rspl points failed");
for (j = 0; j < devchan; j++) {
int npts;
int gotmin, gotmax;
/* Count the number of valid points */
for (npts = i = 0; i < pct->no_tpoints; i++) {
if (pct->tpoints[i].val[j] >= 0.0)
npts++;
}
if (npts == 0)
continue; /* No target curve for this channel */
if ((tcurves[j] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL)
error("new_rspl(1,1) failed");
gotmin = gotmax = 0;
for (npts = i = 0; i < pct->no_tpoints; i++) {
if (pct->tpoints[i].val[j] < 0.0)
continue;
pnts[npts].p[0] = pct->tpoints[i].loc;
pnts[npts].v[0] = pct->tpoints[i].val[j];
if (pnts[npts].p[0] < 0.0)
pnts[npts].p[0] = 0.0;
else if (pnts[npts].p[0] > 1.0)
pnts[npts].p[0] = 1.0;
if (pnts[npts].v[0] < 0.0)
pnts[npts].v[0] = 0.0;
else if (pnts[npts].v[0] > 1.0)
pnts[npts].v[0] = 1.0;
if (pnts[npts].p[0] <= 0.05)
gotmin = 1;
if (pnts[npts].p[0] >= 0.95)
gotmax = 1;
npts++;
}
/* Add default anchors if there are none supplied */
if (gotmin == 0) {
pnts[npts].p[0] = 0.0;
pnts[npts++].v[0] = 0.0;
}
if (gotmax == 0) {
pnts[npts].p[0] = 1.0;
pnts[npts++].v[0] = 1.0;
}
/* Fit the curve to the given points */
tcurves[j]->fit_rspl(tcurves[j], RSPLFLAGS, pnts, npts,
NULL, NULL, gres, NULL, NULL, TCURVESMOOTH, NULL, NULL);
}
free(pnts);
/* Plot the target curves */
if (doplot) {
double xx[PRES];
double yy[MXGPHS][PRES];
printf("Relative dE94 target curves plot:\n");
for (i = 0; i < PRES; i++) {
co tp; /* Test point */
double pp = i/(PRES-1.0);
xx[i] = pp;
for (j = 0; j < 10 && j < devchan; j++) {
if (tcurves[j] != NULL) {
tp.p[0] = pp;
tcurves[j]->interp(tcurves[j], &tp);
yy[j][i] = tp.v[0];
} else
yy[j][i] = pp;
}
}
do_plot10(xx, devchan > 3 ? yy[3] : NULL,
devchan > 1 ? yy[1] : NULL,
devchan > 4 ? yy[4] : NULL,
devchan > 0 ? yy[0] : NULL,
devchan > 2 ? yy[2] : NULL,
devchan > 5 ? yy[5] : NULL,
devchan > 6 ? yy[6] : NULL,
devchan > 7 ? yy[7] : NULL,
devchan > 8 ? yy[8] : NULL,
devchan > 9 ? yy[9] : NULL,
PRES, 0);
}
}
/* Do inverse lookup to create relative dE94 linearization curves */
n_cvals = cal_res;
for (j = 0; j < devchan; j++) {
co tp;
double rdemax, rdemin; /* Relative DE max and min target */
/* Convert absolute dE max aim to relative dE aim */
if ((rdemax = rspl_ilookup(ade[j], 0.0, pct->ademax[j])) < 0.0)
error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]);
tp.p[0] = rdemax;
rde[j]->interp(rde[j], &tp);
rdemax = tp.v[0];
/* Convert absolute dE min aim to relative dE aim */
if ((rdemin = rspl_ilookup(ade[j], 1.0, pct->ademin[j])) < 0.0)
error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]);
tp.p[0] = rdemin;
rde[j]->interp(rde[j], &tp);
rdemin = tp.v[0];
if (verb > 0)
printf("Chan %d: rDE Max = %f, rDE Min = %f\n",j,rdemax,rdemin);
if ((cvals[j] = (wval *)malloc(sizeof(wval) * n_cvals)) == NULL)
error("Malloc of %d cvals failed",n_cvals);
/* Convert relative delta E aim to device value */
for (i = 0; i < n_cvals; i++) {
double x = i/(n_cvals-1.0);
double inv;
cvals[j][i].inv = x;
/* Apply any aim tweak curve */
if (tcurves[j] != NULL) {
tp.p[0] = x;
tcurves[j]->interp(tcurves[j], &tp);
x = tp.v[0];
}
inv = x * (rdemax -rdemin) + rdemin;
if ((cvals[j][i].dev = rspl_ilookup(rde[j], 0.5, inv)) < 0.0)
error("Unexpected failure to invert curve %d for DE %f",j,inv);
//printf("~1 chan %d, step %d, inv %f, detarg %f, got dev %f\n",j,i,x,pp[0].v[0],pp[k].p[0]);
}
}
/* End initial */
} else if (recal || imitate) {
/* Lookup the expected adE76 for each input device value, and */
/* then do inverse lookup in measured adE76 to create calibration */
/* to previous/imitation adE76 target */
n_cvals = cal_res;
for (j = 0; j < devchan; j++) {
co tp;
if ((cvals[j] = (wval *)malloc(sizeof(wval) * n_cvals)) == NULL)
error("Malloc of %d cvals failed",n_cvals);
for (i = 0; i < n_cvals; i++) {
double x = i/(n_cvals-1.0);
cvals[j][i].inv = tp.p[0] = x;
pcade[j]->interp(pcade[j], &tp);
if ((cvals[j][i].dev = rspl_ilookup(ade[j], 0.5, tp.v[0])) < 0.0)
error("Unexpected failure to invert curve %d for DE %f",j,tp.v[0]);
//printf("~1 chan %d, ix %d, inv %f, pcade %f, iade %f\n",j,i,x,tp.v[0],cvals[j][i].dev);
}
}
/* End recal or imitate */
}
if (initial || recal || imitate) {
if (verb > 2) {
printf("Calibration curve values:\n");
for (i = 0; i < n_cvals; i++) {
printf(" inv %f, dev",cvals[0][i].inv);
for (j = 0; j < devchan; j++) {
printf(" %f",cvals[j][i].dev);
}
printf("\n");
}
}
/* Plot the calibration curves */
if (doplot) {
double xx[PRES];
double yy[MXGPHS][PRES];
printf("Calibration curve plot:\n");
for (i = 0; i < PRES; i++) {
xx[i] = i/(PRES-1.0);
for (j = 0; j < 10 && j < devchan; j++) {
yy[j][i] = interp_cal(cvals[j], n_cvals, xx[i]);
}
}
do_plot10(xx, devchan > 3 ? yy[3] : NULL, /* Black */
devchan > 1 ? yy[1] : NULL, /* Red */
devchan > 4 ? yy[4] : NULL, /* Green */
devchan > 0 ? yy[0] : NULL, /* Blue */
devchan > 2 ? yy[2] : NULL, /* Yellow */
devchan > 5 ? yy[5] : NULL, /* Purple */
devchan > 6 ? yy[6] : NULL, /* Brown */
devchan > 7 ? yy[7] : NULL, /* Orange */
devchan > 8 ? yy[8] : NULL, /* Grey */
devchan > 9 ? yy[9] : NULL, /* White */
PRES, 0);
}
}
/* Compute and plot the target and expected relative dE94 curves */
if (doplot
) {
double xx1[PRES];
double yy1[MAX_CHAN][PRES];
double xx2[PRES];
double yy2[MAX_CHAN][PRES];
/* Compute the target calibrated relative deltaE '94 */
if (initial) {
/* Target is linear rdE with tcurves tweaks amd scaled by min & max. */
for (j = 0; j < devchan; j++) {
co tp;
double rdemin, rdemax; /* Relative DE min and max targets */
/* Convert absolute dE max aim to relative dE aim */
if ((rdemax = rspl_ilookup(ade[j], 0.0, pct->ademax[j])) < 0.0)
error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]);
tp.p[0] = rdemax;
rde[j]->interp(rde[j], &tp);
rdemax = tp.v[0];
if ((rdemin = rspl_ilookup(ade[j], 1.0, pct->ademin[j])) < 0.0)
error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]);
tp.p[0] = rdemin;
rde[j]->interp(rde[j], &tp);
rdemin = tp.v[0];
/* Convert relative delta E aim to device value */
for (i = 0; i < PRES; i++) {
double x = i/(PRES-1.0);
double inv;
xx1[i] = x;
/* Apply any aim tweak curve */
if (tcurves[j] != NULL) {
tp.p[0] = x;
tcurves[j]->interp(tcurves[j], &tp);
x = tp.v[0];
}
yy1[j][i] = x * (rdemax - rdemin) + rdemin;
}
}
} else {
/* Equivalent rdE94 target is pcade[] converted through dE762dE94 */
for (j = 0; j < devchan; j++) {
co tp;
for (i = 0; i < PRES; i++) {
double x = i/(PRES-1.0);
xx1[i] = x;
tp.p[0] = x;
pcade[j]->interp(pcade[j], &tp);
tp.p[0] = tp.v[0];
dE762dE94[j]->interp(dE762dE94[j], &tp);
yy1[j][i] = tp.v[0];
}
}
}
/* Plot the expected calibrated relative deltaE '94 */
if (initial) {
/* Compute rdE94 of the calibrated response from this measurement */
for (j = 0; j < devchan; j++) {
double prev[3], tot = 0.0;
for (i = 0; i < PRES; i++) {
double x = i/(PRES-1.0);
co tp; /* Test point */
xx2[i] = x;
tp.p[0] = interp_cal(cvals[j], n_cvals, x); /* Calibrated device value */
raw[j]->interp(raw[j], &tp); /* Expected L*b*a response */
if (i == 0) {
prev[0] = tp.v[0];
prev[1] = tp.v[1];
prev[2] = tp.v[2];
if (tp.p[0] > 0) { /* Hmm. */
rde[j]->interp(rde[j], &tp); /* Initial relative dE94 */
tot = tp.v[0];
}
} else {
tot += icmCIE94(tp.v, prev);
prev[0] = tp.v[0];
prev[1] = tp.v[1];
prev[2] = tp.v[2];
}
yy2[j][i] = tot;
}
}
/* Comute the adE76 of the raw response from this measurement, */
/* and convert this to the equivalent rdE94 */
} else { /* recal, verify or imitate */
/* Comute the adE76 of the calibrated response from this measurement, */
/* and convert this to the equivalent rdE94 */
for (j = 0; j < devchan; j++) {
double wh[3];
for (i = 0; i < PRES; i++) {
double x = i/(PRES-1.0);
co tp; /* Test point */
xx2[i] = x;
if (verify) /* raw==pre-calibrated device value */
tp.p[0] = x;
else /* Lookup calibrated device value */
tp.p[0] = interp_cal(cvals[j], n_cvals, x);
raw[j]->interp(raw[j], &tp); /* Expected L*a*b* response */
if (i == 0) {
wh[0] = tp.v[0];
wh[1] = tp.v[1];
wh[2] = tp.v[2];
}
tp.p[0] = icmLabDE(tp.v, wh); /* adE76 */
tp.p[0] += pct->ademin[j];
dE762dE94[j]->interp(dE762dE94[j], &tp);
yy2[j][i] = tp.v[0];
}
}
}
if (doplot) {
plot_g gg = { 0 };
for (j = 0; j < devchan; j++) {
float rgb[3];
/* Make targets red, so they show when not covered by expected */
rgb[0] = 1.0;
rgb[1] = 0.0;
rgb[2] = 0.0;
if ((j % MXGPHS) == 1 /* Except redish colors */
|| (j % MXGPHS) == 6
|| (j % MXGPHS) == 7
|| (j % MXGPHS) == 9
|| (j % MXGPHS) == 11
|| (j % MXGPHS) == 13
|| (j % MXGPHS) == 15) {
rgb[0] = 0.0; /* where we make it blue */
rgb[1] = 0.0;
rgb[2] = 1.0;
}
for (i = 0; i < PRES; i++) {
if (i == 0)
start_line_g(&gg, xx1[i], yy1[j][i], rgb);
else
extend_line_g(&gg, xx1[i], yy1[j][i]);
}
for (i = 0; i < PRES; i++) {
if (i == 0)
start_line_g(&gg, xx2[i], yy2[j][i], cal_plot_colors[j % MXGPHS]);
else
extend_line_g(&gg, xx2[i], yy2[j][i]);
}
}
printf("Relative dE94 Target vs. Expected plots:\n");
do_plot_g(&gg, 0.0, 0.0, 0.0, 0.0, 1.0, 0, 1);
clear_g(&gg);
}
}
if (initial || recal || imitate) {
/* Create 3D plot of calibrated colorant loci */
if (do3dplot) {
char vname[MAXNAMEL+3] = "";
vrml *wrl;
int doaxes = 0;
int devres = 21;
strcpy(vname, baname);
strcat(vname, "_c");
if ((wrl = new_vrml(vname, doaxes, vrml_lab)) == NULL)
error("new_vrml failed for '%s%s'",vname,vrml_ext());
for (j = 0; j < devchan; j++) {
double last[3];
for (i = 0; i < devres; i++) {
co tp; /* Test point */
double x;
x = i/(double)(devres-1);
tp.p[0] = interp_cal(cvals[j], n_cvals, x);
raw[j]->interp(raw[j], &tp);
if (i > 0) {
wrl->add_cone(wrl, last, tp.v, NULL, 20/(devres-1.0));
}
icmCpy3(last, tp.v);
}
}
wrl->del(wrl); /* Write file and delete */
}
/* Write out an Argyll .CAL file */
if (dowrite) {
cgats *ocg; /* output cgats structure */
time_t clk = time(0);
struct tm *tsp = localtime(&clk);
char *atm = asctime(tsp); /* Ascii time */
char *ident = icx_inkmask2char(devmask, 1);
char *bident = icx_inkmask2char(devmask, 0);
cgats_set_elem *setel; /* Array of set value elements */
int nsetel = 0;
int ncps; /* Number of curve parameters */
double *cps[3]; /* Arrays of curve parameters */
char *bp = NULL, buf[100]; /* Buffer to sprintf into */
co tp;
ocg = new_cgats(); /* Create a CGATS structure */
ocg->add_other(ocg, "CAL"); /* our special type is Calibration file */
ocg->add_table(ocg, tt_other, 0); /* Add a table for RAMDAC values */
ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Device Calibration Curves",NULL);
ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll printcal", NULL);
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
ocg->add_kword(ocg, 0, "COLOR_REP", ident, NULL);
if (xpi.deviceMfgDesc != NULL)
ocg->add_kword(ocg, 0, "MANUFACTURER", xpi.deviceMfgDesc, NULL);
if (xpi.modelDesc != NULL)
ocg->add_kword(ocg, 0, "MODEL", xpi.modelDesc, NULL);
if (xpi.profDesc != NULL)
ocg->add_kword(ocg, 0, "DESCRIPTION", xpi.profDesc, NULL);
if (xpi.copyright != NULL)
ocg->add_kword(ocg, 0, "COPYRIGHT", xpi.copyright, NULL);
/* Setup the table which holds the translation from calibrated */
/* device value "I" to the raw device channel value */
sprintf(buf, "%s_I",bident);
ocg->add_field(ocg, 0, buf, r_t);
nsetel++;
for (j = 0; j < devchan; j++) {
inkmask imask = icx_index2ink(devmask, j);
sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
ocg->add_field(ocg, 0, buf, r_t);
nsetel++;
}
if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
error("Malloc failed!");
/* Write the per channel device to device loolup curve values */
if (devmask & ICX_ADDITIVE) {
for (i = n_cvals-1; i >= 0; i--) {
setel[0].d = 1.0 - cvals[0][i].inv;
for (j = 0; j < devchan; j++)
setel[1+j].d = 1.0 - cvals[j][i].dev;
ocg->add_setarr(ocg, 0, setel);
}
} else {
for (i = 0; i < n_cvals; i++) {
setel[0].d = cvals[0][i].inv;
for (j = 0; j < devchan; j++)
setel[1+j].d = cvals[j][i].dev;
ocg->add_setarr(ocg, 0, setel);
}
}
free(setel);
/* Add a second table with the adE76 calibration target information. */
if (pct->write(pct, ocg, 1) != 0)
error("Writing cal target info to cal file '%s'",outname);
/* Add a third table which is the expected absolute DE response */
/* of the calibrated device. */
ocg->add_table(ocg, tt_other, 0); /* Add a table for RAMDAC values */
ocg->add_kword(ocg, 2, "DESCRIPTOR", "Argyll Output Calibration Expected Absolute DE Response",NULL);
ocg->add_kword(ocg, 2, "ORIGINATOR", "Argyll printcal", NULL);
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
ocg->add_kword(ocg, 2, "CREATED",atm, NULL);
ocg->add_kword(ocg, 2, "DEVICE_CLASS","OUTPUT", NULL);
ocg->add_kword(ocg, 2, "COLOR_REP", ident, NULL);
/* Setup the table which holds the translation from calibrated */
/* device value "I" to the expected absolute deltaE value for */
/* each colorant. */
sprintf(buf, "%s_I_DE",bident);
ocg->add_field(ocg, 2, buf, r_t);
nsetel++;
for (j = 0; j < devchan; j++) {
inkmask imask = icx_index2ink(devmask, j);
sprintf(buf, "%s_%s_DE",bident,icx_ink2char(imask));
ocg->add_field(ocg, 2, buf, r_t);
nsetel++;
}
if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
error("Malloc failed!");
if (initial) {
/* Compute expected calibration adE76 curves */
for (i = 0; i < n_cvals; i++) {
int ix = i;
/* Calibrated device value */
if (devmask & ICX_ADDITIVE) {
ix = n_cvals -1 - i;
setel[0].d = 1.0 - cvals[0][ix].inv;
} else
setel[0].d = cvals[0][ix].inv;
for (j = 0; j < devchan; j++) {
tp.p[0] = cvals[j][ix].dev; /* Calibrated device value */
ade[j]->interp(ade[j], &tp); /* Corresponding ade value */
setel[1+j].d = tp.v[0];
}
ocg->add_setarr(ocg, 2, setel);
}
} else {
/* Copy out original expected calibration adE76 curves pcade, for consistency.. */
for (i = 0; i < n_cvals; i++) {
int ix = i;
double inv = i/(n_cvals-1.0);
setel[0].d = inv;
for (j = 0; j < devchan; j++) {
tp.p[0] = inv;
pcade[j]->interp(pcade[j], &tp); /* Corresponding ade value */
setel[1+j].d = tp.v[0];
}
ocg->add_setarr(ocg, 2, setel);
}
}
free(setel);
if (dE762dE94[0] != NULL) {
double inmax[MAX_CHAN];
/* Add a fourth table which is the original adE76 to rdE94 equivalence */
/* of the calibrated device. */
ocg->add_other(ocg, "CALDEEQ"); /* our special type is calibration dE equivalence */
ocg->add_table(ocg, tt_other, 1); /* Add a table for values */
ocg->add_kword(ocg, 3, "DESCRIPTOR", "Argyll Output Calibration adE76 to rdE94 equivalence",NULL);
ocg->add_kword(ocg, 3, "ORIGINATOR", "Argyll printcal", NULL);
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
ocg->add_kword(ocg, 3, "CREATED",atm, NULL);
ocg->add_kword(ocg, 3, "DEVICE_CLASS","OUTPUT", NULL);
ocg->add_kword(ocg, 3, "COLOR_REP", ident, NULL);
ocg->add_field(ocg, 3, "ID", i_t);
nsetel++;
for (j = 0; j < devchan; j++) {
inkmask imask = icx_index2ink(devmask, j);
sprintf(buf, "%s_%s_ADE",bident,icx_ink2char(imask));
ocg->add_field(ocg, 3, buf, r_t);
nsetel++;
sprintf(buf, "%s_%s_RDE",bident,icx_ink2char(imask));
ocg->add_field(ocg, 3, buf, r_t);
nsetel++;
}
if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
error("Malloc failed!");
for (i = 0; i < n_cvals; i++) {
int ix = i;
setel[0].i = i+1;
for (j = 0; j < devchan; j++) {
double ival = mxade[j] * i/(n_cvals-1.0);
/* adE76 value */
setel[1 + 2 * j + 0].d = ival;
tp.p[0] = ival;
dE762dE94[j]->interp(dE762dE94[j], &tp); /* Corresponding rdE94 value */
setel[1 + 2 * j + 1].d = tp.v[0];
}
ocg->add_setarr(ocg, 3, setel);
}
free(setel);
}
if (ocg->write_name(ocg, outname))
error("Write error to file '%s': %s",outname,ocg->e.m);
if (verb)
printf("Written calibration file '%s'\n",outname);
ocg->del(ocg); /* Clean up */
free(ident);
free(bident);
}
/*
The structure of *.AMP is very simple.
It has 5 tables that have
256 entries that are 8-bit (byte), even for 16 bit mode.
The first table 0000..00FFh is the CMYK channel.
The second table 0100..01FFh is the C channel.
The third table 0200..02FFh is the M channel.
The fourth table 0300..03FFh is the Y channel.
The fifth table 0300..03FFh is the K channel.
Table position nn00h is the black end and table position nnFFh
is the white end.
*/
/* Write an Adobe map format (.AMP) file */
/* (It's not clear if more than 4 channels is allowed) */
if (dowrite && doamp) {
FILE *fp;
cgatsFile *p;
char nmode[50] = { '\000' };
strcpy(nmode, "w");
#if defined(O_BINARY) || defined(_O_BINARY)
strcat(nmode, "b");
#endif
if ((fp = fopen(ampname, nmode)) == NULL)
error("Couldn't open '%s' for writing",ampname);
/* CMYK table is unity */
for (i = 0; i < 256; i++) {
if (putc(i,fp) == EOF)
error("Error writing to fle '%s'",ampname);
}
for (j = 0; j < devchan; j++) {
for (i = 0; i < 256; i++) {
int x;
if (devmask & ICX_ADDITIVE)
x = (int)(cvals[j][i].dev * 255.0 + 0.5); /* ??? */
else
x = 255 - (int)(cvals[j][255 - i].dev * 255.0 + 0.5);
if (putc(x,fp) == EOF)
error("Error writing to fle '%s'",ampname);
//printf("~1 chan %d, inv %d, dev %d\n",j,i,x);
}
}
/* Extra 1:1 table */
for (i = 0; i < 256; i++) {
if (putc(i,fp) == EOF)
error("Error writing to fle '%s'",ampname);
}
if (fclose(fp) != 0)
error("Closing '%s' failed",ampname);
if (verb)
printf("Written calibration curves to '%s'\n",ampname);
}
}
/* Free up various possible allocations */
for (j = 0; j < devchan; j++) {
if (pvals[j] != NULL)
free(pvals[j]);
if (raw[j] != NULL)
raw[j]->del(raw[j]);
if (ade[j] != NULL)
ade[j]->del(ade[j]);
if (rde[j] != NULL)
rde[j]->del(rde[j]);
if (dE762dE94[j] != NULL)
dE762dE94[j]->del(dE762dE94[j]);
if (pcade[j] != NULL)
pcade[j]->del(pcade[j]);
if (cvals[j] != NULL)
free(cvals[j]);
if (tcurves[j] != NULL)
tcurves[j]->del(tcurves[j]);
}
if (!verified)
return 1;
return 0;
}