1 /*
2  * Argyll Color Correction System
3  * DTP92/Spectrolino display target reader
4  *
5  * Author: Graeme W. Gill
6  * Date:   4/10/96
7  *
8  * Copyright 1996 - 2013 Graeme W. Gill
9  * All rights reserved.
10  *
11  * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
12  * see the License.txt file for licencing details.
13  */
14 
15 /* This program displays test patches, and takes readings from a display device */
16 
17 /* TTBD
18 
19 	Add support for black recalibration using i1pro or munki.
20 	Setable timeout ? Need to allow placing instrument back
21 	on screen. Need this to properly handle ss anyway ?
22 
23     Add bell at end of readings ?
24 
25 	Ideally this should be changed to always create non-normalized (absolute)
26 	readings, since normalised readings are not natural, but everything
27 	that deals with .ti3 data then needs fixing to deal with non-normalized
28 	readings !
29  */
30 
31 #undef DEBUG
32 #undef DEBUG_OFFSET	/* Keep test window out of the way */
33 
34 /* Invoke with -dfake for testing with a fake device */
35 /* Will use fake.icm/.icc if present */
36 
37 #define COMPORT 1		/* Default com port 1..4 */
38 
39 #ifdef __MINGW32__
40 # define WINVER 0x0500
41 #endif
42 
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <stdarg.h>
46 #include <math.h>
47 #include <sys/types.h>
48 #include <time.h>
49 #include <string.h>
50 #if defined (NT)
51 #include <conio.h>
52 #endif
53 #include "copyright.h"
54 #include "aconfig.h"
55 #include "numlib.h"
56 #include "xspect.h"
57 #include "cgats.h"
58 #include "insttypes.h"
59 #include "conv.h"
60 #include "icoms.h"
61 #include "inst.h"
62 #include "ccmx.h"
63 #include "ccss.h"
64 #include "ccast.h"
65 #include "dispwin.h"
66 #include "dispsup.h"
67 #include "sort.h"
68 #include "instappsup.h"
69 #ifdef ENABLE_USB
70 # include "spyd2.h"
71 #endif
72 #include "ui.h"
73 
74 /* ------------------------------------------------------------------- */
75 #if defined(__APPLE__) && defined(__POWERPC__)
76 
77 /* Workaround for a PPC gcc 3.3 optimiser bug... */
78 /* It seems to cause a segmentation fault instead of */
79 /* converting an integer loop index into a float, */
80 /* when there are sufficient variables in play. */
gcc_bug_fix(int i)81 static int gcc_bug_fix(int i) {
82 	static int nn;
83 	nn += i;
84 	return nn;
85 }
86 #endif	/* APPLE */
87 
88 /* ------------------------------------------------------------------- */
89 
90 /*
91 
92   Flags used:
93 
94          ABCDEFGHIJKLMNOPQRSTUVWXYZ
95   upper    .... .... .. ..    .....
96   lower    ..      .  . .  .  .. .
97 
98 */
99 
100 /* Flag = 0x0000 = default */
101 /* Flag & 0x0001 = list ChromCast's */
usage(int flag,char * diag,...)102 void usage(int flag, char *diag, ...) {
103 	int i;
104 	disppath **dp;
105 	icompaths *icmps;
106 	inst2_capability cap2 = inst2_none;
107 
108 	fprintf(stderr,"Read a Display, Version %s\n",ARGYLL_VERSION_STR);
109 	fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
110 	if (diag != NULL) {
111 		va_list args;
112 		fprintf(stderr,"Diagnostic: ");
113 		va_start(args, diag);
114 		vfprintf(stderr, diag, args);
115 		va_end(args);
116 		fprintf(stderr,"\n");
117 	}
118 	fprintf(stderr,"usage: dispread [options] outfile\n");
119 	fprintf(stderr," -v              Verbose mode\n");
120 #if defined(UNIX_X11)
121 	fprintf(stderr," -display displayname Choose X11 display name\n");
122 	fprintf(stderr," -d n[,m]             Choose the display n from the following list (default 1)\n");
123 	fprintf(stderr,"                      Optionally choose different display m for VideoLUT access\n");
124 #else
125 	fprintf(stderr," -d n                 Choose the display from the following list (default 1)\n");
126 #endif
127 	dp = get_displays();
128 	if (dp == NULL || dp[0] == NULL)
129 		fprintf(stderr,"    ** No displays found **\n");
130 	else {
131 		int i;
132 		for (i = 0; ; i++) {
133 			if (dp[i] == NULL)
134 				break;
135 			fprintf(stderr,"    %d = '%s'\n",i+1,dp[i]->description);
136 		}
137 	}
138 	free_disppaths(dp);
139 	fprintf(stderr," -dweb[:port]         Display via a web server at port (default 8080)\n");
140 	fprintf(stderr," -dcc[:n]             Display via n'th ChromeCast (default 1, ? for list)\n");
141 	if (flag & 0x001) {
142 		ccast_id **ids;
143 		if ((ids = get_ccids()) == NULL) {
144 			fprintf(stderr,"    ** Error discovering ChromCasts **\n");
145 		} else {
146 			if (ids[0] == NULL)
147 				fprintf(stderr,"    ** No ChromCasts found **\n");
148 			else {
149 				int i;
150 				for (i = 0; ids[i] != NULL; i++)
151 					fprintf(stderr,"    %d = '%s'\n",i+1,ids[i]->name);
152 				free_ccids(ids);
153 			}
154 		}
155 	}
156 #ifdef NT
157 	fprintf(stderr," -dmadvr              Display via MadVR Video Renderer\n");
158 #endif
159 //	fprintf(stderr," -d fake              Use a fake display device for testing, fake%s if present\n",ICC_FILE_EXT);
160 	fprintf(stderr," -c listno            Set communication port from the following list (default %d)\n",COMPORT);
161 	if ((icmps = new_icompaths(g_log)) != NULL) {
162 		icompath **paths;
163 		if ((paths = icmps->paths) != NULL) {
164 			int i;
165 			for (i = 0; ; i++) {
166 				if (paths[i] == NULL)
167 					break;
168 				if ((paths[i]->itype == instSpyder1 && setup_spyd2(0) == 0)
169 				 || (paths[i]->itype == instSpyder2 && setup_spyd2(1) == 0))
170 					fprintf(stderr,"    %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
171 				else
172 					fprintf(stderr,"    %d = '%s'\n",i+1,paths[i]->name);
173 			}
174 		} else
175 			fprintf(stderr,"    ** No ports found **\n");
176 	}
177 	fprintf(stderr," -p                   Use telephoto mode (ie. for a projector) (if available)\n");
178 	cap2 = inst_show_disptype_options(stderr, " -y                   ", icmps, 0);
179 	fprintf(stderr," -k file.cal          Load calibration file into display while reading\n");
180 	fprintf(stderr," -K file.cal          Apply calibration file to test values while reading\n");
181 #ifdef NT
182 	fprintf(stderr," -V                   Enable MadVR color management (3dLut)\n");
183 #endif
184 	fprintf(stderr," -s                   Save spectral information (default don't save)\n");
185 	fprintf(stderr," -P ho,vo,ss[,vs]     Position test window and scale it\n");
186 	fprintf(stderr,"                      ho,vi: 0.0 = left/top, 0.5 = center, 1.0 = right/bottom etc.\n");
187 	fprintf(stderr,"                      ss: 0.5 = half, 1.0 = normal, 2.0 = double etc.\n");
188 	fprintf(stderr," -F                   Fill whole screen with black background\n");
189 #if defined(UNIX_X11)
190 	fprintf(stderr," -n                   Don't set override redirect on test window\n");
191 #endif
192 	fprintf(stderr," -E                   Encode the test values for video range 16..235/255\n");
193 	fprintf(stderr," -Z nbits             Quantize test values to fit in nbits\n");
194 	fprintf(stderr," -J                   Run instrument calibration first (used rarely)\n");
195 	fprintf(stderr," -N                   Disable initial calibration of instrument if possible\n");
196 	fprintf(stderr," -H                   Use high resolution spectrum mode (if available)\n");
197 //	fprintf(stderr," -V                   Use adaptive measurement mode (if available)\n");
198 	fprintf(stderr," -w                   Disable normalisation of white to Y = 100\n");
199 	if (cap2 & inst2_ccmx)
200 		fprintf(stderr," -X file.ccmx         Apply Colorimeter Correction Matrix\n");
201 	if (cap2 & inst2_ccss) {
202 		fprintf(stderr," -X file.ccss         Use Colorimeter Calibration Spectral Samples for calibration\n");
203 		fprintf(stderr," -Q observ            Choose CIE Observer for spectrometer or CCSS colorimeter data:\n");
204 		fprintf(stderr,"                      1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2, 1964_10c\n");
205 	}
206 	fprintf(stderr," -I b|w               Drift compensation, Black: -Ib, White: -Iw, Both: -Ibw\n");
207 	fprintf(stderr," -Y R:rate            Override measured refresh rate with rate Hz\n");
208 	fprintf(stderr," -Y A                 Use non-adaptive integration time mode (if available).\n");
209 	fprintf(stderr," -Y p                 Don't wait for the instrument to be placed on the display\n");
210 	fprintf(stderr," -C \"command\"         Invoke shell \"command\" each time a color is set\n");
211 	fprintf(stderr," -M \"command\"         Invoke shell \"command\" each time a color is measured\n");
212 //	fprintf(stderr," -x [lx]              Take manually entered values, either L*a*b* (-xl) or XYZ (-xx).\n");
213 	fprintf(stderr," -x x                 Take manually entered XYZ values\n");
214 	fprintf(stderr," -W n|h|x             Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
215 	fprintf(stderr," -D [level]           Print debug diagnostics to stderr\n");
216 	fprintf(stderr," outfile              Base name for input[ti1]/output[ti3] file\n");
217 	if (icmps != NULL)
218 		icmps->del(icmps);
219 	exit(1);
220 }
221 
main(int argc,char * argv[])222 int main(int argc, char *argv[]) {
223 	int i, j;
224 	int fa, nfa, mfa;					/* current argument we're looking at */
225 	disppath *disp = NULL;				/* Display being used */
226 	double hpatscale = 1.0, vpatscale = 1.0;	/* scale factor for test patch size */
227 	double ho = 0.0, vo = 0.0;			/* Test window offsets, -1.0 to 1.0 */
228 	int out_tvenc = 0;					/* 1 to use RGB Video Level encoding */
229 	int qbits = 0;						/* Quantization bits, 0 = not set */
230 	int fullscreen = 0;            		/* NZ if whole screen should be filled with black */
231 	int verb = 0;
232 	int debug = 0;
233 	int fake = 0;						/* Use the fake device for testing */
234 	int override = 1;					/* Override redirect on X11 */
235 	int comport = COMPORT;				/* COM port used */
236 	icompaths *icmps = NULL;
237 	icompath *ipath = NULL;
238 	flow_control fc = fc_nc;			/* Default flow control */
239 	int docalib = 0;					/* Do a calibration */
240 	int highres = 0;					/* Use high res mode if available */
241 	double refrate = 0.0;			    /* 0.0 = default, > 0.0 = override refresh rate */
242 	int nadaptive = 0;					/* Use non-adaptive mode if available */
243 	int bdrift = 0;						/* Flag, nz for black drift compensation */
244 	int wdrift = 0;						/* Flag, nz for white drift compensation */
245 	int dtype = 0;						/* Display type selection charater */
246 	int tele = 0;						/* NZ if telephoto mode */
247 	int noautocal = 0;					/* Disable auto calibration */
248 	int noplace = 0;					/* Disable user instrument placement */
249 	int donorm = 1;						/* Enable Y = 100 normalisation */
250 	char ccxxname[MAXNAMEL+1] = "\000";  /* Colorimeter Correction Matrix name */
251 	ccmx *cmx = NULL;					/* Colorimeter Correction Matrix */
252 	ccss *ccs = NULL;					/* Colorimeter Calibration Spectral Samples */
253 	int spec = 0;						/* Don't save spectral information */
254 	icxObserverType obType = icxOT_default;
255 	int webdisp = 0;					/* NZ for web display, == port number */
256 	int ccdisp = 0;			 			/* NZ for ChromeCast, == list index */
257 	ccast_id **ccids = NULL;
258 	ccast_id *ccid = NULL;
259 #ifdef NT
260 	int madvrdisp = 0;					/* NZ for MadVR display */
261 #endif
262 	char *ccallout = NULL;				/* Change color Shell callout */
263 	char *mcallout = NULL;				/* Measure color Shell callout */
264 	int xtern = 0;						/* Use external (user supplied) values rather than */
265 										/* instument read 1 = Lab, 2 = XYZ */
266 	char inname[MAXNAMEL+1] = "\000";	/* Input cgats file base name */
267 	char outname[MAXNAMEL+1] = "\000";	/* Output cgats file base name */
268 	char calname[MAXNAMEL+1] = "\000";	/* Calibration file name (if any) */
269 	int native = 2;						/* X0 = use current per channel calibration curve */
270 										/* X1 = set native linear output and use ramdac high prec */
271 										/* 0X = use current color management cLut (MadVR) */
272 										/* 1X = disable color management cLUT (MadVR) */
273 	double cal[3][MAX_CAL_ENT];			/* Display calibration */
274 	int ncal = 256;						/* Default number of cal entries used */
275 	cgats *icg;							/* input cgats structure */
276 	cgats *ocg;							/* output cgats structure */
277 	time_t clk = time(0);
278 	struct tm *tsp = localtime(&clk);
279 	char *atm = asctime(tsp);			/* Ascii time */
280 	col *cols;							/* Internal storage of all the patch colors */
281 	int dim = 0;						/* Dimensionality - 1, 3, or 4 */
282 	int npat;							/* Number of patches/colors */
283 	int xpat = 0;						/* Set to number of extra patches */
284 	int wpat;							/* Set to index of white patch */
285 	int si;								/* Sample id index */
286 	int ti;								/* Temp index */
287 	int fi;								/* Colorspace index */
288 	int nsetel = 0;
289 	cgats_set_elem *setel;				/* Array of set value elements */
290 	disprd *dr;							/* Display patch read object */
291 	int noramdac = 0;					/* Will be set to nz if can't set ramdac */
292 	int nocm = 0;						/* Will be set to nz if can't set color management */
293 	int errc;							/* Return value from new_disprd() */
294 	int rv;
295 
296 	set_exe_path(argv[0]);				/* Set global exe_path and error_program */
297 	check_if_not_interactive();
298 
299 #ifdef DEBUG_OFFSET
300 	ho = 0.8;
301 	vo = -0.8;
302 #endif
303 
304 #if defined(DEBUG) || defined(DEBUG_OFFSET)
305 	printf("!!!!!! Debug turned on !!!!!!\n");
306 #endif
307 
308 	if (argc <= 1)
309 		usage(0,"Too few arguments");
310 
311 	if (ncal > MAX_CAL_ENT)
312 		error("Internal, ncal = %d > MAX_CAL_ENT %d\n",ncal,MAX_CAL_ENT);
313 
314 	/* Process the arguments */
315 	mfa = 1;        /* Minimum final arguments */
316 	for (fa = 1;fa < argc;fa++) {
317 		nfa = fa;					/* skip to nfa if next argument is used */
318 		if (argv[fa][0] == '-') {		/* Look for any flags */
319 			char *na = NULL;		/* next argument after flag, null if none */
320 
321 			if (argv[fa][2] != '\000')
322 				na = &argv[fa][2];		/* next is directly after flag */
323 			else {
324 				if ((fa+1+mfa) < argc) {
325 					if (argv[fa+1][0] != '-') {
326 						nfa = fa + 1;
327 						na = argv[nfa];		/* next is seperate non-flag argument */
328 					}
329 				}
330 			}
331 
332 			if (argv[fa][1] == '?' || argv[fa][1] == '-') {
333 				usage(0,"Usage requested");
334 
335 			} else if (argv[fa][1] == 'v') {
336 				verb = 1;
337 				g_log->verb = verb;
338 
339 			/* Display number */
340 			} else if (argv[fa][1] == 'd') {
341 				if (strncmp(na,"web",3) == 0
342 				 || strncmp(na,"WEB",3) == 0) {
343 					webdisp = 8080;
344 					if (na[3] == ':') {
345 						webdisp = atoi(na+4);
346 						if (webdisp == 0 || webdisp > 65535)
347 							usage(0,"Web port number must be in range 1..65535");
348 					}
349 					fa = nfa;
350 				} else if (strncmp(na,"cc",2) == 0
351 				 || strncmp(na,"CC",2) == 0) {
352 					ccdisp = 1;
353 					if (na[2] == ':') {
354 						if (na[3] < '0' || na[3] > '9')
355 							usage(0x0001,"Available ChromeCasts");
356 
357 						ccdisp = atoi(na+3);
358 						if (ccdisp <= 0)
359 							usage(0,"ChromCast number must be in range 1..N");
360 					}
361 					fa = nfa;
362 #ifdef NT
363 				} else if (strncmp(na,"madvr",5) == 0
364 				 || strncmp(na,"MADVR",5) == 0) {
365 					madvrdisp = 1;
366 					fa = nfa;
367 #endif
368 				} else {
369 #if defined(UNIX_X11)
370 					int ix, iv;
371 
372 					if (strcmp(&argv[fa][2], "isplay") == 0 || strcmp(&argv[fa][2], "ISPLAY") == 0) {
373 						if (++fa >= argc || argv[fa][0] == '-') usage(0,"Parameter expected following -display");
374 						setenv("DISPLAY", argv[fa], 1);
375 					} else {
376 						if (na == NULL) usage(0,"Parameter expected following -d");
377 						fa = nfa;
378 						if (strcmp(na,"fake") == 0) {
379 							fake = 1;
380 						} else {
381 							if (sscanf(na, "%d,%d",&ix,&iv) != 2) {
382 								ix = atoi(na);
383 								iv = 0;
384 							}
385 							if (disp != NULL)
386 								free_a_disppath(disp);
387 							if ((disp = get_a_display(ix-1)) == NULL)
388 								usage(0,"-d parameter %d out of range",ix);
389 							if (iv > 0)
390 								disp->rscreen = iv-1;
391 						}
392 					}
393 #else
394 					int ix;
395 					if (na == NULL) usage(0,"Parameter expected following -d");
396 					fa = nfa;
397 					if (strcmp(na,"fake") == 0) {
398 						fake = 1;
399 					} else {
400 						ix = atoi(na);
401 						if (disp != NULL)
402 							free_a_disppath(disp);
403 						if ((disp = get_a_display(ix-1)) == NULL)
404 							usage(0,"-d parameter %d out of range",ix);
405 					}
406 #endif
407 				}
408 #if defined(UNIX_X11)
409 			} else if (argv[fa][1] == 'n') {
410 				override = 0;
411 #endif /* UNIX */
412 
413 			/* COM port  */
414 			} else if (argv[fa][1] == 'c') {
415 				fa = nfa;
416 				if (na == NULL) usage(0,"Paramater expected following -c");
417 				comport = atoi(na);
418 				if (comport < 1 || comport > 50) usage(0,"-c parameter %d out of range",comport);
419 
420 			/* Telephoto */
421 			} else if (argv[fa][1] == 'p') {
422 				tele = 1;
423 
424 			/* Display type */
425 			} else if (argv[fa][1] == 'y') {
426 				fa = nfa;
427 				if (na == NULL) usage(0,"Parameter expected after -y");
428 				dtype = na[0];
429 
430 			/* Calibration file */
431 			} else if (argv[fa][1] == 'k'
432 			        || argv[fa][1] == 'K') {
433 				if (na == NULL) usage(0,"Parameter expected after -%c",argv[fa][1]);
434 				strncpy(calname,na,MAXNAMEL); calname[MAXNAMEL] = '\000';
435 				if (argv[fa][1] == 'K')
436 					native |= 1;			/* Use native linear & soft cal */
437 				else
438 					native &= ~1;			/* Use HW cal */
439 				fa = nfa;
440 
441 #ifdef NT
442 			/* MadVR verify mode */
443 			} else if (argv[fa][1] == 'V') {
444 				native &= ~2;
445 #endif
446 
447 			/* Save spectral data */
448 			} else if (argv[fa][1] == 's') {
449 				spec = 1;
450 
451 			/* Test patch offset and size */
452 			} else if (argv[fa][1] == 'P') {
453 				fa = nfa;
454 				if (na == NULL) usage(0,"Parameter expected after -P");
455 				if (sscanf(na, " %lf,%lf,%lf,%lf ", &ho, &vo, &hpatscale, &vpatscale) == 4) {
456 					;
457 				} else if (sscanf(na, " %lf,%lf,%lf ", &ho, &vo, &hpatscale) == 3) {
458 					vpatscale = hpatscale;
459 				} else {
460 					usage(0,"-P parameter '%s' not recognised",na);
461 				}
462 				if (ho < 0.0 || ho > 1.0
463 				 || vo < 0.0 || vo > 1.0
464 				 || hpatscale <= 0.0 || hpatscale > 50.0
465 				 || vpatscale <= 0.0 || vpatscale > 50.0)
466 					usage(0,"-P parameters %f %f %f %f out of range",ho,vo,hpatscale,vpatscale);
467 				ho = 2.0 * ho - 1.0;
468 				vo = 2.0 * vo - 1.0;
469 
470 			/* Full screen black background */
471 			} else if (argv[fa][1] == 'F') {
472 				fullscreen = 1;
473 
474 			/* Video encoded output */
475 			} else if (argv[fa][1] == 'E') {
476 				out_tvenc = 1;
477 				if (qbits == 0)
478 					qbits = 8;
479 
480 			/* Specify quantization bits */
481 			} else if (argv[fa][1] == 'Z') {
482 				fa = nfa;
483 				if (na == NULL) usage(0,"Expected argument to -Z");
484 				qbits = atoi(na);
485 				if (qbits < 1 || qbits > 32)
486 					usage(0,"Argument to -Q must be between 1 and 32");
487 
488 			/* Force calibration */
489 			} else if (argv[fa][1] == 'J') {
490 				docalib = 1;
491 
492 			/* No auto-cal */
493 			} else if (argv[fa][1] == 'N') {
494 				noautocal = 1;
495 
496 			/* High res mode */
497 			} else if (argv[fa][1] == 'H') {
498 				highres = 1;
499 
500 			/* Disable normalisation of values to white Y = 100 */
501 			} else if (argv[fa][1] == 'w') {
502 				donorm = 0;
503 
504 			/* Colorimeter Correction Matrix */
505 			/* or Colorimeter Calibration Spectral Samples */
506 			} else if (argv[fa][1] == 'X') {
507 				int ix;
508 				fa = nfa;
509 				if (na == NULL) usage(0,"Parameter expected following -X");
510 				strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
511 
512 			} else if (argv[fa][1] == 'I') {
513 				fa = nfa;
514 				if (na == NULL || na[0] == '\000') usage(0,"Parameter expected after -I");
515 				for (i=0; ; i++) {
516 					if (na[i] == '\000')
517 						break;
518 					if (na[i] == 'b' || na[i] == 'B')
519 						bdrift = 1;
520 					else if (na[i] == 'w' || na[i] == 'W')
521 						wdrift = 1;
522 					else
523 						usage(0,"-I parameter '%c' not recognised",na[i]);
524 				}
525 
526 			/* Spectral Observer type */
527 			} else if (argv[fa][1] == 'Q') {
528 				fa = nfa;
529 				if (na == NULL) usage(0,"Parameter expecte after -Q");
530 				if (strcmp(na, "1931_2") == 0) {			/* Classic 2 degree */
531 					obType = icxOT_CIE_1931_2;
532 				} else if (strcmp(na, "1964_10") == 0) {	/* Classic 10 degree */
533 					obType = icxOT_CIE_1964_10;
534 				} else if (strcmp(na, "1964_10c") == 0) {	/* 10 degree corrected */
535 					obType = icxOT_CIE_1964_10c;
536 				} else if (strcmp(na, "1955_2") == 0) {		/* Stiles and Burch 1955 2 degree */
537 					obType = icxOT_Stiles_Burch_2;
538 				} else if (strcmp(na, "1978_2") == 0) {		/* Judd and Voss 1978 2 degree */
539 					obType = icxOT_Judd_Voss_2;
540 				} else if (strcmp(na, "shaw") == 0) {		/* Shaw and Fairchilds 1997 2 degree */
541 					obType = icxOT_Shaw_Fairchild_2;
542 				} else
543 					usage(0,"-Q parameter '%s' not recognised",na);
544 
545 
546 			/* Change color callout */
547 			} else if (argv[fa][1] == 'C') {
548 				fa = nfa;
549 				if (na == NULL) usage(0,"Parameter expected after -C");
550 				ccallout = na;
551 
552 			/* Measure color callout */
553 			} else if (argv[fa][1] == 'M') {
554 				fa = nfa;
555 				if (na == NULL) usage(0,"Parameter expected after -M");
556 				mcallout = na;
557 
558 			/* Request external values */
559 			} else if (argv[fa][1] == 'x') {
560 				fa = nfa;
561 				if (na == NULL) usage(0, "Parameter expected after -x");
562 
563 				if (na[0] == 'x' || na[0] == 'X')
564 					xtern = 2;
565 //				else if (na[0] == 'l' || na[0] == 'L')
566 //					xtern = 1;
567 				else
568 					usage(0, "Unexpected parameter -x %c",na[0]);
569 
570 			/* Serial port flow control */
571 			} else if (argv[fa][1] == 'W') {
572 				fa = nfa;
573 				if (na == NULL) usage(0,"Parameter expected after -W");
574 				if (na[0] == 'n' || na[0] == 'N')
575 					fc = fc_None;
576 				else if (na[0] == 'h' || na[0] == 'H')
577 					fc = fc_Hardware;
578 				else if (na[0] == 'x' || na[0] == 'X')
579 					fc = fc_XonXOff;
580 				else
581 					usage(0,"-W parameter '%s' not recognised",na);
582 
583 			} else if (argv[fa][1] == 'D') {
584 				debug = 1;
585 				if (na != NULL && na[0] >= '0' && na[0] <= '9') {
586 					debug = atoi(na);
587 					fa = nfa;
588 				}
589 				g_log->debug = debug;
590 				callback_ddebug = 1;		/* dispwin global */
591 
592 			/* Extra flags */
593 			} else if (argv[fa][1] == 'Y') {
594 				if (na == NULL)
595 					usage(0,"Flag '-Y' expects extra flag");
596 
597 				if (na[0] == 'R') {
598 					if (na[1] != ':')
599 						usage(0,"-Y R:rate syntax incorrect");
600 					refrate = atof(na+2);
601 					if (refrate < 5.0 || refrate > 150.0)
602 						usage(0,"-Y R:rate %f Hz not in valid range",refrate);
603 				} else if (na[0] == 'p') {
604 					noplace = 1;
605 				} else if (na[0] == 'A') {
606 					nadaptive = 1;
607 				} else {
608 					usage(0,"Flag '-Y %c' not recognised",na[0]);
609 				}
610 				fa = nfa;
611 
612 			} else
613 				usage(0,"Flag '-%c' not recognised",argv[fa][1]);
614 			}
615 		else
616 			break;
617 	}
618 
619 	/* No explicit display has been set */
620 	if (!fake
621 #ifdef NT
622 	 && madvrdisp == 0
623 #endif
624 	 && webdisp == 0
625 	 && ccdisp == 0
626 	 && disp == NULL) {
627 		int ix = 0;
628 #if defined(UNIX_X11)
629 		char *dn, *pp;
630 
631 		if ((dn = getenv("DISPLAY")) != NULL) {
632 			if ((pp = strrchr(dn, ':')) != NULL) {
633 				if ((pp = strchr(pp, '.')) != NULL) {
634 					if (pp[1] != '\000')
635 						ix = atoi(pp+1);
636 				}
637 			}
638 		}
639 #endif
640 		if ((disp = get_a_display(ix)) == NULL)
641 			error("Unable to open the default display");
642 	}
643 
644 	/* See if there is an environment variable ccxx */
645 	if (ccxxname[0] == '\000') {
646 		char *na;
647 		if ((na = getenv("ARGYLL_COLMTER_CAL_SPEC_SET")) != NULL) {
648 			strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
649 
650 		} else if ((na = getenv("ARGYLL_COLMTER_COR_MATRIX")) != NULL) {
651 			strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
652 		}
653 	}
654 
655 	/* Load up CCMX or CCSS */
656 	if (ccxxname[0] != '\000') {
657 		if ((cmx = new_ccmx()) == NULL
658 		  || cmx->read_ccmx(cmx, ccxxname)) {
659 			if (cmx != NULL) {
660 				cmx->del(cmx);
661 				cmx = NULL;
662 			}
663 
664 			/* CCMX failed, try CCSS */
665 			if ((ccs = new_ccss()) == NULL
666 			  || ccs->read_ccss(ccs, ccxxname)) {
667 				if (ccs != NULL) {
668 					ccs->del(ccs);
669 					ccs = NULL;
670 					error("Reading CCMX/CCSS File '%s' failed\n", ccxxname);
671 				}
672 			}
673 		}
674 	}
675 
676 	if (fake)
677 		comport = FAKE_DEVICE_PORT;
678 	if ((icmps = new_icompaths(g_log)) == NULL)
679 		error("Finding instrument paths failed");
680 	if ((ipath = icmps->get_path(icmps, comport)) == NULL)
681 		error("No instrument at port %d",comport);
682 
683 	/* If we've requested ChromeCast, look it up */
684 	if (ccdisp) {
685 		if ((ccids = get_ccids()) == NULL)
686 			error("discovering ChromCasts failed");
687 		if (ccids[0] == NULL)
688 			error("There are no ChromCasts to use\n");
689 		for (i = 0; ccids[i] != NULL; i++)
690 			;
691 		if (ccdisp < 1 || ccdisp > i)
692 			error("Chosen ChromCasts (%d) is outside list (1..%d)\n",ccdisp,i);
693 		ccid = ccids[ccdisp-1];
694 	}
695 
696 	if (docalib) {
697 		if ((rv = disprd_calibration(ipath, fc, dtype, -1, 0, tele, nadaptive, noautocal,
698 			                         disp, webdisp, ccid,
699 #ifdef NT
700 			                         madvrdisp,
701 #endif
702 			                         out_tvenc, fullscreen, override,
703 			                         100.0 * hpatscale, 100.0 * vpatscale, ho, vo,
704 		                             g_log)) != 0) {
705 			error("docalibration failed with return value %d\n",rv);
706 		}
707 	}
708 
709 	/* Get the file name argument */
710 	if (fa >= argc || argv[fa][0] == '-') usage(0,"Filname parameter not found");
711 	strncpy(inname,argv[fa++],MAXNAMEL-4); inname[MAXNAMEL-4] = '\000';
712 	strcpy(outname,inname);
713 	strcat(inname,".ti1");
714 	strcat(outname,".ti3");
715 
716 	icg = new_cgats();			/* Create a CGATS structure */
717 	icg->add_other(icg, "CTI1"); 	/* our special input type is Calibration Target Information 1 */
718 
719 	if (icg->read_name(icg, inname))
720 		error("CGATS file read error : %s",icg->err);
721 
722 	if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
723 		error ("Input file '%s' isn't a CTI1 format file",inname);
724 	if (icg->ntables < 1)		/* We don't use second table at the moment */
725 		error ("Input file '%s' doesn't contain at least one table",inname);
726 
727 	if ((npat = icg->t[0].nsets) <= 0)
728 		error ("Input file '%s' has no sets of data",inname);
729 
730 	/* Setup output cgats file */
731 	ocg = new_cgats();					/* Create a CGATS structure */
732 	ocg->add_other(ocg, "CTI3"); 		/* our special type is Calibration Target Information 3 */
733 	ocg->add_table(ocg, tt_other, 0);	/* Start the first table */
734 
735 	ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
736 	ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll dispread", NULL);
737 	atm[strlen(atm)-1] = '\000';	/* Remove \n from end */
738 	ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
739 	ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);	/* What sort of device this is */
740 
741 	if ((ti = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0)
742 		ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[ti], NULL);
743 
744 	if ((ti = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0)
745 		ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[ti], NULL);
746 
747 	if ((ti = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0)
748 		ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[ti], NULL);
749 
750 	if ((ti = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0)
751 		ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[ti], NULL);
752 
753 	if ((ti = icg->find_kword(icg, 0, "DARK_REGION_EMPHASIS")) >= 0)
754 		ocg->add_kword(ocg, 0, "DARK_REGION_EMPHASIS",icg->t[0].kdata[ti], NULL);
755 
756 	if (verb) {
757 		printf("Number of patches = %d\n",npat);
758 	}
759 
760 	/* Fields we want */
761 	ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
762 
763 	if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
764 		error ("Input file '%s' doesn't contain field SAMPLE_ID",inname);
765 	if (icg->t[0].ftype[si] != nqcs_t)
766 		error ("Input file %s' field SAMPLE_ID is wrong type",inname);
767 
768 	if ((cols = (col *)malloc(sizeof(col) * (npat+1))) == NULL)
769 		error("Malloc failed!");
770 
771 	/* Figure out the color space */
772 	/* Read all the test patches in, and quantize them */
773 	if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
774 		error ("Input file '%s' doesn't contain keyword COLOR_REP",inname);
775 	if (strcmp(icg->t[0].kdata[fi],"RGB") == 0) {
776 		int ri, gi, bi;
777 		double rgb[3];
778 		double qscale = (1 << qbits) - 1.0;
779 		dim = 3;
780 		if ((ri = icg->find_field(icg, 0, "RGB_R")) < 0)
781 			error ("Input file '%s' doesn't contain field RGB_R",inname);
782 		if (icg->t[0].ftype[ri] != r_t)
783 			error ("Input file '%s' field RGB_R is wrong type - expect float",inname);
784 		if ((gi = icg->find_field(icg, 0, "RGB_G")) < 0)
785 			error ("Input file '%s' doesn't contain field RGB_G",inname);
786 		if (icg->t[0].ftype[gi] != r_t)
787 			error ("Input file '%s' field RGB_G is wrong type - expect float",inname);
788 		if ((bi = icg->find_field(icg, 0, "RGB_B")) < 0)
789 			error ("Input file '%s' doesn't contain field RGB_B",inname);
790 		if (icg->t[0].ftype[bi] != r_t)
791 			error ("Input file '%s' field RGB_B is wrong type - expect float",inname);
792 		ocg->add_field(ocg, 0, "RGB_R", r_t);
793 		ocg->add_field(ocg, 0, "RGB_G", r_t);
794 		ocg->add_field(ocg, 0, "RGB_B", r_t);
795 		ocg->add_kword(ocg, 0, "COLOR_REP","RGB_XYZ", NULL);
796 		ocg->add_field(ocg, 0, "XYZ_X", r_t);
797 		ocg->add_field(ocg, 0, "XYZ_Y", r_t);
798 		ocg->add_field(ocg, 0, "XYZ_Z", r_t);
799 		for (i = 0; i < npat; i++) {
800 			cols[i].id = ((char *)icg->t[0].fdata[i][si]);
801 			rgb[0] = *((double *)icg->t[0].fdata[i][ri]) / 100.0;
802 			rgb[1] = *((double *)icg->t[0].fdata[i][gi]) / 100.0;
803 			rgb[2] = *((double *)icg->t[0].fdata[i][bi]) / 100.0;
804 			if (qbits > 0) {
805 				double vr;
806 				for (j = 0; j < 3; j++) {
807 					rgb[j] *= qscale;
808 					vr = floor(rgb[j] + 0.5);
809 					if ((vr - rgb[j]) == 0.5 && (((int)vr) & 1) != 0) /* Round to even */
810 						vr -= 1.0;
811 					rgb[j] = vr/qscale;
812 				}
813 			}
814 			cols[i].r = rgb[0];
815 			cols[i].g = rgb[1];
816 			cols[i].b = rgb[2];
817 			cols[i].XYZ[0] = cols[i].XYZ[1] = cols[i].XYZ[2] = -1.0;
818 		}
819 	} else
820 		error ("Input file '%s' keyword COLOR_REP has illegal value (RGB colorspace expected)",inname);
821 
822 	/* Check that there is a white patch, and if not, add one, */
823 	/* so that we can normalize the values to white. */
824 	for (wpat = 0; wpat < npat; wpat++) {
825 		if (cols[wpat].r > 0.9999999 &&
826 		    cols[wpat].g > 0.9999999 &&
827 		    cols[wpat].b > 0.9999999) {
828 			break;
829 		}
830 	}
831 	if (wpat >= npat) {	/* Create a white patch */
832 		if (verb)
833 			printf("Adding one white patch\n");
834 		xpat = 1;
835 		cols[wpat].r = cols[wpat].g = cols[wpat].b = 1.0;
836 	}
837 
838 	/* Setup a display calibration set if we are given one */
839 	/* (Should switch to xcal ?) */
840 	if (calname[0] != '\000') {
841 		cgats *ccg;			/* calibration cgats structure */
842 		int ii, ri, gi, bi;
843 
844 		ccg = new_cgats();			/* Create a CGATS structure */
845 		ccg->add_other(ccg, "CAL"); /* our special calibration type */
846 
847 		if (ccg->read_name(ccg, calname))
848 			error("CGATS calibration file read error %s on file '%s'",ccg->err,calname);
849 
850 		if (ccg->ntables == 0 || ccg->t[0].tt != tt_other || ccg->t[0].oi != 0)
851 			error ("Calibration file isn't a CAL format file");
852 		if (ccg->ntables < 1)
853 			error ("Calibration file '%s' doesn't contain at least one table",calname);
854 
855 		if ((ncal = ccg->t[0].nsets) <= 0)
856 			error ("No data in set of file '%s'",calname);
857 
858 		if (ncal < 2 || ncal > MAX_CAL_ENT)
859 			error("Data set size %d is out of range for '%s'",ncal,calname);
860 
861 		if (ncal > MAX_CAL_ENT)
862 			error ("Cant handle %d data sets in file '%s', max is %d",ncal,calname,MAX_CAL_ENT);
863 
864 		if ((fi = ccg->find_kword(ccg, 0, "DEVICE_CLASS")) < 0)
865 			error ("Calibration file '%s' doesn't contain keyword DEVICE_CLASS",calname);
866 		if (strcmp(ccg->t[0].kdata[fi],"DISPLAY") != 0)
867 			error ("Calibration file '%s' doesn't have DEVICE_CLASS of DISPLAY",calname);
868 
869 		if ((fi = ccg->find_kword(ccg, 0, "VIDEO_LUT_CALIBRATION_POSSIBLE")) >= 0) {
870 			if (stricmp(ccg->t[0].kdata[fi],"NO") == 0) {
871 				native = 1;
872 				if (verb) printf("Switching to soft cal because there is no access to VideoLUTs\n");
873 			}
874 		}
875 
876 		if ((fi = ccg->find_kword(ccg, 0, "TV_OUTPUT_ENCODING")) >= 0) {
877 			if (!out_tvenc && (strcmp(ccg->t[0].kdata[fi], "YES") == 0
878 			 || strcmp(ccg->t[0].kdata[fi], "yes")) == 0) {
879 				if (verb) printf("Using Video range (16-235)/255 range encoding because cal. used it\n");
880 				out_tvenc = 1;
881 			}
882 		}
883 
884 		if ((fi = ccg->find_kword(ccg, 0, "COLOR_REP")) < 0)
885 			error ("Calibration file '%s' doesn't contain keyword COLOR_REP",calname);
886 		if (strcmp(ccg->t[0].kdata[fi],"RGB") != 0)
887 			error ("Calibration file '%s' doesn't have COLOR_REP of RGB",calname);
888 
889 		if ((ii = ccg->find_field(ccg, 0, "RGB_I")) < 0)
890 			error ("Calibration file '%s' doesn't contain field RGB_I",calname);
891 		if (ccg->t[0].ftype[ii] != r_t)
892 			error ("Field RGB_R in file '%s' is wrong type - expect float",calname);
893 		if ((ri = ccg->find_field(ccg, 0, "RGB_R")) < 0)
894 			error ("Calibration file '%s' doesn't contain field RGB_R",calname);
895 		if (ccg->t[0].ftype[ri] != r_t)
896 			error ("Field RGB_R in file '%s' is wrong type - expect float",calname);
897 		if ((gi = ccg->find_field(ccg, 0, "RGB_G")) < 0)
898 			error ("Calibration file '%s' doesn't contain field RGB_G",calname);
899 		if (ccg->t[0].ftype[gi] != r_t)
900 			error ("Field RGB_G in file '%s' is wrong type - expect float",calname);
901 		if ((bi = ccg->find_field(ccg, 0, "RGB_B")) < 0)
902 			error ("Calibration file '%s' doesn't contain field RGB_B",calname);
903 		if (ccg->t[0].ftype[bi] != r_t)
904 			error ("Field RGB_B in file '%s' is wrong type - expect float",calname);
905 		for (i = 0; i < ncal; i++) {
906 			cal[0][i] = *((double *)ccg->t[0].fdata[i][ri]);
907 			cal[1][i] = *((double *)ccg->t[0].fdata[i][gi]);
908 			cal[2][i] = *((double *)ccg->t[0].fdata[i][bi]);
909 		}
910 		ccg->del(ccg);
911 	} else {
912 		cal[0][0] = -1.0;	/* Not used */
913 	}
914 
915 	if ((dr = new_disprd(&errc, ipath, fc, dtype, -1, 0, tele, nadaptive, noautocal, noplace,
916 	                     highres, refrate, native, &noramdac, &nocm, cal, ncal, disp,
917 		                 out_tvenc, fullscreen, override, webdisp, ccid,
918 #ifdef NT
919 						 madvrdisp,
920 #endif
921 		                 ccallout, mcallout, xtern,
922 		                 100.0 * hpatscale, 100.0 * vpatscale, ho, vo,
923 	                     ccs != NULL ? ccs->dtech : cmx != NULL ? cmx->dtech : disptech_unknown,
924 	                     cmx != NULL ? cmx->cc_cbid : 0,
925 	                     cmx != NULL ? cmx->matrix : NULL,
926 	                     ccs != NULL ? ccs->samples : NULL, ccs != NULL ? ccs->no_samp : 0,
927 		                 spec, obType, NULL, bdrift, wdrift,
928 		                 "fake" ICC_FILE_EXT, g_log)) == NULL)
929 		error("new_disprd failed with '%s'\n",disprd_err(errc));
930 
931 	if (cmx != NULL)
932 		cmx->del(cmx);
933 	if (ccs != NULL)
934 		ccs->del(ccs);
935 
936 	/* Test the CRT with all of the test points */
937 	if ((rv = dr->read(dr, cols, npat + xpat, 1, npat + xpat, 1, 0, instNoClamp)) != 0) {
938 		dr->del(dr);
939 		error("dispd->read returned error code %d\n",rv);
940 	}
941 	/* Note what instrument the chart was read with */
942 	if (dr->it != NULL) {
943 		int refrmode, cbid;
944 		ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(dr->it->get_itype(dr->it)) , NULL);
945 		dr->get_disptype(dr, &refrmode, &cbid);
946 		if (refrmode >= 0)
947 			ocg->add_kword(ocg, 0, "DISPLAY_TYPE_REFRESH", refrmode ? "YES" : "NO", NULL);
948 		if (cbid != 0) {
949 			char buf[100];
950 			sprintf(buf, "%d", cbid);
951 			ocg->add_kword(ocg, 0, "DISPLAY_TYPE_BASE_ID", buf, NULL);
952 		}
953 	} else {
954 		ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", "Fake" , NULL);
955 	}
956 
957 	/* Note if the instrument is natively spectral or not */
958 	if (dr->it != NULL) {
959 		inst_mode cap;
960 		dr->it->capabilities(dr->it, &cap, NULL, NULL);
961 
962 		if (dr->it != NULL && cap & inst_mode_spectral)
963 			ocg->add_kword(ocg, 0, "INSTRUMENT_TYPE_SPECTRAL", "YES" , NULL);
964 		else
965 			ocg->add_kword(ocg, 0, "INSTRUMENT_TYPE_SPECTRAL", "NO" , NULL);
966 	} else {
967 		ocg->add_kword(ocg, 0, "INSTRUMENT_TYPE_SPECTRAL", "NO" , NULL);
968 	}
969 
970 	dr->del(dr);
971 
972 	/* And save the result: */
973 
974 	/* Convert from absolute XYZ to relative XYZ */
975 	if (npat > 0) {
976 		double nn;
977 
978 		/* Make sure there is a copy of the white patch beyond npat, */
979 		/* so that it can be left absolute. */
980 		if (wpat != npat) {
981 			cols[npat].r = cols[npat].g = cols[npat].b = 1.0;
982 			cols[npat].XYZ[0] = cols[wpat].XYZ[0];
983 			cols[npat].XYZ[1] = cols[wpat].XYZ[1];
984 			cols[npat].XYZ[2] = cols[wpat].XYZ[2];
985 			cols[npat].XYZ_v = cols[wpat].XYZ_v;
986 			wpat = npat;
987 		}
988 
989 		if (donorm) {
990 			if (cols[wpat].XYZ_v == 0)
991 				error("XYZ of white patch is not valid!\nCan't normalise value to white",i);
992 
993 			nn = 100.0 / cols[wpat].XYZ[1];		/* Normalise Y of white to 100 */
994 		} else
995 			nn = 1.0;
996 
997 		for (i = 0; i < npat; i++) {
998 
999 			if (cols[i].XYZ_v == 0)
1000 				warning("XYZ patch %d is not valid!",i+1);
1001 
1002 			for (j = 0; j < 3; j++)
1003 				cols[i].XYZ[j] = nn * cols[i].XYZ[j];
1004 
1005 			/* Keep spectral aligned with normalised XYZ */
1006 			if (cols[i].sp.spec_n > 0) {
1007 				for (j = 0; j < cols[i].sp.spec_n; j++)
1008 					cols[i].sp.spec[j] *= nn;
1009 			}
1010 		}
1011 	}
1012 
1013 	nsetel += 1;		/* For id */
1014 	nsetel += dim;		/* For device values */
1015 	nsetel += 3;		/* For XYZ */
1016 
1017 	/* If we have spectral information, output it too */
1018 	if (npat > 0 && cols[0].sp.spec_n > 0) {
1019 		char buf[100];
1020 
1021 		nsetel += cols[0].sp.spec_n;		/* Spectral values */
1022 		sprintf(buf,"%d", cols[0].sp.spec_n);
1023 		ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
1024 		sprintf(buf,"%f", cols[0].sp.spec_wl_short);
1025 		ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
1026 		sprintf(buf,"%f", cols[0].sp.spec_wl_long);
1027 		ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
1028 
1029 		/* Generate fields for spectral values */
1030 		for (i = 0; i < cols[0].sp.spec_n; i++) {
1031 			int nm;
1032 
1033 			/* Compute nearest integer wavelength */
1034 			nm = (int)(cols[0].sp.spec_wl_short + ((double)i/(cols[0].sp.spec_n-1.0))
1035 			            * (cols[0].sp.spec_wl_long - cols[0].sp.spec_wl_short) + 0.5);
1036 
1037 			sprintf(buf,"SPEC_%03d",nm);
1038 			ocg->add_field(ocg, 0, buf, r_t);
1039 		}
1040 	}
1041 
1042 	if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
1043 		error("Malloc failed!");
1044 
1045 	/* Write out the patch info to the output CGATS file */
1046 	for (i = 0; i < npat; i++) {
1047 		int k = 0;
1048 
1049 		if (cols[i].XYZ_v == 0) {
1050 			warning("Omitting patch %d from .ti3 file!",i+1);
1051 			continue;
1052 		}
1053 
1054 		setel[k++].c = cols[i].id;
1055 		setel[k++].d = 100.0 * cols[i].r;
1056 		setel[k++].d = 100.0 * cols[i].g;
1057 		setel[k++].d = 100.0 * cols[i].b;
1058 
1059 		setel[k++].d = cols[i].XYZ[0];
1060 		setel[k++].d = cols[i].XYZ[1];
1061 		setel[k++].d = cols[i].XYZ[2];
1062 
1063 		for (j = 0; j < cols[i].sp.spec_n; j++) {
1064 			setel[k++].d = cols[i].sp.spec[j];
1065 		}
1066 
1067 		ocg->add_setarr(ocg, 0, setel);
1068 	}
1069 
1070 	free(setel);
1071 
1072 	/* If we have the absolute brightness of the display, record it */
1073 	if (cols[wpat].XYZ_v != 0) {
1074 		char buf[100];
1075 
1076 		sprintf(buf,"%f %f %f", cols[wpat].XYZ[0], cols[wpat].XYZ[1], cols[wpat].XYZ[2]);
1077 		ocg->add_kword(ocg, 0, "LUMINANCE_XYZ_CDM2",buf, NULL);
1078 	}
1079 
1080 	if (donorm)
1081 		ocg->add_kword(ocg, 0, "NORMALIZED_TO_Y_100","YES", NULL);
1082 	else
1083 		ocg->add_kword(ocg, 0, "NORMALIZED_TO_Y_100","NO", NULL);
1084 
1085 	/* Write out the calibration if we have it */
1086 	if (cal != NULL && cal[0][0] >= 0.0) {
1087 		ocg->add_other(ocg, "CAL"); 		/* our special type is Calibration file */
1088 		ocg->add_table(ocg, tt_other, 1);	/* Add another table for RAMDAC values */
1089 		ocg->add_kword(ocg, 1, "DESCRIPTOR", "Argyll Device Calibration State",NULL);
1090 		ocg->add_kword(ocg, 1, "ORIGINATOR", "Argyll dispread", NULL);
1091 		ocg->add_kword(ocg, 1, "CREATED",atm, NULL);
1092 
1093 		ocg->add_kword(ocg, 1, "DEVICE_CLASS","DISPLAY", NULL);
1094 		ocg->add_kword(ocg, 1, "COLOR_REP","RGB", NULL);
1095 		ocg->add_kword(ocg, 0, "VIDEO_LUT_CALIBRATION_POSSIBLE",noramdac ? "NO" : "YES", NULL);
1096 
1097 		ocg->add_field(ocg, 1, "RGB_I", r_t);
1098 		ocg->add_field(ocg, 1, "RGB_R", r_t);
1099 		ocg->add_field(ocg, 1, "RGB_G", r_t);
1100 		ocg->add_field(ocg, 1, "RGB_B", r_t);
1101 
1102 		if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * 4)) == NULL)
1103 			error("Malloc failed!");
1104 
1105 		for (i = 0; i < ncal; i++) {
1106 			double vv;
1107 
1108 #if defined(__APPLE__) && defined(__POWERPC__)
1109 			gcc_bug_fix(i);
1110 #endif
1111 			vv = i/(ncal-1.0);
1112 
1113 			setel[0].d = vv;
1114 			setel[1].d = cal[0][i];
1115 			setel[2].d = cal[1][i];
1116 			setel[3].d = cal[2][i];
1117 
1118 			ocg->add_setarr(ocg, 1, setel);
1119 		}
1120 
1121 		free(setel);
1122 	}
1123 
1124 	if (ocg->write_name(ocg, outname))
1125 		error("Write error : %s",ocg->err);
1126 
1127 	if (verb)
1128 		printf("Written '%s'\n",outname);
1129 
1130 	icmps->del(icmps);
1131 	free(cols);
1132 	ocg->del(ocg);		/* Clean up */
1133 	icg->del(icg);		/* Clean up */
1134 	free_a_disppath(disp);
1135 	free_ccids(ccids);
1136 
1137 	return 0;
1138 }
1139 
1140 
1141