summaryrefslogtreecommitdiff
path: root/spectro/fakeread.c
diff options
context:
space:
mode:
Diffstat (limited to 'spectro/fakeread.c')
-rw-r--r--spectro/fakeread.c1025
1 files changed, 1025 insertions, 0 deletions
diff --git a/spectro/fakeread.c b/spectro/fakeread.c
new file mode 100644
index 0000000..df77930
--- /dev/null
+++ b/spectro/fakeread.c
@@ -0,0 +1,1025 @@
+/*
+ * Argyll Color Correction System
+ * Fake print target chart reader - use ICC or MPP profile rather than instrument
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/2/2002
+ *
+ * Copyright 2002 - 2013 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.
+ */
+
+/*
+ * TTBD:
+
+ Do we need to deterct & mark display values normalized to Y = 100 ??
+ */
+
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "icc.h"
+
+void usage(char *diag, ...) {
+ fprintf(stderr,"Fake test chart reader - lookup values in ICC/MPP profile, 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: fakeread [-v] [-s] [separation.icm] profile.[%s|mpp|ti3] outfile\n",ICC_FILE_EXT_ND);
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -s Lookup MPP spectral values\n");
+ fprintf(stderr," -p Use separation profile\n");
+ fprintf(stderr," -l Output Lab rather than XYZ\n");
+ fprintf(stderr," -k file.cal Apply calibration (after sep.) and include in .ti3\n");
+ fprintf(stderr," -i file.cal Include calibration in .ti3 (but don't apply it)\n");
+ fprintf(stderr," -r level Add average random deviation of <level>%% to input device values (after sep. & cal.)\n");
+ fprintf(stderr," -0 pow Apply power to input device chanel 0-9 (after sep. cal. & rand)\n");
+ fprintf(stderr," -R level Add average random deviation of <level>%% to output PCS values\n");
+ fprintf(stderr," -u Make random deviations have uniform distributions rather than normal\n");
+ fprintf(stderr," -S seed Set random seed\n");
+ fprintf(stderr," -b L,a,b Scale black point to target Lab value\n");
+ fprintf(stderr," -I intent r = relative colorimetric, a = absolute (default)\n");
+ fprintf(stderr," [separation.%s] Device link separation profile\n",ICC_FILE_EXT_ND);
+ fprintf(stderr," profile.[%s|mpp|ti3] ICC, MPP profile or TI3 to use\n",ICC_FILE_EXT_ND);
+ fprintf(stderr," outfile Base name for input[ti1]/output[ti3] file\n");
+ exit(1);
+ }
+
+int main(int argc, char *argv[])
+{
+ int j;
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0; /* Verbose flag */
+ int dosep = 0; /* Use separation before profile */
+ int dolab = 0; /* Output Lab rather than XYZ */
+ int gfudge = 0; /* Do grey fudge, 1 = W->RGB, 2 = K->xxxK */
+ double chpow[10] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
+ double rdlevel = 0.0; /* Random device average deviation level (0.0 - 1.0) */
+ double rplevel = 0.0; /* Random PCS average deviation level (0.0 - 1.0) */
+ int unidist = 0; /* Use uniform distribution of errors */
+ unsigned int seed = time(NULL); /* Random seed value */
+ double tbp[3] = { -1.0, 0.0, 0.0 }; /* Target black point */
+ int applycal = 0; /* NZ to apply calibration */
+ static char sepname[MAXNAMEL+1] = { 0 }; /* ICC separation profile */
+ static char calname[MAXNAMEL+1] = { 0 }; /* device calibration */
+ static char profname[MAXNAMEL+1] = { 0 }; /* ICC or MPP Profile name */
+ static char inname[MAXNAMEL+1] = { 0 }; /* Input cgats file base name */
+ static char outname[MAXNAMEL+1] = { 0 }; /* Output cgats file base name */
+ cgats *icg; /* input cgats structure */
+ cgats *ocg; /* output cgats structure */
+ int nmask = 0; /* Test chart device colorant mask */
+ int nchan = 0; /* Test chart number of device chanels */
+ int npat; /* Number of patches */
+ int si; /* Sample id index */
+ int ti; /* Temp index */
+ int fi; /* Colorspace index */
+ int inti3 = 0; /* Input is a renamed .ti3 file rather than .ti1 */
+
+ /* ICC separation device link profile */
+ icmFile *sep_fp = NULL; /* Color profile file */
+ icc *sep_icco = NULL; /* Profile object */
+ icmLuBase *sep_luo = NULL; /* Conversion object */
+ icColorSpaceSignature sep_ins, sep_outs; /* Type of input and output spaces */
+ int sep_inn; /* Number of input channels to separation */
+ inkmask sep_nmask = 0; /* Colorant mask for separation input */
+ double wp[3], bp[3]; /* ICC profile Lab white and black points */
+ double bpt[3][3]; /* Black point transform matrix (Lab->Lab) */
+
+ /* Calibration */
+ xcal *cal = NULL; /* calibration */
+
+ /* ICC profile based */
+ icRenderingIntent intent = icAbsoluteColorimetric;
+ icmFile *icc_fp = NULL; /* Color profile file */
+ icc *icc_icco = NULL; /* Profile object */
+ icmLuBase *icc_luo = NULL; /* Conversion object */
+
+ /* MPP profile based */
+ mpp *mlu = NULL; /* Conversion object */
+ instType itype = instUnknown; /* Type of instrument used */
+ int dospec = 0; /* Do spectral MPP lookup */
+ int spec_n = 0; /* Number of spectral bands */
+ double spec_wl_short;/* First reading wavelength in nm (shortest) */
+ double spec_wl_long; /* Last reading wavelength in nm (longest) */
+
+ /* TI3 based fake read */
+ cgats *ti3 = NULL; /* input cgats structure */
+ int ti3_npat = 0; /* Number of patches in reference file */
+ int ti3_chix[ICX_MXINKS]; /* Device chanel indexes */
+ int ti3_pcsix[3]; /* Device chanel indexes */
+ int ti3_spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ int ti3_isLab = 0; /* Flag indicating PCS for TI3 file */
+
+ int rv = 0;
+ int inn, outn; /* Number of channels for conversion input, output */
+ icColorSpaceSignature ins, outs; /* Type of conversion input and output spaces */
+ inkmask cnv_nmask = 0; /* Conversion input nmask */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
+ char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" };
+
+ error_program = "Fakeread";
+ if (argc < 3)
+ usage("Too few arguments");
+
+ /* Process the 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) < 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");
+
+ /* Verbose */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ /* Spectral MPP lookup */
+ else if (argv[fa][1] == 's')
+ dospec = 1;
+
+ /* Separation */
+ else if (argv[fa][1] == 'p' || argv[fa][1] == 'P')
+ dosep = 1;
+
+ /* Lab */
+ else if (argv[fa][1] == 'l' || argv[fa][1] == 'L')
+ dolab = 1;
+
+ /* Uniform distrivuted errors */
+ else if (argv[fa][1] == 'u' || argv[fa][1] == 'U')
+ unidist = 1;
+
+ /* Random seed value */
+ else if (argv[fa][1] == 'S') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -S");
+ if (sscanf(na, " %u ",&seed) != 1)
+ usage("Couldn't parse argument to -S");
+ }
+
+ /* calibration to device values */
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'i') {
+ if (argv[fa][1] == 'k')
+ applycal = 1;
+ else
+ applycal = 0;
+ fa = nfa;
+ if (na == NULL) usage("Expected an argument to -%c",argv[fa][1]);
+ strncpy(calname,na,MAXNAMEL); calname[MAXNAMEL] = '\000';
+ }
+
+ /* Random addition to device levels */
+ else if (argv[fa][1] == 'r') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -r");
+ rdlevel = atof(na) * 0.01;
+ }
+
+ /* Power applied to device channels */
+ else if (argv[fa][1] >= '0' && argv[fa][1] <= '9') {
+ int ch;
+ if (na == NULL) usage("Expect argument to -[0-9]");
+ ch = argv[fa][1]-'0';
+ if (ch < 0 || ch > 9)
+ usage("Channel no. %d is out of range\n",ch);
+ chpow[ch] = atof(na);
+ fa = nfa;
+ }
+
+ /* Random addition to PCS levels */
+ else if (argv[fa][1] == 'R') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -R");
+ rplevel = atof(na) * 0.01;
+ }
+
+ /* Black point scale */
+ else if (argv[fa][1] == 'b' || argv[fa][1] == 'B') {
+ if (na == NULL) usage("Expect argument to -b");
+ fa = nfa;
+ if (sscanf(na, " %lf , %lf , %lf ",&tbp[0], &tbp[1], &tbp[2]) != 3)
+ usage("Couldn't parse argument to -b");
+ if (tbp[0] < 0.0 || tbp[0] > 100.0) usage("-b L* value out of range");
+ if (tbp[1] < -128.0 || tbp[1] > 128.0) usage("-b a* value out of range");
+ if (tbp[2] < -128.0 || tbp[2] > 128.0) usage("-b b* value out of range");
+ }
+
+ /* Intent (only applies to ICC profile) */
+ else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to -I");
+ switch (na[0]) {
+ case 'r':
+ intent = icRelativeColorimetric;
+ break;
+ case 'a':
+ intent = icAbsoluteColorimetric;
+ break;
+ default:
+ usage("Unexpected argument value '%c' to -I optyion",na[0]);
+ }
+ }
+
+ else
+ usage("Unrecognised flag");
+ }
+ else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (dosep) {
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing separation profile filename argument");
+ strncpy(sepname,argv[fa++],MAXNAMEL); sepname[MAXNAMEL] = '\000';
+ }
+
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing profile filename argument");
+ strncpy(profname,argv[fa++],MAXNAMEL); profname[MAXNAMEL] = '\000';
+
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing basename argument");
+ strncpy(inname,argv[fa],MAXNAMEL-4); inname[MAXNAMEL-4] = '\000';
+ strcat(inname,".ti1");
+ strncpy(outname,argv[fa],MAXNAMEL-4); outname[MAXNAMEL-4] = '\000';
+ strcat(outname,".ti3");
+
+ rand32(seed); /* Init seed */
+ /* Deal with separation */
+ if (dosep) {
+ if ((sep_fp = new_icmFileStd_name(sepname,"r")) == NULL)
+ error ("Can't open file '%s'",sepname);
+
+ if ((sep_icco = new_icc()) == NULL)
+ error ("Creation of ICC object failed");
+
+ /* Deal with ICC separation */
+ if ((rv = sep_icco->read(sep_icco,sep_fp,0)) == 0) {
+
+ /* Get a conversion object */
+ if ((sep_luo = sep_icco->get_luobj(sep_icco, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm)) == NULL) {
+ error ("%d, %s",sep_icco->errc, sep_icco->err);
+ }
+
+ /* Get details of conversion */
+ sep_luo->spaces(sep_luo, &sep_ins, &sep_inn, &sep_outs, NULL, NULL, NULL, NULL, NULL, NULL);
+ sep_nmask = icx_icc_to_colorant_comb(sep_ins, sep_icco->header->deviceClass);
+ }
+ }
+
+ /* Deal with calibration */
+ if (calname[0] != '\000') {
+ if ((cal = new_xcal()) == NULL)
+ error("new_xcal failed");
+ if ((cal->read(cal, calname)) != 0)
+ error("%s",cal->err);
+ if (verb)
+ if (applycal)
+ printf("Applying calibration curves from '%s'\n",calname);
+ else
+ printf("Embedding calibration curves from '%s' in output\n",calname);
+ }
+
+ /* Deal with ICC profile */
+ if ((icc_fp = new_icmFileStd_name(profname,"r")) == NULL)
+ error ("Can't open file '%s'",profname);
+
+ if ((icc_icco = new_icc()) == NULL)
+ error ("Creation of ICC object failed");
+
+ /* Deal with ICC profile */
+ if ((rv = icc_icco->read(icc_icco,icc_fp,0)) == 0) {
+
+ /* Embed any calibration in the output if it's present */
+ if (cal == NULL) {
+ applycal = 0;
+ cal = xiccReadCalTag(icc_icco);
+
+ if (verb && cal != NULL)
+ printf("Embedding calibration curves from ICC profile in output\n");
+ }
+
+ /* Get a Device to PCS conversion object */
+ if ((icc_luo = icc_icco->get_luobj(icc_icco, icmFwd, intent,
+ dolab ? icSigLabData : icSigXYZData, icmLuOrdNorm)) == NULL) {
+ if ((icc_luo = icc_icco->get_luobj(icc_icco, icmFwd, icmDefaultIntent,
+ dolab ? icSigLabData : icSigXYZData, icmLuOrdNorm)) == NULL)
+ error ("%d, %s",icc_icco->errc, icc_icco->err);
+ }
+
+ /* Get details of conversion */
+ icc_luo->spaces(icc_luo, &ins, &inn, &outs, &outn, NULL, NULL, NULL, NULL, NULL);
+ cnv_nmask = icx_icc_to_colorant_comb(ins, icc_icco->header->deviceClass);
+
+ if (dospec)
+ error("Can't lookup spectral values for ICC profile");
+
+ if (tbp[0] >= 0.0) {
+ double ss[3], tt[3];
+
+ icc_luo->wh_bk_points(icc_luo, wp, bp);
+ if (dolab) {
+ icmXYZ2Lab(&icmD50, wp, wp);
+ icmXYZ2Lab(&icmD50, bp, bp);
+ } else {
+ icmLab2XYZ(&icmD50, tbp, tbp);
+ }
+
+ /* Create scaling matrix */
+ for (j = 0; j < 3; j++) {
+ tt[j] = tbp[j] - wp[j];
+ ss[j] = bp[j] - wp[j];
+ }
+ icmRotMat(bpt, ss, tt);
+
+ if (verb) {
+ printf("White point = %f %f %f (%s)\n",wp[0],wp[1],wp[2], dolab ? "Lab" : "XYZ");
+ printf("Black point = %f %f %f (%s)\n",bp[0],bp[1],bp[2], dolab ? "Lab" : "XYZ");
+ printf("Target Black point = %f %f %f (%s)\n",tbp[0],tbp[1],tbp[2], dolab ? "Lab" : "XYZ");
+ }
+ }
+
+ } else { /* Not a valid ICC */
+ /* Close out the ICC profile */
+ icc_icco->del(icc_icco);
+ icc_icco = NULL;
+ icc_fp->del(icc_fp);
+ icc_fp = NULL;
+ icc_luo = NULL;
+ }
+
+ /* If we don't have an ICC lookup object, look for an MPP */
+ if (icc_luo == NULL) {
+
+ if ((mlu = new_mpp()) == NULL)
+ error ("Creation of MPP object failed");
+
+ if ((rv = mlu->read_mpp(mlu, profname)) == 0) {
+
+ /* mlu defaults to absolute XYZ lookup */
+ if (dolab) {
+ mlu->set_ilob(mlu, icxIT_default, NULL, icxOT_default, NULL, icSigLabData, 0);
+ }
+
+ mlu->get_info(mlu, &cnv_nmask, &inn, NULL,
+ &spec_n, &spec_wl_short, &spec_wl_long, &itype, NULL);
+
+ outn = 3;
+ outs = dolab ? icSigLabData : icSigXYZData;
+
+ if ((ins = icx_colorant_comb_to_icc(cnv_nmask)) == 0)
+ error ("Couldn't match MPP mask to valid ICC colorspace");
+
+ if (dospec && spec_n <= 0)
+ error("Can't lookup spectral values for non-spectral MPP profile");
+ } else {
+ mlu->del(mlu);
+ mlu = NULL;
+ }
+ }
+
+ /* If we don't have an ICC or MPP lookup object, look for a TI3 */
+ if (icc_luo == NULL && mlu == NULL) {
+ char *rbuf, *outc;
+ char *ti3_bident;
+ int ti3_nchan;
+
+ ti3 = new_cgats(); /* Create a CGATS structure */
+ ti3->add_other(ti3, "CTI3");/* our special input type is Calibration Target Information 3 */
+
+ if (ti3->read_name(ti3, profname))
+ error("CGATS file read error : %s",ti3->err);
+
+ if (ti3->ntables == 0 || ti3->t[0].tt != tt_other || ti3->t[0].oi != 0)
+ error ("Profile file '%s' isn't a CTI3 format file",profname);
+ if (ti3->ntables != 1)
+ error ("Input file '%s' doesn't contain one table",profname);
+
+ if ((ti = ti3->find_kword(ti3, 0, "COLOR_REP")) < 0)
+ error("Input file doesn't contain keyword COLOR_REP");
+
+ if ((rbuf = strdup(ti3->t[0].kdata[ti])) == NULL)
+ error("Malloc failed");
+
+ /* Split COLOR_REP into device and PCS space */
+ if ((outc = strchr(rbuf, '_')) == NULL)
+ error("COLOR_REP '%s' invalid", ti3->t[0].kdata[ti]);
+ *outc++ = '\000';
+
+ outn = 3;
+ if (strcmp(outc, "XYZ") == 0) {
+ ti3_isLab = 0;
+ outs = icSigXYZData;
+ } else if (strcmp(outc, "LAB") == 0) {
+ ti3_isLab = 1;
+ outs = icSigLabData;
+ } else
+ error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", ti3->t[0].kdata[ti]);
+
+ if ((cnv_nmask = icx_char2inkmask(rbuf)) == 0) {
+ error ("File '%s' keyword COLOR_REP has unknown device value '%s'",profname,rbuf);
+ }
+
+ free(rbuf);
+
+ if ((ins = icx_colorant_comb_to_icc(cnv_nmask)) == 0)
+ error ("Couldn't match MPP mask to valid ICC colorspace");
+
+ if ((inn = icmCSSig2nchan(ins)) == 0)
+ error ("TI3 Colorspace with unknown number of channels");
+
+ if ((ti3_npat = ti3->t[0].nsets) <= 0)
+ error ("No sets of data in reference TI3 file");
+
+ ti3_nchan = icx_noofinks(cnv_nmask);
+ ti3_bident = icx_inkmask2char(cnv_nmask, 0);
+
+ /* Find device fields */
+ for (j = 0; j < ti3_nchan; j++) {
+ int ii, imask;
+ char fname[100];
+
+ imask = icx_index2ink(cnv_nmask, j);
+ sprintf(fname,"%s_%s",cnv_nmask == ICX_W || cnv_nmask == ICX_K ? "GRAY" : ti3_bident,
+ icx_ink2char(imask));
+
+ if ((ii = ti3->find_field(ti3, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (ti3->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+ ti3_chix[j] = ii;
+ }
+
+ /* Find PCS fields */
+ for (j = 0; j < 3; j++) {
+ int ii;
+
+ if ((ii = ti3->find_field(ti3, 0, ti3_isLab ? labfname[j] : xyzfname[j])) < 0)
+ error ("Input file doesn't contain field %s",ti3_isLab ? labfname[j] : xyzfname[j]);
+ if (ti3->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",ti3_isLab ? labfname[j] : xyzfname[j]);
+ ti3_pcsix[j] = ii;
+ }
+
+ /* Find spectral fields */
+ if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_BANDS")) >= 0) {
+ char buf[100];
+
+ spec_n = atoi(ti3->t[0].kdata[ti]);
+ if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ spec_wl_short = atof(ti3->t[0].kdata[ti]);
+ if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ spec_wl_long = atof(ti3->t[0].kdata[ti]);
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(spec_wl_short + ((double)j/(spec_n-1.0))
+ * (spec_wl_long - spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((ti3_spi[j] = ti3->find_field(ti3, 0, buf)) < 0)
+ error("Input file doesn't contain field %s",buf);
+ }
+ }
+
+ if (dospec && spec_n <= 0)
+ error("Can't lookup spectral values for non-spectral TI3 file");
+
+ free(ti3_bident);
+ }
+
+ /* Some sanity checking */
+ if (tbp[0] >= 0.0 && icc_luo == NULL)
+ error("Black scaling only works with ICC profile");
+
+ if (verb) {
+// printf("Random seed is %u\n",seed);
+
+ if (rdlevel > 0.0)
+ printf("Adding %.3f%% average deviation %s noise to device values\n",rdlevel * 100.0,unidist ? "uniform" : "normal");
+ for (j = 0; j < inn && j < 10; j++) {
+ if (chpow[j] != 1.0)
+ printf("Applying chan %d power %f\n",j,chpow[j]);
+ }
+ if (rplevel > 0.0)
+ printf("Adding %.3f%% average deviation %s noise to %s PCS values\n",rplevel * 100.0,unidist ? "uniform" : "normal", dolab ? "L*a*b*": "XYZ");
+ fflush(stdout);
+ }
+
+ /* Deal with input CGATS files */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI1"); /* our special input type is Calibration Target Information 1 */
+ icg->add_other(icg, "CTI3"); /* also accept renamed .ti3 file */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || (icg->t[0].oi != 0 && icg->t[0].oi != 1))
+ error ("Input file isn't a CTI1 format file");
+ if (icg->t[0].oi == 1)
+ inti3 = 1; /* It's a renamed .ti3 file */
+ if (icg->ntables != 1 && icg->ntables != 2 && icg->ntables != 3)
+ error ("Input file doesn't contain one, two or three tables");
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error ("No sets of data");
+
+ /* Figure out the color space of the .ti1 */
+ if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error ("Input file doesn't contain keyword COLOR_REP");
+
+ if (inti3) {
+ char *rbuf, *outc;
+
+ if ((rbuf = strdup(icg->t[0].kdata[fi])) == NULL)
+ error("Malloc failed");
+
+ /* Split COLOR_REP into device and PCS space */
+ if ((outc = strchr(rbuf, '_')) == NULL)
+ error("Input file '%s' COLOR_REP '%s' invalid", inname, icg->t[0].kdata[fi]);
+ *outc++ = '\000';
+
+ if ((nmask = icx_char2inkmask(rbuf)) == 0) {
+ error ("Input file '%s' keyword COLOR_REP has unknown device value '%s'",inname,rbuf);
+ }
+
+ free(rbuf);
+ } else {
+ if ((nmask = icx_char2inkmask(icg->t[0].kdata[fi])) == 0)
+ error ("Input file '%s' keyword COLOR_REP has unknown value '%s'",inname, icg->t[0].kdata[fi]);
+ }
+
+ /* Setup output cgats file */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll fakeread", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+
+ /* What sort of device this is */
+ if (ti3 != NULL) {
+ if ((ti = ti3->find_kword(ti3, 0, "DEVICE_CLASS")) < 0)
+ error("Input TI3 doesn't contain keyword DEVICE_CLASS");
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS",ti3->t[0].kdata[ti], NULL); /* Copy */
+ } else if (mlu != NULL) {
+ /* This is a guess. It may not be correct. */
+ if ((cnv_nmask & ICX_ADDITIVE) ^ (cnv_nmask & ICX_INVERTED)) {
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
+ if (nmask == ICX_IRGB)
+ warning("It's unusual to have an iRGB display device !");
+ } else {
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
+ if (nmask == ICX_RGB)
+ warning("It's unusual to have an RGB printing device !");
+ }
+ } else { /* Assume ICC */
+ if (icc_icco->header->deviceClass == icSigDisplayClass) {
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
+ if (nmask == ICX_IRGB)
+ warning("It's unusual to have an iRGB display device !");
+ } else {
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
+ if (nmask == ICX_RGB)
+ warning("It's unusual to have an RGB printing device !");
+ }
+ }
+
+ if ((ti = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0)
+ ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[ti], NULL);
+
+ if ((ti = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0)
+ ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[ti], NULL);
+
+ if (icc_luo == NULL) { /* If MPP profile, we know what the target instrument is */
+ ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(itype) , NULL);
+ }
+
+ /* Fields we want */
+ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
+
+ if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
+ error ("Input file doesn't contain field SAMPLE_ID");
+
+ {
+ int i, j, ii;
+ int chix[ICX_MXINKS]; /* Device chanel indexes */
+ char *ident, *bident;
+ int nsetel = 0;
+ cgats_set_elem *setel; /* Array of set value elements */
+
+ nchan = icx_noofinks(nmask);
+ ident = icx_inkmask2char(nmask, 1);
+ bident = icx_inkmask2char(nmask, 0);
+
+ /* Sanity check what we're going to do */
+ if (dosep) {
+
+ /* Check if sep ICC input is compatible with .ti1 */
+ if (nmask == ICX_W && sep_ins == icSigRgbData)
+ gfudge = 1;
+ else if (nmask == ICX_K && sep_ins == icSigCmykData)
+ gfudge = 2;
+ else if (icx_colorant_comb_match_icc(nmask, sep_ins) == 0) {
+ error("Separation ICC device space '%s' dosen't match TI1 '%s'",
+ icm2str(icmColorSpaceSignature, sep_ins),
+ ident); /* Should free(). */
+ }
+
+ /* Check if separation ICC output is compatible with ICC/MPP/TI3 conversion */
+ if (icc_luo != NULL) {
+ /* Check if icc is compatible with .ti1 */
+ if (sep_outs != ins)
+ error("ICC device space '%s' dosen't match Separation ICC '%s'",
+ icm2str(icmColorSpaceSignature, ins),
+ icm2str(icmColorSpaceSignature, sep_outs));
+ } else if (mlu != NULL) {
+ /* Check if mpp is compatible with .ti1 */
+ if (icx_colorant_comb_match_icc(cnv_nmask, sep_outs) == 0)
+ error("MPP device space '%s' doesn't match Separation ICC '%s'",
+ icx_inkmask2char(cnv_nmask, 1), /* Should free(). */
+ icm2str(icmColorSpaceSignature, sep_outs));
+ } else if (ti3 != NULL) {
+ /* Check if .ti3 is compatible with .ti1 */
+ if (icx_colorant_comb_match_icc(cnv_nmask, sep_outs) == 0)
+ error("TI3 device space '%s' doesn't match Separation ICC '%s'",
+ icx_inkmask2char(cnv_nmask, 1), /* Should free(). */
+ icm2str(icmColorSpaceSignature, sep_outs));
+ }
+ } else if (icc_luo != NULL) {
+ /* Check if icc is compatible with .ti1 */
+ if (nmask == ICX_W && ins == icSigRgbData)
+ gfudge = 1;
+ else if (nmask == ICX_K && ins == icSigCmykData)
+ gfudge = 2; /* Should allow for other colorant combo's that include black */
+ else if (icx_colorant_comb_match_icc(nmask, ins) == 0) {
+ error("ICC device space '%s' dosen't match TI1 '%s'",
+ icm2str(icmColorSpaceSignature, ins),
+ ident); // Should free().
+ }
+ } else if (mlu != NULL) {
+ /* Check if mpp is compatible with .ti1 */
+ if (nmask == ICX_W && (cnv_nmask == ICX_RGB || cnv_nmask == ICX_IRGB))
+ gfudge = 1;
+ else if (nmask == ICX_K && (cnv_nmask & ICX_BLACK))
+ gfudge = 2;
+ else if (cnv_nmask != nmask)
+ error("MPP device space '%s' doesn't match TI1 '%s'",
+ icx_inkmask2char(cnv_nmask, 1), ident); // Should free().
+ } else if (ti3 != NULL) {
+ /* Check if .ti3 is compatible with .ti1 */
+ if (nmask == ICX_W && (cnv_nmask == ICX_RGB || cnv_nmask == ICX_IRGB))
+ gfudge = 1;
+ else if (nmask == ICX_K && (cnv_nmask & ICX_BLACK))
+ gfudge = 2;
+ else if (cnv_nmask != nmask)
+ error("TI3 device space '%s' doesn't match TI1 '%s'",
+ icx_inkmask2char(cnv_nmask, 1), ident); // Should free().
+ }
+
+ if ((ii = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0)
+ ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL);
+
+ nsetel += 1; /* For id */
+ nsetel += nchan; /* For device values */
+ nsetel += 3; /* For XYZ/Lab */
+
+ for (j = 0; j < nchan; j++) {
+ int imask;
+ char fname[100];
+
+ imask = icx_index2ink(nmask, j);
+ sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
+ icx_ink2char(imask));
+
+ if ((ii = icg->find_field(icg, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (icg->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+
+ ocg->add_field(ocg, 0, fname, r_t);
+ chix[j] = ii;
+ }
+
+ /* Add PCS fields */
+ for (j = 0; j < 3; j++) {
+ ocg->add_field(ocg, 0, dolab ? labfname[j] : xyzfname[j], r_t);
+ }
+
+ /* If we have spectral information, output it too */
+ if (dospec > 0 && spec_n > 0) {
+ char buf[100];
+
+ nsetel += spec_n; /* Spectral values */
+ sprintf(buf,"%d", spec_n);
+ ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
+ sprintf(buf,"%f", spec_wl_short);
+ ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
+ sprintf(buf,"%f", spec_wl_long);
+ ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
+
+ /* Generate fields for spectral values */
+ for (i = 0; i < spec_n; i++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(spec_wl_short + ((double)i/(spec_n-1.0))
+ * (spec_wl_long - spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+ ocg->add_field(ocg, 0, buf, r_t);
+ }
+ }
+
+ {
+ char fname[100];
+ sprintf(fname, dolab ? "%s_LAB" : "%s_XYZ", ident);
+ ocg->add_kword(ocg, 0, "COLOR_REP", fname, NULL);
+ }
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
+ error("Malloc failed!");
+
+ /* Read all the test patches in, convert them, */
+ /* and write them out. */
+ for (i = 0; i < npat; i++) {
+ int k = 0;
+ char *id;
+ double odev[ICX_MXINKS], dev[ICX_MXINKS], sep[ICX_MXINKS], PCS[3];
+ xspect out;
+
+ id = ((char *)icg->t[0].fdata[i][si]);
+ for (j = 0; j < nchan; j++) {
+ double dv = *((double *)icg->t[0].fdata[i][chix[j]]) / 100.0;
+ odev[j] = dev[j] = sep[j] = dv;
+ }
+
+ if (gfudge) {
+ int nch;
+
+ if (dosep) /* Figure number of channels into conversion */
+ nch = sep_inn;
+ else
+ nch = inn;
+
+ if (gfudge == 1) { /* Convert W -> RGB */
+ double wval = dev[0];
+ for (j = 0; j < nch; j++)
+ dev[j] = sep[j] = wval;
+
+ } else { /* Convert K->xxxK */
+ int kch;
+ int inmask;
+ double kval = dev[0];
+
+ if (dosep) /* Figure number of channels into conversion */
+ inmask = sep_nmask;
+ else
+ inmask = cnv_nmask;
+
+ if (inmask == 0)
+ error("Input colorspace ambiguous - can't determine if it has black");
+
+ if ((kch = icx_ink2index(inmask, ICX_BLACK)) == -1)
+ error("Can't find black colorant for K fudge");
+ for (j = 0; j < nch; j++) {
+ if (j == kch)
+ dev[j] = sep[j] = kval;
+ else
+ dev[j] = sep[j] = 0.0;
+ }
+ }
+ }
+
+ if (dosep)
+ if (sep_luo->lookup(sep_luo, sep, dev) > 1)
+ error ("%d, %s",icc_icco->errc,icc_icco->err);
+
+ /* Do calibration */
+ if (applycal && cal != NULL)
+ cal->interp(cal, sep, sep);
+
+ /* Add randomness and non-linearity to device values. */
+ /* rdlevel = avg. dev. */
+ /* Note dev/sep is 0-1.0 at this stage */
+ for (j = 0; j < inn; j++) {
+ double dv = sep[j];
+ if (rdlevel > 0.0) {
+ double rr;
+ if (unidist)
+ rr = d_rand(-2.0 * rdlevel, 2.0 * rdlevel);
+ else
+ rr = 1.2533 * rdlevel * norm_rand();
+ dv += rr;
+ if (dv < 0.0)
+ dv = 0.0;
+ else if (dv > 1.0)
+ dv = 1.0;
+ }
+ if (j < 10 && chpow[j] != 1.0) {
+ dv = pow(dv, chpow[j]);
+ }
+ sep[j] = dv;
+ }
+
+ /* Do color conversion */
+ if (icc_luo != NULL) {
+ if (icc_luo->lookup(icc_luo, PCS, sep) > 1)
+ error ("%d, %s",icc_icco->errc,icc_icco->err);
+
+ if (tbp[0] >= 0) { /* Doing black point scaling */
+
+ for (j = 0; j < 3; j++)
+ PCS[j] -= wp[j];
+ icmMulBy3x3(PCS, bpt, PCS);
+ for (j = 0; j < 3; j++)
+ PCS[j] += wp[j];
+ }
+
+ } else if (mlu != NULL) {
+ mlu->lookup(mlu, PCS, sep);
+ if (dospec && spec_n > 0) {
+ mlu->lookup_spec(mlu, &out, sep);
+ }
+ } else if (ti3 != NULL) {
+ int m;
+ double bdif = 1e6;
+ int bix = -1;
+
+ /* Search for the closest device values in TI3 file */
+ for (m = 0; m < ti3_npat; m++) {
+ double dif;
+
+ for (dif = 0.0, j = 0; j < nchan; j++) {
+ double xx;
+
+ xx = (*((double *)ti3->t[0].fdata[m][ti3_chix[j]]) / 100.0) - sep[j];
+ dif += xx * xx;
+ }
+ if (dif < bdif) {
+ bdif = dif;
+ bix = m;
+ }
+ }
+ /* Copy best value over */
+ if (!dosep) /* Doesn't make sense for separation */
+ for (j = 0; j < nchan; j++) {
+ dev[j] = *((double *)ti3->t[0].fdata[bix][ti3_chix[j]]) / 100.0;
+ }
+ for (j = 0; j < 3; j++) {
+ PCS[j] = *((double *)ti3->t[0].fdata[bix][ti3_pcsix[j]]);
+ }
+ if (ti3_isLab && !dolab) { /* Convert Lab to XYZ */
+ icmLab2XYZ(&icmD50, PCS, PCS);
+ } else if (!ti3_isLab && dolab) { /* Convert XYZ to Lab */
+ icmXYZ2Lab(&icmD50, PCS, PCS);
+ } else if (!ti3_isLab) { /* Convert XYZ100 to XYZ1 */
+ PCS[0] /= 100.0;
+ PCS[1] /= 100.0;
+ PCS[2] /= 100.0;
+ }
+ if (dospec && spec_n > 0) {
+ for (j = 0; j < spec_n; j++) {
+ out.spec[j] = *((double *)ti3->t[0].fdata[bix][ti3_spi[j]]);
+ }
+ }
+ }
+
+ setel[k++].c = id;
+
+ for (j = 0; j < nchan; j++)
+ setel[k++].d = 100.0 * odev[j];
+
+ if (dolab == 0) {
+ PCS[0] *= 100.0;
+ PCS[1] *= 100.0;
+ PCS[2] *= 100.0;
+ }
+
+ /* Add randomness. rplevel is avg. dev. */
+ /* Note PCS is 0..100 XYZ or Lab at this point */
+ if (rplevel > 0.0) {
+ double opcs[3];
+ for (j = 0; j < 3; j++) {
+ double dv = PCS[j];
+ double rr;
+ opcs[j] = dv;
+ if (unidist)
+ rr = 100.0 * d_rand(-2.0 * rplevel, 2.0 * rplevel);
+ else
+ rr = 100.0 * 1.2533 * rplevel * norm_rand();
+ dv += rr;
+
+ /* Don't let L*, X, Y or Z go negative */
+ if ((!dolab || j == 0) && dv < 0.0)
+ dv = 0.0;
+ PCS[j] = dv;
+ }
+//printf("~1 pcs %f %f %f -> %f %f %f\n", opcs[0], opcs[1], opcs[2], PCS[0], PCS[1], PCS[2]);
+ }
+
+ setel[k++].d = PCS[0];
+ setel[k++].d = PCS[1];
+ setel[k++].d = PCS[2];
+
+ if (dospec && spec_n > 0) {
+ for (j = 0; j < spec_n; j++) {
+ setel[k++].d = 100.0 * out.spec[j];
+ }
+ }
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+ free(ident);
+ free(bident);
+ }
+
+ if (sep_luo != NULL) {
+ sep_luo->del(sep_luo);
+ sep_icco->del(sep_icco);
+ sep_fp->del(sep_fp);
+ }
+
+ /* If there is a calibration, append it to the .ti3 file */
+ if (cal != NULL) {
+ if (cal->write_cgats(cal, ocg) != 0)
+ error("Writing cal error : %s",cal->err);
+ }
+
+ if (cal != NULL)
+ cal->del(cal);
+
+ if (icc_luo != NULL) {
+ /* Cleanup ICC profile */
+ icc_luo->del(icc_luo);
+ icc_icco->del(icc_icco);
+ icc_fp->del(icc_fp);
+ } else if (mlu != NULL) {
+ mlu->del(mlu);
+ } else if (ti3 != NULL) {
+ ti3->del(ti3);
+ }
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ ocg->del(ocg); /* Clean up */
+ icg->del(icg); /* Clean up */
+
+ return 0;
+}
+
+