/* Apply inverse BT.1886 processing black point hue adjustment to the XYZ value */ void bt1886_inv_wp_adjust(bt1886_info *p, double *out, double *in) { double vv; #ifdef DEBUG a1logd(g_log, 2, "bt1886 XYZ inv. wp adj. in %f %f %f\n", in[0],in[1],in[2]); #endif icmXYZ2Lab(&p->w, out, in); #ifdef DEBUG a1logd(g_log, 2, "bt1886 Lab inv. wp adj. in %f %f %f\n", out[0],out[1],out[2]); #endif /* Blend ab to required black point offset p->tab[] as L approaches black. */ vv = (out[0] - p->outL)/(100.0 - p->outL); /* 0 at bp, 1 at wp */ vv = 1.0 - vv; if (vv < 0.0) vv = 0.0; else if (vv > 1.0) vv = 1.0; vv = pow(vv, 40.0); out[1] -= vv * p->tab[0]; out[2] -= vv * p->tab[1]; #ifdef DEBUG a1logd(g_log, 2, "bt1886 Lab after inv. wp adj. %f %f %f\n", out[0],out[1],out[2]); #endif icmLab2XYZ(&p->w, out, out); #ifdef DEBUG a1logd(g_log, 2, "bt1886 XYZ after inv. wp adj. %f %f %f\n", out[0],out[1],out[2]); #endif }
/* Pure BT.1886 will have outopro = 0.0 and gamma = 2.4 */ void bt1886_setup( bt1886_info *p, icmXYZNumber *w, /* wp used for L*a*b* conversion */ double *XYZbp, /* normalised bp used for black offset and black point hue "bend" */ double outoprop, /* 0..1 proportion of output black point compensation */ double gamma, /* technical or effective gamma */ int effg /* nz if effective gamma, z if technical gamma */ ) { double Lab[3], ino, bkipow, wtipow; icmXYZ2XYZ(p->w, *w); #ifdef DEBUG a1logd(g_log, 2, "bt1886_setup wp.Y %f, bp.Y %f, outprop %f, gamma %f, effg %d", p->w.Y, XYZbp[1], outoprop, gamma, effg); #endif if (effg) { p->gamma = xicc_tech_gamma(gamma, XYZbp[1], outoprop); #ifdef DEBUG a1logd(g_log, 2, "bt1886_setup tgamma %f", p->gamma); #endif } else { p->gamma = gamma; } icmXYZ2Lab(&p->w, Lab, XYZbp); p->outL = Lab[0]; /* For bp blend comp. */ p->tab[0] = Lab[1]; /* a* b* correction needed */ p->tab[1] = Lab[2]; #ifdef DEBUG a1logd(g_log, 2, "bt1886_setup bend Lab = %f %f %f", p->outL, p->tab[0], p->tab[1]); #endif if (XYZbp[1] < 0.0) XYZbp[1] = 0.0; p->outo = XYZbp[1] * outoprop; /* Offset acounted for in output */ ino = XYZbp[1] - p->outo; /* Balance of offset accounted for in input */ bkipow = pow(ino, 1.0/p->gamma); /* Input offset black to 1/pow */ wtipow = pow((1.0 - p->outo), 1.0/p->gamma); /* Input offset white to 1/pow */ #ifdef DEBUG a1logd(g_log, 2, "bt1886_setup outo %f, ino %f, bkipow %f, wtipow %f", p->outo, ino, bkipow, wtipow); #endif p->ingo = bkipow/(wtipow - bkipow); /* non-linear Y that makes input offset */ /* proportion of black point */ p->outsc = pow(wtipow - bkipow, p->gamma); /* Scale to make input of 1 map to */ /* 1.0 - p->outo */ #ifdef DEBUG a1logd(g_log, 2, "bt1886_setup ingo %f, outsc %f", p->ingo, p->outsc); #endif }
/* It's not clear if the white weighting is advantagous or not. */ double optf(void *fdata, double *tp) { cntx *cx = (cntx *)fdata; int i; double de; double m[3][3]; m[0][0] = tp[0]; m[0][1] = tp[1]; m[0][2] = tp[2]; m[1][0] = tp[3]; m[1][1] = tp[4]; m[1][2] = tp[5]; m[2][0] = tp[6]; m[2][1] = tp[7]; m[2][2] = tp[8]; for (de = 0.0, i = 0; i < cx->npat; i++) { double tlab[3], xyz[3], lab[3]; icmXYZ2Lab(&cx->wh, tlab, cx->refs[i]); icmMulBy3x3(xyz, m, cx->cols[i]); icmXYZ2Lab(&cx->wh, lab, xyz); if (i == cx->wix) de += cx->npat/4.0 * wCIE94sq(tlab, lab); /* Make white weight = 1/4 all others */ else de += wCIE94sq(tlab, lab); //printf("~1 %d: txyz %f %f %f, tlab %f %f %f\n", i,cx->refs[i][0], cx->refs[i][1], cx->refs[i][2], tlab[0], tlab[1], tlab[2]); //printf("~1 %d: xyz %f %f %f\n", i,cx->cols[i][0], cx->cols[i][1], cx->cols[i][2]); //printf("~1 %d: mxyz %f %f %f, lab %f %f %f\n", i,xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]); //printf("~1 %d: de %f\n", i,wCIE94(tlab, lab)); } de /= cx->npat; #ifdef DEBUG // printf("~1 return values = %f\n",de); #endif return de; }
/* (This is used in generating gamut compression in B2A tables) */ void icxLuMatrix_bwd_outpcs_relpcs( icxLuBase *pp, icColorSpaceSignature os, /* Output space, XYZ or Lab */ double *out, double *in) { icxLuMatrix *p = (icxLuMatrix *)pp; icxLuMatrixFwd_abs(p, out, in); if (os == icSigXYZData && p->natpcs == icSigLabData) { icmLab2XYZ(&icmD50, out, out); } else if (os == icSigXYZData && p->natpcs == icSigLabData) { icmXYZ2Lab(&icmD50, out, out); } }
/* This function is for optimising the gamma function */ double efunc1(void *edata, double p[]) { edatas *ed = (edatas *)edata; double rv; col *cp; for (rv = 0.0, cp = &ed->cols[ed->npat-1]; cp >= &ed->cols[0]; cp--) { double cc,mm,yy,kk; double nc,nm,ny,nk; double XYZ[3], Lab[3]; int j; /* Apply gamma correction to each input */ for (j = 0; j < 4; j++) { if (p[j] < 0.2) p[j] = 0.2; else if (p[j] > 5.0) p[j] = 5.0; } cc = pow(cp->c, p[0]); nc = 1.0 - cc; mm = pow(cp->m, p[1]); nm = 1.0 - mm; yy = pow(cp->y, p[2]); ny = 1.0 - yy; kk = pow(cp->k, p[3]); nk = 1.0 - kk; /* Then interpolate between all combinations of primaries. */ /* plus one that stands for all that are close to black */ for (j = 0; j < 3; j++) { XYZ[j] = nc * nm * ny * nk * ed->k[j][0] + nc * nm * yy * nk * ed->k[j][1] + nc * mm * ny * nk * ed->k[j][2] + nc * mm * yy * nk * ed->k[j][3] + cc * nm * ny * nk * ed->k[j][4] + cc * nm * yy * nk * ed->k[j][5] + cc * mm * ny * nk * ed->k[j][6] + (cc * mm * yy * nk + kk) * ed->k[j][7]; } icmXYZ2Lab(&icmD50, Lab, XYZ); rv += cp->err = icmLabDEsq(Lab, cp->Lab); } printf("Efunc1 returning %f\n",rv); return rv; }
/* Matrix optimisation function handed to powell() */ static double mxoptfunc(void *edata, double *v) { mxopt *p = (mxopt *)edata; double rv = 0.0, smv; double xyz[3], lab[3]; int i; for (i = 0; i < p->nodp; i++) { /* Apply our function */ //printf("%f %f %f -> %f %f %f\n", p->points[i].p[0], p->points[i].p[1], p->points[i].p[2], //xyz[0], xyz[1], xyz[2]); mxmfunc(p, v, xyz, p->points[i].p); /* Convert to Lab */ icmXYZ2Lab(&icmD50, lab, xyz); //printf("%f %f %f -> %f %f %f, target %f %f %f\n", p->points[i].p[0], p->points[i].p[1], p->points[i].p[2], //lab[0], lab[1], lab[2], p->points[i].v[0], p->points[i].v[1], p->points[i].v[2]); /* Accumulate total delta E squared */ #ifdef USE_CIE94_DE rv += p->points[i].w * icmCIE94sq(lab, p->points[i].v); #else rv += p->points[i].w * icmLabDEsq(lab, p->points[i].v); #endif } /* Normalise error to be an average delta E squared */ rv /= (double)p->nodp; /* Sum with shaper parameters squared, to */ /* minimise unsconstrained "wiggles" */ smv = xshapmag(p, v); rv += smv; #ifdef DEBUG printf("~9(%f)mxoptfunc returning %f\n",smv,rv); #endif return rv; }
/* This function is for optimising the primary values */ double efunc2(void *edata, double p[]) { edatas *ed = (edatas *)edata; int j, os = ed->xyzi; double tt, rv; col *cp; rv = 0.0; for (j = 0; j < 8; j++) { if (p[j] < 0.0) { /* Protect against silly values */ p[j] = 0.0; rv += 1000.0; } else if (p[j] > 1.5) { p[j] = 1.5; rv += 1000.0; } ed->k[os][j] = p[j]; /* Load into current */ } /* Compute error */ for (cp = &ed->cols[ed->npat-1]; cp >= &ed->cols[0]; cp--) { double XYZ[3], Lab[3]; for (os = 0; os < 3; os++) { /* Interpolate between all combinations of primaries. */ for (tt = 0.0, j = 0; j < 8; j++) tt += cp->bc[j] * ed->k[os][j]; XYZ[os] = tt; } icmXYZ2Lab(&icmD50, Lab, XYZ); rv += cp->err = icmLabDEsq(Lab, cp->Lab); } printf("Efunc2 returning %f\n",rv); return rv; }
int main(int argc, char *argv[]) { int fa,nfa; /* argument we're looking at */ struct { char name[MAXNAMEL+1]; /* Patch filename */ int npat; /* Number of patches */ pval *pat; /* patch values */ } cg[2]; /* Target and current patch file information */ char dev_name[MAXNAMEL+1]; /* Output device ICC filename for gamut */ char rd_name[MAXNAMEL+1]; /* Abstract profile ICC to modify */ char wr_name[MAXNAMEL+1]; /* Modified/created abstract profile ICC */ int dorel = 0; /* Do white point relative match */ int *match; /* Array mapping first list indexes to corresponding second */ int fwacomp = 0; /* FWA compensation on spectral ? */ int spec = 0; /* Use spectral data flag */ icxIllumeType illum = icxIT_D50; /* Spectral defaults */ xspect cust_illum; /* Custom illumination spectrum */ icxObserverType observ = icxOT_Judd_Voss_2; callback cb; /* Callback support stucture for setting abstract profile */ icmFile *rd_fp = NULL; /* Existing abstract profile to modify */ icc *rd_icc = NULL; icmFile *wr_fp; /* Modified/created abstract profile to write */ icc *wr_icc; int verb = 0; int nogamut = 0; /* Don't impose a gamut limit */ int docreate = 0; /* Create an initial abstract correction profile */ int clutres = DEF_CLUTRES; /* Output abstract profile clut resolution */ double damp1 = DEF_DAMP1; /* Initial damping factor */ double damp2 = DEF_DAMP2; /* Subsequent damping factor */ double smoothf = SMOOTHF; /* RSPL Smoothing factor */ double avgdev[MXDO]; /* RSPL Average Deviation */ double wweight = WWEIGHT; /* weak default function weight */ int whitepatch = -1; /* Index of white patch */ double merr = 0.0, aerr = 0.0; /* Stats on color change */ int i, j, e, n, rv = 0; error_program = argv[0]; if (argc < 6) 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"); /* Verbosity */ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { verb = 1; } /* Create initial abstract correction profile */ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { docreate = 1; } /* Don't impose a gamut limit */ else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') { nogamut = 1; } /* Override the correction clut resolution */ else if (argv[fa][1] == 'r') { fa = nfa; if (na == NULL) usage("Expect argument to -r"); clutres = atoi(na); } /* Override the damping factor */ else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') { fa = nfa; if (na == NULL) usage("Expect argument to -d"); damp2 = atof(na); } /* Aim for white point relative match */ else if (argv[fa][1] == 'R') { dorel = 1; } /* Spectral Illuminant type */ else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { fa = nfa; if (na == NULL) usage("Expect argument to -i"); if (strcmp(na, "A") == 0) { spec = 1; illum = icxIT_A; } else if (strcmp(na, "C") == 0) { spec = 1; illum = icxIT_C; } else if (strcmp(na, "D50") == 0) { spec = 1; illum = icxIT_D50; } else if (strcmp(na, "D65") == 0) { spec = 1; illum = icxIT_D65; } else if (strcmp(na, "F5") == 0) { spec = 1; illum = icxIT_F5; } else if (strcmp(na, "F8") == 0) { spec = 1; illum = icxIT_F8; } else if (strcmp(na, "F10") == 0) { spec = 1; illum = icxIT_F10; } else { /* Assume it's a filename */ spec = 1; illum = icxIT_custom; if (read_xspect(&cust_illum, na) != 0) usage("Unable to read custom spectrum '%s'",na); } } /* Spectral Observer type */ else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { fa = nfa; if (na == NULL) usage("Expected argument to -o"); if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */ spec = 1; observ = icxOT_CIE_1931_2; } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */ spec = 1; observ = icxOT_CIE_1964_10; } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */ spec = 1; observ = icxOT_Stiles_Burch_2; } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */ spec = 1; observ = icxOT_Judd_Voss_2; } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */ spec = 1; observ = icxOT_Shaw_Fairchild_2; } else usage("Unrecogised argument '%s' to -o",na); } /* FWA compensation */ else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') fwacomp = 1; else usage("Unrecognised flag -%c",argv[fa][1]); } else break; } /* Grab all the filenames: */ /* The two CIE value files */ if (fa >= argc || argv[fa][0] == '-') usage("Expected cietarget file argument"); strncpy(cg[0].name,argv[fa++],MAXNAMEL); cg[0].name[MAXNAMEL] = '\000'; if (fa >= argc || argv[fa][0] == '-') usage("Expected ciecurrent file argument"); strncpy(cg[1].name,argv[fa++],MAXNAMEL); cg[1].name[MAXNAMEL] = '\000'; /* Optional output device name */ if (nogamut == 0) { if (fa >= argc || argv[fa][0] == '-') usage("Expected outdevicc file argument"); strncpy(dev_name,argv[fa++],MAXNAMEL); dev_name[MAXNAMEL] = '\000'; } /* Optional input abstract profile name */ if (docreate == 0) { if (fa >= argc || argv[fa][0] == '-') usage("Expected inabs file argument"); strncpy(rd_name,argv[fa++],MAXNAMEL); rd_name[MAXNAMEL] = '\000'; } /* Output abstract profile name */ if (fa >= argc || argv[fa][0] == '-') usage("Expected outabs file argument"); strncpy(wr_name,argv[fa++],MAXNAMEL); wr_name[MAXNAMEL] = '\000'; /* ======================= */ /* Open up each CIE file in turn, target then measured, */ /* and read in the CIE values. */ for (n = 0; n < 2; n++) { cgats *cgf = NULL; /* cgats file data */ int isLab = 0; /* 0 if file CIE is XYZ, 1 if is Lab */ int sidx; /* Sample ID index */ int xix, yix, zix; /* Open CIE target values */ cgf = new_cgats(); /* Create a CGATS structure */ cgf->add_other(cgf, ""); /* Allow any signature file */ if (cgf->read_name(cgf, cg[n].name)) error("CGATS file '%s' read error : %s",cg[n].name,cgf->err); if (cgf->ntables < 1) error ("Input file '%s' doesn't contain at least one table",cg[n].name); /* Check if the file is suitable */ if (!spec && cgf->find_field(cgf, 0, "LAB_L") < 0 && cgf->find_field(cgf, 0, "XYZ_X") < 0) { if (cgf->find_kword(cgf, 0, "SPECTRAL_BANDS") < 0) error ("Neither CIE nor spectral data found in file '%s'",cg[n].name); /* Switch to using spectral information */ if (verb) printf("No CIE data found, switching to spectral with standard observer & D50 for file '%s'\n",cg[n].name); spec = 1; illum = icxIT_D50; observ = icxOT_CIE_1931_2; } if (spec && cgf->find_kword(cgf, 0, "SPECTRAL_BANDS") < 0) error ("No spectral data data found in file '%s' when spectral expected",cg[n].name); if (!spec && cgf->find_field(cgf, 0, "LAB_L") >= 0) isLab = 1; cg[n].npat = cgf->t[0].nsets; /* Number of patches */ /* Read all the target patches */ if (cg[n].npat <= 0) error("No sets of data in file '%s'",cg[n].name); if (verb && n == 0) { fprintf(verbo,"No of test patches = %d\n",cg[n].npat); } /* Allocate arrays to hold test patch input and output values */ if ((cg[n].pat = (pval *)malloc(sizeof(pval) * cg[n].npat)) == NULL) error("Malloc failed - pat[]"); /* Read in the CGATs fields */ if ((sidx = cgf->find_field(cgf, 0, "SAMPLE_ID")) < 0 && (sidx = cgf->find_field(cgf, 0, "SampleName")) < 0 && (sidx = cgf->find_field(cgf, 0, "Sample_Name")) < 0 && (sidx = cgf->find_field(cgf, 0, "SAMPLE_NAME")) < 0 && (sidx = cgf->find_field(cgf, 0, "SAMPLE_LOC")) < 0) error("Input file '%s' doesn't contain field SAMPLE_ID, SampleName, Sample_Name, SAMPLE_NAME or SAMPLE_LOC",cg[n].name); if (cgf->t[0].ftype[sidx] != nqcs_t && cgf->t[0].ftype[sidx] != cs_t) error("Sample ID/Name field isn't a quoted or non quoted character string"); if (spec == 0) { /* Using instrument tristimulous value */ if (isLab) { /* Expect Lab */ if ((xix = cgf->find_field(cgf, 0, "LAB_L")) < 0) error("Input file '%s' doesn't contain field LAB_L",cg[n].name); if (cgf->t[0].ftype[xix] != r_t) error("Field LAB_L is wrong type"); if ((yix = cgf->find_field(cgf, 0, "LAB_A")) < 0) error("Input file '%s' doesn't contain field LAB_A",cg[n].name); if (cgf->t[0].ftype[yix] != r_t) error("Field LAB_A is wrong type"); if ((zix = cgf->find_field(cgf, 0, "LAB_B")) < 0) error("Input file '%s' doesn't contain field LAB_B",cg[n].name); if (cgf->t[0].ftype[zix] != r_t) error("Field LAB_B is wrong type"); } else { /* Expect XYZ */ if ((xix = cgf->find_field(cgf, 0, "XYZ_X")) < 0) error("Input file '%s' doesn't contain field XYZ_X",cg[n].name); if (cgf->t[0].ftype[xix] != r_t) error("Field XYZ_X is wrong type"); if ((yix = cgf->find_field(cgf, 0, "XYZ_Y")) < 0) error("Input file '%s' doesn't contain field XYZ_Y",cg[n].name); if (cgf->t[0].ftype[yix] != r_t) error("Field XYZ_Y is wrong type"); if ((zix = cgf->find_field(cgf, 0, "XYZ_Z")) < 0) error("Input file '%s' doesn't contain field XYZ_Z",cg[n].name); if (cgf->t[0].ftype[zix] != r_t) error("Field XYZ_Z is wrong type"); } for (i = 0; i < cg[n].npat; i++) { strcpy(cg[n].pat[i].sid, (char *)cgf->t[0].fdata[i][sidx]); cg[n].pat[i].v[0] = *((double *)cgf->t[0].fdata[i][xix]); cg[n].pat[i].v[1] = *((double *)cgf->t[0].fdata[i][yix]); cg[n].pat[i].v[2] = *((double *)cgf->t[0].fdata[i][zix]); if (!isLab) { cg[n].pat[i].v[0] /= 100.0; /* Normalise XYZ to range 0.0 - 1.0 */ cg[n].pat[i].v[1] /= 100.0; cg[n].pat[i].v[2] /= 100.0; } if (!isLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */ icmXYZ2Lab(&icmD50, cg[n].pat[i].v, cg[n].pat[i].v); } } } else { /* Using spectral data */ int ii; xspect sp; char buf[100]; int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */ xsp2cie *sp2cie; /* Spectral conversion object */ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_BANDS")) < 0) error ("Input file doesn't contain keyword SPECTRAL_BANDS"); sp.spec_n = atoi(cgf->t[0].kdata[ii]); if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_START_NM")) < 0) error ("Input file doesn't contain keyword SPECTRAL_START_NM"); sp.spec_wl_short = atof(cgf->t[0].kdata[ii]); if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_END_NM")) < 0) error ("Input file doesn't contain keyword SPECTRAL_END_NM"); sp.spec_wl_long = atof(cgf->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] = cgf->find_field(cgf, 0, buf)) < 0) error("Input file doesn't contain field %s",buf); } /* Figure out what sort of device it is */ { int ti; if ((ti = cgf->find_kword(cgf, 0, "DEVICE_CLASS")) < 0) error ("Input file '%s' doesn't contain keyword DEVICE_CLASS",cg[n].name); if (strcmp(cgf->t[0].kdata[ti],"DISPLAY") == 0) { illum = icxIT_none; /* Displays are assumed to be self luminous */ } } /* Create a spectral conversion object */ if ((sp2cie = new_xsp2cie(illum, illum == icxIT_none ? NULL : &cust_illum, observ, NULL, icSigLabData)) == NULL) error("Creation of spectral conversion object failed"); if (fwacomp) { int ti; xspect mwsp; /* Medium spectrum */ instType itype; /* Spectral instrument type */ xspect insp; /* Instrument illuminant */ mwsp = sp; /* Struct copy */ if ((ti = cgf->find_kword(cgf, 0, "TARGET_INSTRUMENT")) < 0) error ("Can't find target instrument in '%s' needed for FWA compensation",cg[n].name); if ((itype = inst_enum(cgf->t[0].kdata[ti])) == instUnknown) error ("Unrecognised target instrument '%s'", cgf->t[0].kdata[ti]); if (inst_illuminant(&insp, itype) != 0) error ("Instrument doesn't have an FWA illuminent"); /* Determine a media white spectral reflectance */ for (j = 0; j < mwsp.spec_n; j++) mwsp.spec[j] = 0.0; /* Track the maximum reflectance for any band to determine white. */ /* This might silently fail, if there isn't white in the sampe set. */ for (i = 0; i < cg[0].npat; i++) { for (j = 0; j < mwsp.spec_n; j++) { double rv = *((double *)cgf->t[0].fdata[i][spi[j]]); if (rv > mwsp.spec[j]) mwsp.spec[j] = rv; } } if (sp2cie->set_fwa(sp2cie, &insp, &mwsp)) error ("Set FWA on sp2cie failed"); } for (i = 0; i < cg[0].npat; i++) { strcpy(cg[n].pat[i].sid, (char *)cgf->t[0].fdata[i][sidx]); /* Read the spectral values for this patch */ for (j = 0; j < sp.spec_n; j++) { sp.spec[j] = *((double *)cgf->t[0].fdata[i][spi[j]]); } /* Convert it to CIE space */ sp2cie->convert(sp2cie, cg[n].pat[i].v, &sp); } sp2cie->del(sp2cie); /* Done with this */ } /* End of reading in CGATs file */ cgf->del(cgf); /* Clean up */ } /* Check that the number of test patches matches */ if (cg[0].npat != cg[1].npat) error("Number of patches between '%s' and '%s' doesn't match",cg[0].name,cg[1].name); /* Create a list to map the second list (measured) of patches to the first (target) */ if ((match = (int *)malloc(sizeof(int) * cg[0].npat)) == NULL) error("Malloc failed - match[]"); for (i = 0; i < cg[0].npat; i++) { for (j = 0; j < cg[1].npat; j++) { if (strcmp(cg[0].pat[i].sid, cg[1].pat[j].sid) == 0) break; /* Found it */ } if (j < cg[1].npat) { match[i] = j; } else { error("Failed to find matching patch to '%s'",cg[0].pat[i].sid); } } /* Try and figure out which is the white patch */ { double hL = -1.0; for (i = 0; i < cg[0].npat; i++) { if (cg[0].pat[i].v[0] > hL) { hL = cg[0].pat[i].v[0]; whitepatch = i; } } } /* If we are aiming for a white point relative match, adjust the */ /* measured and target values to have a D50 white point */ if (dorel) { for (n = 0; n < 2; n++) { int wpix; /* White patch index */ double wp_xyz[3]; icmXYZNumber wp; /* White value */ double mat[3][3]; /* Chromatic transform */ if (n == 0) wpix = whitepatch; else wpix = match[whitepatch]; /* Compute a chromatic correction matrix */ icmLab2XYZ(&icmD50, wp_xyz, cg[n].pat[wpix].v); icmAry2XYZ(wp, wp_xyz); icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, wp, mat); for (i = 0; i < cg[n].npat; i++) { icmLab2XYZ(&icmD50, cg[n].pat[i].v, cg[n].pat[i].v); icmMulBy3x3(cg[n].pat[i].v, mat, cg[n].pat[i].v); icmXYZ2Lab(&icmD50, cg[n].pat[i].v, cg[n].pat[i].v); //printf("Table %d, patch %d, Lab %f %f %f\n",n,i,cg[n].pat[i].v[0],cg[n].pat[i].v[1],cg[n].pat[i].v[2]); } } } /* Compute the delta E's just for information */ for (i = 0; i < cg[0].npat; i++) { double de = icmLabDE(cg[0].pat[i].v, cg[1].pat[match[i]].v); cg[0].pat[i].de = de; if (de > merr) merr = de; aerr += de; } if (cg[0].npat > 0) aerr /= (double)cg[0].npat; if (verb) { fprintf(verbo,"No of correction patches = %d\n",cg[0].npat); fprintf(verbo,"Average dE = %f, Maximum dE = %f\n",aerr,merr); fprintf(verbo,"White patch assumed to be patch %s\n",cg[0].pat[whitepatch].sid); } /* ======================= */ /* Possible limiting gamut */ if (nogamut == 0) { icmFile *dev_fp; icc *dev_icc; xicc *dev_xicc; icxLuBase *dev_luo; icxInk ink; /* Ink parameters */ /* Open up the device ICC profile, so that we can create a gamut */ /* and get an absolute PCS->device conversion */ if ((dev_fp = new_icmFileStd_name(dev_name,"r")) == NULL) error ("Can't open file '%s'",dev_name); if ((dev_icc = new_icc()) == NULL) error("Creation of ICC object failed"); /* Read header etc. */ if ((rv = dev_icc->read(dev_icc,dev_fp,0)) != 0) error("Reading profile '%s' failed: %d, %s",dev_name,rv,dev_icc->err); /* Check that the profile is appropriate */ if (dev_icc->header->deviceClass != icSigInputClass && dev_icc->header->deviceClass != icSigDisplayClass && dev_icc->header->deviceClass != icSigOutputClass) error("Device Profile '%s' isn't a device profile",dev_name); ink.tlimit = -1.0; /* No ink limit by default */ ink.klimit = -1.0; /* Wrap with an expanded icc */ if ((dev_xicc = new_xicc(dev_icc)) == NULL) error ("Creation of xicc failed"); /* Use a heuristic to guess the ink limit */ icxGetLimits(dev_xicc, &ink.tlimit, &ink.klimit); ink.tlimit += 0.05; /* allow a slight margine */ if (verb) printf("Estimated Total inklimit is %f%%, Black %f%% \n",100.0 * ink.tlimit,ink.klimit < 0.0 ? 100.0 : 100.0 * ink.klimit); /* Get a expanded color conversion object suitable for gamut */ if ((dev_luo = dev_xicc->get_luobj(dev_xicc, ICX_CLIP_NEAREST, icmFwd, dorel ? icRelativeColorimetric : icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm, NULL, &ink)) == NULL) error ("%d, %s",dev_xicc->errc, dev_xicc->err); /* Creat a gamut surface */ if ((cb.dev_gam = dev_luo->get_gamut(dev_luo, GAMRES)) == NULL) error ("%d, %s",dev_xicc->errc, dev_xicc->err); dev_luo->del(dev_luo); dev_xicc->del(dev_xicc); dev_icc->del(dev_icc); dev_fp->del(dev_fp); } else { cb.dev_gam = NULL; } /* ======================= */ /* Open up the existing abstract profile that is to be refined. */ if (docreate == 0) { if ((rd_fp = new_icmFileStd_name(rd_name,"r")) == NULL) error ("Can't open file '%s'",rd_name); if ((rd_icc = new_icc()) == NULL) error ("Creation of ICC object failed"); /* Read header etc. */ if ((rv = rd_icc->read(rd_icc,rd_fp,0)) != 0) error ("%d, %s",rv,rd_icc->err); if (rd_icc->header->deviceClass != icSigAbstractClass) error("Input Profile '%s' isn't abstract type",rd_name); if ((cb.rd_luo = rd_icc->get_luobj(rd_icc, icmFwd, dorel ? icRelativeColorimetric : icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) error ("%d, %s",rd_icc->errc, rd_icc->err); } else { cb.rd_luo = NULL; } /* ======================= */ /* Create refining rspl */ { cow *rp; /* rspl setup points */ int npnts = 0; /* Total number of test points */ int gres[MXDI]; /* rspl grid resolution */ double damp; datai mn, mx; if ((rp = (cow *)malloc(sizeof(cow) * cg[0].npat)) == NULL) error("Malloc failed - rp[]"); /* Create mapping points */ for (i = 0; i < cg[0].npat; i++) { double temp[3]; double ccor[3], cmag; /* Current correction vector */ double ncor[3], nmag; /* New correction vector */ /* Input is target [0] */ for (j = 0; j < 3; j++) rp[i].p[j] = cg[0].pat[i].v[j]; /* Cull out of range points */ if (rp[i].p[0] < 0.0 || rp[i].p[0] > 100.0 || rp[i].p[1] < -127.0 || rp[i].p[1] > 127.0 || rp[i].p[2] < -127.0 || rp[i].p[2] > 127.0) { #ifdef DEBUG1 printf("Ignoring %f %f %f\n",rp[i].p[0],rp[i].p[1],rp[i].p[2]); #endif continue; } #ifdef DEBUG1 printf("%d: Target %f %f %f\n",i,rp[i].p[0],rp[i].p[1],rp[i].p[2]); #endif damp = cb.rd_luo != NULL ? damp2 : damp1; ccor[0] = ccor[1] = ccor[2] = 0.0; cmag = 0.0; /* Lookup the current correction applied to the target */ if (cb.rd_luo != NULL) { /* Subsequent pass */ double corval[3]; cb.rd_luo->lookup(cb.rd_luo, corval, cg[0].pat[i].v); icmSub3(ccor, corval, cg[0].pat[i].v); cmag = icmNorm3(ccor); #ifdef DEBUG1 printf("%d: ccor %f %f %f, mag %f\n",i, ccor[0],ccor[1],ccor[2],cmag); #endif } /* Create a trial 100% full correction point */ for (j = 0; j < 3; j++) rp[i].v[j] = ccor[j] + 2.0 * cg[0].pat[i].v[j] - cg[1].pat[match[i]].v[j]; /* If a first pass and the target or the correction are out of gamut, */ /* use a damping factor of 1.0 */ if (cb.rd_luo == NULL && cb.dev_gam != NULL && cb.dev_gam->nradial(cb.dev_gam, temp, rp[i].p) > 1.0 && cb.dev_gam->nradial(cb.dev_gam, temp, rp[i].v) > 1.0) { damp = 1.0; } #ifdef DEBUG1 printf("%d: damp = %f\n",i, damp); #endif /* Create a new correction that does a damped correction to the current error */ /* [0] = target, [1] = measured */ for (j = 0; j < 3; j++) ncor[j] = ccor[j] + damp * (cg[0].pat[i].v[j] - cg[1].pat[match[i]].v[j]); nmag = icmNorm3(ncor); #ifdef DEBUG1 printf("%d: ncor %f %f %f, mag %f\n",i, ncor[0],ncor[1],ncor[2],nmag); #endif /* If this is not the first pass, limit the new correction */ /* to be 1 + damp as big as the previous correction */ if (cb.rd_luo != NULL) { if ((nmag/cmag) > (1.0 + damp2)) { #ifdef DEBUG1 printf("%d: Limited cor mag from %f to %f\n",i, nmag, (1.0 + damp2) * cmag); #endif icmScale3(ncor, ncor, (1.0 + damp2) * cmag/nmag); } } /* Create correction point */ for (j = 0; j < 3; j++) rp[i].v[j] = cg[0].pat[i].v[j] + ncor[j]; /* If the target point or corrected point is likely to be outside */ /* the gamut, limit the magnitude of the correction to be the same */ /* as the previous correction. */ if (cb.rd_luo != NULL && cb.dev_gam != NULL) { if (cb.dev_gam->nradial(cb.dev_gam, temp, rp[i].p) > 1.0 || cb.dev_gam->nradial(cb.dev_gam, temp, rp[i].v) > 1.0) { #ifdef DEBUG1 printf("%d: Limited cor mag from %f to %f\n",i, nmag, cmag); #endif icmScale3(ncor, ncor, cmag/nmag); } /* Create correction point again */ for (j = 0; j < 3; j++) rp[i].v[j] = cg[0].pat[i].v[j] + ncor[j]; } #ifdef DEBUG1 printf("%d: Was %f %f %f\n",i, cg[1].pat[match[i]].v[0], cg[1].pat[match[i]].v[1], cg[1].pat[match[i]].v[2]); printf("%d: Correction to %f %f %f\n",i, rp[i].v[0], rp[i].v[1], rp[i].v[2]); #endif #ifdef COMPLOOKUP /* Remove current correction from new change */ for (j = 0; j < 3; j++) rp[i].v[j] -= ccor[j]; #endif /* Set weighting */ if (i == whitepatch) rp[i].w = WHITEWEIGHT; else rp[i].w = 1.0; npnts++; #ifdef DEBUG3 { char fname[50], tmp[50]; FILE *lf; int mi = match[i]; double tig, cig, rig; double vv[3], temp[3]; double del[3], delt; double corrdel[3], corrdelt; double pcval[3], pcorrdel[3], pcorrdelt; for (j = 0;; j++) { if (poi[j] == (i+1) || poi[j] < 0) break; } if (poi[j] < 0) { continue; } #ifdef COMPLOOKUP /* Compute total correction point */ for (j = 0; j < 3; j++) vv[j] = rp[i].v[j] + ccor[j]; #else for (j = 0; j < 3; j++) vv[j] = rp[i].v[j]; #endif sprintf(fname,"patch%04d.log",i+1); if ((lf = fopen(fname, "a")) == NULL) error("Unable to open debug3 log file '%s'\n",fname); cig = cb.dev_gam->nradial(cb.dev_gam, temp, cg[1].pat[mi].v) - 1.0; if (cig > 0.0) sprintf(tmp, " OUT %f",cig); else sprintf(tmp, ""); fprintf(lf,"Currently %f %f %f%s\n", cg[1].pat[mi].v[0], cg[1].pat[mi].v[1], cg[1].pat[mi].v[2], tmp); tig = cb.dev_gam->nradial(cb.dev_gam, temp, cg[0].pat[i].v) - 1.0; if (tig > 0.0) sprintf(tmp, " OUT %f",tig); else sprintf(tmp, ""); fprintf(lf,"Target %f %f %f%s\n", cg[0].pat[i].v[0], cg[0].pat[i].v[1], cg[0].pat[i].v[2], tmp); icmSub3(del, cg[1].pat[mi].v, cg[0].pat[i].v); delt = icmNorm3(del); fprintf(lf,"DE %f %f %f (%f)\n", del[0], del[1], del[2], delt); rig = cb.dev_gam->nradial(cb.dev_gam, temp, vv) - 1.0; if (rig > 0.0) sprintf(tmp, " OUT %f",rig); else sprintf(tmp, ""); fprintf(lf,"Correction %f %f %f%s\n", vv[0], vv[1], vv[2], tmp); icmSub3(corrdel, vv, cg[0].pat[i].v); corrdelt = icmNorm3(corrdel); fprintf(lf,"CorrDelta %f %f %f (%f)\n", corrdel[0], corrdel[1], corrdel[2], corrdelt); /* Note the previous correction we're compunded with */ if (cb.rd_luo != NULL) { cb.rd_luo->lookup(cb.rd_luo, pcval, cg[0].pat[i].v); icmSub3(pcorrdel, pcval, cg[0].pat[i].v); pcorrdelt = icmNorm3(pcorrdel); fprintf(lf,"PrevCorrDelta %f %f %f (%f)\n", pcorrdel[0], pcorrdel[1], pcorrdel[2], pcorrdelt); } fprintf(lf,"\n"); fclose(lf); } #endif /* DEBUG3 */ } /* Create refining rspl */ mn[0] = 0.0, mn[1] = mn[2] = -128.0; /* Allow for 16 bit grid range */ mx[0] = 100.0, mx[1] = mx[2] = (65535.0 * 255.0)/65280.0 - 128.0; cb.verb = verb; if ((cb.r = new_rspl(RSPL_NOFLAGS, 3, 3)) == NULL) error("new_rspl failed"); for (e = 0; e < 3; e++) gres[e] = clutres; for (e = 0; e < 3; e++) avgdev[e] = AVGDEV; cb.r->fit_rspl_w_df(cb.r, RSPLFLAGS /* Extra flags */ | verb ? RSPL_VERBOSE : 0, rp, /* Test points */ npnts, /* Number of test points */ mn, mx, gres, /* Low, high, resolution of grid */ NULL, NULL, /* Default data scale */ smoothf, /* Smoothing */ avgdev, /* Average Deviation */ NULL, /* Grid width */ wweight, /* weak default function weight */ NULL, /* No context */ wfunc /* Weak function */ ); if (verb) printf("\n"); /* Report how good the fit is */ if (verb) { co tco; /* Test point */ double maxe = -1e6, avge = 0.0; for (i = 0; i < npnts; i++) { double de; icmAry2Ary(tco.p, rp[i].p); cb.r->interp(cb.r, &tco); de = icmLabDE(tco.v, rp[i].v); if (de > maxe) maxe = de; avge += de; } avge /= (double)npnts; printf("Refining transform has error to defining points avg: %f, max %f\n",avge,maxe); } free(rp); } /* ======================= */ /* Create new abstract ICC profile */ if ((wr_fp = new_icmFileStd_name(wr_name,"w")) == NULL) error ("Can't open file '%s' for writing",wr_name); if ((wr_icc = new_icc()) == NULL) error ("Creation of write ICC object failed"); /* Add all the tags required */ /* The header: */ { icmHeader *wh = wr_icc->header; /* Values that must be set before writing */ wh->deviceClass = icSigAbstractClass; wh->colorSpace = icSigLabData; wh->pcs = icSigLabData; if (dorel) wh->renderingIntent = icRelativeColorimetric; /* White point relative */ else wh->renderingIntent = icAbsoluteColorimetric; /* Instrument reading based */ } /* Profile Description Tag: */ { icmTextDescription *wo; char *dst = "Argyll refine output"; if ((wo = (icmTextDescription *)wr_icc->add_tag( wr_icc, icSigProfileDescriptionTag, icSigTextDescriptionType)) == NULL) error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */ wo->allocate((icmBase *)wo);/* Allocate space */ strcpy(wo->desc, dst); /* Copy the string in */ } /* Copyright Tag: */ { icmText *wo; char *crt = "Copyright the user who created it"; if ((wo = (icmText *)wr_icc->add_tag( wr_icc, icSigCopyrightTag, icSigTextType)) == NULL) error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); wo->size = strlen(crt)+1; /* Allocated and used size of text, inc null */ wo->allocate((icmBase *)wo);/* Allocate space */ strcpy(wo->data, crt); /* Copy the text in */ } /* White Point Tag: */ { icmXYZArray *wo; /* Note that tag types icSigXYZType and icSigXYZArrayType are identical */ if ((wo = (icmXYZArray *)wr_icc->add_tag( wr_icc, icSigMediaWhitePointTag, icSigXYZArrayType)) == NULL) error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); wo->size = 1; wo->allocate((icmBase *)wo); /* Allocate space */ wo->data[0] = icmD50; /* So absolute/relative rendering is the same */ } /* 16 bit pcs -> pcs lut: */ { icmLut *wo; int flags = ICM_CLUT_SET_EXACT; /* Assume we're setting from RSPL's */ /* Intent 0 = default/perceptual */ if ((wo = (icmLut *)wr_icc->add_tag( wr_icc, icSigAToB0Tag, icSigLut16Type)) == NULL) error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); wo->inputChan = 3; wo->outputChan = 3; wo->clutPoints = clutres; wo->inputEnt = 256; /* Not actually used */ wo->outputEnt = 256; wo->allocate((icmBase *)wo);/* Allocate space */ /* The matrix is only applicable to XYZ input space, */ /* so it is not used here. */ /* Use helper function to do the hard work. */ if (cb.verb) { int extra; for (cb.total = 1, i = 0; i < 3; i++, cb.total *= wo->clutPoints) ; /* Add in cell center points */ for (extra = 1, i = 0; i < wo->inputChan; i++, extra *= (wo->clutPoints-1)) ; cb.total += extra; cb.count = 0; cb.last = -1; printf(" 0%%"), fflush(stdout); } #ifdef COMPLOOKUP /* Compound with previous correction */ if (cb.rd_luo != NULL) flags = ICM_CLUT_SET_APXLS; /* Won't be least squares, so do extra sampling */ #endif if (wo->set_tables(wo, flags, &cb, icSigLabData, /* Input color space */ icSigLabData, /* Output color space */ NULL, /* Linear input transform Lab->Lab' (NULL = default) */ NULL, NULL, /* Use default Maximum range of Lab' values */ PCSp_PCSp, /* Lab' -> Lab' transfer function */ NULL, NULL, /* Use default Maximum range of Lab' values */ NULL /* Linear output transform Lab'->Lab */ ) != 0) error("Setting 16 bit Lab->Lab Lut failed: %d, %s",wr_icc->errc,wr_icc->err); if (verb) printf("\n"); #ifdef WARN_CLUT_CLIPPING if (wr_icc->warnc) warning("Values clipped in setting abstract LUT"); #endif /* WARN_CLUT_CLIPPING */ if (verb) printf("Done filling abstract table\n"); } /* Write the file out */ if ((rv = wr_icc->write(wr_icc,wr_fp,0)) != 0) error ("Write file: %d, %s",rv,wr_icc->err); /* ======================================= */ /* Clean everything up */ wr_icc->del(wr_icc); wr_fp->del(wr_fp); if (docreate == 0) { cb.rd_luo->del(cb.rd_luo); rd_icc->del(rd_icc); rd_fp->del(rd_fp); } if (nogamut == 0) { cb.dev_gam->del(cb.dev_gam); } cb.r->del(cb.r); free(match); free(cg[0].pat); free(cg[1].pat); return 0; }
int main(int argc, char *argv[]) { int i; int fa,nfa; /* current argument we're looking at */ int verb = 0; int limit = -1; static char tarname[200] = { 0 }; /* optional 928 patch reference file */ static char inname[200] = { 0 }; /* Input .pat file base name */ static char outname[200] = { 0 }; /* Output cgats .ti3 file base name */ FILE *d928_fp = NULL; FILE *pat_fp; cgats *ocg; /* output cgats structure */ time_t clk = time(0); struct tm *tsp = localtime(&clk); char *atm = asctime(tsp); /* Ascii time */ error_program = "kodak2ti3"; if (argc <= 1) usage(); /* 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(); else if (argv[fa][1] == 'l') { if (na == NULL) usage(); fa = nfa; limit = atoi(na); if (limit < 1) limit = 1; } else if (argv[fa][1] == 'r') { if (na == NULL) usage(); fa = nfa; strcpy(tarname, na); } else if (argv[fa][1] == 'v') verb = 1; else usage(); } else break; } /* Get the file name argument */ if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(inname,argv[fa++]); strcat(inname,".pat"); if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(outname, argv[fa++]); strcat(outname,".ti3"); /* Open up the Test chart reference file if we were given one */ if (tarname[0] != '\000') { if (verb) printf("Using alternate reference file '%s'\n",tarname); if ((d928_fp = open_928(tarname)) == NULL) error ("Read: Can't open file '%s'",tarname); } if ((pat_fp = open_pat(inname)) == NULL) error ("Read: Can't open file '%s'",inname); /* 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 target", 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); /* What sort of device this is */ /* Fields we want */ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t); if (limit > 0) { char buf[100]; sprintf(buf, "%d", limit); ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT", buf, NULL); } ocg->add_field(ocg, 0, "CMYK_C", r_t); ocg->add_field(ocg, 0, "CMYK_M", r_t); ocg->add_field(ocg, 0, "CMYK_Y", r_t); ocg->add_field(ocg, 0, "CMYK_K", r_t); ocg->add_kword(ocg, 0, "COLOR_REP","CMYK_LAB", NULL); ocg->add_field(ocg, 0, "LAB_L", r_t); ocg->add_field(ocg, 0, "LAB_A", r_t); ocg->add_field(ocg, 0, "LAB_B", r_t); /* Write out the patch info to the output CGATS file */ for (i = 0; i < NPAT; i++) { char id[100]; double cmykv[4]; double labv[3]; if (next_928(d928_fp, i, cmykv) != 0) error("Error reading reference information from '%s' file",tarname); if (next_pat(pat_fp, labv) != 0) error("Error reading Kodak .pat file"); sprintf(id, "%d", i+1); ocg->add_set(ocg, 0, id, 100.0 * cmykv[0], 100.0 * cmykv[1], 100.0 * cmykv[2], 100.0 * cmykv[3], labv[0], labv[1], labv[2]); } { double wp[3]; /* Paper white XYZ */ double D50wp[3] /* D50 white point Kodak uses */ = { 0.9642, 1.0000, 0.8249 }; double tt[3]; int li; /* Get offset of Lab values in cgats file */ if ((li = ocg->find_field(ocg, 0, "LAB_L")) < 0) error("Internal - cgats doesn't field LAB_L"); /* Get last line of pat file - this is the paper white in XYZ */ if (next_pat(pat_fp, wp) != 0) error("Error reading Kodak .pat file"); //printf("~1 white point is XYZ %f %f %f\n",wp[0],wp[1],wp[2]); /* Run through all the data points, and adjust them back to the absolute */ /* white point. */ for (i = 0; i < NPAT; i++) { double in[3], out[3]; tt[0] = *((double *)ocg->t[0].fdata[i][li+0]); tt[1] = *((double *)ocg->t[0].fdata[i][li+1]); tt[2] = *((double *)ocg->t[0].fdata[i][li+2]); icmLab2XYZ(&icmD50, in, tt); /* Undo Kodak's D50->paper white point adjustment */ out[0] = in[0] * (D50wp[1] * wp[0])/(D50wp[0] * wp[1]); out[1] = in[1]; /* Y remains unchanged */ out[2] = in[2] * (D50wp[1] * wp[2])/(D50wp[2] * wp[1]); icmXYZ2Lab(&icmD50, tt, out); *((double *)ocg->t[0].fdata[i][li+0]) = tt[0]; *((double *)ocg->t[0].fdata[i][li+1]) = tt[1]; *((double *)ocg->t[0].fdata[i][li+2]) = tt[2]; } } if (ocg->write_name(ocg, outname)) error("Write error : %s",ocg->err); ocg->del(ocg); /* Clean up */ if (d928_fp != NULL) close_928(d928_fp); close_pat(pat_fp); return 0; }
int main(int argc, char *argv[]) { int fa,nfa; /* argument we're looking at */ char prof_name[MAXNAMEL+1] = { '\000' }; /* ICC profile name, "" if none */ char in_name[MAXNAMEL+1]; /* TIFF input file */ char *xl = NULL, out_name[MAXNAMEL+4+1] = "locus.ts"; /* locus output file */ int verb = 0; int dovrml = 0; int doaxes = 1; int usevec = 0; double vec[3]; int rv = 0; icc *icco = NULL; xicc *xicco = NULL; icxViewCond vc; /* Viewing Condition for CIECAM */ int vc_e = -1; /* Enumerated viewing condition */ int vc_s = -1; /* Surround override */ double vc_wXYZ[3] = {-1.0, -1.0, -1.0}; /* Adapted white override in XYZ */ double vc_wxy[2] = {-1.0, -1.0}; /* Adapted white override in x,y */ double vc_a = -1.0; /* Adapted luminance */ double vc_b = -1.0; /* Background % overid */ double vc_f = -1.0; /* Flare % overid */ double vc_fXYZ[3] = {-1.0, -1.0, -1.0}; /* Flare color override in XYZ */ double vc_fxy[2] = {-1.0, -1.0}; /* Flare color override in x,y */ icxLuBase *luo = NULL; /* Generic lookup object */ icColorSpaceSignature ins = icSigLabData, outs; /* Type of input and output spaces */ int inn, outn; /* Number of components */ icmLuAlgType alg; /* Type of lookup algorithm */ icmLookupFunc func = icmFwd; /* Must be */ icRenderingIntent intent = -1; /* Default */ icColorSpaceSignature pcsor = icSigLabData; /* Default */ icmLookupOrder order = icmLuOrdNorm; /* Default */ TIFF *rh = NULL; int x, y, width, height; /* Size of image */ uint16 samplesperpixel, bitspersample; uint16 pconfig, photometric, pmtc; uint16 resunits; float resx, resy; tdata_t *inbuf; void (*cvt)(double *out, double *in); /* TIFF conversion function, NULL if none */ icColorSpaceSignature tcs; /* TIFF colorspace */ uint16 extrasamples; /* Extra "alpha" samples */ uint16 *extrainfo; /* Info about extra samples */ int sign_mask; /* Handling of encoding sign */ int i, j; int nipoints = 0; /* Number of raster sample points */ co *inp = NULL; /* Input point values */ double tdel = 0.0; /* Total delta along locus */ rspl *rr = NULL; int nopoints = 0; /* Number of raster sample points */ co *outp = NULL; error_program = argv[0]; if (argc < 2) usage(); /* 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(); /* Verbosity */ else if (argv[fa][1] == 'v') { verb = 1; } /* Intent */ else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { fa = nfa; if (na == NULL) usage(); switch (na[0]) { case 'd': intent = icmDefaultIntent; break; case 'a': intent = icAbsoluteColorimetric; break; case 'p': intent = icPerceptual; break; case 'r': intent = icRelativeColorimetric; break; case 's': intent = icSaturation; break; /* Argyll special intents to check spaces underlying */ /* icxPerceptualAppearance & icxSaturationAppearance */ case 'P': intent = icmAbsolutePerceptual; break; case 'S': intent = icmAbsoluteSaturation; break; default: usage(); } } /* Search order */ else if (argv[fa][1] == 'o') { fa = nfa; if (na == NULL) usage(); switch (na[0]) { case 'n': case 'N': order = icmLuOrdNorm; break; case 'r': case 'R': order = icmLuOrdRev; break; default: usage(); } } /* PCS override */ else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { fa = nfa; if (na == NULL) usage(); switch (na[0]) { case 'l': pcsor = icSigLabData; break; case 'j': pcsor = icxSigJabData; break; default: usage(); } } /* Viewing conditions */ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { fa = nfa; if (na == NULL) usage(); /* Switch to Jab automatically */ pcsor = icxSigJabData; /* Set the viewing conditions */ if (na[1] != ':') { if ((vc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) usage(); } else if (na[0] == 's' || na[0] == 'S') { if (na[1] != ':') usage(); if (na[2] == 'a' || na[2] == 'A') { vc_s = vc_average; } else if (na[2] == 'm' || na[2] == 'M') { vc_s = vc_dim; } else if (na[2] == 'd' || na[2] == 'D') { vc_s = vc_dark; } else if (na[2] == 'c' || na[2] == 'C') { vc_s = vc_cut_sheet; } else usage(); } else if (na[0] == 'w' || na[0] == 'W') { double x, y, z; if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { vc_wXYZ[0] = x; vc_wXYZ[1] = y; vc_wXYZ[2] = z; } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { vc_wxy[0] = x; vc_wxy[1] = y; } else usage(); } else if (na[0] == 'a' || na[0] == 'A') { if (na[1] != ':') usage(); vc_a = atof(na+2); } else if (na[0] == 'b' || na[0] == 'B') { if (na[1] != ':') usage(); vc_b = atof(na+2); } else if (na[0] == 'f' || na[0] == 'F') { double x, y, z; if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { vc_fXYZ[0] = x; vc_fXYZ[1] = y; vc_fXYZ[2] = z; } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { vc_fxy[0] = x; vc_fxy[1] = y; } else if (sscanf(na+1,":%lf",&x) == 1) { vc_f = x; } else usage(); } else usage(); } /* VRML output */ else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { dovrml = 1; } /* No axis output */ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { doaxes = 0; } /* Vector direction for span */ else if (argv[fa][1] == 'V') { usevec = 1; if (na == NULL) usage(); fa = nfa; if (sscanf(na, " %lf , %lf , %lf ",&vec[0], &vec[1], &vec[2]) != 3) usage(); } /* Output file name */ else if (argv[fa][1] == 'O') { fa = nfa; if (na == NULL) usage(); strncpy(out_name,na,MAXNAMEL); out_name[MAXNAMEL] = '\000'; } else usage(); } else break; } if (fa >= argc || argv[fa][0] == '-') usage(); if (fa < (argc-1)) strncpy(prof_name,argv[fa++],MAXNAMEL); prof_name[MAXNAMEL] = '\000'; if (fa >= argc || argv[fa][0] == '-') usage(); strncpy(in_name,argv[fa],MAXNAMEL); in_name[MAXNAMEL] = '\000'; if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ xl = out_name + strlen(out_name); if (verb) { printf("Profile = '%s'\n",prof_name); printf("Input TIFF = '%s'\n",in_name); printf("Output file = '%s'\n",out_name); } if (intent == -1) { if (pcsor == icxSigJabData) intent = icRelativeColorimetric; /* Default to icxAppearance */ else intent = icAbsoluteColorimetric; /* Default to icAbsoluteColorimetric */ } /* - - - - - - - - - - - - - - - - */ /* If we were provided an ICC profile to use */ if (prof_name[0] != '\000') { /* Open up the profile or TIFF embedded profile for reading */ if ((icco = read_embedded_icc(prof_name)) == NULL) error ("Can't open profile in file '%s'",prof_name); if (verb) { icmFile *op; if ((op = new_icmFileStd_fp(stdout)) == NULL) error ("Can't open stdout"); icco->header->dump(icco->header, op, 1); op->del(op); } /* Check that the profile is appropriate */ if (icco->header->deviceClass != icSigInputClass && icco->header->deviceClass != icSigDisplayClass && icco->header->deviceClass != icSigOutputClass && icco->header->deviceClass != icSigColorSpaceClass) error("Profile type isn't device or colorspace"); /* Wrap with an expanded icc */ if ((xicco = new_xicc(icco)) == NULL) error ("Creation of xicc failed"); /* Setup the default viewing conditions */ if (xicc_enum_viewcond(xicco, &vc, -1, NULL, 0, NULL) == -999) error ("%d, %s",xicco->errc, xicco->err); if (vc_e != -1) if (xicc_enum_viewcond(xicco, &vc, vc_e, NULL, 0, NULL) == -999) error ("%d, %s",xicco->errc, xicco->err); if (vc_s >= 0) vc.Ev = vc_s; if (vc_wXYZ[1] > 0.0) { /* Normalise it to current media white */ vc.Wxyz[0] = vc_wXYZ[0]/vc_wXYZ[1] * vc.Wxyz[1]; vc.Wxyz[2] = vc_wXYZ[2]/vc_wXYZ[1] * vc.Wxyz[1]; } if (vc_wxy[0] >= 0.0) { double x = vc_wxy[0]; double y = vc_wxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ double z = 1.0 - x - y; vc.Wxyz[0] = x/y * vc.Wxyz[1]; vc.Wxyz[2] = z/y * vc.Wxyz[1]; } if (vc_a >= 0.0) vc.La = vc_a; if (vc_b >= 0.0) vc.Yb = vc_b/100.0; if (vc_f >= 0.0) vc.Yf = vc_f/100.0; if (vc_fXYZ[1] > 0.0) { /* Normalise it to current media white */ vc.Fxyz[0] = vc_fXYZ[0]/vc_fXYZ[1] * vc.Fxyz[1]; vc.Fxyz[2] = vc_fXYZ[2]/vc_fXYZ[1] * vc.Fxyz[1]; } if (vc_fxy[0] >= 0.0) { double x = vc_fxy[0]; double y = vc_fxy[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ double z = 1.0 - x - y; vc.Fxyz[0] = x/y * vc.Fxyz[1]; vc.Fxyz[2] = z/y * vc.Fxyz[1]; } /* Get a expanded color conversion object */ if ((luo = xicco->get_luobj(xicco, ICX_CLIP_NEAREST , func, intent, pcsor, order, &vc, NULL)) == NULL) error ("%d, %s",xicco->errc, xicco->err); luo->spaces(luo, &ins, &inn, &outs, &outn, &alg, NULL, NULL, NULL); } /* Establish the PCS range if we are filtering */ { double pcsmin[3], pcsmax[3]; /* PCS range for filter stats array */ if (luo) { gamut *csgam; if ((csgam = luo->get_gamut(luo, 20.0)) == NULL) error("Getting the gamut of the source colorspace failed"); csgam->getrange(csgam, pcsmin, pcsmax); csgam->del(csgam); } else { pcsmin[0] = 0.0; pcsmax[0] = 100.0; pcsmin[1] = -128.0; pcsmax[1] = 128.0; pcsmin[2] = -128.0; pcsmax[2] = 128.0; } if (verb) printf("PCS range = %f..%f, %f..%f. %f..%f\n\n", pcsmin[0], pcsmax[0], pcsmin[1], pcsmax[1], pcsmin[2], pcsmax[2]); /* Allocate and initialize the filter */ set_fminmax(pcsmin, pcsmax); } /* - - - - - - - - - - - - - - - */ /* Open up input tiff file ready for reading */ /* Got arguments, so setup to process the file */ if ((rh = TIFFOpen(in_name, "r")) == NULL) error("error opening read file '%s'",in_name); TIFFGetField(rh, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(rh, TIFFTAG_IMAGELENGTH, &height); TIFFGetField(rh, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); TIFFGetField(rh, TIFFTAG_BITSPERSAMPLE, &bitspersample); if (bitspersample != 8 && bitspersample != 16) error("TIFF Input file must be 8 bit/channel"); TIFFGetFieldDefaulted(rh, TIFFTAG_EXTRASAMPLES, &extrasamples, &extrainfo); TIFFGetField(rh, TIFFTAG_PHOTOMETRIC, &photometric); if (inn != (samplesperpixel-extrasamples)) error ("TIFF Input file has %d input chanels mismatched to colorspace '%s'", samplesperpixel, icm2str(icmColorSpaceSignature, ins)); if ((tcs = TiffPhotometric2ColorSpaceSignature(&cvt, &sign_mask, photometric, bitspersample, samplesperpixel, extrasamples)) == 0) error("Can't handle TIFF file photometric %s", Photometric2str(photometric)); if (tcs != ins) { if (luo != NULL) error("TIFF photometric '%s' doesn't match ICC input colorspace '%s' !", Photometric2str(photometric), icm2str(icmColorSpaceSignature,ins)); else error("No profile provided and TIFF photometric '%s' isn't Lab !", Photometric2str(photometric)); } TIFFGetField(rh, TIFFTAG_PLANARCONFIG, &pconfig); if (pconfig != PLANARCONFIG_CONTIG) error ("TIFF Input file must be planar"); TIFFGetField(rh, TIFFTAG_RESOLUTIONUNIT, &resunits); TIFFGetField(rh, TIFFTAG_XRESOLUTION, &resx); TIFFGetField(rh, TIFFTAG_YRESOLUTION, &resy); if (verb) { printf("Input TIFF file '%s'\n",in_name); printf("TIFF file colorspace is %s\n",icm2str(icmColorSpaceSignature,tcs)); printf("TIFF file photometric is %s\n",Photometric2str(photometric)); printf("\n"); } /* - - - - - - - - - - - - - - - */ /* Process colors to translate */ /* (Should fix this to process a group of lines at a time ?) */ nipoints = width * height; // if ((inp = malloc(sizeof(co) * nipoints)) == NULL) // error("Unable to allocate co array"); inbuf = _TIFFmalloc(TIFFScanlineSize(rh)); for (i = y = 0; y < height; y++) { /* Read in the next line */ if (TIFFReadScanline(rh, inbuf, y, 0) < 0) error ("Failed to read TIFF line %d",y); /* Do floating point conversion */ for (x = 0; x < width; x++) { int e; double in[MAX_CHAN], out[MAX_CHAN]; if (bitspersample == 8) { for (e = 0; e < samplesperpixel; e++) { int v = ((unsigned char *)inbuf)[x * samplesperpixel + e]; if (sign_mask & (1 << i)) /* Treat input as signed */ v = (v & 0x80) ? v - 0x80 : v + 0x80; in[e] = v/255.0; } } else { for (e = 0; e < samplesperpixel; e++) { int v = ((unsigned short *)inbuf)[x * samplesperpixel + e]; if (sign_mask & (1 << i)) /* Treat input as signed */ v = (v & 0x8000) ? v - 0x8000 : v + 0x8000; in[e] = v/65535.0; } } if (cvt != NULL) { /* Undo TIFF encoding */ cvt(in, in); } if (luo != NULL) { if ((rv = luo->lookup(luo, out, in)) > 1) error ("%d, %s",icco->errc,icco->err); if (outs == icSigXYZData) /* Convert to Lab */ icmXYZ2Lab(&icco->header->illuminant, out, out); } else { for (e = 0; e < samplesperpixel; e++) out[e] = in[e]; } //printf("~1 %f %f %f -> %f %f %f\n", in[0], in[1], in[2], out[0], out[1], out[2]); add_fpixel(out); #ifdef NEVER /* Store PCS value in array */ inp[i].v[0] = out[0]; inp[i].v[1] = out[1]; inp[i].v[2] = out[2]; i++; #endif } } _TIFFfree(inbuf); TIFFClose(rh); /* Close Input file */ /* Done with lookup object */ if (luo != NULL) { luo->del(luo); xicco->del(xicco); /* Expansion wrapper */ icco->del(icco); /* Icc */ } nipoints = flush_filter(verb, 80.0); if ((inp = malloc(sizeof(co) * nipoints)) == NULL) error("Unable to allocate co array"); get_filter(inp); printf("~1 There are %d points\n",nipoints); //for (i = 0; i < nipoints; i++) //printf("~1 point %d = %f %f %f\n", i, inp[i].v[0], inp[i].v[1], inp[i].v[2]); del_filter(); /* Create the locus */ { double s0[3], s1[3]; double t0[3], t1[3]; double mm[3][4]; double im[3][4]; int gres[MXDI] = { 256 } ; if (usevec) { double max = -1e6; double min = 1e6; double dist; icmScale3(vec, vec, 1.0/icmNorm3(vec)); /* Locate the two furthest distant points measured along the vector */ for (i = 0; i < nipoints; i++) { double tt; tt = icmDot3(vec, inp[i].v); if (tt > max) { max = tt; icmAry2Ary(s1, inp[i].v); } if (tt < min) { min = tt; icmAry2Ary(s0, inp[i].v); } } dist = icmNorm33sq(s0, s1); printf("~1 most distant in vector %f %f %f = %f %f %f -> %f %f %f dist %f\n", vec[0], vec[1], vec[2], s0[0], s0[1], s0[2], s1[0], s1[1], s1[2], sqrt(dist)); t0[0] = 0.0; t0[1] = 0.0; t0[2] = 0.0; t1[0] = sqrt(dist); t1[1] = 0.0; t1[2] = 0.0; } else { double dist = 0.0; /* Locate the two furthest distant points (brute force) */ for (i = 0; i < (nipoints-1); i++) { for (j = i+1; j < nipoints; j++) { double tt; if ((tt = icmNorm33sq(inp[i].v, inp[j].v)) > dist) { dist = tt; icmAry2Ary(s0, inp[i].v); icmAry2Ary(s1, inp[j].v); } } } printf("~1 most distant = %f %f %f -> %f %f %f dist %f\n", s0[0], s0[1], s0[2], s1[0], s1[1], s1[2], sqrt(dist)); t0[0] = 0.0; t0[1] = 0.0; t0[2] = 0.0; t1[0] = sqrt(dist); t1[1] = 0.0; t1[2] = 0.0; } /* Transform our direction vector to the L* axis, and create inverse too */ icmVecRotMat(mm, s1, s0, t1, t0); icmVecRotMat(im, t1, t0, s1, s0); /* Setup for rspl to create smoothed locus */ for (i = 0; i < nipoints; i++) { icmMul3By3x4(inp[i].v, mm, inp[i].v); inp[i].p[0] = inp[i].v[0]; inp[i].v[0] = inp[i].v[1]; inp[i].v[1] = inp[i].v[2]; //printf("~1 point %d = %f -> %f %f\n", i, inp[i].p[0], inp[i].v[0], inp[i].v[1]); } /* Create rspl */ if ((rr = new_rspl(RSPL_NOFLAGS, 1, 2)) == NULL) error("Creating rspl failed"); rr->fit_rspl(rr, RSPL_NOFLAGS,inp, nipoints, NULL, NULL, gres, NULL, NULL, 5.0, NULL, NULL); #ifdef DEBUG_PLOT { #define XRES 100 double xx[XRES]; double y1[XRES]; double y2[XRES]; for (i = 0; i < XRES; i++) { co pp; double x; x = i/(double)(XRES-1); xx[i] = x * (t1[0] - t0[0]); pp.p[0] = xx[i]; rr->interp(rr, &pp); y1[i] = pp.v[0]; y2[i] = pp.v[1]; } do_plot(xx,y1,y2,NULL,XRES); } #endif /* DEBUG_PLOT */ free(inp); nopoints = t1[0] / DE_SPACE; if (nopoints < 2) nopoints = 2; /* Create the output points */ if ((outp = malloc(sizeof(co) * nopoints)) == NULL) error("Unable to allocate co array"); /* Setup initial division of locus */ for (i = 0; i < nopoints; i++) { double xx; xx = i/(double)(nopoints-1); xx *= (t1[0] - t0[0]); outp[i].p[0] = xx; //printf("~1 div %d = %f\n",i,outp[i].p[0]); } for (i = 0; i < (nopoints-1); i++) { outp[i].p[1] = outp[i+1].p[0] - outp[i].p[0]; //printf("~1 del div %d = %f\n",i,outp[i].p[1]); } /* Itterate until the delta between samples is even */ for (j = 0; j < 10; j++) { double alen, minl, maxl; double tdiv; alen = 0.0; minl = 1e38; maxl = -1.0; for (i = 0; i < nopoints; i++) { rr->interp(rr, &outp[i]); outp[i].v[2] = outp[i].v[1]; outp[i].v[1] = outp[i].v[0]; outp[i].v[0] = outp[i].p[0]; icmMul3By3x4(outp[i].v, im, outp[i].v); //printf("~1 locus pnt %d = %f %f %f\n", i,outp[i].v[0],outp[i].v[1],outp[i].v[1]); if (i > 0) { double tt[3], len; icmSub3(tt, outp[i].v, outp[i-1].v); len = icmNorm3(tt); outp[i-1].p[2] = len; if (len > maxl) maxl = len; if (len < minl) minl = len; alen += len; } } alen /= (nopoints-1.0); printf("~1 itter %d, alen = %f, minl = %f, maxl = %f\n",j,alen,minl,maxl); /* Adjust spacing */ tdiv = 0.0; for (i = 0; i < (nopoints-1); i++) { outp[i].p[1] *= pow(alen/outp[i].p[2], 1.0); tdiv += outp[i].p[1]; } //printf("~1 tdiv = %f\n",tdiv); for (i = 0; i < (nopoints-1); i++) { outp[i].p[1] *= (t1[0] - t0[0])/tdiv; //printf("~1 del div %d = %f\n",i,outp[i].p[1]); } tdiv = 0.0; for (i = 0; i < (nopoints-1); i++) { tdiv += outp[i].p[1]; } //printf("~1 tdiv now = %f\n",tdiv); for (i = 1; i < nopoints; i++) { outp[i].p[0] = outp[i-1].p[0] + outp[i-1].p[1]; //printf("~1 div %d = %f\n",i,outp[i].p[0]); } } /* Write the CGATS file */ { time_t clk = time(0); struct tm *tsp = localtime(&clk); char *atm = asctime(tsp); /* Ascii time */ cgats *pp; pp = new_cgats(); /* Create a CGATS structure */ pp->add_other(pp, "TS"); /* Test Set */ pp->add_table(pp, tt_other, 0); /* Add the first table for target points */ pp->add_kword(pp, 0, "DESCRIPTOR", "Argyll Test Point set",NULL); pp->add_kword(pp, 0, "ORIGINATOR", "Argyll tiffgmts", NULL); atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ pp->add_kword(pp, 0, "CREATED",atm, NULL); pp->add_field(pp, 0, "SAMPLE_ID", cs_t); pp->add_field(pp, 0, "LAB_L", r_t); pp->add_field(pp, 0, "LAB_A", r_t); pp->add_field(pp, 0, "LAB_B", r_t); for (i = 0; i < nopoints; i++) { char buf[100]; cgats_set_elem ary[1 + 3]; sprintf(buf,"%d",i+1); ary[0].c = buf; ary[1 + 0].d = outp[i].v[0]; ary[1 + 1].d = outp[i].v[1]; ary[1 + 2].d = outp[i].v[2]; pp->add_setarr(pp, 0, ary); } if (pp->write_name(pp, out_name)) error("Write error : %s",pp->err); } /* Create the VRML file */ if (dovrml) { vrml *vv; strcpy(xl,".wrl"); printf("Output vrml file '%s'\n",out_name); if ((vv = new_vrml(out_name, doaxes)) == NULL) error ("Creating VRML object failed"); #ifdef NEVER vv->start_line_set(vv); for (i = 0; i < nopoints; i++) { vv->add_vertex(vv, outp[i].v); } vv->make_lines(vv, nopoints); #else for (i = 1; i < nopoints; i++) { vv->add_cone(vv, outp[i-1].v, outp[i].v, NULL, 0.5); } #endif vv->del(vv); } free(outp); } rr->del(rr); return 0; }
/* Might be easier to close it and re-open it ? */ static icxLuBase * set_icxLuMatrix( xicc *xicp, icmLuBase *plu, /* Pointer to Lu we are expanding (ours) */ int flags, /* white/black point flags */ int nodp, /* Number of points */ cow *ipoints, /* Array of input points in XYZ space */ double dispLuminance, /* > 0.0 if display luminance value and is known */ double wpscale, /* > 0.0 if input white point is to be scaled */ int quality /* Quality metric, 0..3 */ ) { icxLuMatrix *p; /* Object being created */ icc *icco = xicp->pp; /* Underlying icc object */ icmLuMatrix *pmlu = (icmLuMatrix *)plu; /* icc matrix lookup object */ int luflags = 0; /* icxLuMatrix alloc clip, merge flags */ int isLinear = 0; /* NZ if pure linear, gamma = 1.0 */ int isGamma = 0; /* NZ if gamma rather than shaper */ int isShTRC = 0; /* NZ if shared TRCs */ int inputChan = 3; /* Must be RGB like */ int outputChan = 3; /* Must be the PCS */ icmHeader *h = icco->header; /* Pointer to icc header */ int rsplflags = 0; /* Flags for scattered data rspl */ int e, f, i, j; int maxits = 200; /* Optimisation stop params */ double stopon = 0.01; /* Absolute delta E change to stop on */ cow *points; /* Copy of ipoints */ mxopt os; /* Optimisation information */ double rerr; #ifdef DEBUG_PLOT #define XRES 100 double xx[XRES]; double y1[XRES]; #endif /* DEBUG_PLOT */ if (flags & ICX_VERBOSE) rsplflags |= RSPL_VERBOSE; luflags = flags; /* Transfer straight though ? */ /* Check out some things about the profile */ { icmCurve *wor, *wog, *wob; wor = pmlu->redCurve; wog = pmlu->greenCurve; wob = pmlu->blueCurve; if (wor == wog) { if (wog != wob) { xicp->errc = 1; sprintf(xicp->err,"icx_set_matrix: TRC sharing is inconsistent"); return NULL; } isShTRC = 1; } if (wor->flag != wog->flag || wog->flag != wob->flag) { xicp->errc = 1; sprintf(xicp->err,"icx_set_matrix: TRC type is inconsistent"); return NULL; } if (wor->flag == icmCurveGamma) { isGamma = 1; } if (flags & ICX_NO_IN_SHP_LUTS) { isLinear = 1; } } /* Do basic icxLu creation and initialisation */ if ((p = alloc_icxLuMatrix(xicp, plu, 0, luflags)) == NULL) return NULL; p->func = icmFwd; /* Assumed by caller */ /* Get the effective spaces of underlying icm, and set icx the same */ plu->spaces(plu, &p->ins, NULL, &p->outs, NULL, NULL, &p->intent, NULL, &p->pcs, NULL); /* For set_icx the effective pcs has to be the same as the native pcs */ /* Sanity check for matrix */ if (p->pcs != icSigXYZData) { p->pp->errc = 1; sprintf(p->pp->err,"Can't create matrix profile with PCS of Lab !"); p->del((icxLuBase *)p); return NULL; } /* In general the native and effective ranges of the icx will be the same as the */ /* underlying icm lookup object. */ p->plu->get_lutranges(p->plu, p->ninmin, p->ninmax, p->noutmin, p->noutmax); p->plu->get_ranges(p->plu, p->inmin, p->inmax, p->outmin, p->outmax); /* If we have a Jab PCS override, reflect this in the effective icx range. */ /* Note that the ab ranges are nominal. They will exceed this range */ /* for colors representable in L*a*b* PCS */ if (p->ins == icxSigJabData) { p->inmin[0] = 0.0; p->inmax[0] = 100.0; p->inmin[1] = -128.0; p->inmax[1] = 128.0; p->inmin[2] = -128.0; p->inmax[2] = 128.0; } else if (p->outs == icxSigJabData) { p->outmin[0] = 0.0; p->outmax[0] = 100.0; p->outmin[1] = -128.0; p->outmax[1] = 128.0; p->outmin[2] = -128.0; p->outmax[2] = 128.0; } /* ------------------------------- */ /* Allocate the array passed to fit_rspl() */ if ((points = (cow *)malloc(sizeof(cow) * nodp)) == NULL) { p->pp->errc = 2; sprintf(p->pp->err,"Allocation of scattered coordinate array failed"); p->del((icxLuBase *)p); return NULL; } /* Setup points ready for optimisation */ for (i = 0; i < nodp; i++) { for (e = 0; e < inputChan; e++) points[i].p[e] = ipoints[i].p[e]; for (f = 0; f < outputChan; f++) points[i].v[f] = ipoints[i].v[f]; points[i].w = ipoints[i].w; /* Make sure its Lab for delta E calculation */ icmXYZ2Lab(&icmD50, points[i].v, points[i].v); } /* Setup for optimising run */ if (flags & ICX_VERBOSE) os.verb = 1; else os.verb = 0; os.points = points; os.nodp = nodp; os.isShTRC = 0; /* Set quality/effort factors */ if (quality >= 3) { /* Ultra high */ os.norders = 20; maxits = 5000; stopon = 0.000001; } else if (quality == 2) { /* High */ os.norders = 15; maxits = 2000; stopon = 0.00001; } else if (quality == 1) { /* Medium */ os.norders = 10; maxits = 1000; stopon = 0.0001; } else { /* Low */ os.norders = 5; maxits = 500; stopon = 0.001; } if (os.norders > MXNORDERS) os.norders = MXNORDERS; /* Set initial optimisation values */ os.v[0] = 0.4; os.v[1] = 0.4; os.v[2] = 0.2; /* Matrix */ os.v[3] = 0.2; os.v[4] = 0.8; os.v[5] = 0.1; os.v[6] = 0.02; os.v[7] = 0.15; os.v[8] = 1.3; if (isLinear) { /* Just gamma curve */ os.isLinear = 1; os.isGamma = 1; os.optdim = 9; os.v[9] = os.v[10] = os.v[11] = 1.0; /* Linear */ } else if (isGamma) { /* Just gamma curve */ os.isLinear = 0; os.isGamma = 1; os.optdim = 12; os.v[9] = os.v[10] = os.v[11] = 2.4; /* Gamma */ } else { /* Creating input curves */ os.isLinear = 0; os.isGamma = 0; os.optdim = 12 + 3 * os.norders; os.v[9] = os.v[10] = os.v[11] = 0.0; /* Offset */ os.v[12] = os.v[13] = os.v[14] = 2.0; /* 0th Harmonic */ for (i = 15; i < os.optdim; i++) os.v[i] = 0.0; /* Higher orders */ } /* Set search area to starting values */ for (j = 0; j < os.optdim; j++) os.sa[j] = 0.2; /* Matrix, Gamma, Offsets, harmonics */ if (isShTRC) { /* Adjust things for shared */ os.isShTRC = 1; if (os.optdim > 9) { /* Pack red paramenters down */ for (i = 9; i < os.optdim; i++) { os.v[i] = os.v[(i - 9) * 3 + 9]; os.sa[i] = os.sa[(i - 9) * 3 + 9]; } os.optdim = ((os.optdim - 9)/3) + 9; } } if (os.verb) { if (os.isLinear) printf("Creating matrix...\n"); else printf("Creating matrix and curves...\n"); } if (powell(&rerr, os.optdim, os.v, os.sa, stopon, maxits, mxoptfunc, (void *)&os, mxprogfunc, (void *)&os) != 0) warning("Powell failed to converge, residual error = %f",rerr); #ifdef NEVER printf("Matrix = %f %f %f\n",os.v[0], os.v[1], os.v[2]); printf(" %f %f %f\n",os.v[3], os.v[4], os.v[5]); printf(" %f %f %f\n",os.v[6], os.v[7], os.v[8]); if (!isLinear) { /* Creating input curves */ if (isGamma) { /* Creating input curves */ if (isShTRC) printf("Gamma = %f\n",os.v[9]); else printf("Gamma = %f %f %f\n",os.v[9], os.v[10], os.v[11]); } else { /* Creating input curves */ if (isShTRC) printf("Offset = %f\n",os.v[9]); else printf("Offset = %f %f %f\n",os.v[9], os.v[10], os.v[11]); for (j = 0; j < os.norders; j++) { if (isShTRC) printf("%d harmonics = %f\n",j, os.v[10 + j]); else printf("%d harmonics = %f %f %f\n",j, os.v[12 + j * 3], os.v[13 + j * 3], os.v[14 + j * 3]); } } } #endif /* NEVER */ /* Deal with white/black points */ if (flags & (ICX_SET_WHITE | ICX_SET_BLACK)) { double wp[3]; /* Absolute White point in XYZ */ double bp[3]; /* Absolute Black point in XYZ */ if (flags & ICX_VERBOSE) printf("Find white & black points\n"); icmXYZ2Ary(wp, icmD50); /* Set a default value - D50 */ icmXYZ2Ary(bp, icmBlack); /* Set a default value - absolute black */ /* Figure out the device values for white */ if (h->deviceClass == icSigInputClass) { double dwhite[MXDI], dblack[MXDI]; /* Device white and black values */ double Lmax = -1e60; double Lmin = 1e60; /* We assume that the input target is well behaved, */ /* and that it includes a white and black point patch, */ /* and that they have the extreme L values */ /* NOTE that this may not be the best approach ! It may be better to average the chromaticity of all the neutral seeming patches, since the whitest patch may have (for instance) a blue tint. */ /* Discover the white and black points */ for (i = 0; i < nodp; i++) { if (points[i].v[0] > Lmax) { Lmax = wp[0] = points[i].v[0]; wp[1] = points[i].v[1]; wp[2] = points[i].v[2]; dwhite[0] = points[i].p[0]; dwhite[1] = points[i].p[1]; dwhite[2] = points[i].p[2]; } if (points[i].v[0] < Lmin) { Lmin = bp[0] = points[i].v[0]; bp[1] = points[i].v[1]; bp[2] = points[i].v[2]; dblack[0] = points[i].p[0]; dblack[1] = points[i].p[1]; dblack[2] = points[i].p[2]; } } /* Lookup device white/black values in model */ mxmfunc(&os, os.v, wp, dwhite); mxmfunc(&os, os.v, bp, dblack); /* If we were given an input white point scale factor, apply it */ if (wpscale >= 0.0) { wp[0] *= wpscale; wp[1] *= wpscale; wp[2] *= wpscale; } } else { /* Assume Monitor class */ switch(h->colorSpace) { case icSigCmyData: { double cmy[3]; /* Lookup white value */ for (e = 0; e < inputChan; e++) cmy[e] = 0.0; mxmfunc(&os, os.v, wp, cmy); if (flags & ICX_VERBOSE) printf("Initial white point = %f %f %f\n",wp[0],wp[1],wp[2]); /* Lookup black value */ for (e = 0; e < inputChan; e++) cmy[e] = 1.0; mxmfunc(&os, os.v, bp, cmy); if (flags & ICX_VERBOSE) printf("Initial black point = %f %f %f\n",bp[0],bp[1],bp[2]); break; } case icSigRgbData: { double rgb[3]; /* Lookup white value */ for (e = 0; e < inputChan; e++) rgb[e] = 1.0; mxmfunc(&os, os.v, wp, rgb); if (flags & ICX_VERBOSE) printf("Initial white point = %f %f %f\n",wp[0],wp[1],wp[2]); /* Lookup black value */ for (e = 0; e < inputChan; e++) rgb[e] = 0.0; mxmfunc(&os, os.v, bp, rgb); if (flags & ICX_VERBOSE) printf("Initial black point = %f %f %f\n",bp[0],bp[1],bp[2]); break; } default: { xicp->errc = 1; sprintf(xicp->err,"set_icxLuMatrix: can't handle color space %s", icm2str(icmColorSpaceSignature, h->colorSpace)); p->del((icxLuBase *)p); return NULL; break; } } } /* If this is a display, adjust the white point to be */ /* exactly Y = 1.0, and compensate the matrix, dispLuminance */ /* and black point accordingly. */ if (h->deviceClass == icSigDisplayClass) { double scale = 1.0/wp[1]; int i; for (i = 0; i < 9; i++) { os.v[i] *= scale; } dispLuminance *= wp[1]; for (i = 0; i < 3; i++) { wp[i] *= scale; bp[i] *= scale; } } /* Absolute luminance tag */ if (flags & ICX_WRITE_WBL && h->deviceClass == icSigDisplayClass && dispLuminance > 0.0) { icmXYZArray *wo; if ((wo = (icmXYZArray *)icco->read_tag( icco, icSigLuminanceTag)) == NULL) { xicp->errc = 1; sprintf(xicp->err,"icx_set_luminance: couldn't find luminance tag"); p->del((icxLuBase *)p); return NULL; } if (wo->ttype != icSigXYZArrayType) { xicp->errc = 1; sprintf(xicp->err,"luminance: tag has wrong type"); p->del((icxLuBase *)p); return NULL; } wo->size = 1; wo->allocate((icmBase *)wo); /* Allocate space */ wo->data[0].X = 0.0; wo->data[0].Y = dispLuminance; wo->data[0].Z = 0.0; if (flags & ICX_VERBOSE) printf("Display Luminance = %f\n", wo->data[0].Y); } /* Write white and black tags */ if ((flags & ICX_WRITE_WBL) && (flags & ICX_SET_WHITE)) { /* White Point Tag: */ icmXYZArray *wo; if ((wo = (icmXYZArray *)icco->read_tag( icco, icSigMediaWhitePointTag)) == NULL) { xicp->errc = 1; sprintf(xicp->err,"icx_set_white_black: couldn't find white tag"); p->del((icxLuBase *)p); return NULL; } if (wo->ttype != icSigXYZArrayType) { xicp->errc = 1; sprintf(xicp->err,"icx_set_white_black: white tag has wrong type"); p->del((icxLuBase *)p); return NULL; } wo->size = 1; wo->allocate((icmBase *)wo); /* Allocate space */ wo->data[0].X = wp[0]; wo->data[0].Y = wp[1]; wo->data[0].Z = wp[2]; if (flags & ICX_VERBOSE) printf("White point XYZ = %f %f %f\n",wp[0],wp[1],wp[2]); } if ((flags & ICX_WRITE_WBL) && (flags & ICX_SET_BLACK)) { /* Black Point Tag: */ icmXYZArray *wo; if ((wo = (icmXYZArray *)icco->read_tag( icco, icSigMediaBlackPointTag)) == NULL) { xicp->errc = 1; sprintf(xicp->err,"icx_set_white_black: couldn't find black tag"); p->del((icxLuBase *)p); return NULL; } if (wo->ttype != icSigXYZArrayType) { xicp->errc = 1; sprintf(xicp->err,"icx_set_white_black: black tag has wrong type"); p->del((icxLuBase *)p); return NULL; } wo->size = 1; wo->allocate((icmBase *)wo); /* Allocate space */ wo->data[0].X = bp[0]; wo->data[0].Y = bp[1]; wo->data[0].Z = bp[2]; if (flags & ICX_VERBOSE) printf("Black point XYZ = %f %f %f\n",bp[0],bp[1],bp[2]); } /* Fix matrix to be relative to D50 white point, rather than absolute */ if (flags & ICX_SET_WHITE) { icmXYZNumber swp; double mat[3][3]; if (flags & ICX_VERBOSE) printf("Fixup matrix for white point\n"); icmAry2XYZ(swp, wp); /* Transfer from parameter to matrix */ mat[0][0] = os.v[0]; mat[0][1] = os.v[1]; mat[0][2] = os.v[2]; mat[1][0] = os.v[3]; mat[1][1] = os.v[4]; mat[1][2] = os.v[5]; mat[2][0] = os.v[6]; mat[2][1] = os.v[7]; mat[2][2] = os.v[8]; /* Adapt matrix */ icmChromAdaptMatrix(ICM_CAM_MULMATRIX | ICM_CAM_BRADFORD, icmD50, swp, mat); /* Transfer back to parameters */ os.v[0] = mat[0][0]; os.v[1] = mat[0][1]; os.v[2] = mat[0][2]; os.v[3] = mat[1][0]; os.v[4] = mat[1][1]; os.v[5] = mat[1][2]; os.v[6] = mat[2][0]; os.v[7] = mat[2][1]; os.v[8] = mat[2][2]; if (flags & ICX_VERBOSE) { printf("After white point adjust:\n"); printf("Matrix = %f %f %f\n",os.v[0], os.v[1], os.v[2]); printf(" %f %f %f\n",os.v[3], os.v[4], os.v[5]); printf(" %f %f %f\n",os.v[6], os.v[7], os.v[8]); } } } if (flags & ICX_VERBOSE) printf("Done gamma/shaper and matrix creation\n"); /* Write the gamma/shaper and matrix to the icc memory structures */ if (!isGamma) { /* Creating input curves */ unsigned int ui; icmCurve *wor, *wog, *wob; wor = pmlu->redCurve; wog = pmlu->greenCurve; wob = pmlu->blueCurve; for (ui = 0; ui < wor->size; ui++) { double in, rgb[3]; for (j = 0; j < 3; j++) { in = (double)ui / (wor->size - 1.0); mxmfunc1(&os, j, os.v, &rgb[j], &in); } wor->data[ui] = rgb[0]; /* Curve values 0.0 - 1.0 */ if (!isShTRC) { wog->data[ui] = rgb[1]; wob->data[ui] = rgb[2]; } } #ifdef DEBUG_PLOT /* Display the result fit */ for (j = 0; j < 3; j++) { for (i = 0; i < XRES; i++) { double x, y; xx[i] = x = i/(double)(XRES-1); mxmfunc1(&os, j, os.v, &y, &x); y1[i] = y; } do_plot(xx,y1,NULL,NULL,XRES); } #endif /* DEBUG_PLOT */ } else { /* Gamma */ icmCurve *wor, *wog, *wob; wor = pmlu->redCurve; wog = pmlu->greenCurve; wob = pmlu->blueCurve; wor->data[0] = os.v[9]; /* Gamma values */ if (!isShTRC) { wog->data[0] = os.v[10]; wob->data[0] = os.v[11]; } } /* Matrix values */ { icmXYZArray *wor, *wog, *wob; wor = pmlu->redColrnt; wog = pmlu->greenColrnt; wob = pmlu->blueColrnt; wor->data[0].X = os.v[0]; wor->data[0].Y = os.v[3]; wor->data[0].Z = os.v[6]; wog->data[0].X = os.v[1]; wog->data[0].Y = os.v[4]; wog->data[0].Z = os.v[7]; wob->data[0].X = os.v[2]; wob->data[0].Y = os.v[5]; wob->data[0].Z = os.v[8]; /* Load into pmlu matrix and inverse ??? */ } /* Free the coordinate lists */ free(points); if (flags & ICX_VERBOSE) printf("Profile done\n"); return (icxLuBase *)p; }
int main( int argc, char *argv[] ) { int fa,nfa; /* argument we're looking at */ int verb = 0; int chan = 0; /* Chosen channel to plot against */ char in_name[100]; char *buf, *outc; int ti; cgats *cgf = NULL; /* cgats file data */ int isLab = 0; /* cgats output is Lab, else XYZ */ char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" }; int npat; /* Number of patches */ inkmask nmask; /* Device inkmask */ int nchan; /* Number of input chanels */ char *bident; /* Base ident */ int chix[ICX_MXINKS]; /* Device chanel indexes */ int pcsix[3]; /* Device chanel indexes */ pval *pat; /* patch values */ int i, j; error_program = argv[0]; if (argc < 2) usage(); /* 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 */ } } } /* Verbosity */ if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { verb = 1; } else if (argv[fa][1] >= '0' && argv[fa][1] <= '9') { chan = argv[fa][1] - '0'; } else if (argv[fa][1] == '?') usage(); else usage(); } else break; } if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(in_name,argv[fa]); /* Open CIE target values */ cgf = new_cgats(); /* Create a CGATS structure */ cgf->add_other(cgf, "CTI3");/* our special input type is Calibration Target Information 3 */ if (cgf->read_name(cgf, in_name)) error("CGATS file read error %s on file '%s'",cgf->err, in_name); if (cgf->ntables == 0 || cgf->t[0].tt != tt_other || cgf->t[0].oi != 0) error ("Profile file '%s' isn't a CTI3 format file",in_name); if (cgf->ntables < 1) error ("Input file '%s' doesn't contain at least one table",in_name); if ((ti = cgf->find_kword(cgf, 0, "COLOR_REP")) < 0) error("Input file doesn't contain keyword COLOR_REPS"); if ((buf = strdup(cgf->t[0].kdata[ti])) == NULL) error("Malloc failed"); /* Split COLOR_REP into device and PCS space */ if ((outc = strchr(buf, '_')) == NULL) error("COLOR_REP '%s' invalid", cgf->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)", cgf->t[0].kdata[ti]); if ((nmask = icx_char2inkmask(buf)) == 0) { error ("File '%s' keyword COLOR_REPS has unknown device value '%s'",in_name,buf); } free(buf); nchan = icx_noofinks(nmask); bident = icx_inkmask2char(nmask, 0); /* Base ident (No possible 'i') */ /* Find device fields */ for (j = 0; j < nchan; j++) { int ii, 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 = cgf->find_field(cgf, 0, fname)) < 0) error ("Input file doesn't contain field %s",fname); if (cgf->t[0].ftype[ii] != r_t) error ("Field %s is wrong type",fname); chix[j] = ii; } /* Find PCS fields */ for (j = 0; j < 3; j++) { int ii; if ((ii = cgf->find_field(cgf, 0, isLab ? labfname[j] : xyzfname[j])) < 0) error ("Input file doesn't contain field %s",isLab ? labfname[j] : xyzfname[j]); if (cgf->t[0].ftype[ii] != r_t) error ("Field %s is wrong type",isLab ? labfname[j] : xyzfname[j]); pcsix[j] = ii; } npat = cgf->t[0].nsets; /* Number of patches */ if (npat <= 0) error("No sets of data in file '%s'",in_name); /* Allocate arrays to hold test patch input and output values */ if ((pat = (pval *)malloc(sizeof(pval) * npat)) == NULL) error("Malloc failed - pat[]"); /* Grab all the values */ for (i = 0; i < npat; i++) { pat[i].v[0] = *((double *)cgf->t[0].fdata[i][pcsix[0]]); pat[i].v[1] = *((double *)cgf->t[0].fdata[i][pcsix[1]]); pat[i].v[2] = *((double *)cgf->t[0].fdata[i][pcsix[2]]); if (!isLab) { pat[i].v[0] /= 100.0; /* Normalise XYZ to range 0.0 - 1.0 */ pat[i].v[1] /= 100.0; pat[i].v[2] /= 100.0; } if (!isLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */ icmXYZ2Lab(&icmD50, pat[i].v, pat[i].v); } for (j = 0; j < nchan; j++) { pat[i].d[j] = *((double *)cgf->t[0].fdata[i][chix[j]]); } } /* Sort by the selected channel */ #define HEAP_COMPARE(A,B) (A.d[chan] < B.d[chan]) HEAPSORT(pval, pat, npat); #undef HEAP_COMPARE /* Create the plot */ { int i; double *xx; double *y0; double *y1; double *y2; if ((xx = (double *)malloc(sizeof(double) * npat)) == NULL) error("Malloc failed - xx[]"); if ((y0 = (double *)malloc(sizeof(double) * npat)) == NULL) error("Malloc failed - y0[]"); if ((y1 = (double *)malloc(sizeof(double) * npat)) == NULL) error("Malloc failed - y1[]"); if ((y2 = (double *)malloc(sizeof(double) * npat)) == NULL) error("Malloc failed - y2[]"); for (i = 0; i < npat; i++) { xx[i] = pat[i].d[chan]; y0[i] = pat[i].v[0]; y1[i] = 50 + pat[i].v[1]/2.0; y2[i] = 50 + pat[i].v[2]/2.0; // printf("~1 %d: xx = %f, y = %f %f %f\n",i,xx[i],y0[i],y1[i],y2[i]); } do_plot6(xx,y0,y1,NULL,NULL,y2,NULL,npat); free(y2); free(y1); free(y0); free(xx); } free(pat); cgf->del(cgf); return 0; }
/* Create a ccmx from measurements. return nz on error. */ static int create_ccmx(ccmx *p, char *desc, /* General description (optional) */ char *inst, /* Instrument description to copy from */ char *disp, /* Display make and model (optional) */ disptech dtech, /* Display technology enum */ int refrmode, /* Display refresh mode, -1 = unknown, 0 = n, 1 = yes */ int cbid, /* Display type calibration base index, 0 = unknown */ char *sel, /* UI selector characters - NULL for none */ char *refd, /* Reference spectrometer description (optional) */ int oem, /* NZ if OEM source */ int npat, /* Number of samples in following arrays */ double (*refs)[3], /* Array of XYZ values from spectrometer */ double (*cols)[3] /* Array of XYZ values from colorimeter */ ) { int i, mxix; double maxy = -1e6; cntx cx; double cp[9], sa[9]; if ((p->desc = desc) != NULL && (p->desc = strdup(desc)) == NULL) { sprintf(p->err, "create_ccmx: malloc failed"); return 2; } if ((p->inst = inst) != NULL && (p->inst = strdup(inst)) == NULL) { sprintf(p->err, "create_ccmx: malloc failed"); return 2; } if ((p->disp = disp) != NULL && (p->disp = strdup(disp)) == NULL) { sprintf(p->err, "create_ccmx: malloc failed"); return 2; } p->dtech = dtech; p->refrmode = refrmode; p->cc_cbid = cbid; if (sel != NULL) { if ((p->sel = strdup(sel)) == NULL) { sprintf(p->err, "create_ccmx: malloc sel failed"); return 2; } } if ((p->ref = refd) != NULL && (p->ref = strdup(refd)) == NULL) { sprintf(p->err, "create_ccmx: malloc failed"); return 2; } p->oem = oem; /* Find the white patch */ cx.npat = npat; cx.refs = refs; cx.cols = cols; for (i = 0; i < npat; i++) { if (refs[i][1] > maxy) { maxy = refs[i][1]; cx.wix = i; } } #ifdef DEBUG printf("white = %f %f %f\n",refs[cx.wix][0],refs[cx.wix][1],refs[cx.wix][1]); #endif cx.wh.X = refs[cx.wix][0]; cx.wh.Y = refs[cx.wix][1]; cx.wh.Z = refs[cx.wix][2]; /* Starting matrix */ cp[0] = 1.0; cp[1] = 0.0; cp[2] = 0.0; cp[3] = 0.0; cp[4] = 1.0; cp[5] = 0.0; cp[6] = 0.0; cp[7] = 0.0; cp[8] = 1.0; for (i = 0; i < 9; i++) sa[i] = 0.1; if (powell(NULL, 9, cp, sa, 1e-6, 2000, optf, &cx, NULL, NULL) < 0.0) { sprintf(p->err, "create_ccmx: powell() failed"); return 1; } p->matrix[0][0] = cp[0]; p->matrix[0][1] = cp[1]; p->matrix[0][2] = cp[2]; p->matrix[1][0] = cp[3]; p->matrix[1][1] = cp[4]; p->matrix[1][2] = cp[5]; p->matrix[2][0] = cp[6]; p->matrix[2][1] = cp[7]; p->matrix[2][2] = cp[8]; /* Compute the average and max errors */ p->av_err = p->mx_err = 0.0; for (i = 0; i < npat; i++) { double tlab[3], xyz[3], lab[3], de; icmXYZ2Lab(&cx.wh, tlab, cx.refs[i]); icmMulBy3x3(xyz, p->matrix, cx.cols[i]); icmXYZ2Lab(&cx.wh, lab, xyz); de = icmCIE94(tlab, lab); p->av_err += de; if (de > p->mx_err) { p->mx_err = de; mxix = i; } //printf("~1 %d: de %f, tlab %f %f %f, lab %f %f %f\n",i,de,tlab[0], tlab[1], tlab[2], lab[0], lab[1], lab[2]); } p->av_err /= (double)npat; //printf("~1 max error is index %d\n",mxix); #ifdef DEBUG printf("Average error %f, max %f\n",p->av_err,p->mx_err); printf("Correction matrix is:\n"); printf(" %f %f %f\n", cp[0], cp[1], cp[2]); printf(" %f %f %f\n", cp[3], cp[4], cp[5]); printf(" %f %f %f\n", cp[6], cp[7], cp[8]); #endif return 0; }
static int do_spec(char *name, xspect *sp) { int i; double xyz[3]; /* Color temperature */ double Yxy[3]; double Lab[3]; /* D50 Lab value */ double xx[XRES]; double y1[XRES]; double cct, vct; double cct_xyz[3], vct_xyz[3]; double cct_lab[3], vct_lab[3]; icmXYZNumber wp; double de; printf("\n"); /* Compute XYZ of illuminant */ if (icx_ill_sp2XYZ(xyz, icxOT_CIE_1931_2, NULL, icxIT_custom, 0, sp) != 0) error ("icx_sp_temp2XYZ returned error"); icmXYZ2Yxy(Yxy, xyz); icmXYZ2Lab(&icmD50, Lab, xyz); printf("Type = %s\n",name); printf("XYZ = %f %f %f, x,y = %f %f\n", xyz[0], xyz[1], xyz[2], Yxy[1], Yxy[2]); printf("D50 L*a*b* = %f %f %f\n", Lab[0], Lab[1], Lab[2]); /* Compute CCT */ if ((cct = icx_XYZ2ill_ct(cct_xyz, BBTYPE, icxOT_CIE_1931_2, NULL, xyz, NULL, 0)) < 0) error ("Got bad cct\n"); /* Compute VCT */ if ((vct = icx_XYZ2ill_ct(vct_xyz, BBTYPE, icxOT_CIE_1931_2, NULL, xyz, NULL, 1)) < 0) error ("Got bad vct\n"); #ifdef PLANKIAN printf("CCT = %f, VCT = %f\n",cct, vct); #else printf("CDT = %f, VDT = %f\n",cct, vct); #endif { int invalid = 0; double cri; cri = icx_CIE1995_CRI(&invalid, sp); printf("CRI = %.1f%s\n",cri,invalid ? " (Invalid)" : ""); } /* Use modern color difference - gives a better visual match */ icmAry2XYZ(wp, vct_xyz); icmXYZ2Lab(&wp, cct_lab, cct_xyz); icmXYZ2Lab(&wp, vct_lab, vct_xyz); de = icmCIE2K(cct_lab, vct_lab); printf("CIEDE2000 Delta E = %f\n",de); /* Plot spectrum out */ for (i = 0; i < XRES; i++) { double ww; ww = (sp->spec_wl_long - sp->spec_wl_short) * ((double)i/(XRES-1.0)) + sp->spec_wl_short; xx[i] = ww; y1[i] = value_xspect(sp, ww); } do_plot(xx,y1,NULL,NULL,i); return 0; }
int main( int argc, char *argv[] ) { int fa,nfa; /* argument we're looking at */ int k; int verb = 0; char in_name[100] = { '\000' }; /* Spectrum name */ double temp; xspect sp; /* Spectra */ icxIllumeType ilType; char buf[200]; error_program = argv[0]; /* 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 */ } } } /* Verbosity */ if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { verb = 1; } else { usage(); } } else break; } if (fa < argc && argv[fa][0] != '-') strcpy(in_name,argv[fa]); #ifdef NEVER /* hack test */ { #define NTESTS 5 int i; double cct, vct; double de1, de2; double xyz[NTESTS][3] = { { 92.250000, 97.750000, 89.650000 }, /* Rogers spotread verification result */ { 94.197915, 97.686362, 80.560411, }, /* Rogers target */ { 93.51, 97.12, 80.86 }, /* Rogers test ? */ { 75.31, 78.20, 64.59 }, /* Mac LCD test */ { 42.98, 44.62, 36.95 } /* Hitachie CRT test */ }; double axyz[3]; double lab[3], alab[3]; for (i = 0; i < NTESTS; i++) { /* Compute CCT */ if ((cct = icx_XYZ2ill_ct(axyz, icxIT_Ptemp, icxOT_CIE_1931_2, NULL, xyz[i], NULL, 0)) < 0) error ("Got bad cct\n"); axyz[0] /= axyz[1]; axyz[2] /= axyz[1]; axyz[1] /= axyz[1]; icmXYZ2Lab(&icmD50, alab, axyz); axyz[0] = xyz[i][0] / xyz[i][1]; axyz[2] = xyz[i][2] / xyz[i][1]; axyz[1] = xyz[i][1] / xyz[i][1]; icmXYZ2Lab(&icmD50, lab, axyz); de1 = icmCIE2K(lab, alab); /* Compute VCT */ if ((vct = icx_XYZ2ill_ct(axyz, icxIT_Ptemp, icxOT_CIE_1931_2, NULL, xyz[i], NULL, 1)) < 0) error ("Got bad vct\n"); axyz[0] /= axyz[1]; axyz[2] /= axyz[1]; axyz[1] /= axyz[1]; icmXYZ2Lab(&icmD50, alab, axyz); axyz[0] = xyz[i][0] / xyz[i][1]; axyz[2] = xyz[i][2] / xyz[i][1]; axyz[1] = xyz[i][1] / xyz[i][1]; icmXYZ2Lab(&icmD50, lab, axyz); de2 = icmCIE2K(lab, alab); printf("XYZ %f %f %f, CCT = %f de %f, VCT = %f de %f\n",xyz[i][0], xyz[i][1], xyz[i][2], cct, de1, vct, de1); /* Compute CCT */ if ((cct = icx_XYZ2ill_ct(axyz, icxIT_Dtemp, icxOT_CIE_1931_2, NULL, xyz[i], NULL, 0)) < 0) error ("Got bad cct\n"); axyz[0] /= axyz[1]; axyz[2] /= axyz[1]; axyz[1] /= axyz[1]; icmXYZ2Lab(&icmD50, alab, axyz); axyz[0] = xyz[i][0] / xyz[i][1]; axyz[2] = xyz[i][2] / xyz[i][1]; axyz[1] = xyz[i][1] / xyz[i][1]; icmXYZ2Lab(&icmD50, lab, axyz); de1 = icmCIE2K(lab, alab); /* Compute VCT */ if ((vct = icx_XYZ2ill_ct(axyz, icxIT_Dtemp, icxOT_CIE_1931_2, NULL, xyz[i], NULL, 1)) < 0) error ("Got bad vct\n"); axyz[0] /= axyz[1]; axyz[2] /= axyz[1]; axyz[1] /= axyz[1]; icmXYZ2Lab(&icmD50, alab, axyz); axyz[0] = xyz[i][0] / xyz[i][1]; axyz[2] = xyz[i][2] / xyz[i][1]; axyz[1] = xyz[i][1] / xyz[i][1]; icmXYZ2Lab(&icmD50, lab, axyz); de2 = icmCIE2K(lab, alab); printf("XYZ %f %f %f, CDT = %f de %f, VDT = %f de %f\n",xyz[i][0], xyz[i][1], xyz[i][2], cct, de1, vct, de2); } } #endif if (in_name[0] != '\000') { if (read_xspect(&sp, in_name) != 0) error ("Unable to read custom spectrum '%s'",in_name); sprintf(buf, "File '%s'",in_name); do_spec(buf, &sp); } else { /* For each standard illuminant */ for (ilType = icxIT_A; ilType <= icxIT_F10; ilType++) { char *inm = NULL; switch (ilType) { case icxIT_A: inm = "A"; break; case icxIT_C: inm = "C"; break; case icxIT_D50: inm = "D50"; break; case icxIT_D65: inm = "D65"; break; case icxIT_F5: inm = "F5"; break; case icxIT_F8: inm = "F8"; break; case icxIT_F10: inm = "F10"; break; default: inm = "Unknown"; break; break; } if (standardIlluminant(&sp, ilType, 0) != 0) error ("standardIlluminant returned error"); do_spec(inm, &sp); } /* For each material and illuminant */ for (temp = 2500; temp <= 9000; temp += 500) { for (k = 0; k < 2; k++) { ilType = k == 0 ? icxIT_Dtemp : icxIT_Ptemp; if (standardIlluminant(&sp, ilType, temp) != 0) error ("standardIlluminant returned error"); sprintf(buf, "%s at %f", k == 0 ? "Daylight" : "Black body", temp); do_spec(buf, &sp); } } } return 0; }
int main(int argc, char *argv[]) { int i,j,k; int fa,nfa; /* current argument we're looking at */ int verb = 0; static char inname[200] = { 0 }; /* Input cgats file base name */ static char outname[200] = { 0 }; /* Output cgats file base name */ cgats *icg; /* input cgats structure */ cgats *ocg; /* output cgats structure */ time_t clk = time(0); struct tm *tsp = localtime(&clk); char *atm = asctime(tsp); /* Ascii time */ int ti; /* Temporary index */ edatas ed; /* Optimising function data structure */ double resid[4]; double presid,dresid; double sarea; error_program = argv[0]; if (argc <= 1) usage(); /* 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(); else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') verb = 1; else usage(); } else break; } /* Get the file name argument */ if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(inname,argv[fa]); strcat(inname,".ti3"); strcpy(outname,argv[fa]); strcat(outname,".pr1"); 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->err); 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 exactly one table"); if ((ed.npat = icg->t[0].nsets) <= 0) error ("No sets of data"); if (verb) { printf("No of test patches = %d\n",ed.npat); } if ((ed.cols = (col *)malloc(sizeof(col) * ed.npat)) == NULL) error("Malloc failed!"); /* Setup output cgats file */ /* This is a simple interpolation CMYK -> XYZ device profile */ ocg = new_cgats(); /* Create a CGATS structure */ ocg->add_other(ocg, "PROF1"); /* our special type is Profile type 1 */ ocg->add_table(ocg, tt_other, 0); /* Start the first table */ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Device Profile Type 1",NULL); ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll sprof", NULL); atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ ocg->add_kword(ocg, 0, "CREATED",atm, NULL); /* Figure out the color space */ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0) error ("Input file doesn't contain keyword COLOR_REPS"); if (strcmp(icg->t[0].kdata[ti],"CMYK_XYZ") == 0) { int ci, mi, yi, ki; int Xi, Yi, Zi; if ((ci = icg->find_field(icg, 0, "CMYK_C")) < 0) error ("Input file doesn't contain field CMYK_C"); if (icg->t[0].ftype[ci] != r_t) error ("Field CMYK_C is wrong type"); if ((mi = icg->find_field(icg, 0, "CMYK_M")) < 0) error ("Input file doesn't contain field CMYK_M"); if (icg->t[0].ftype[mi] != r_t) error ("Field CMYK_M is wrong type"); if ((yi = icg->find_field(icg, 0, "CMYK_Y")) < 0) error ("Input file doesn't contain field CMYK_Y"); if (icg->t[0].ftype[yi] != r_t) error ("Field CMYK_Y is wrong type"); if ((ki = icg->find_field(icg, 0, "CMYK_K")) < 0) error ("Input file doesn't contain field CMYK_K"); if (icg->t[0].ftype[ki] != r_t) error ("Field CMYK_K is wrong type"); if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0) error ("Input file doesn't contain field XYZ_X"); if (icg->t[0].ftype[Xi] != r_t) error ("Field XYZ_X is wrong type"); if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0) error ("Input file doesn't contain field XYZ_Y"); if (icg->t[0].ftype[Yi] != r_t) error ("Field XYZ_Y is wrong type"); if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0) error ("Input file doesn't contain field XYZ_Z"); if (icg->t[0].ftype[Zi] != r_t) error ("Field XYZ_Z is wrong type"); for (i = 0; i < ed.npat; i++) { double XYZ[3]; ed.cols[i].c = *((double *)icg->t[0].fdata[i][ci]) / 100.0; ed.cols[i].m = *((double *)icg->t[0].fdata[i][mi]) / 100.0; ed.cols[i].y = *((double *)icg->t[0].fdata[i][yi]) / 100.0; ed.cols[i].k = *((double *)icg->t[0].fdata[i][ki]) / 100.0; XYZ[0] = *((double *)icg->t[0].fdata[i][Xi]) / 100.0; XYZ[1] = *((double *)icg->t[0].fdata[i][Yi]) / 100.0; XYZ[2] = *((double *)icg->t[0].fdata[i][Zi]) / 100.0; icmXYZ2Lab(&icmD50, ed.cols[i].Lab, XYZ); } /* Initialise the model */ ed.gam[0] = 1.0; /* First four are CMYK gamma values */ ed.gam[1] = 1.0; ed.gam[2] = 1.0; ed.gam[3] = 1.0; /* Initialise interpolation end points for each combination of primary, */ /* with all combinations close to black being represented by param[7]. */ ed.k[0][0] = .82; ed.k[1][0] = .83; ed.k[2][0] = .75; /* White */ ed.k[0][1] = .66; ed.k[1][1] = .72; ed.k[2][1] = .05; /* Y */ ed.k[0][2] = .27; ed.k[1][2] = .12; ed.k[2][2] = .06; /* M */ ed.k[0][3] = .27; ed.k[1][3] = .12; ed.k[2][3] = .00; /* MY */ ed.k[0][4] = .09; ed.k[1][4] = .13; ed.k[2][4] = .44; /* C */ ed.k[0][5] = .03; ed.k[1][5] = .10; ed.k[2][5] = .04; /* C Y */ ed.k[0][6] = .02; ed.k[1][6] = .01; ed.k[2][6] = .05; /* CM */ ed.k[0][7] = .01; ed.k[1][7] = .01; ed.k[2][7] = .01; /* Black */ sarea = 0.3; presid = dresid = 100.0; for (k=0; /* dresid > 0.0001 && */ k < 40; k++) { /* Untill we're done */ double sresid; double sr[8]; double p[8]; /* Adjust the gamma */ for (i = 0; i < 4; i++) sr[i] = 0.1; /* Device space search radius */ if (powell(&resid[3], 4, &ed.gam[0], sr, 0.1, 1000, efunc1, (void *)&ed, NULL, NULL) != 0) error ("Powell failed"); /* Adjust the primaries */ calc_bc(&ed); /* Calculate blend coefficients */ for (i = 0; i < 8; i++) sr[i] = 0.2; /* Device space search radius */ sresid = 99.0; for (j = 0; j < 3; j++) { /* For each of X, Y and Z */ ed.xyzi = j; for (i = 0; i < 8; i++) p[i] = ed.k[j][i]; printf("##############\n"); printf("XYZ = %d\n",j); if (powell(&resid[j], 8, p, sr, 0.1, 1000, efunc2, (void *)&ed, NULL, NULL) != 0) error ("Powell failed"); for (i = 0; i < 8; i++) ed.k[j][i] = p[i]; if (sresid > resid[j]) sresid = resid[j]; } dresid = presid - sresid; if (dresid < 0.0) dresid = 100.0; presid = sresid; printf("~1 presid = %f, sresid = %f, dresid = %f\n",presid, sresid, dresid); } /* Fields we want */ ocg->add_kword(ocg, 0, "DSPACE","CMYK", NULL); ocg->add_kword(ocg, 0, "DTYPE","PRINTER", NULL); ocg->add_field(ocg, 0, "PARAM_ID", i_t); ocg->add_field(ocg, 0, "PARAM", r_t); /* Output model parameters */ for (j = 0; j < 4; j++) ocg->add_set(ocg, 0, j, ed.gam[j]); for (j = 0; j < 3; j++) { for (i = 0; i < 8; i++) ocg->add_set(ocg, 0, 10 * (j + 1) + i, 100.0 * ed.k[j][i]); } if (verb) { double aver = 0.0; double maxer = 0.0; for (i = 0; i < ed.npat; i++) { double err = sqrt(ed.cols[i].err); if (err > maxer) maxer = err; aver += err; } aver = aver/((double)i); printf("Average fit error = %f, maximum = %f\n",aver,maxer); } } else if (strcmp(icg->t[0].kdata[ti],"RGB") == 0) { error ("We can't handle RGB !"); } else if (strcmp(icg->t[0].kdata[ti],"W") == 0) { error ("We can't handle Grey !"); } else error ("Input file keyword COLOR_REPS has unknown value"); if (ocg->write_name(ocg, outname)) error("Write error : %s",ocg->err); free(ed.cols); ocg->del(ocg); /* Clean up */ icg->del(icg); /* Clean up */ return 0; }