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; }
/* 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; char out_name[1000]; icmFile *wr_fp; icc *wr_icco; int rv = 0; int verb = 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' || argv[fa][1] == 'V') { verb = 1; } else usage(); } else break; } if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(out_name,argv[fa]); /* ---------------------------------------- */ /* Create a matrix/shaper based XYZ profile */ /* ---------------------------------------- */ /* Open up the file for writing */ if ((wr_fp = new_icmFileStd_name(out_name,"w")) == NULL) error ("Write: Can't open file '%s'",out_name); if ((wr_icco = new_icc()) == NULL) error ("Write: Creation of ICC object failed"); /* Add all the tags required */ /* The header: */ { icmHeader *wh = wr_icco->header; /* Values that must be set before writing */ wh->deviceClass = icSigDisplayClass; wh->colorSpace = icSigRgbData; /* It's RGB space */ wh->pcs = icSigXYZData; wh->renderingIntent = icPerceptual; /* Values that should be set before writing */ wh->manufacturer = str2tag("????"); wh->model = str2tag("????"); } /* Profile Description Tag: */ { icmTextDescription *wo; char *dst; dst = "sRGB like Matrix Display profile"; if ((wo = (icmTextDescription *)wr_icco->add_tag( wr_icco, icSigProfileDescriptionTag, icSigTextDescriptionType)) == NULL) error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err); wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */ wo->scCode = 0; wo->scSize = strlen(dst)+1; if (wo->scSize > 67) error("Description scriptCode string longer than 67"); wo->allocate((icmBase *)wo);/* Allocate space */ strcpy(wo->desc, dst); /* Copy the string in */ strcpy((char *)wo->scDesc, dst); /* Copy the string in */ } /* Copyright Tag: */ { icmText *wo; char *crt = "Copyright tag goes here"; if ((wo = (icmText *)wr_icco->add_tag( wr_icco, icSigCopyrightTag, icSigTextType)) == NULL) error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->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 */ } /* Could add other relevant tags here, such as: Device Manufacturers Description Tag Device Model Description Tag Device technology Tag Viewing conditions Description Tag Viewing conditions Display Luminance Tag Measurement information Tag etc. */ /* Setup the primaries */ { /* Compute primaries as XYZ */ double wrgb[4][3] = { /* Primaries in Yxy from the standard */ { 1.0, 0.3127, 0.3290 }, /* White */ { 1.0, 0.6400, 0.3300 }, /* Red */ { 1.0, 0.3000, 0.6000 }, /* Green */ { 1.0, 0.1500, 0.0600 } /* Blue */ }; double mat[3][3]; int i; /* Convert yxy to normalised 3x3 matrix */ icmRGBYxyprim2matrix(wrgb[1], wrgb[2], wrgb[3], wrgb[0], mat, wrgb[0]); icmTranspose3x3(mat, mat); #ifdef NEVER /* Dump XYZ */ printf("sRGB: XYZ\n"); printf("{ %f, %f, %f }, /* Red */\n" "{ %f, %f, %f }, /* Green */\n" "{ %f, %f, %f }, /* Blue */\n" "{ %f, %f, %f } /* White */\n", mat[0][0], mat[0][1], mat[0][2], mat[1][0], mat[1][1], mat[1][2], mat[2][0], mat[2][1], mat[2][2], wrgb[0][0], wrgb[0][1], wrgb[0][2]); #endif #ifdef NEVER /* Dump xyz */ { double mat2[4][3]; double tt; for (i = 0; i < 3; i++) { tt = mat[i][0] + mat[i][1] + mat[i][2]; mat2[i][0] = mat[i][0] / tt; mat2[i][1] = mat[i][1] / tt; mat2[i][2] = mat[i][2] / tt; } tt = wrgb[0][0] + wrgb[0][1] + wrgb[0][2]; mat2[i][0] = wrgb[0][0] / tt; mat2[i][1] = wrgb[0][1] / tt; mat2[i][2] = wrgb[0][2] / tt; printf("sRGB: xyz\n"); printf("{ %f, %f, %f }, /* Red */\n" "{ %f, %f, %f }, /* Green */\n" "{ %f, %f, %f }, /* Blue */\n" "{ %f, %f, %f } /* White */\n", mat2[0][0], mat2[0][1], mat2[0][2], mat2[1][0], mat2[1][1], mat2[1][2], mat2[2][0], mat2[2][1], mat2[2][2], mat2[3][0], mat2[3][1], mat2[3][2]); } #endif /* White Point Tag: */ { icmXYZArray *wo; if ((wo = (icmXYZArray *)wr_icco->add_tag( wr_icco, icSigMediaWhitePointTag, icSigXYZArrayType)) == NULL) error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err); wo->size = 1; wo->allocate((icmBase *)wo); /* Allocate space */ wo->data[0].X = wrgb[0][0]; wo->data[0].Y = wrgb[0][1]; wo->data[0].Z = wrgb[0][2]; } /* Black Point Tag: */ { icmXYZArray *wo; if ((wo = (icmXYZArray *)wr_icco->add_tag( wr_icco, icSigMediaBlackPointTag, icSigXYZArrayType)) == NULL) error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err); wo->size = 1; wo->allocate((icmBase *)wo); /* Allocate space */ wo->data[0].X = 0.00; wo->data[0].Y = 0.00; wo->data[0].Z = 0.00; } /* Red, Green and Blue Colorant Tags: */ { icmXYZNumber white; icmXYZArray *wor, *wog, *wob; double fromAbs[3][3]; double d50m[3][3]; /* Convert to D50 adapated */ icmAry2XYZ(white, wrgb[0]); wr_icco->chromAdaptMatrix(wr_icco, ICM_CAM_NONE, icmD50, white, fromAbs); icmMulBy3x3(d50m[0], fromAbs, mat[0]); icmMulBy3x3(d50m[1], fromAbs, mat[1]); icmMulBy3x3(d50m[2], fromAbs, mat[2]); /* Make sure rounding doesn't wreck white point */ quantizeRGBprimsS15Fixed16(d50m); if ((wor = (icmXYZArray *)wr_icco->add_tag( wr_icco, icSigRedColorantTag, icSigXYZArrayType)) == NULL) error("add_tag failed: %d, %s",rv,wr_icco->err); if ((wog = (icmXYZArray *)wr_icco->add_tag( wr_icco, icSigGreenColorantTag, icSigXYZArrayType)) == NULL) error("add_tag failed: %d, %s",rv,wr_icco->err); if ((wob = (icmXYZArray *)wr_icco->add_tag( wr_icco, icSigBlueColorantTag, icSigXYZArrayType)) == NULL) error("add_tag failed: %d, %s",rv,wr_icco->err); wor->size = wog->size = wob->size = 1; wor->allocate((icmBase *)wor); /* Allocate space */ wog->allocate((icmBase *)wog); wob->allocate((icmBase *)wob); wor->data[0].X = d50m[0][0]; wor->data[0].Y = d50m[0][1]; wor->data[0].Z = d50m[0][2]; wog->data[0].X = d50m[1][0]; wog->data[0].Y = d50m[1][1]; wog->data[0].Z = d50m[1][2]; wob->data[0].X = d50m[2][0]; wob->data[0].Y = d50m[2][1]; wob->data[0].Z = d50m[2][2]; } } /* Red, Green and Blue Gamma Curve Tags: */ { icmCurve *wor, *wog, *wob; int i; if ((wor = (icmCurve *)wr_icco->add_tag( wr_icco, icSigRedTRCTag, icSigCurveType)) == NULL) error("add_tag failed: %d, %s",rv,wr_icco->err); wor->flag = icmCurveSpec; wor->size = 1024; wor->allocate((icmBase *)wor); /* Allocate space */ for (i = 0; i < wor->size; i++) wor->data[i] = gdv2dv(i/(wor->size-1.0)); /* Link other channels to the red */ if ((wog = (icmCurve *)wr_icco->link_tag( wr_icco, icSigGreenTRCTag, icSigRedTRCTag)) == NULL) error("link_tag failed: %d, %s",rv,wr_icco->err); if ((wob = (icmCurve *)wr_icco->link_tag( wr_icco, icSigBlueTRCTag, icSigRedTRCTag)) == NULL) error("link_tag failed: %d, %s",rv,wr_icco->err); } /* Write the file out */ if ((rv = wr_icco->write(wr_icco,wr_fp,0)) != 0) error ("Write file: %d, %s",rv,wr_icco->err); wr_icco->del(wr_icco); wr_fp->del(wr_fp); 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; }