summaryrefslogtreecommitdiff
path: root/profile/colprof.c
diff options
context:
space:
mode:
Diffstat (limited to 'profile/colprof.c')
-rw-r--r--profile/colprof.c1116
1 files changed, 1116 insertions, 0 deletions
diff --git a/profile/colprof.c b/profile/colprof.c
new file mode 100644
index 0000000..99e02cf
--- /dev/null
+++ b/profile/colprof.c
@@ -0,0 +1,1116 @@
+/*
+ * Argyll Color Correction System
+ * Color Device profile generator.
+ *
+ * Author: Graeme W. Gill
+ * Date: 15/2/97
+ *
+ * Copyright 1996-2011 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in the scattered test chart
+ * points, and interpolates them into a gridded
+ * forward ICC device profile, as well as creating
+ * backward conversions based on the forward grid.
+ *
+ * Preview profiles are not currently generated.
+ *
+ * The gamut cLUT should be implemented with xicc/rspl
+ */
+
+/*
+ * TTBD:
+ * Should allow ICC Device attributes to be set.
+ *
+ * Add Argyll private tag to record ink limit etc. to automate link parameters.
+ * Estimate ink limit from B2A tables if no private tag ?
+ * Add used option for black relative
+ * Add used option for separate high res reverse tables
+ * Fix 400% assumptions for > 4 color devices ?
+ *
+ * Should allow creating profiles from .MPP directly for <= 4 dev channels.
+ * Should allow creating profiles from existing ICC profiles (deprecate revfix ?)
+ *
+ * Should allow creating profiles >4 channels by providing .MPP for input,
+ * dev link .icm for psudo-dev to device & .ti3 for Pseudo-dev to PCS.
+ * Note gamut should come from psudo-dev to PCS.
+ */
+
+#undef DEBUG
+#undef DO_TIME /* Time the operation */
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "prof.h"
+
+#define DEFAVGDEV 0.5 /* Default average deviation percentage */
+ /* This equates to a uniform added error of +/- 1% */
+
+/*
+
+ Flags used:
+
+ ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ upper . .. . ... .. .... .
+ lower .... .. . .. ......... .
+
+*/
+
+void usage(char *diag, ...) {
+ int i;
+ fprintf(stderr,"Create ICC profile, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr," Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: %s [-options] inoutfile\n",error_program);
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -A manufacturer Manufacturer description string\n");
+ fprintf(stderr," -M model Model description string\n");
+ fprintf(stderr," -D description Profile Description string (Default \"inoutfile\")\n");
+ fprintf(stderr," -C copyright Copyright string\n");
+ fprintf(stderr," -Z tmnb Attributes: Transparency, Matte, Negative, BlackAndWhite\n");
+ fprintf(stderr," -Z prsa Default intent: Perceptual, Rel. Colorimetric, Saturation, Abs. Colorimetric\n");
+
+ fprintf(stderr," -q lmhu Quality - Low, Medium (def), High, Ultra\n");
+// fprintf(stderr," -q fmsu Speed - Fast, Medium (def), Slow, Ultra Slow\n");
+ fprintf(stderr," -b [lmhun] Low quality B2A table - or specific B2A quality or none for input device\n");
+// fprintf(stderr," -b [fmsun] B2A Speed - Fast, Medium, Slow, Ultra Slow, None, same as -q (def)\n");
+ fprintf(stderr," -y Verify A2B profile\n");
+ fprintf(stderr," -ni Don't create input (Device) shaper curves\n");
+ fprintf(stderr," -np Don't create input (Device) grid position curves\n");
+ fprintf(stderr," -no Don't create output (PCS) shaper curves\n");
+ fprintf(stderr," -nc Don't put the input .ti3 data in the profile\n");
+ fprintf(stderr," -k zhxr Black value target: z = zero K,\n");
+ fprintf(stderr," h = 0.5 K, x = max K, r = ramp K (def.)\n");
+ fprintf(stderr," -k p stle stpo enpo enle shape\n");
+ fprintf(stderr," stle: K level at White 0.0 - 1.0\n");
+ fprintf(stderr," stpo: start point of transition Wh 0.0 - Bk 1.0\n");
+ fprintf(stderr," enpo: End point of transition Wh 0.0 - Bk 1.0\n");
+ fprintf(stderr," enle: K level at Black 0.0 - 1.0\n");
+ fprintf(stderr," shape: 1.0 = straight, 0.0-1.0 concave, 1.0-2.0 convex\n");
+ fprintf(stderr," -K parameters Same as -k, but target is K locus rather than K value itself\n");
+ fprintf(stderr," -l tlimit override total ink limit, 0 - 400%% (default from .ti3)\n");
+ fprintf(stderr," -L klimit override black ink limit, 0 - 100%% (default from .ti3)\n");
+ fprintf(stderr," -a lxXgsmGS Algorithm type override\n");
+ fprintf(stderr," l = Lab cLUT (def.), x = XYZ cLUT, X = display XYZ cLUT + matrix\n");
+ fprintf(stderr," g = gamma+matrix, s = shaper+matrix, m = matrix only,\n");
+ fprintf(stderr," G = single gamma+matrix, S = single shaper+matrix\n");
+// Development - not supported
+// fprintf(stderr," -I ver Set ICC profile version > 2.2.0\n");
+// fprintf(stderr," ver = 4, Enable ICC V4 creation\n");
+ fprintf(stderr," -u If input profile, auto scale WP to allow extrapolation\n");
+ fprintf(stderr," -uc If input profile, clip cLUT values above WP\n");
+ fprintf(stderr," -U scale If input profile, scale media white point by scale\n");
+ fprintf(stderr," -R Restrict white <= 1.0, black and primaries to be +ve\n");
+ fprintf(stderr," -f [illum] Use Fluorescent Whitening Agent compensation [opt. simulated inst. illum.:\n");
+ fprintf(stderr," M0, M1, M2, A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp]\n");
+ fprintf(stderr," -i illum Choose illuminant for computation of CIE XYZ from spectral data & FWA:\n");
+ fprintf(stderr," A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp\n");
+ fprintf(stderr," -o observ Choose CIE Observer for spectral data:\n");
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n");
+ fprintf(stderr," -r avgdev Average deviation of device+instrument readings as a percentage (default %4.2f%%)\n",DEFAVGDEV);
+/* Research options: */
+/* fprintf(stderr," -r sSMOOTH RSPL or shaper suplimental optimised smoothing factor\n"); */
+/* fprintf(stderr," -r rSMOOTH RSPL or shaper raw underlying smoothing factor\n"); */
+ fprintf(stderr," -s src%s Apply gamut mapping to output profile perceptual B2A table for given source space\n",ICC_FILE_EXT);
+ fprintf(stderr," -S src%s Apply gamut mapping to output profile perceptual and saturation B2A table\n",ICC_FILE_EXT);
+ fprintf(stderr," -nP Use colormetric source gamut to make output profile perceptual table\n");
+ fprintf(stderr," -nS Use colormetric source gamut to make output profile saturation table\n");
+ fprintf(stderr," -g src.gam Use source image gamut as well for output profile gamut mapping\n");
+ fprintf(stderr," -p absprof,... Incorporate abstract profile(s) into output tables\n");
+ fprintf(stderr," -t intent Override gamut mapping intent for output profile perceptual table:\n");
+ fprintf(stderr," -T intent Override gamut mapping intent for output profile saturation table:\n");
+ for (i = 0; ; i++) {
+ icxGMappingIntent gmi;
+ if (xicc_enum_gmapintent(&gmi, i, NULL) == icxIllegalGMIntent)
+ break;
+ fprintf(stderr," %s\n",gmi.desc);
+ }
+ fprintf(stderr," -c viewcond set input viewing conditions for output profile %s gamut mapping,\n",icxcam_description(cam_default));
+ fprintf(stderr," either an enumerated choice, or a parameter\n");
+ fprintf(stderr," -d viewcond set output viewing conditions for output profile %s gamut mapping\n",icxcam_description(cam_default));
+ fprintf(stderr," either an enumerated choice, or a parameter\n");
+ fprintf(stderr," Also sets out of gamut clipping CAM space.\n");
+ fprintf(stderr," either an enumerated choice, or a series of parameters:value changes\n");
+ for (i = 0; ; i++) {
+ icxViewCond vc;
+ if (xicc_enum_viewcond(NULL, &vc, i, NULL, 1, NULL) == -999)
+ break;
+
+ fprintf(stderr," %s\n",vc.desc);
+ }
+ fprintf(stderr," -P Create gamut gammap_p.wrl and gammap_s.wrl diagostics\n");
+ fprintf(stderr," -O outputfile Override the default output filename.\n");
+ fprintf(stderr," inoutfile Base name for input.ti3/output%s file\n",ICC_FILE_EXT);
+ exit(1);
+}
+
+int main(int argc, char *argv[]) {
+ int fa,nfa,mfa; /* current argument we're looking at */
+#ifdef DO_TIME /* Time the operation */
+ clock_t stime, ttime; /* Start and total times */
+#endif
+ int verb = 0;
+ int iquality = 1; /* A2B quality */
+ int oquality = -1; /* B2A quality same as A2B */
+ int verify = 0;
+ int noisluts = 0; /* No input shaper luts */
+ int noipluts = 0; /* No input position luts */
+ int nooluts = 0; /* No output shaper luts */
+ int nocied = 0; /* No .ti3 CIE data in profile */
+ int noptop = 0; /* Use colormetric source gamut to make perceptual table */
+ int nostos = 0; /* Use colormetric source gamut to make saturation table */
+ int gamdiag = 0; /* Make gamut mapping diagnostic wrl plots */
+ int autowpsc = 0; /* Auto scale the WP to prevent clipping above WP patch */
+ int clipovwp = 0; /* Clip cLUT values above WP */
+ int clipprims = 0; /* Clip white, black and primaries */
+ double iwpscale = -1.0; /* Input white point scale factor */
+ int doinextrap = 1; /* Sythesize extra sample points for input device cLUT */
+ int doinb2a = 1; /* Create an input device B2A table */
+ int inking = 3; /* Default K target ramp K */
+ int locus = 0; /* Default K value target */
+ double Kstle = 0.0, Kstpo = 0.0, Kenle = 0.0, Kenpo = 0.0, Kshap = 0.0;
+ int tlimit = -1; /* Total ink limit as a % */
+ int klimit = -1; /* Black ink limit as a % */
+ int fwacomp = 0; /* FWA compensation */
+ double avgdev = DEFAVGDEV/100.0; /* Average measurement deviation */
+ double smooth = 1.0; /* RSPL Smoothness factor (relative, for verification) */
+ int spec = 0; /* Use spectral data flag */
+ icxIllumeType tillum = icxIT_none; /* Target/simulated instrument illuminant */
+ xspect cust_tillum; /* Custom target/simulated illumination spectrum */
+ icxIllumeType illum = icxIT_D50; /* Spectral defaults */
+ xspect cust_illum; /* Custom illumination spectrum */
+ icxObserverType observ = icxOT_CIE_1931_2; /* The classic observer */
+ char ipname[MAXNAMEL+1] = ""; /* Input icc profile - enables gamut map */
+ char sgname[MAXNAMEL+1] = ""; /* Image source gamut name */
+ char absstring[3 * MAXNAMEL +1]; /* Storage for absnames */
+ char *absnames[3] = { NULL, NULL, NULL }; /* Abstract profile name */
+ int sepsat = 0; /* Create separate saturation B2A table */
+ icxViewCond ivc_p; /* Input Viewing Parameters for CAM */
+ icxViewCond ovc_p; /* Output Viewing Parameters for CAM (enables CAM clip) */
+ int ivc_e = -1, ovc_e = -1; /* Enumerated viewing condition */
+ icxGMappingIntent pgmi; /* default Perceptual gamut mapping intent */
+ int pgmi_set = 0; /* Set by user option */
+ icxGMappingIntent sgmi; /* default Saturation gamut mapping intent */
+ int sgmi_set = 0; /* Set by user option */
+ char baname[MAXNAMEL+1] = ""; /* Input & Output base name */
+ char inname[MAXNAMEL+1] = ""; /* Input cgats file base name */
+ char outname[MAXNAMEL+1] = ""; /* Output cgats file base name */
+ cgats *icg; /* input cgats structure */
+ int ti; /* Temporary CGATs index */
+ prof_atype ptype = prof_default; /* Default for each type of device */
+ int mtxtoo = 0; /* NZ if matrix tags should be created for Display XYZ cLUT */
+ icmICCVersion iccver = icmVersionDefault; /* ICC profile version to create */
+ profxinf xpi; /* Extra profile information */
+
+
+#ifdef DO_TIME /* Time the operation */
+ stime = clock();
+#endif /* DO_TIME */
+ error_program = argv[0];
+ check_if_not_interactive();
+ memset((void *)&xpi, 0, sizeof(profxinf)); /* Init extra profile info to defaults */
+ xpi.default_ri = icMaxEnumIntent; /* Default default */
+
+ /* Init VC overrides so that we know when the've been set */
+ ivc_p.Ev = -1;
+ ivc_p.Wxyz[0] = -1.0; ivc_p.Wxyz[1] = -1.0; ivc_p.Wxyz[2] = -1.0;
+ ivc_p.La = -1.0;
+ ivc_p.Yb = -1.0;
+ ivc_p.Lv = -1.0;
+ ivc_p.Yf = -1.0;
+ ivc_p.Fxyz[0] = -1.0; ivc_p.Fxyz[1] = -1.0; ivc_p.Fxyz[2] = -1.0;
+
+ ovc_p.Ev = -1;
+ ovc_p.Wxyz[0] = -1.0; ovc_p.Wxyz[1] = -1.0; ovc_p.Wxyz[2] = -1.0;
+ ovc_p.La = -1.0;
+ ovc_p.Yb = -1.0;
+ ovc_p.Lv = -1.0;
+ ovc_p.Yf = -1.0;
+ ovc_p.Fxyz[0] = -1.0; ovc_p.Fxyz[1] = -1.0; ovc_p.Fxyz[2] = -1.0;
+
+ xicc_enum_gmapintent(&pgmi, icxPerceptualGMIntent, NULL);
+ xicc_enum_gmapintent(&sgmi, icxSaturationGMIntent, NULL);
+
+ if (argc <= 1)
+ usage("Too few arguments, got %d expect at least %d",argc-1,1);
+
+ /* Process the arguments */
+ mfa = 1; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ /* Manufacturer description string */
+ else if (argv[fa][1] == 'A') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to manufacturer description flag -A");
+ xpi.deviceMfgDesc = na;
+ }
+
+ /* Model description string */
+ else if (argv[fa][1] == 'M') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to model description flag -M");
+ xpi.modelDesc = na;
+ }
+
+ /* Profile Description */
+ else if (argv[fa][1] == 'D') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to profile description flag -D");
+ xpi.profDesc = na;
+ }
+
+ /* Copyright string */
+ else if (argv[fa][1] == 'C') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to copyright flag -C");
+ xpi.copyright = na;
+ }
+
+ /* Attribute bits */
+ else if (argv[fa][1] == 'Z') {
+ int i, j;
+
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to attribute flag -Z");
+ for (j = i = 0; j == 0; i++) {
+
+ switch(na[i]) {
+
+ /* Attribute */
+ case 't':
+ case 'T':
+ xpi.transparency = 1;
+ break;
+ case 'm':
+ case 'M':
+ xpi.matte = 1;
+ break;
+ case 'n':
+ case 'N':
+ xpi.negative = 1;
+ break;
+ case 'b':
+ case 'B':
+ xpi.blackandwhite = 1;
+ break;
+
+ /* Default intent */
+ case 'p':
+ case 'P':
+ xpi.default_ri = icPerceptual;
+ break;
+ case 'r':
+ case 'R':
+ xpi.default_ri = icRelativeColorimetric;
+ break;
+ case 's':
+ case 'S':
+ xpi.default_ri = icSaturation;
+ break;
+ case 'a':
+ case 'A':
+ xpi.default_ri = icAbsoluteColorimetric;
+ break;
+ default:
+ j = 1;
+ break;
+ }
+ }
+ if (na[i-1] != '\000') usage("Unknown argument '%c' to attribute flag -Z",na[i-1]);
+ }
+
+ /* Quality */
+ else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') {
+ fa = nfa;
+// if (na == NULL) usage("Expect argument to quality flag -q");
+ if (na == NULL) usage("Expect argument to speed flag -q");
+ switch (na[0]) {
+ case 'f': /* Fast */
+ case 'l':
+ case 'L':
+ iquality = 0;
+ break;
+ case 'm': /* Medium */
+ case 'M':
+ iquality = 1;
+ break;
+ case 's': /* Slow */
+ case 'h':
+ case 'H':
+ iquality = 2;
+ break;
+ case 'u': /* Ultra Slow */
+ case 'U':
+ iquality = 3;
+ break;
+ default:
+ usage("Unknown argument '%c' to quality flag -q",na[0]);
+// usage("Unknown argument '%c' to speed flag -q",na[0]);
+ }
+ }
+ else if (argv[fa][1] == 'b') {
+ if (na != NULL) { /* Got a B2A quaiity */
+ fa = nfa;
+ switch (na[0]) {
+ case 'f': /* Fast */
+ case 'l':
+ case 'L':
+ oquality = 0;
+ break;
+ case 'm': /* Medium */
+ case 'M':
+ oquality = 1;
+ break;
+ case 's': /* Slow */
+ case 'h':
+ case 'H':
+ oquality = 2;
+ break;
+ case 'u': /* Ultra Slow */
+ case 'U':
+ oquality = 3;
+ break;
+ case 'n': /* No B2A for input device */
+ case 'N':
+ oquality = -2;
+ doinb2a = 0;
+ break;
+ default:
+ usage("Unknown argument '%c' to quality flag -q",na[0]);
+ }
+ } else
+ oquality = 0;
+ }
+
+ else if (argv[fa][1] == 'B') {
+ oquality = -2;
+ doinb2a = 0;
+ }
+
+ else if (argv[fa][1] == 'y' || argv[fa][1] == 'Y')
+ verify = 1;
+
+ /* Disable input or output luts */
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ fa = nfa;
+ if (na == NULL) { /* Backwards compatible */
+ nooluts = 1;
+ } else {
+ if (na[0] == 'i')
+ noisluts = 1;
+ else if (na[0] == 'p')
+ noipluts = 1;
+ else if (na[0] == 'o')
+ nooluts = 1;
+ else if (na[0] == 'c')
+ nocied = 1;
+ else if (na[0] == 'P')
+ noptop = 1;
+ else if (na[0] == 'S')
+ nostos = 1;
+ else
+ usage("Unknown argument '%c' to flag -n",na[0]);
+ }
+ }
+
+ else if (argv[fa][1] == 'u') {
+ autowpsc = 1;
+ clipovwp = 0;
+ if (argv[fa][2] == 'c') {
+ autowpsc = 0;
+ clipovwp = 1;
+ } else if (argv[fa][2] != '\000') {
+ usage("Unknown flag '%c' after -u",argv[fa][2]);
+ }
+ }
+ else if (argv[fa][1] == 'U') {
+ fa = nfa;
+ if (na == NULL) usage("Expected argument to input white point scale flag -U");
+ iwpscale = atof(na);
+ if (iwpscale < 0.0 || iwpscale > 100.0)
+ usage("Argument '%s' to flag -U out of range",na);
+ }
+ /* Clip primaries */
+ else if (argv[fa][1] == 'R') {
+ clipprims = 1;
+ }
+
+ /* Inking rule */
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to inking flag -k");
+ if (argv[fa][1] == 'k')
+ locus = 0; /* Use K value target */
+ else
+ locus = 1; /* Use K locus target */
+ switch (na[0]) {
+ case 'z':
+ case 'Z':
+ inking = 0; /* Use minimum k */
+ break;
+ case 'h':
+ case 'H':
+ inking = 1; /* Use 0.5 k */
+ break;
+ case 'x':
+ case 'X':
+ inking = 2; /* Use maximum K */
+ break;
+ case 'r':
+ case 'R':
+ inking = 3; /* Use ramping K */
+ break;
+ case 'p':
+ case 'P':
+ inking = 4; /* Use parameter curve */
+ ++fa;
+ if (fa >= argc) usage("Too few arguments to inking flag -kp");
+ Kstle = atof(argv[fa]);
+
+ ++fa;
+ if (fa >= argc) usage("Too few arguments to inking flag -kp");
+ Kstpo = atof(argv[fa]);
+
+ ++fa;
+ if (fa >= argc || argv[fa][0] == '-') usage("Too few arguments to inking flag -kp");
+ Kenpo = atof(argv[fa]);
+
+ ++fa;
+ if (fa >= argc) usage("Too few arguments to inking flag -kp");
+ Kenle = atof(argv[fa]);
+
+ ++fa;
+ if (fa >= argc || argv[fa][0] == '-') usage("Too few arguments to inking flag -kp");
+ Kshap = atof(argv[fa]);
+ break;
+ default:
+ usage("Unknown inking rule (-k) argument '%c'",na[0]);
+ }
+ }
+
+ /* Total Ink Limit */
+ else if (argv[fa][1] == 'l') {
+ fa = nfa;
+ if (na == NULL) usage("Expected argument to total ink limit flag -l");
+ tlimit = atoi(na);
+ }
+
+ /* Black Ink Limit */
+ else if (argv[fa][1] == 'L') {
+ fa = nfa;
+ if (na == NULL) usage("Expected argument to black ink limit flag -L");
+ klimit = atoi(na);
+ }
+
+ /* Algorithm type */
+ else if (argv[fa][1] == 'a' || argv[fa][1] == 'A') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to algorithm flag -a");
+ switch (na[0]) {
+ case 'l':
+ case 'L':
+ ptype = prof_clutLab;
+ break;
+ case 'X':
+ mtxtoo = 1;
+ /* Fall though */
+ case 'x':
+ ptype = prof_clutXYZ;
+ break;
+ case 'g':
+ ptype = prof_gammat;
+ break;
+ case 'G':
+ ptype = prof_gam1mat;
+ break;
+ case 's':
+ ptype = prof_shamat;
+ break;
+ case 'S':
+ ptype = prof_sha1mat;
+ break;
+ case 'm':
+ ptype = prof_matonly;
+ break;
+ default:
+ usage("Unknown argument '%c' to algorithm flag -a",na[0] );
+ }
+ }
+ /* Profile version */
+ else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to version flag -I");
+ switch (na[0]) {
+ case '4':
+ iccver = icmVersion4_1;
+ break;
+ default:
+ usage("Unknown argument '%c' to version flag -I",na[0] );
+ }
+ }
+
+ /* FWA compensation */
+ else if (argv[fa][1] == 'f') {
+ fwacomp = 1;
+
+ if (na != NULL) { /* Argument is present - target/simulated instr. illum. */
+ fa = nfa;
+ if (strcmp(na, "A") == 0
+ || strcmp(na, "M0") == 0) {
+ spec = 1;
+ tillum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ tillum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0
+ || strcmp(na, "M1") == 0) {
+ spec = 1;
+ tillum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0
+ || strcmp(na, "M2") == 0) {
+ spec = 1;
+ tillum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ tillum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ tillum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ tillum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ tillum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ tillum = icxIT_custom;
+ if (read_xspect(&cust_tillum, na) != 0)
+ usage("Failed to read custom target illuminant spectrum in file '%s'",na);
+ }
+ }
+ }
+
+ /* Spectral Illuminant type */
+ else if (argv[fa][1] == 'i') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to illuminant flag -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, "D50M2") == 0) {
+ spec = 1;
+ illum = icxIT_D50M2;
+ } 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("Failed to read custom illuminant spectrum in file '%s'",na);
+ }
+ }
+
+ /* Spectral Observer type */
+ else if (argv[fa][1] == 'o') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to observer flag -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("Unrecognised argument '%s' to observer flag -o",na);
+ }
+
+
+ /* Average Deviation percentage */
+ else if (argv[fa][1] == 'r') {
+ fa = nfa;
+ if (na == NULL) usage("Expected argument to average deviation flag -r");
+ if (na[0] == 's') { /* (relative, for verification) */
+ smooth = atof(na+1);
+ if (smooth < 0.0)
+ usage("Optimised smoothing factor argument to '-rs' must be over 0.0");
+ } else if (na[0] == 'r') { /* (absolute, for testing) */
+ smooth = atof(na+1);
+ if (smooth < 0.0)
+ usage("Raw smoothing factor argument to '-rr' must be over 0.0");
+ smooth = -smooth; /* Signal raw factor */
+ } else {
+ avgdev = 0.01 * atof(na);
+ if (avgdev < 0.0 || avgdev > 1.0)
+ usage("Average Deviation argument must be between 0.0 and 100.0");
+ }
+ }
+
+ /* Percetual Source Gamut and Perceptual/Saturation Gamut Maping mode enable */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S') {
+ if (argv[fa][1] == 'S')
+ sepsat = 1;
+ if (na == NULL)
+ usage("Unrecognised argument to source gamut flag -%c",argv[fa][1]);
+
+ fa = nfa;
+ strncpy(ipname,na,MAXNAMEL); ipname[MAXNAMEL] = '\000';
+ }
+
+ /* Source image gamut */
+ else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') {
+ if (na == NULL)
+ usage("Unrecognised argument to source image gamut flag -g",argv[fa][1]);
+ fa = nfa;
+ strncpy(sgname,na,MAXNAMEL); sgname[MAXNAMEL] = '\000';
+ }
+
+ /* Abstract profile */
+ else if (argv[fa][1] == 'p') {
+ char *f1 = NULL, *f2 = NULL;
+ if (na == NULL) usage("Expected abstract profile filename after -p");
+ fa = nfa;
+ strncpy(absstring,na,MAXNAMEL*3); absstring[MAXNAMEL*3] = '\000';
+ if ((f1 = strchr(absstring, ',')) == NULL) { /* Only one profile */
+ absnames[2] = absnames[1] = absnames[0] = absstring; /* Duplicate */
+ } else { /* At least one comma */
+ *f1++ = '\000';
+ if ((f2 = strchr(f1, ',')) != NULL) /* Two commas */
+ *f2++ = '\000';
+ if (*absstring != '\000')
+ absnames[0] = absstring;
+ if (*f1 != '\000')
+ absnames[1] = f1;
+ if (f2 != NULL && *f2 != '\000')
+ absnames[2] = f2;
+ }
+ }
+
+ /* Perceptual Mapping intent override */
+ else if (argv[fa][1] == 't') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to perceptul intent override flag -t");
+ if (xicc_enum_gmapintent(&pgmi, icxNoGMIntent, na) == icxIllegalGMIntent)
+ usage("Unrecognised intent '%s' to perceptual override flag -t",na);
+ pgmi_set = 1;
+ }
+
+ /* Saturation Mapping intent override */
+ else if (argv[fa][1] == 'T') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to saturation intent override flag -T");
+ if (xicc_enum_gmapintent(&sgmi, icxNoGMIntent, na) == icxIllegalGMIntent)
+ usage("Unrecognised intent '%s' to saturation override flag -T",na);
+ sgmi_set = 1;
+ }
+
+ /* Viewing conditions */
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'd') {
+ icxViewCond *vc;
+
+ if (argv[fa][1] == 'c') {
+ vc = &ivc_p;
+ } else {
+ vc = &ovc_p;
+ }
+
+ fa = nfa;
+ if (na == NULL) usage("Viewing conditions flag (-c) needs an argument");
+ if (na[1] != ':') {
+ if (vc == &ivc_p) {
+ if ((ivc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999)
+ usage("Urecognised Enumerated Viewing conditions '%s'",na);
+ } else {
+ if ((ovc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999)
+ usage("Urecognised Enumerated Viewing conditions '%s'",na);
+ }
+ } else if (na[0] == 's' || na[0] == 'S') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-cs) missing ':'");
+ if (na[2] == 'n' || na[2] == 'N') {
+ vc->Ev = vc_none; /* Automatic */
+ } else if (na[2] == 'a' || na[2] == 'A') {
+ vc->Ev = vc_average;
+ } else if (na[2] == 'm' || na[2] == 'M') {
+ vc->Ev = vc_dim;
+ } else if (na[2] == 'd' || na[2] == 'D') {
+ vc->Ev = vc_dark;
+ } else if (na[2] == 'c' || na[2] == 'C') {
+ vc->Ev = vc_cut_sheet;
+ } else
+ usage("Viewing condition (-c) unrecognised surround '%c'",na[2]);
+ } 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->Wxyz[0] = x; vc->Wxyz[1] = y;
+ } else
+ usage("Viewing condition (-cw) unrecognised white point '%s'",na+1);
+ } else if (na[0] == 'a' || na[0] == 'A') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-ca) missing ':'");
+ vc->La = atof(na+2);
+ } else if (na[0] == 'b' || na[0] == 'B') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-cb) missing ':'");
+ vc->Yb = atof(na+2)/100.0;
+ } else if (na[0] == 'l' || na[0] == 'L') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-[cd]l) missing ':'");
+ vc->Lv = 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->Fxyz[0] = x; vc->Fxyz[1] = y;
+ } else if (sscanf(na+1,":%lf",&x) == 1) {
+ vc->Yf = x/100.0;
+ } else
+ usage("Viewing condition (-cf) unrecognised flare '%s'",na+1);
+ } else
+ usage("Viewing condition (-c) unrecognised sub flag '%c'",na[0]);
+ }
+
+ /* Gammut mapping diagnostic plots */
+ else if (argv[fa][1] == 'P')
+ gamdiag = 1;
+
+ /* Output file name */
+ else if (argv[fa][1] == 'O') {
+ fa = nfa;
+ if (na == NULL) usage("Output filename override (-O) needs an argument");
+ strncpy(outname,na,MAXNAMEL); outname[MAXNAMEL] = '\000';
+ }
+
+ else
+ usage("Unknown flag '%c'",argv[fa][1]);
+ } else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing input .ti3 and output ICC basename");
+ strncpy(baname,argv[fa++],MAXNAMEL-4); baname[MAXNAMEL-4] = '\000';
+ if (xpi.profDesc == NULL)
+ xpi.profDesc = baname; /* Default description */
+ strcpy(inname,baname);
+ strcat(inname,".ti3");
+ if (outname[0] == '\000') { /* If not overridden */
+ strcpy(outname,baname);
+ strcat(outname,ICC_FILE_EXT);
+ }
+
+ /* Issue some errors & warnings for strange combinations */
+ if (fwacomp && spec == 0)
+ error("FWA compensation only works when viewer and/or illuminant selected");
+
+ if (pgmi_set && ipname[0] == '\000')
+ warning("-t perceptual intent override only works if -s srcprof or -S srcprof is used");
+
+ if (sgmi_set && ipname[0] == '\000')
+ warning("-T saturation intent override only works if -S srcprof is used");
+
+ if (sgmi_set && sepsat == 0) { /* Won't do much otherwise */
+ if (verb)
+ printf("Saturation intent override was set, so adding saturation intent table\n");
+ sepsat = 1;
+ }
+
+ if (gamdiag && ipname[0] == '\000')
+ warning("no gamut mapping called for, so -P will produce nothing");
+
+ if (sgname[0] != '\000' && ipname[0] == '\000')
+ warning("-g srcgam will do nothing without -s srcprof or -S srcprof");
+
+ if (oquality == -1) { /* B2A tables will be used */
+ oquality = iquality;
+ }
+
+ /* Open and look at the .ti3 profile patches file */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI3"); /* our special input type is Calibration Target Information 3 */
+ icg->add_other(icg, "CAL"); /* our special device Calibration state */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI3 format file");
+ if (icg->ntables < 1)
+ error ("Input file doesn't contain at least one table");
+
+ /* See if CIE is actually available - some sources of .TI3 don't provide it */
+ if (!spec
+ && icg->find_field(icg, 0, "LAB_L") < 0
+ && icg->find_field(icg, 0, "XYZ_X") < 0) {
+
+ if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
+ error("Neither CIE nor spectral data found in file '%s'",inname);
+
+ /* Switch to using spectral information */
+ if (verb)
+ printf("No CIE data found, switching to spectral with standard observer & D50\n");
+ spec = 1;
+ illum = icxIT_D50;
+ observ = icxOT_CIE_1931_2;
+ }
+
+ /* If we requested spectral, check that it is available */
+ if (spec) {
+ if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
+ error ("Requested spectral interpretation when data not available");
+ }
+
+ /* read the device class, and call function to create profile. */
+ if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file doesn't contain keyword DEVICE_CLASS");
+
+ if (strcmp(icg->t[0].kdata[ti],"OUTPUT") == 0) {
+ icxInk ink; /* Ink parameters */
+
+ if ((ti = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) {
+ int imax;
+ imax = atoi(icg->t[0].kdata[ti]);
+ if (imax > 0 && imax <= 400.0) {
+ if (tlimit > 0 && tlimit <= 400.0) { /* User has specified limit as option */
+ if (imax < tlimit) {
+ warning("Ink limit greater than original chart! (%d%% > %d%%)",tlimit,imax);
+ }
+ } else {
+ if (imax > 80.0)
+ tlimit = (int)imax - 10; /* Rule of thumb - 10% below chart maximum */
+ else
+ tlimit = (int)imax;
+ }
+ }
+ }
+
+ /* (Note that this isn't set by any of the Argyll tools currently, */
+ /* but can be set manually.) */
+ if ((ti = icg->find_kword(icg, 0, "BLACK_INK_LIMIT")) >= 0) {
+ int kmax;
+ kmax = atoi(icg->t[0].kdata[ti]);
+ if (kmax > 0 && kmax <= 100.0) {
+ if (klimit > 0 && klimit <= 100.0) { /* User has specified limit as option */
+ if (kmax < klimit) {
+ warning("Black ink limit greater than original chart! (%d%% > %d%%)",klimit,kmax);
+ }
+ } else {
+ klimit = (int)kmax;
+ }
+ }
+ }
+
+ if (tlimit >= 0 && tlimit < 400.0) {
+ if (verb)
+ printf("Total ink limit being used is %d%%\n",tlimit);
+ ink.tlimit = tlimit/100.0; /* Set a total ink limit */
+ } else {
+ if (verb)
+ printf("No total ink limit being used\n");
+ ink.tlimit = -1.0; /* Don't use a limit */
+ }
+
+ if (klimit >= 0 && klimit < 100.0) {
+ if (verb)
+ printf("Black ink limit being used is %d%%\n",klimit);
+ ink.klimit = klimit/100.0; /* Set a black ink limit */
+ } else {
+ if (verb)
+ printf("No black ink limit being used\n");
+ ink.klimit = -1.0; /* Don't use a limit */
+ }
+
+ ink.KonlyLmin = 0; /* Use normal black Lmin for locus */
+ ink.c.Ksmth = ICXINKDEFSMTH; /* default black curve smoothing */
+ ink.c.Kskew = ICXINKDEFSKEW; /* default black curve skew */
+ ink.x.Ksmth = ICXINKDEFSMTH;
+ ink.x.Kskew = ICXINKDEFSKEW;
+
+ if (inking == 0) { /* Use minimum */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = 0.0;
+ ink.c.Kstpo = 0.0;
+ ink.c.Kenpo = 1.0;
+ ink.c.Kenle = 0.0;
+ ink.c.Kshap = 1.0;
+ } else if (inking == 1) { /* Use 0.5 */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = 0.5;
+ ink.c.Kstpo = 0.0;
+ ink.c.Kenpo = 1.0;
+ ink.c.Kenle = 0.5;
+ ink.c.Kshap = 1.0;
+ } else if (inking == 2) { /* Use maximum */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = 1.0;
+ ink.c.Kstpo = 0.0;
+ ink.c.Kenpo = 1.0;
+ ink.c.Kenle = 1.0;
+ ink.c.Kshap = 1.0;
+ } else if (inking == 3) { /* Use ramp */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = 0.0;
+ ink.c.Kstpo = 0.0;
+ ink.c.Kenpo = 1.0;
+ ink.c.Kenle = 1.0;
+ ink.c.Kshap = 1.0;
+ } else { /* Use specified curve */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = Kstle;
+ ink.c.Kstpo = Kstpo;
+ ink.c.Kenpo = Kenpo;
+ ink.c.Kenle = Kenle;
+ ink.c.Kshap = Kshap;
+ }
+
+ if (ptype == prof_default)
+ ptype = prof_clutLab;
+ else if (ptype != prof_clutLab && ptype != prof_clutXYZ) {
+ error ("Output profile can only be a cLUT algorithm");
+ }
+
+ if (autowpsc)
+ error ("Input auto WP scale mode isn't applicable to an output device");
+ if (clipovwp)
+ error ("Input cLUT clipping above WP mode isn't applicable to an output device");
+
+ make_output_icc(ptype, 0, iccver, verb, iquality, oquality,
+ noisluts, noipluts, nooluts, nocied, noptop, nostos,
+ gamdiag, verify, clipprims, &ink, inname, outname, icg,
+ spec, tillum, &cust_tillum, illum, &cust_illum, observ, fwacomp,
+ smooth, avgdev,
+ ipname[0] != '\000' ? ipname : NULL,
+ sgname[0] != '\000' ? sgname : NULL,
+ absnames,
+ sepsat, &ivc_p, &ovc_p, ivc_e, ovc_e,
+ &pgmi, &sgmi, &xpi);
+
+ } else if (strcmp(icg->t[0].kdata[ti],"INPUT") == 0) {
+
+ if (ptype == prof_default)
+ ptype = prof_clutLab; /* For best possible quality */
+
+ if (clipovwp && ptype != prof_clutLab && ptype != prof_clutXYZ)
+ error ("Input cLUT clipping above WP mode isn't applicable to a matrix profile");
+
+ make_input_icc(ptype, iccver, verb, iquality, oquality, noisluts, noipluts, nooluts, nocied,
+ verify, autowpsc, clipovwp, iwpscale, doinb2a, doinextrap, clipprims,
+ inname, outname, icg, spec, illum, &cust_illum, observ,
+ smooth, avgdev, &xpi);
+
+ } else if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) {
+
+ if (autowpsc)
+ error ("Input auto WP scale mode isn't applicable to an output device");
+ if (clipovwp)
+ error ("Input cLUT clipping above WP mode isn't applicable to an output device");
+
+ if (fwacomp)
+ error ("FWA compensation isn't applicable to a display device");
+
+ if (ptype == prof_default)
+ ptype = prof_clutLab; /* ?? or should it default to prof_shamat ?? */
+
+ /* If a source gamut is provided for a Display, then a V2.4.0 profile will be created */
+ make_output_icc(ptype, mtxtoo, iccver, verb, iquality, oquality,
+ noisluts, noipluts, nooluts, nocied, noptop, nostos,
+ gamdiag, verify, clipprims, NULL, inname, outname, icg,
+ spec, icxIT_none, NULL, illum, &cust_illum, observ, 0,
+ smooth, avgdev,
+ ipname[0] != '\000' ? ipname : NULL,
+ sgname[0] != '\000' ? sgname : NULL,
+ absnames,
+ sepsat, &ivc_p, &ovc_p, ivc_e, ovc_e,
+ &pgmi, &sgmi, &xpi);
+
+ } else
+ error ("Input file keyword DEVICE_CLASS has unknown value");
+
+ icg->del(icg); /* Clean up */
+
+#ifdef DO_TIME /* Time the operation */
+ ttime = clock() - stime;
+ printf("Exectution time = %f seconds\n",(double)ttime/(double)CLOCKS_PER_SEC);
+#endif /* DO_TIME */
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+