2916 lines
83 KiB
C
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|