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 fa,nfa; /* argument we're looking at */ char in_name[100]; /* Raster file name */ char out_name[100]; /* Raster file name */ int slow = 0; int check = 0; int i, rv = 0; TIFF *rh = NULL, *wh = NULL; int x, y, width, height; /* Size of image */ uint16 samplesperpixel, bitspersample; int no_pmtc; /* Number of input photometrics */ uint16 photometric, pmtc[10]; /* Photometrics of input file, and input profile */ uint16 pconfig; /* Planar configuration */ uint16 resunits; float resx, resy; tdata_t *inbuf, *outbuf, *checkbuf; /* IMDI */ imdi *s = NULL; sucntx su; /* Setup context */ unsigned char *inp[MAX_CHAN]; unsigned char *outp[MAX_CHAN]; int clutres = 33; /* Error check */ int mxerr = 0; double avgerr = 0.0; double avgcount = 0.0; if (argc < 2) usage(); su.verb = 0; su.icombine = 0; su.ocombine = 0; su.link = 0; su.in.intent = icmDefaultIntent; su.out.intent = icmDefaultIntent; /* 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(); /* Slow, Precise */ else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { slow = 1; } /* Combine per channel curves */ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { su.icombine = 1; su.ocombine = 1; } /* Check curves */ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { check = 1; } /* Link profiles */ else if (argv[fa][1] == 'l' || argv[fa][1] == 'L') { su.link = 1; } /* Input profile Intent */ else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { fa = nfa; if (na == NULL) usage(); switch (na[0]) { case 'p': case 'P': su.in.intent = icPerceptual; break; case 'r': case 'R': su.in.intent = icRelativeColorimetric; break; case 's': case 'S': su.in.intent = icSaturation; break; case 'a': case 'A': su.in.intent = icAbsoluteColorimetric; break; default: usage(); } } /* Output profile Intent */ else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { fa = nfa; if (na == NULL) usage(); switch (na[0]) { case 'p': case 'P': su.out.intent = icPerceptual; break; case 'r': case 'R': su.out.intent = icRelativeColorimetric; break; case 's': case 'S': su.out.intent = icSaturation; break; case 'a': case 'A': su.out.intent = icAbsoluteColorimetric; break; default: usage(); } } /* Verbosity */ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { su.verb = 1; } else usage(); } else break; } if (su.link) { if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(su.in.name,argv[fa++]); if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(su.out.name,argv[fa++]); } else { if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(su.dev.name,argv[fa++]); } if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(in_name,argv[fa++]); if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(out_name,argv[fa++]); /* - - - - - - - - - - - - - - - - */ if (su.link) { icColorSpaceSignature natpcs; /* Open up the input device profile for reading */ if ((su.in.fp = new_icmFileStd_name(su.in.name,"r")) == NULL) error ("Can't open file '%s'",su.in.name); if ((su.in.c = new_icc()) == NULL) error ("Creation of Input profile ICC object failed"); /* Read header etc. */ if ((rv = su.in.c->read(su.in.c,su.in.fp,0)) != 0) error ("%d, %s on file '%s'",rv,su.in.c->err,su.in.name); su.in.h = su.in.c->header; /* Check that it is a suitable device input icc */ if (su.in.h->deviceClass != icSigInputClass && su.in.h->deviceClass != icSigDisplayClass && su.in.h->deviceClass != icSigOutputClass && su.in.h->deviceClass != icSigColorSpaceClass) /* For sRGB etc. */ error("Input profile isn't a device profile"); /* Get a conversion object */ if ((su.in.luo = su.in.c->get_luobj(su.in.c, icmFwd, su.in.intent, icSigLabData, icmLuOrdNorm)) == NULL) error ("%d, %s for profile '%s'",su.in.c->errc, su.in.c->err, su.in.name); /* Get details of conversion (Arguments may be NULL if info not needed) */ su.in.luo->spaces(su.in.luo, &su.ins, &su.id, NULL, NULL, &su.in.alg, NULL, NULL, NULL); /* Get native PCS space */ su.in.luo->lutspaces(su.in.luo, NULL, NULL, NULL, NULL, &natpcs); if (natpcs == icSigXYZData) { su.icombine = 1; /* XYZ is to non-linear to be a benefit */ } /* Open up the output device profile for reading */ if ((su.out.fp = new_icmFileStd_name(su.out.name,"r")) == NULL) error ("Can't open file '%s'",su.out.name); if ((su.out.c = new_icc()) == NULL) error ("Creation of Output profile ICC object failed"); /* Read header etc. */ if ((rv = su.out.c->read(su.out.c,su.out.fp,0)) != 0) error ("%d, %s on file '%s'",rv,su.out.c->err,su.out.name); su.out.h = su.out.c->header; /* Check that it is a suitable device output icc */ if (su.out.h->deviceClass != icSigInputClass && su.out.h->deviceClass != icSigDisplayClass && su.out.h->deviceClass != icSigOutputClass && su.out.h->deviceClass != icSigColorSpaceClass) /* For sRGB etc. */ error("Output profile isn't a device profile"); /* Get a conversion object */ if ((su.out.luo = su.out.c->get_luobj(su.out.c, icmBwd, su.out.intent, icSigLabData, icmLuOrdNorm)) == NULL) error ("%d, %s for profile '%s'",su.out.c->errc, su.out.c->err, su.out.name); /* Get details of conversion (Arguments may be NULL if info not needed) */ su.out.luo->spaces(su.out.luo, NULL, NULL, &su.outs, &su.od, &su.out.alg, NULL, NULL, NULL); /* Get native PCS space */ su.out.luo->lutspaces(su.out.luo, NULL, NULL, NULL, NULL, &natpcs); if (natpcs == icSigXYZData) { su.ocombine = 1; /* XYZ is to non-linear to be a benefit */ } /* See discussion in imdi/imdi_gen.c for ideal numbers */ /* Use "high quality" resolution numbers */ switch (su.id) { case 0: error ("Illegal number of input chanels"); case 1: clutres = 256; break; case 2: clutres = 256; break; case 3: clutres = 33; break; case 4: clutres = 18; break; case 5: clutres = 16; break; case 6: clutres = 9; break; case 7: clutres = 7; break; case 8: clutres = 6; break; deault: /* > 8 chan */ clutres = 3; break; } } else { icmLut *lut; /* ICC LUT table */ icmLuLut *luluo; /* LUT lookup object */ /* Open up the device link profile for reading */ if ((su.dev.fp = new_icmFileStd_name(su.dev.name,"r")) == NULL) error ("Can't open file '%s'",su.dev.name); if ((su.dev.c = new_icc()) == NULL) error ("Creation of ICC object failed"); if ((rv = su.dev.c->read(su.dev.c, su.dev.fp, 0)) != 0) error ("%d, %s",rv,su.dev.c->err); su.dev.h = su.dev.c->header; if (su.verb) { icmFile *op; if ((op = new_icmFileStd_fp(stdout)) == NULL) error ("Can't open stdout"); su.dev.h->dump(su.dev.h, op, 1); op->del(op); } /* Check that the profile is appropriate */ if (su.dev.h->deviceClass != icSigLinkClass) error("Profile isn't a device link profile"); /* Get a conversion object */ if ((su.dev.luo = su.dev.c->get_luobj(su.dev.c, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm)) == NULL) error ("%d, %s",su.dev.c->errc, su.dev.c->err); /* Get details of conversion (Arguments may be NULL if info not needed) */ su.dev.luo->spaces(su.dev.luo, &su.ins, &su.id, &su.outs, &su.od, &su.dev.alg, NULL, NULL, NULL); if (su.dev.alg != icmLutType) error ("DeviceLink profile doesn't have Lut !"); luluo = (icmLuLut *)su.dev.luo; /* Safe to coerce */ luluo->get_info(luluo, &lut, NULL, NULL, NULL); /* Get some details */ clutres = lut->clutPoints; /* Desired table resolution */ } /* - - - - - - - - - - - - - - - */ /* 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_BITSPERSAMPLE, &bitspersample); if (bitspersample != 8 && bitspersample != 16) { error("TIFF Input file must be 8 or 16 bit/channel"); } TIFFGetField(rh, TIFFTAG_PHOTOMETRIC, &photometric); if ((no_pmtc = ColorSpaceSignature2TiffPhotometric(pmtc, su.ins)) == 0) error("ICC input colorspace '%s' can't be handled by a TIFF file!", icm2str(icmColorSpaceSignature, su.ins)); for (i = 0; i < no_pmtc; i++) { if (pmtc[i] == photometric) break; /* Matches */ } if (i >= no_pmtc) { switch (no_pmtc) { case 1: error("ICC input colorspace '%s' doesn't match TIFF photometric '%s'!", icm2str(icmColorSpaceSignature, su.ins), Photometric2str(pmtc[0])); case 2: error("ICC input colorspace '%s' doesn't match TIFF photometric '%s' or '%s'!", icm2str(icmColorSpaceSignature, su.ins), Photometric2str(pmtc[0]), Photometric2str(pmtc[1])); default: error("ICC input colorspace '%s' doesn't match TIFF photometric '%s', '%s' or '%s'!", icm2str(icmColorSpaceSignature, su.ins), Photometric2str(pmtc[0]), Photometric2str(pmtc[1]), Photometric2str(pmtc[2])); } } TIFFGetField(rh, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); if (su.id != samplesperpixel) error ("TIFF Input file has %d input channels mismatched to colorspace '%s'", samplesperpixel, icm2str(icmColorSpaceSignature, su.ins)); 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 ((wh = TIFFOpen(out_name, "w")) == NULL) error("Can\'t create TIFF file '%s'!",out_name); TIFFSetField(wh, TIFFTAG_IMAGEWIDTH, width); TIFFSetField(wh, TIFFTAG_IMAGELENGTH, height); TIFFSetField(wh, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(wh, TIFFTAG_SAMPLESPERPIXEL, su.od); TIFFSetField(wh, TIFFTAG_BITSPERSAMPLE, bitspersample); TIFFSetField(wh, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); if ((no_pmtc = ColorSpaceSignature2TiffPhotometric(pmtc, su.outs)) == 0) error("TIFF file can't handle output colorspace '%s'!", icm2str(icmColorSpaceSignature, su.outs)); TIFFSetField(wh, TIFFTAG_PHOTOMETRIC, pmtc[0]); /* Use first returned */ if (pmtc[0] == PHOTOMETRIC_SEPARATED) { int iset; int inlen; char *inames; iset = ColorSpaceSignature2TiffInkset(su.outs, &inlen, &inames); if (iset != 0xffff && inlen > 0 && inames != NULL) { TIFFSetField(wh, TIFFTAG_INKSET, iset); if (inames != NULL) { TIFFSetField(wh, TIFFTAG_INKNAMES, inlen, inames); } } } TIFFSetField(wh, TIFFTAG_COMPRESSION, COMPRESSION_NONE); if (resunits) { TIFFSetField(wh, TIFFTAG_RESOLUTIONUNIT, resunits); TIFFSetField(wh, TIFFTAG_XRESOLUTION, resx); TIFFSetField(wh, TIFFTAG_YRESOLUTION, resy); } TIFFSetField(wh, TIFFTAG_IMAGEDESCRIPTION, "Color corrected by Argyll"); /* - - - - - - - - - - - - - - - */ /* Setup the imdi */ if (!slow) { s = new_imdi( su.id, /* Number of input dimensions */ su.od, /* Number of output dimensions */ /* Input pixel representation */ bitspersample == 8 ? pixint8 : pixint16, /* Output pixel representation */ 0x0, /* Treat every channel as unsigned */ bitspersample == 8 ? pixint8 : pixint16, 0x0, /* Treat every channel as unsigned */ clutres, /* Desired table resolution */ input_curve, /* Callback functions */ md_table, output_curve, (void *)&su /* Context to callbacks */ ); if (s == NULL) error("new_imdi failed"); } /* - - - - - - - - - - - - - - - */ /* Process colors to translate */ /* (Should fix this to process a group of lines at a time ?) */ inbuf = _TIFFmalloc(TIFFScanlineSize(rh)); outbuf = _TIFFmalloc(TIFFScanlineSize(wh)); if (check) checkbuf = _TIFFmalloc(TIFFScanlineSize(wh)); inp[0] = (unsigned char *)inbuf; outp[0] = (unsigned char *)outbuf; if (!slow) { /* Fast */ for (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 fast conversion */ s->interp(s, (void **)outp, (void **)inp, width); if (check) { /* Do floating point conversion */ for (x = 0; x < width; x++) { int i; double in[MAX_CHAN], out[MAX_CHAN]; if (bitspersample == 8) for (i = 0; i < su.id; i++) in[i] = ((unsigned char *)inbuf)[x * su.id + i]/255.0; else for (i = 0; i < su.id; i++) in[i] = ((unsigned short *)inbuf)[x * su.id + i]/65535.0; if (su.link) { if ((rv = su.in.luo->lookup(su.in.luo, out, in)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); if ((rv = su.out.luo->lookup(su.out.luo, out, out)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); } else { if ((rv = su.dev.luo->lookup(su.dev.luo, out, in)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); } if (bitspersample == 8) for (i = 0; i < su.od; i++) ((unsigned char *)checkbuf)[x * su.od + i] = (int)(out[i] * 255.0 + 0.5); else for (i = 0; i < su.od; i++) ((unsigned short *)checkbuf)[x * su.od + i] = (int)(out[i] * 65535.0 + 0.5); } /* Compute the errors */ for (x = 0; x < (width * su.od); x++) { int err; if (bitspersample == 8) err = ((unsigned char *)outbuf)[x] - ((unsigned char *)checkbuf)[x]; else err = ((unsigned short *)outbuf)[x] - ((unsigned short *)checkbuf)[x]; if (err < 0) err = -err; if (err > mxerr) mxerr = err; avgerr += (double)err; avgcount++; } } if (TIFFWriteScanline(wh, outbuf, y, 0) < 0) error ("Failed to write TIFF line %d",y); } } else { /* Slow but precise */ if (bitspersample == 8) { for (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 i; double in[MAX_CHAN], out[MAX_CHAN]; for (i = 0; i < su.id; i++) { in[i] = ((unsigned char *)inbuf)[x * su.id + i]/255.0; } if (su.link) { if ((rv = su.in.luo->lookup(su.in.luo, out, in)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); if ((rv = su.out.luo->lookup(su.out.luo, out, out)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); } else { if ((rv = su.dev.luo->lookup(su.dev.luo, out, in)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); } for (i = 0; i < su.od; i++) { double outi = out[i]; if (outi < 0.0) /* Protect against sillies */ outi = 0.0; else if (outi > 1.0) outi = 1.0; ((unsigned char *)outbuf)[x * su.od + i] = (int)(outi * 255.0 + 0.5); } } if (TIFFWriteScanline(wh, outbuf, y, 0) < 0) error ("Failed to write TIFF line %d",y); } } else if (bitspersample == 16) { for (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 i; double in[MAX_CHAN], out[MAX_CHAN]; for (i = 0; i < su.id; i++) { in[i] = ((unsigned short *)inbuf)[x * su.id + i]/65535.0; } if (su.link) { if ((rv = su.in.luo->lookup(su.in.luo, out, in)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); if ((rv = su.out.luo->lookup(su.out.luo, out, out)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); } else { if ((rv = su.dev.luo->lookup(su.dev.luo, out, in)) > 1) error ("%d, %s",su.dev.c->errc,su.dev.c->err); } for (i = 0; i < su.od; i++) { double outi = out[i]; if (outi < 0.0) /* Protect against sillies */ outi = 0.0; else if (outi > 1.0) outi = 1.0; ((unsigned short *)outbuf)[x * su.od + i] = (int)(outi * 65535.0 + 0.5); } } if (TIFFWriteScanline(wh, outbuf, y, 0) < 0) error ("Failed to write TIFF line %d",y); } } } if (check) { printf("Worst error = %d bits, average error = %f bits\n", mxerr, avgerr/avgcount); if (bitspersample == 8) printf("Worst error = %f%%, average error = %f%%\n", mxerr/2.55, avgerr/(2.55 * avgcount)); else printf("Worst error = %f%%, average error = %f%%\n", mxerr/655.35, avgerr/(655.35 * avgcount)); } /* Done with lookup object */ if (s != NULL) s->done(s); if (su.link) { su.in.luo->del(su.in.luo); su.in.c->del(su.in.c); su.in.fp->del(su.in.fp); su.out.luo->del(su.out.luo); su.out.c->del(su.out.c); su.out.fp->del(su.out.fp); } else { su.dev.luo->del(su.dev.luo); su.dev.c->del(su.dev.c); su.dev.fp->del(su.dev.fp); } _TIFFfree(inbuf); _TIFFfree(outbuf); if (check) _TIFFfree(checkbuf); TIFFClose(rh); /* Close Input file */ TIFFClose(wh); /* Close Output file */ return 0; }
int main(int argc, char *argv[]) { int fa,nfa; /* argument we're looking at */ char prof_name[100]; char *xl, out_name[100]; icmFile *fp; icc *icco; xicc *xicco; gamut *gam; int verb = 0; int rv = 0; int vrml = 0; int doaxes = 1; int docusps = 0; double gamres = GAMRES; /* Surface resolution */ int special = 0; /* Special surface plot */ int fl = 0; /* luobj flags */ icxInk ink; /* Ink parameters */ int tlimit = -1; /* Total ink limit as a % */ int klimit = -1; /* Black ink limit as a % */ 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; /* Lookup parameters */ icmLookupFunc func = icmFwd; /* Default */ icRenderingIntent intent = -1; /* Default */ icColorSpaceSignature pcsor = icSigLabData; /* Default */ icmLookupOrder order = icmLuOrdNorm; /* Default */ error_program = argv[0]; if (argc < 2) usage("Too few parameters"); /* 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(NULL); /* function */ else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { fa = nfa; if (na == NULL) usage("No parameter after flag -f"); switch (na[0]) { case 'f': case 'F': func = icmFwd; break; case 'b': case 'B': func = icmBwd; break; default: usage("Unrecognised parameter after flag -f"); } } /* Intent */ else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { fa = nfa; if (na == NULL) usage("No parameter after flag -i"); 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("Unrecognised parameter after flag -i"); } } /* Search order */ else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { fa = nfa; if (na == NULL) usage("No parameter after flag -o"); switch (na[0]) { case 'n': case 'N': order = icmLuOrdNorm; break; case 'r': case 'R': order = icmLuOrdRev; break; default: usage("Unrecognised parameter after flag -o"); } } /* PCS override */ else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { fa = nfa; if (na == NULL) usage("No parameter after flag -p"); switch (na[0]) { case 'l': pcsor = icSigLabData; break; case 'j': pcsor = icxSigJabData; break; default: usage("Unrecognised parameter after flag -p"); } } /* Verbosity */ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { verb = 1; } /* VRML output */ else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { vrml = 1; } /* No axis output in vrml */ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { doaxes = 0; } /* Do cusp markers in vrml */ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { docusps = 1; } /* Special */ else if (argv[fa][1] == 's' || argv[fa][1] == 'S') { special = 1; } /* Ink limit */ else if (argv[fa][1] == 'l') { fa = nfa; if (na == NULL) usage("No parameter after flag -l"); tlimit = atoi(na); } else if (argv[fa][1] == 'L') { fa = nfa; if (na == NULL) usage("No parameter after flag -L"); klimit = atoi(na); } /* Surface Detail */ else if (argv[fa][1] == 'd' || argv[fa][1] == 'D') { fa = nfa; if (na == NULL) usage("No parameter after flag -d"); gamres = atof(na); } /* Viewing conditions */ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { fa = nfa; if (na == NULL) usage("No parameter after flag -c"); #ifdef NEVER if (na[0] >= '0' && na[0] <= '9') { vc_e = atoi(na); } else #endif if (na[1] != ':') { if ((vc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) usage("Urecognised Enumerated Viewing conditions"); } else if (na[0] == 's' || na[0] == 'S') { if (na[1] != ':') usage("Unrecognised parameters after -cs"); 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("Unrecognised parameters after -cs:"); } 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("Unrecognised parameters after -cw"); } else if (na[0] == 'a' || na[0] == 'A') { if (na[1] != ':') usage("Unrecognised parameters after -ca"); vc_a = atof(na+2); } else if (na[0] == 'b' || na[0] == 'B') { if (na[1] != ':') usage("Unrecognised parameters after -cb"); 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("Unrecognised parameters after -cf"); } else usage("Unrecognised parameters after -c"); } else usage("Unknown flag"); } else break; } if (intent == -1) { if (pcsor == icxSigJabData) intent = icRelativeColorimetric; /* Default to icxAppearance */ else intent = icAbsoluteColorimetric; /* Default to icAbsoluteColorimetric */ } if (fa >= argc || argv[fa][0] == '-') usage("Expected profile name"); strcpy(prof_name,argv[fa]); /* Open up the profile for reading */ if ((fp = new_icmFileStd_name(prof_name,"r")) == NULL) error ("Can't open file '%s'",prof_name); if ((icco = new_icc()) == NULL) error ("Creation of ICC object failed"); if ((rv = icco->read(icco,fp,0)) != 0) error ("%d, %s",rv,icco->err); 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); } /* Wrap with an expanded icc */ if ((xicco = new_xicc(icco)) == NULL) error ("Creation of xicc failed"); /* Set the ink limits */ icxDefaultLimits(xicco, &ink.tlimit, tlimit/100.0, &ink.klimit, klimit/100.0); if (verb) { if (ink.tlimit >= 0.0) printf("Total ink limit assumed is %3.0f%%\n",100.0 * ink.tlimit); if (ink.klimit >= 0.0) printf("Black ink limit assumed is %3.0f%%\n",100.0 * ink.klimit); } /* Setup a safe ink generation (not used) */ ink.KonlyLmin = 0; /* Use normal black Lmin for locus */ ink.k_rule = icxKluma5k; ink.c.Ksmth = ICXINKDEFSMTH; /* Default smoothing */ ink.c.Kskew = ICXINKDEFSKEW; /* default curve skew */ ink.c.Kstle = 0.0; /* Min K at white end */ ink.c.Kstpo = 0.0; /* Start of transition is at white */ ink.c.Kenle = 1.0; /* Max K at black end */ ink.c.Kenpo = 1.0; /* End transition at black */ ink.c.Kshap = 1.0; /* Linear transition */ /* Setup the default viewing conditions */ if (xicc_enum_viewcond(xicco, &vc, -1, NULL, 0, NULL) == -2) error ("%d, %s",xicco->errc, xicco->err); if (vc_e != -1) if (xicc_enum_viewcond(xicco, &vc, vc_e, NULL, 0, NULL) == -2) 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]; } fl |= ICX_CLIP_NEAREST; /* Don't setup rev uncessarily */ #ifdef USE_CAM_CLIP_OPT fl |= ICX_CAM_CLIP; #endif #ifdef NEVER printf("~1 output space flags = 0x%x\n",fl); printf("~1 output space intent = %s\n",icx2str(icmRenderingIntent,intent)); printf("~1 output space pcs = %s\n",icx2str(icmColorSpaceSignature,pcsor)); printf("~1 output space viewing conditions =\n"); xicc_dump_viewcond(&vc); printf("~1 output space inking =\n"); xicc_dump_inking(&ink); #endif strcpy(out_name, prof_name); if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ xl = out_name + strlen(out_name); strcpy(xl,".gam"); /* Get a expanded color conversion object */ if ((luo = xicco->get_luobj(xicco, fl, func, intent, pcsor, order, &vc, &ink)) == NULL) error ("%d, %s",xicco->errc, xicco->err); if (special) { if (func != icmFwd) error("Must be forward direction for special plot"); strcpy(xl,".wrl"); diag_gamut(luo, gamres, doaxes, tlimit/100.0, klimit/100.0, out_name); } else { /* Creat a gamut surface */ if ((gam = luo->get_gamut(luo, gamres)) == NULL) error ("%d, %s",xicco->errc, xicco->err); if (gam->write_gam(gam,out_name)) error ("write gamut failed on '%s'",out_name); if (vrml) { strcpy(xl,".wrl"); if (gam->write_vrml(gam,out_name, doaxes, docusps)) error ("write vrml failed on '%s'",out_name); } if (verb) { printf("Total volume of gamut is %f cubic colorspace units\n",gam->volume(gam)); } gam->del(gam); } luo->del(luo); /* Done with lookup object */ xicco->del(xicco); /* Expansion wrapper */ icco->del(icco); /* Icc */ fp->del(fp); return 0; }
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; }
int gx_load_icc_profile(gs_cie_icc *picc_info) { stream * instrp = picc_info->instrp; icc * picc; icmLuBase * plu = NULL; icmFile *pfile = NULL; #if SAVEICCPROFILE unsigned int num_bytes; unsigned char *iccbuffer; FILE *fid; #endif /* verify that the file is legitimate */ if (picc_info->file_id != (instrp->read_id | instrp->write_id)) return_error(gs_error_ioerror); /* * Load the top-level ICC profile. * * If an ICC profile fails to load, generate an error. * * Testing demonstrates, however, Acrobat Reader silently * ignores the error and uses the alternate color space. * This behaviour is implemented by catching the error using * a stopped context from within the interpreter (gs_icc.ps). * * Failure to allocate the top-level profile object is considered * a limitcheck rather than a VMerror, as profile data structures * are stored in "foreign" memory. */ if ((picc = new_icc()) == NULL) return_error(gs_error_limitcheck); { icProfileClassSignature profile_class; icColorSpaceSignature cspace_type; gs_vector3 * ppt; pfile = gx_wrap_icc_stream (instrp); if ((picc->read(picc, pfile, 0)) != 0) goto return_rangecheck; #if SAVEICCPROFILE num_bytes = picc->header->size; iccbuffer = (unsigned char *) malloc(num_bytes); pfile->seek(pfile,0); pfile->read(pfile,iccbuffer,1,num_bytes); fid = fopen("DumpedICC.icm","wb"); fwrite(iccbuffer,sizeof(unsigned char),num_bytes,fid); fclose(fid); free(iccbuffer); #endif /* verify the profile type */ profile_class = picc->header->deviceClass; if ( profile_class != icSigInputClass && profile_class != icSigDisplayClass && profile_class != icSigOutputClass && profile_class != icSigColorSpaceClass ) goto return_rangecheck; /* verify the profile connection space */ cspace_type = picc->header->pcs; if (cspace_type == icSigLabData) picc_info->pcs_is_cielab = true; else if (cspace_type == icSigXYZData) picc_info->pcs_is_cielab = false; else goto return_rangecheck; /* verify the source color space */ cspace_type = picc->header->colorSpace; if (cspace_type == icSigCmykData) { if (picc_info->num_components != 4) goto return_rangecheck; } else if ( cspace_type == icSigRgbData || cspace_type == icSigLabData ) { if (picc_info->num_components != 3) goto return_rangecheck; } else if (cspace_type == icSigGrayData) { if (picc_info->num_components != 1) goto return_rangecheck; } /* * Fetch the lookup object. * * PostScript and PDF deal with rendering intent as strictly a * rendering dictionary facility. ICC profiles allow a rendering * intent to be specified for both the input (device ==> pcs) and * output (pcs ==> device) operations. Hence, when using ICCBased * color spaces with PDF, no clue is provided as to which source * mapping to select. * * In the absence of other information, there are two possible * selections. If our understanding is correct, when relative * colorimetry is specified, the icclib code will map source * color values to XYZ or L*a*b* values such that the relationship * of the source color, relative to the source white and black * points, will be the same as the output colors and the * profile connection space illuminant (currently always D50) * and pure black ([0, 0, 0]). In this case, the white and black * points that should be listed in the color space are the * profile connection space illuminant (D50) and pure black. * * If absolute colorimetry is employed, the XYZ or L*a*b* values * generated will be absolute in the chromatic sense (they are * not literally "absolute", as we still must have overall * intensity information in order to determine weighted spectral * power levels). To achieve relative colorimetry for the output, * these colors must be evaluated relative to the source white * and black points. Hence, in this case, the appropriate white * and black points to list in the color space are the source * white and black points provided in the profile tag array. * * In this implementation, we will always request relative * colorimetry from the icclib, and so will use the profile * connection space illuminant and pure black as the white and * black points of the color space. This approach is somewhat * simpler, as it allows the color space white point to also * be used for L*a*b* to XYZ conversion (otherwise we would * need to store the profile connection space illuminant * separately for that purpose). The approach does reduce to * to some extent the range of mappings that can be achieved * via the color rendering dictionary, but for now we believe * this loss is not significant. * * For reasons that are not clear to us, the icclib code does * not support relative colorimetry for all color profiles. For * this reason, we specify icmDefaultIntent rather than * icRelativeColormetric. * * NB: We are not color experts; our understanding of this area * may well be incorrect. */ plu = picc->get_luobj( picc, icmFwd, icmDefaultIntent, 0, /* PCS override */ icmLuOrdNorm ); if (plu == NULL) goto return_rangecheck; /* * Get the appropriate white and black points. See the note on * rendering intent above for a discussion of why we are using * the profile space illuminant and pure black. (Pure black need * not be set explicitly, as it is the default.) */ ppt = &picc_info->common.points.WhitePoint; ppt->u = picc->header->illuminant.X; ppt->v = picc->header->illuminant.Y; ppt->w = picc->header->illuminant.Z; picc_info->picc = picc; picc_info->plu = plu; picc_info->pfile = pfile; } return 0; return_rangecheck: if (plu != NULL) plu->del(plu); if (picc != NULL) picc->del(picc); if (pfile != NULL) pfile->del(pfile); return_error(gs_error_rangecheck); }
int main( int argc, char *argv[] ) { int fa,nfa; /* argument we're looking at */ int verb = 0; int cchan = -1; /* default all */ int dovrml = 0; int doaxes = 0; char in_name[500]; char out_name[500], *xl; icmFile *rd_fp; icc *wr_icco, *rd_icco; /* Keep object separate */ int rv = 0; /* Check variables */ icmLuBase *luo; icmLuLut *luluto; /* Lookup xLut type object */ int gres; /* Grid resolution */ icColorSpaceSignature ins, outs; /* Type of input and output spaces */ int inn; /* Number of input chanels */ icmLuAlgType alg; FILE *wrl; int dx[4]; /* Device index mapping */ int chan, cs, ce; 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; } /* VRML */ else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { dovrml = 1; } /* Cyan */ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { cchan = 0; } /* Magenta */ else if (argv[fa][1] == 'm' || argv[fa][1] == 'M') { cchan = 1; } /* Yellow */ else if (argv[fa][1] == 'y' || argv[fa][1] == 'Y') { cchan = 2; } /* Black */ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { cchan = 3; } else if (argv[fa][1] == '?') usage(); else usage(); } else break; } if (fa >= argc || argv[fa][0] == '-') usage(); strcpy(in_name,argv[fa]); strcpy(out_name, in_name); if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */ xl = out_name + strlen(out_name); strcpy(xl,".wrl"); /* Open up the file for reading */ if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL) error ("Read: Can't open file '%s'",in_name); if ((rd_icco = new_icc()) == NULL) error ("Read: Creation of ICC object failed"); /* Read the header and tag list */ if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0) error ("Read: %d, %s",rv,rd_icco->err); /* Get a Device to PCS conversion object */ if ((luo = rd_icco->get_luobj(rd_icco, icmFwd, icRelativeColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) { if ((luo = rd_icco->get_luobj(rd_icco, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL) error ("%d, %s",rd_icco->errc, rd_icco->err); } /* Get details of conversion */ luo->spaces(luo, &ins, &inn, &outs, NULL, &alg, NULL, NULL, NULL); if (alg != icmLutType) { error("Expecting Lut based profile"); } if (ins != icSigCmykData) { error("Expecting CMYK device"); } if (outs != icSigLabData) { error("Expecting Lab PCS"); } luluto = (icmLuLut *)luo; /* Lookup xLut type object */ gres = luluto->lut->clutPoints; if (gres > MGR) { error("Can't handle grid resolution greater than %d\n",MGR); } if (dovrml) { wrl = start_vrml(out_name, doaxes); start_line_set(wrl); } /* For all the device chanels chosen */ if (cchan < 0) { cs = 0; ce = inn; } else { cs = cchan; ce = cs + 1; } for (chan = cs; chan < ce; chan++) { /* Check the monotonicity of the output for a given device input */ int co[4]; if (chan == 0) { dx[0] = 1; dx[1] = 2; dx[2] = 3; dx[3] = 0; /* Cyan is variable */ } else if (chan == 1) { dx[0] = 0; dx[1] = 2; dx[2] = 3; dx[3] = 1; /* Magenta is variable */ } else if (chan == 2) { dx[0] = 0; dx[1] = 1; dx[2] = 3; dx[3] = 2; /* Yellow is variable */ } else if (chan == 3) { dx[0] = 0; dx[1] = 1; dx[2] = 2; dx[3] = 3; /* Black is variable */ } /* Itterate throught the CMY clut grid points */ for (co[0] = 0; co[0] < gres; co[0]++) { for (co[1] = 0; co[1] < gres; co[1]++) { for (co[2] = 0; co[2] < gres; co[2]++) { int j, k, ck, nm; double dev[MGR][4]; double pcs[MGR][3]; double apcs[3], ss; /* Run up the variable axis */ for (ck = 0; ck < gres; ck++) { dev[ck][dx[0]] = co[0]/(gres-1.0); dev[ck][dx[1]] = co[1]/(gres-1.0); dev[ck][dx[2]] = co[2]/(gres-1.0); dev[ck][dx[3]] = ck/(gres-1.0); /* Device to PCS */ if ((rv = luluto->clut(luluto, pcs[ck], dev[ck])) > 1) error ("%d, %s",rd_icco->errc,rd_icco->err); // if (dovrml) // add_vertex(wrl, pcs[ck]); } /* Compute average vector direction */ for (ss = 0.0, k = 0; k < 3; k++) { double tt; tt = pcs[gres-1][k] - pcs[0][k]; ss += tt * tt; apcs[k] = tt; } for (k = 0; k < 3; k++) apcs[k] /= ss; /* Now compute the dot product for each vector, */ /* and check for reversals. */ j = 0; //printf("Checking CMYK %f %f %f %f Lab %f %f %f\n", // dev[j][0], dev[j][1], dev[j][2], dev[j][3], // pcs[j][0], pcs[j][1], pcs[j][2]); for (nm = 0, j = 1; j < gres; j++) { for (ss = 0.0, k = 0; k < 3; k++) /* Dot product */ ss += (pcs[j][k] - pcs[j-1][k]) * apcs[k]; //printf("Checking %f CMYK %f %f %f %f Lab %f %f %f\n", // ss, dev[j][0], dev[j][1], dev[j][2], dev[j][3], // pcs[j][0], pcs[j][1], pcs[j][2]); if (ss <= 0.0) { nm = 1; printf("NonMon %f at CMYK %f %f %f %f Lab %f %f %f\n", ss, dev[j][0], dev[j][1], dev[j][2], dev[j][3], pcs[j][0], pcs[j][1], pcs[j][2]); } } //printf("\n"); /* Display just the non mono threads */ if (nm && dovrml) { for (j = 0; j < gres; j++) add_vertex(wrl, pcs[j]); } if (verb) { printf("."); fflush(stdout); } } } } } if (dovrml) { make_lines(wrl, gres); end_vrml(wrl); } /* Done with lookup object */ luo->del(luo); rd_icco->del(rd_icco); rd_fp->del(rd_fp); return 0; }