1 
2 /*
3  * Argyll Color Correction System
4  * Display callibrator.
5  *
6  * Author: Graeme W. Gill
7  * Date:   14/10/2005
8  *
9  * Copyright 1996 - 2013 Graeme W. Gill
10  * All rights reserved.
11  *
12  * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
13  * see the License.txt file for licencing details.
14  */
15 
16 /* This program displays test patches, and takes readings from a display device */
17 /* in order to create a RAMDAC calibration curve (usually stored in the ICC vcgt tag) */
18 
19 /* This is the third version of the program. */
20 
21 /* TTBD
22 
23 	Should shift to using xicc code for BT.1886 and target response
24 	curve, for consistency with collink etc.
25 
26 	Add support for black recalibration using i1pro or munki.
27 	Setable timeout ? Need to allow placing instrument back
28 	on screen. Need this to properly handle ss anyway ?
29 
30 	Calibrating the black point of a true power response
31 	device is very slow to converge - the jacobian is always
32 	underestimating the actual delta RGB needed because the
33 	slope is getting shallower and shallower. Need to
34 	be able to figure when to increase rgain in those circumstances,
35 	rather than reducing it ?
36 
37 	Dealing with noisy/inconsistent readings could probably
38 	be improved - the statistical information from a iteration
39 	series is being ignored. ie. do a linear regression/fit
40 	on all the values for a given target, and then
41 	at the end, use a weighted blend of the best solution
42 	and the fit. Weight by something like the number used
43 	for the fit. vs. 1.
44 
45 	Try to improve calibration speed by using adaptive
46 	measurement set, rather than fixed resolution doubling ?
47 	(ie. just measure at troublesome points using a "divide in half"
48 	 strategy ?. Estimate error between measurement points and
49 	pick the next largest error.)
50 
51 	Add option to use L*u*v* DE's, as this is used in
52 	some video standards. They sometime use u*v* as
53 	a color tollerance too (see EBU  TECH 3320).
54 
55 	Add a white point option that makes the target the
56 	closest temperature to the native one of the display :-
57 	ie. it moves the display to the closest point on the
58 	chosen locus to RGB 1,1,1.
59      ie. should it do this if "-t" or "-T"
60 	with no specific temperature is chosen ?
61 
62 	Change white point gamut clipping to be a measurement
63 	search rather than computing from primary XYZ ?
64 
65 	Handling of white and black device clipping is not so good.
66 	White clipping isn't characterized very well due to sparse sampling,
67 	and moncurve tends to smooth over the clip inflection point,
68 	making it innacurate. This particularly hurts the black point
69 	accuracy, leading to raised or crushed blacks.
70 
71 	Add bell at end of calibration ?
72 
73 	Add option to plot graph of native and calibrated RGB ?
74 
75 	Add a "delta E" number to the interactive adjustments,
76 	so the significance of the error can be judged ?
77 
78 	Need to add flare measure/subtract, to improve
79 	projector calibration ? - need to add to dispread too.
80 
81 	Instead of measuring/matching output at 50% device input as
82 	measure of gamma, what about inverting it - measure/match device
83     values at 50% perceptual (18%) output value ?
84 	[ Hmm. Current method is OK because a good perceptual
85 	  display gives about 18% output at 50% device input.]
86 
87 
88 	The verify (-z) may not be being done correctly.
89 	Like update, shouldn't it read the .cal file to set what's
90 	being calibrated aganist ? (This would fix missing ambient value too!)
91 
92 	What about the "Read the base test set" - aren't
93 	there numbers then used to tweak the black aim point
94 	in "Figure out the black point target" - Yes they are !!
95 	Verify probably shouldn't work this way.
96 
97 	Add DICOM Part 14 GSDF support:
98 
99 		* Add absolute DICOM function target to dispcal.
100 		* Add 20% grey background full screen option + 10% patch recommendation
101 		* Add "include Glare" option for contact instruments to dispsup.c
102 		* Add DICOM mode black point hue handling (? what policy ?)
103 		* Add DICOM stats report (JND dE + mean + SD) to verify ??
104  */
105 
106 #ifdef __MINGW32__
107 # define WINVER 0x0500
108 #endif
109 
110 #include <stdio.h>
111 #include <stdlib.h>
112 #include <math.h>
113 #include <sys/types.h>
114 #include <time.h>
115 #include <string.h>
116 #include <stdarg.h>
117 #if defined (NT)
118 #include <conio.h>
119 #endif
120 #include "copyright.h"
121 #include "aconfig.h"
122 #include "numlib.h"
123 #include "xicc.h"
124 #include "xspect.h"
125 #include "xcolorants.h"
126 #include "cgats.h"
127 #include "insttypes.h"
128 #include "conv.h"
129 #include "icoms.h"
130 #include "inst.h"
131 #include "ccmx.h"
132 #include "ccss.h"
133 #include "dispwin.h"
134 #include "ui.h"
135 #include "ccast.h"
136 #include "dispsup.h"
137 #include "rspl.h"
138 #include "moncurve.h"
139 #include "targen.h"
140 #include "ofps.h"
141 #include "icc.h"
142 #include "sort.h"
143 #include "instappsup.h"
144 #ifdef ENABLE_USB
145 # include "spyd2.h"
146 #endif
147 
148 #undef DEBUG
149 #undef DEBUG_OFFSET			/* Keep test window out of the way */
150 #undef DEBUG_PLOT			/* Plot curve each time around */
151 #undef CHECK_MODEL			/* Do readings to check the accuracy of our model */
152 #undef SHOW_WINDOW_ONFAKE	/* Display a test window up for a fake device */
153 
154 /* Invoke with -dfake for testing with a fake device. */
155 /* Will use a fake.icm/.icc profile if present, or a built in fake */
156 /* device behaviour if not. */
157 
158 #define COMPORT 1			/* Default com port 1..4 */
159 #define OPTIMIZE_MODEL		/* Adjust model for best fit */
160 #define REFINE_GAIN 0.90	/* Refinement correction damping/gain */
161 #define VER_RES 100			/* Verification resolution */
162 #define NEUTRAL_BLEND_RATE 4.0		/* Default rate of transition for -k factor < 1.0 (power) */
163 #define ADJ_JACOBIAN		/* Adjust the Jacobian predictor matrix each time */
164 #define JAC_COMP_FACT 0.4	/* Amount to compound Jacobian correction */
165 #define JAC_COR_FACT 0.4	/* Amount to damp Jacobian by (to filter noise) */
166 #define REMEAS_JACOBIAN		/* Re-measure Jacobian if it is a poor predictor */
167 #define MOD_DIST_POW 1.6	/* Power used to distribute test samples for model building */
168 #define REFN_DIST_POW 1.6	/* Power used to distribute test samples for grey axis refinement */
169 #define CHECK_DIST_POW 1.6	/* Power used to distribute test samples for grey axis checking */
170 #define THRESH_SCALE_POW 0.5 /* Amount to loosen threshold for first itterations */
171 #define ADJ_THRESH			/* Adjust threshold to be half a step */
172 #define MIN_THRESH 0.05	 	/* Minimum stopping threshold to allow in ADJ_THRESH */
173 #define POWERR_THR 0.05		/* Point near black to start weighting +ve error */
174 #define POWERR_WEIGHT 999.0	/* Weight to give +ve delta E at black */
175 #define POWERR_WEIGHT_POW 4.0	/* Curve to plend from equal weight to +ve extra weight */
176 #define CAL_RES 256			/* Resolution of calibration table to produce. */
177 #define CLIP				/* Clip RGB during refinement */
178 #define RDAC_SMOOTH 0.3		/* RAMDAC curve fitting smoothness */
179 #define MEAS_RES			/* Measure the RAMNDAC entry size */
180 
181 #ifdef DEBUG_PLOT
182 #include "plot.h"
183 #endif
184 
185 #if defined(DEBUG)
186 
187 #define DBG(xxx) fprintf xxx ;
188 #define dbgo stderr
189 #else
190 #define DBG(xxx)
191 #endif	/* DEBUG */
192 
193 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
194 /* Sample points used in initial device model optimisation */
195 
196 typedef struct {
197 	double dev[3];		/* Device values */
198 	double lab[3];		/* Read value */
199 	double w;			/* Weighting */
200 } optref;
201 
202 /* - - - - - - - - - - - - - - - - - - - */
203 /* device RGB inverse solution code */
204 
205 /* Selected transfer curve */
206 typedef enum {
207 	gt_power     = 0,	/* A simple power */
208 	gt_Lab       = 1,	/* The L* curve */
209 	gt_sRGB      = 2,	/* The sRGB curve */
210 	gt_Rec709    = 3,	/* REC 709 video standard */
211 	gt_SMPTE240M = 4	/* SMTPE 240M video standard */
212 } gammatype;
213 
214 /* Context for calibration solution */
215 typedef struct {
216 	double wh[3];		/* White absolute XYZ value */
217 	double bk[3];		/* Black absolute XYZ value */
218 
219 	/* Target model */
220 	gammatype gammat;	/* Transfer curve type */
221 	double egamma;		/* Effective Gamma target */
222 	double oofff;		/* proportion of output offset vs input offset (default 1.0) */
223 	double gioff;		/* Gamma curve input zero offset */
224 	double gooff;		/* Target output offset (normalised to Y max of 1.0) */
225 	int nat;			/* Flag - nz if native white target */
226 	double nbrate;		/* Neutral blend weight (power) */
227 	int bkhack;			/* Flag - nz if black is hacked to be device zero */
228 
229 	/* Viewing  conditions adjustment */
230 	int vc;				/* Flag, nz to enable viewing conditions adjustment */
231 	icxcam *svc;		/* Source viewing conditions */
232 	icxcam *dvc;		/* Destination viewing conditions */
233 	double vn0, vn1;	/* Normalisation values */
234 
235 	double nwh[3];		/* Target white normalised XYZ value (Y = 1.0) */
236 	double twh[3];		/* Target white absolute XYZ value */
237 	double twYxy[3];	/* Target white Yxy (informational) */
238 	icmXYZNumber twN;	/* Same as above as XYZNumber */
239 
240 	double tbk[3];		/* Target black point color */
241 	icmXYZNumber tbN;	/* Same as above as XYZNumber */
242 
243 	/* Device model */
244 	double fm[3][3];	/* Forward, aprox. linear RGB -> XYZ */
245 	double bm[3][3];	/* Backwards, aprox. XYZ -> linear RGB */
246 	mcv *dcvs[3];		/* Device RGB channel to linearised RGB curves */
247 						/* These are always normalized to map 1.0 to 1.0 */
248 
249 	/* Current state */
250 	mcv *rdac[3];		/* Current RGB to RGB ramdac curves */
251 
252 	double xyz[3];		/* Target xyz value */
253 
254 	/* optimisation information */
255 	int np;				/* Total number of optimisation parameters */
256 	int co[3];			/* Offset in the parameters to each curve offset */
257 	int nc[3];			/* Number of for each curve */
258 	int nrp;			/* Total number of reference points */
259 	optref *rp;			/* reference points */
260 	double *dtin_iv;	/* Temporary array :- dp for input curves */
261 } calx;
262 
263 /* - - - - - - - - - - - - - - - - - - - */
264 /* Ideal target curve definitions */
265 
266 /* Convert ideal device (0..1) to target Y value (0..1) */
dev2Y(calx * x,double egamma,double vv)267 static double dev2Y(calx *x, double egamma, double vv) {
268 
269 	switch(x->gammat) {
270 		case gt_power: {
271 			vv = pow(vv, egamma);
272 			break;
273 		}
274 		case gt_Lab: {
275 			vv = icmL2Y(vv * 100.0);
276 			break;
277 		}
278 		case gt_sRGB: {
279 			if (vv <= 0.03928)
280 				vv = vv/12.92;
281 			else
282 				vv = pow((0.055 + vv)/1.055, 2.4);
283 			break;
284 		}
285 		case gt_Rec709: {
286 			if (vv <= 0.081)
287 				vv = vv/4.5;
288 			else
289 				vv = pow((0.099 + vv)/1.099, 1.0/0.45);
290 			break;
291 		}
292 		case gt_SMPTE240M: {
293 			if (vv <= 0.0913)
294 				vv = vv/4.0;
295 			else
296 				vv = pow((0.1115 + vv)/1.1115, 1.0/0.45);
297 			break;
298 		}
299 		default:
300 			error("Unknown gamma type");
301 	}
302 	return vv;
303 }
304 
305 /* Convert target Y value (0..1) to ideal device (0..1) */
Y2dev(calx * x,double egamma,double vv)306 static double Y2dev(calx *x, double egamma, double vv) {
307 
308 	switch(x->gammat) {
309 		case gt_power: {
310 			vv = pow(vv, 1.0/egamma);
311 			break;
312 		}
313 		case gt_Lab: {
314 			vv = icmY2L(vv) * 0.01;
315 			break;
316 		}
317 		case gt_sRGB: {
318 			if (vv <= 0.00304)
319 				vv = vv * 12.92;
320 			else
321 				vv = pow(vv, 1.0/2.4) * 1.055 - 0.055;
322 			break;
323 		}
324 		case gt_Rec709: {
325 			if (vv <= 0.018)
326 				vv = vv * 4.5;
327 			else
328 				vv = pow(vv, 0.45) * 1.099 - 0.099;
329 			break;
330 		}
331 		case gt_SMPTE240M: {
332 			if (vv <= 0.0228)
333 				vv = vv * 4.0;
334 			else
335 				vv = pow(vv, 0.45) * 1.1115 - 0.1115;
336 			break;
337 		}
338 		default:
339 			error("Unknown gamma type");
340 	}
341 	return vv;
342 }
343 
344 /* - - - - - - - - - - - - - - - - - - - */
345 /* Compute a viewing environment Y transform */
346 
view_xform(calx * x,double in)347 static double view_xform(calx *x, double in) {
348 	double out = in;
349 
350 	if (x->vc != 0) {
351 		double xyz[3], Jab[3];
352 
353 		xyz[0] = in * x->nwh[0];				/* Compute value on neutral axis */
354 		xyz[1] = in * x->nwh[1];
355 		xyz[2] = in * x->nwh[2];
356 		x->svc->XYZ_to_cam(x->svc, Jab, xyz);
357 		x->dvc->cam_to_XYZ(x->dvc, xyz, Jab);
358 
359 		out = xyz[1] * x->vn1 + x->vn0;			/* Apply scaling factors */
360 	}
361 	return out;
362 }
363 
364 /* - - - - - - - - - - - - - - - - - - - */
365 
366 /* Info for optimization */
367 typedef struct {
368 	double thyr;		/* 50% input target */
369 	double roo;			/* 0% input target */
370 } gam_fits;
371 
372 /* gamma + input offset function handed to powell() */
gam_fit(void * dd,double * v)373 static double gam_fit(void *dd, double *v) {
374 	gam_fits *gf = (gam_fits *)dd;
375 	double gamma = v[0];
376 	double ioff = v[1];
377 	double rv = 0.0;
378 	double tt;
379 
380 	if (gamma < 0.0) {
381 		rv += 100.0 * -gamma;
382 		gamma = 0.0;
383 	}
384 	if (ioff < 0.0) {
385 		rv += 100.0 * -ioff;
386 		ioff = 0.0;
387 	} else if (ioff > 0.999) {
388 		rv += 100.0 * (ioff - 0.999);
389 		ioff = 0.999;
390 	}
391 	tt = gf->roo - pow(ioff, gamma);
392 	rv += tt * tt;
393 	tt = gf->thyr - pow(0.5 + (1.0 - 0.5) * ioff, gamma);
394 	rv += tt * tt;
395 
396 //printf("~1 gam_fit %f %f returning %f\n",ioff,gamma,rv);
397 	return rv;
398 }
399 
400 
401 /* Given the advertised gamma and the output offset, compute the */
402 /* effective gamma and input offset needed. */
403 /* Return the expected output value for 50% input. */
404 /* (It's assumed that gooff is normalised the target brightness) */
tech_gamma(calx * x,double * pegamma,double * pooff,double * pioff,double egamma,double gamma,double tooff)405 static double tech_gamma(
406 	calx *x,
407 	double *pegamma,		/* return effective gamma needed */
408 	double *pooff,			/* return output offset needed */
409 	double *pioff,			/* return input offset needed */
410 	double egamma,			/* effective gamma needed (> 0.0 if valid, overrides gamma) */
411 	double gamma,			/* advertised gamma needed */
412 	double tooff			/* Total ouput offset needed */
413 ) {
414 	int i;
415 	double rv;
416 	double gooff = 0.0;		/* The output offset applied */
417 	double gioff = 0.0;		/* The input offset applied */
418 	double roo;				/* Remaining output offset accounted for by input offset */
419 
420 	/* Compute the output offset that will be applied */
421 	gooff = tooff * x->oofff;
422 	roo = (tooff - gooff)/(1.0 - gooff);
423 
424 //printf("~1 gooff = %f, roo = %f\n",gooff,roo);
425 
426 	/* Now compute the input offset that will be needed */
427 	if (x->gammat == gt_power && egamma <= 0.0) {
428 		gam_fits gf;
429 		double op[2], sa[2], rv;
430 
431 		gf.thyr = pow(0.5, gamma);					/* Advetised 50% target */
432 		gf.thyr = (gf.thyr - gooff)/(1.0 - gooff);	/* Target before gooff is added */
433 		gf.roo = roo;
434 
435 		op[0] = gamma;
436 		op[1] = pow(roo, 1.0/gamma);
437 		sa[0] = 0.1;
438 		sa[1] = 0.01;
439 
440 		if (powell(&rv, 2, op, sa, 1e-6, 500, gam_fit, (void *)&gf, NULL, NULL) != 0)
441 			warning("Computing effective gamma and input offset is inaccurate");
442 
443 		if (rv > 1e-5) {
444 			warning("Computing effective gamma and input offset is inaccurate (%f)",rv);
445 		}
446 		egamma = op[0];
447 		gioff = op[1];
448 
449 //printf("~1 Result gioff %f, gooff %f, egamma %f\n",gioff, gooff, egamma);
450 //printf("~1 Verify 0.0 in -> out = %f, tooff = %f\n",gooff + dev2Y(x, egamma, gioff) * (1.0 - gooff),tooff);
451 //printf("~1 Verify 0.5 out = %f, target %f\n",gooff + dev2Y(x, egamma, gioff + 0.5 * (1.0 - gioff)) * (1.0 - gooff), pow(0.5, gamma));
452 
453 	} else {
454 		gioff = Y2dev(x, egamma, roo);
455 //printf("~1 Result gioff %f, gooff %f\n",gioff, gooff);
456 //printf("~1 Verify 0.0 in -> out = %f, tooff = %f\n",gooff + dev2Y(x, egamma, gioff) * (1.0 - gooff),tooff);
457 	}
458 
459 	/* Compute the 50% output value */
460 	rv = gooff + dev2Y(x, egamma, gioff + 0.5 * (1.0 - gioff)) * (1.0 - gooff);
461 
462 	if (pegamma != NULL)
463 		*pegamma = egamma;
464 	if (pooff != NULL)
465 		*pooff = gooff;
466 	if (pioff != NULL)
467 		*pioff = gioff;
468 	return rv;
469 }
470 
471 /* Compute approximate advertised gamma from black/50% grey/white readings, */
472 /* (assumes a zero based gamma curve shape) */
pop_gamma(double bY,double gY,double wY)473 static double pop_gamma(double bY, double gY, double wY) {
474 	int i;
475 	double grat, brat, gioff, gvv, gamma;
476 
477 	grat = gY/wY;
478 	brat = bY/wY;
479 
480 	gamma = log(grat) / log(0.5);
481 	return gamma;
482 }
483 
484 /* - - - - - - - - - - - - - - - - - - - */
485 
486 /* Return the xyz that is predicted by our aproximate device model */
487 /* by the given device RGB. */
fwddev(calx * x,double xyz[3],double rgb[3])488 static void fwddev(calx *x, double xyz[3], double rgb[3]) {
489 	double lrgb[3];
490 	int j;
491 
492 //printf("~1 fwddev called with rgb %f %f %f\n",rgb[0],rgb[1],rgb[2]);
493 
494 	/* Convert device RGB into linear light RGB via curves */
495 	for (j = 0; j < 3; j++)
496 		lrgb[j] = x->dcvs[j]->interp(x->dcvs[j], rgb[j]);
497 
498 //printf("~1 fwddev got linear RGB %f %f %f\n",lrgb[0],lrgb[1],lrgb[2]);
499 
500 	/* Convert linear light RGB into XYZ via the matrix */
501 	icmMulBy3x3(xyz, x->fm, lrgb);
502 
503 //printf("~1 fwddev got final xyz %f %f %f\n",xyz[0],xyz[1],xyz[2]);
504 }
505 
506 /* Return the closest device RGB predicted by our aprox. device model */
507 /* to generate the given xyz. */
invdev(calx * x,double rgb[3],double xyz[3])508 static void invdev(calx *x, double rgb[3], double xyz[3]) {
509 	double lrgb[3];
510 	int j;
511 
512 //printf("~1 invdev called with xyz %f %f %f\n",xyz[0],xyz[1],xyz[2]);
513 
514 	/* Convert XYZ to linear light RGB via the inverse matrix */
515 	icmMulBy3x3(lrgb, x->bm, xyz);
516 //printf("~1 invdev; lin light rgb = %f %f %f\n",lrgb[0],lrgb[1],lrgb[2]);
517 
518 	/* Convert linear light RGB to device RGB via inverse curves */
519 	for (j = 0; j < 3; j++) {
520 		lrgb[j] = x->dcvs[j]->inv_interp(x->dcvs[j], lrgb[j]);
521 		if (lrgb[j] < 0.0) {
522 #ifdef CLIP
523 			lrgb[j] = 0.0;
524 #endif
525 		} else if (lrgb[j] > 1.0) {
526 #ifdef CLIP
527 			lrgb[j] = 1.0;
528 #endif
529 		}
530 	}
531 //printf("~1 invdev; inverse curves rgb = %f %f %f\n",lrgb[0],lrgb[1],lrgb[2]);
532 	if (rgb != NULL) {
533 		rgb[0] = lrgb[0];
534 		rgb[1] = lrgb[1];
535 		rgb[2] = lrgb[2];
536 	}
537 }
538 
539 /* Return the closest linear device RGB predicted by our aprox. device matrix */
540 /* to generate the given xyz. */
541 /* Return > 0 if clipped */
invlindev(calx * x,double rgb[3],double xyz[3])542 static double invlindev(calx *x, double rgb[3], double xyz[3]) {
543 	double lrgb[3];
544 	double clip = 0.0;
545 	int j;
546 
547 //printf("~1 invlindev called with xyz %f %f %f\n",xyz[0],xyz[1],xyz[2]);
548 
549 	/* Convert XYZ to linear light RGB via the inverse matrix */
550 	icmMulBy3x3(lrgb, x->bm, xyz);
551 //printf("~1 invlindev; lin light rgb = %f %f %f\n",lrgb[0],lrgb[1],lrgb[2]);
552 
553 	/* Check for out of gamut */
554 	for (j = 0; j < 3; j++) {
555 		if (lrgb[j] < 0.0) {
556 			if (-lrgb[j] > clip)
557 				clip = -lrgb[j];
558 			lrgb[j] = 0.0;
559 		} else if (lrgb[j] > 1.0) {
560 			if ((lrgb[j]-1.0) > clip)
561 				clip = (lrgb[j]-1.0);
562 			lrgb[j] = 1.0;
563 		}
564 	}
565 //printf("~1 invlindev; clipped rgb = %f %f %f, clip = %f \n",lrgb[0],lrgb[1],lrgb[2],clip);
566 	if (rgb != NULL) {
567 		rgb[0] = lrgb[0];
568 		rgb[1] = lrgb[1];
569 		rgb[2] = lrgb[2];
570 	}
571 	return clip;
572 }
573 
574 /* Overall optimisation support */
575 
576 /* Set the optimsation parameter number and offset values in calx, */
577 /* and return an array filled in with the current parameters. */
578 /* Allocate temporary arrays */
dev_get_params(calx * x)579 static double *dev_get_params(calx *x) {
580 	double *p, *tp;
581 	int i, j;
582 
583 	x->np = 9;
584 	for (i = 0; i < 3; i++)
585 		x->np += x->dcvs[i]->luord;
586 
587 	if ((p = (double *)malloc(x->np * sizeof(double))) == NULL)
588 		error("dev_params malloc failed");
589 
590 	tp = p;
591 
592 	for (i = 0; i < 3; i++)
593 		for (j = 0; j < 3; j++)
594 			*tp++ = x->fm[i][j];
595 
596 	for (i = 0; i < 3; i++) {
597 		x->co[i] = tp - p;				/* Offset to start */
598 		for (j = 0; j < x->dcvs[i]->luord; j++)
599 			*tp++ = x->dcvs[i]->pms[j];
600 		x->nc[i] = (tp - p) - x->co[i];	/* Number */
601 	}
602 
603 	if ((x->dtin_iv = (double *)malloc(x->np * sizeof(double))) == NULL)
604 		error("dev_params malloc failed");
605 
606 	return p;
607 }
608 
609 /* Given a set of parameters, put them back into the model */
610 /* Normalize them so that the curve maximum is 1.0 too. */
dev_put_params(calx * x,double * p)611 static void dev_put_params(calx *x, double *p) {
612 	int i, j;
613 	double scale[3];
614 
615 	for (i = 0; i < 3; i++)
616 		for (j = 0; j < 3; j++)
617 			x->fm[i][j] = *p++;
618 	for (i = 0; i < 3; i++)
619 		for (j = 0; j < x->dcvs[i]->luord; j++)
620 			x->dcvs[i]->pms[j] = *p++;
621 
622 	/* Figure out how we have to scale the curves */
623 	for (j = 0; j < 3; j++) {
624 		scale[j] = x->dcvs[j]->interp(x->dcvs[j], 1.0);
625 		x->dcvs[j]->force_scale(x->dcvs[j], 1.0);
626 	}
627 
628 	/* Scale the matrix to compensate */
629 	for (i = 0; i < 3; i++)
630 		for (j = 0; j < 3; j++)
631 			x->fm[i][j] *= scale[j];
632 }
633 
634 /* Device model optimisation function handed to powell() */
dev_opt_func(void * edata,double * v)635 static double dev_opt_func(void *edata, double *v) {
636 	calx *x = (calx *)edata;
637 	int i, j;
638 	double tw = 0.0;
639 	double rv, smv;
640 
641 #ifdef NEVER
642 	printf("params =");
643 	for (i = 0; i < x->np; i++)
644 		printf(" %f",v[i]);
645 	printf("\n");
646 #endif
647 
648 	/* For all our data points */
649 	rv = 0.0;
650 	for (i = 0; i < x->nrp; i++) {
651 		double lrgb[3];		/* Linear light RGB */
652 		double xyz[3], lab[3];
653 		double de;
654 
655 		/* Convert through device curves */
656 		for (j = 0; j < 3; j++)
657 			lrgb[j] = x->dcvs[j]->interp_p(x->dcvs[j], v + x->co[j], x->rp[i].dev[j]);
658 
659 		/* Convert linear light RGB into XYZ via the matrix */
660 		icxMulBy3x3Parm(xyz, v, lrgb);
661 
662 		/* Convert to Lab */
663 		icmXYZ2Lab(&x->twN, lab, xyz);
664 
665 		/* Compute delta E squared */
666 		de = icmCIE94sq(lab, x->rp[i].lab) * x->rp[i].w;
667 #ifdef NEVER
668 	printf("point %d DE %f, Lab is %f %f %f, should be %f %f %f\n",
669 i, sqrt(de), lab[0], lab[1], lab[2], x->rp[i].lab[0], x->rp[i].lab[1], x->rp[i].lab[2]);
670 #endif
671 		rv += de;
672 		tw += x->rp[i].w;
673 	}
674 
675 	/* Normalise error to be a weighted average delta E squared and scale smoothing */
676 	rv /= (tw * 5.0);
677 
678 	/* Sum with shaper parameters squared, to */
679 	/* minimise unsconstrained "wiggles" */
680 	smv = 0.0;
681 	for (j = 0; j < 3; j++)
682 		smv += x->dcvs[j]->shweight_p(x->dcvs[j], v + x->co[j], 1.0);
683 	rv += smv;
684 
685 #ifdef NEVER
686 	printf("rv = %f (%f)\n",rv, smv);
687 #endif
688 	return rv;
689 }
690 
691 
692 /* Device model optimisation function handed to conjgrad() */
dev_dopt_func(void * edata,double * dv,double * v)693 static double dev_dopt_func(void *edata, double *dv, double *v) {
694 	calx *x = (calx *)edata;
695 	int i, j, k;
696 	int f, ee, ff, jj;
697 	double tw = 0.0;
698 	double rv, smv;
699 
700 	double dmato_mv[3][9];		/* Del in mat out due to del in matrix param vals */
701 	double dmato_tin[3][3];		/* Del in mat out due to del in matrix input values */
702 	double dout_lab[3][3];		/* Del in out due to XYZ to Lab conversion */
703 	double de_dout[2][3];		/* Del in delta E due to input Lab values */
704 
705 #ifdef NEVER
706 	printf("params =");
707 	for (i = 0; i < x->np; i++)
708 		printf(" %f",v[i]);
709 	printf("\n");
710 #endif
711 
712 	/* Zero the accumulated partial derivatives */
713 	for (i = 0; i < x->np; i++)
714 		dv[i] = 0.0;
715 
716 	/* For all our data points */
717 	rv = 0.0;
718 	for (i = 0; i < x->nrp; i++) {
719 		double lrgb[3];		/* Linear light RGB */
720 		double xyz[3], lab[3];
721 
722 		/* Apply the input channel curves */
723 		for (j = 0; j < 3; j++)
724 			lrgb[j] = x->dcvs[j]->dinterp_p(x->dcvs[j], v + x->co[j],
725 			                         x->dtin_iv + x->co[j], x->rp[i].dev[j]);
726 
727 		/* Convert linear light RGB into XYZ via the matrix */
728 		icxdpdiMulBy3x3Parm(xyz, dmato_mv, dmato_tin, v, lrgb);
729 
730 		/* Convert to Lab */
731 		icxdXYZ2Lab(&x->twN, lab, dout_lab, xyz);
732 
733 		/* Compute delta E squared */
734 //printf("~1 point %d: Lab is %f %f %f, should be %f %f %f\n",
735 //i, lab[0], lab[1], lab[2], x->rp[i].lab[0], x->rp[i].lab[1], x->rp[i].lab[2]);
736 		rv += icxdCIE94sq(de_dout, lab, x->rp[i].lab) * x->rp[i].w;
737 		de_dout[0][0] *= x->rp[i].w;
738 		de_dout[0][1] *= x->rp[i].w;
739 		de_dout[0][2] *= x->rp[i].w;
740 		tw += x->rp[i].w;
741 
742 		/* Compute and accumulate partial difference values for each parameter value */
743 
744 		/* Input channel curves */
745 		for (ee = 0; ee < 3; ee++) {				/* Parameter input chanel */
746 			for (k = 0; k < x->nc[ee]; k++) {		/* Param within channel */
747 				double vv = 0.0;
748 				jj = x->co[ee] + k;					/* Overall input curve param */
749 
750 				for (ff = 0; ff < 3; ff++) {		/* Lab channels */
751 					for (f = 0; f < 3; f++) {		/* XYZ channels */
752 						vv += de_dout[0][ff] * dout_lab[ff][f]
753 						    * dmato_tin[f][ee] * x->dtin_iv[jj];
754 					}
755 				}
756 				dv[jj] += vv;
757 			}
758 		}
759 
760 		/* Matrix parameters */
761 		for (k = 0; k < 9; k++) {				/* Matrix parameter */
762 			double vv = 0.0;
763 
764 			for (ff = 0; ff < 3; ff++) {		/* Lab channels */
765 				for (f = 0; f < 3; f++) {		/* XYZ channels */
766 					vv += de_dout[0][ff] * dout_lab[ff][f]
767 					    * dmato_mv[f][k];
768 				}
769 			}
770 			dv[k] += vv;
771 		}
772 	}
773 
774 	/* Normalise error to be a weighted average delta E squared and scale smoothing */
775 	rv /= (tw * 1200.0);
776 	for (i = 0; i < x->np; i++)
777 		dv[i] /= (tw * 900.0);
778 
779 	/* Sum with shaper parameters squared, to */
780 	/* minimise unsconstrained "wiggles" */
781 	smv = 0.0;
782 	for (j = 0; j < 3; j++)
783 		smv += x->dcvs[j]->dshweight_p(x->dcvs[j], v + x->co[j], x->dtin_iv + x->co[j], 1.0);
784 	rv += smv;
785 
786 #ifdef NEVER
787 	printf("drv = %f (%f)\n",rv, smv);
788 #endif
789 	return rv;
790 }
791 
792 #ifdef NEVER
793 /* Check partial derivative function within dev_opt_func() using powell() */
794 
dev_opt_func(void * edata,double * v)795 static double dev_opt_func(void *edata, double *v) {
796 	calx *x = (calx *)edata;
797 	int i;
798 	double dv[2000];
799 	double rv, drv;
800 	double trv;
801 
802 	rv = dev_opt_func_(edata, v);
803 	drv = dev_dopt_func(edata, dv, v);
804 
805 	if (fabs(rv - drv) > 1e-6) {
806 		printf("######## RV MISMATCH is %f should be %f ########\n",drv, rv);
807 		exit(0);
808 	}
809 
810 	/* Check each parameter delta */
811 	for (i = 0; i < x->np; i++) {
812 		double del;
813 
814 		v[i] += 1e-7;
815 		trv = dev_opt_func_(edata, v);
816 		v[i] -= 1e-7;
817 
818 		/* Check that del is correct */
819 		del = (trv - rv)/1e-7;
820 		if (fabs(dv[i] - del) > 1.0) {
821 //printf("~1 del = %f from (trv %f - rv %f)/0.1\n",del,trv,rv);
822 			printf("######## EXCESSIVE at v[%d] is %f should be %f ########\n",i,dv[i],del);
823 			exit(0);
824 		}
825 	}
826 	return rv;
827 }
828 #endif
829 
830 /* =================================================================== */
831 
832 /* White point brightness optimization function handed to powell. */
833 /* Maximize brigtness while staying within gamut */
wp_opt_func(void * edata,double * v)834 static double wp_opt_func(void *edata, double *v) {
835 	calx *x = (calx *)edata;
836 	double wxyz[3], rgb[3];
837 	int j;
838 	double rv = 0.0;
839 
840 	wxyz[0] = v[0] * x->twh[0];
841 	wxyz[1] = v[0] * x->twh[1];
842 	wxyz[2] = v[0] * x->twh[2];
843 
844 //printf("~1 wp_opt_func got scale %f, xyz = %f %f %f\n",
845 //v[0],wxyz[0],wxyz[1],wxyz[2]);
846 
847 	if ((rv = invlindev(x, rgb, wxyz)) > 0.0) {		/* Out of gamut */
848 		rv *= 1e5;
849 //printf("~1 out of gamut %f %f %f returning %f\n", rgb[0], rgb[1], rgb[2], rv);
850 		return rv;
851 	}
852 	/* Maximize scale factor */
853 	if (v[0] < 0.00001)
854 		rv = 1.0/0.00001;
855 	else
856 		rv = 1.0/v[0];
857 
858 //printf("~1 %f %f %f returning %f\n", rgb[0], rgb[1], rgb[2], rv);
859 	return rv;
860 }
861 
862 /* =================================================================== */
863 /* Structure to save aproximate model readings in */
864 typedef struct {
865 	double v;			/* Input value */
866 	double xyz[3];		/* Reading */
867 } sxyz;
868 
869 /* ------------------------------------------------------------------- */
870 #if defined(__APPLE__) && defined(__POWERPC__)
871 
872 /* Workaround for a ppc gcc 3.3 optimiser bug... */
873 /* It seems to cause a segmentation fault instead of */
874 /* converting an integer loop index into a float, */
875 /* when there are sufficient variables in play. */
gcc_bug_fix(int i)876 static int gcc_bug_fix(int i) {
877 	static int nn;
878 	nn += i;
879 	return nn;
880 }
881 #endif	/* APPLE */
882 
883 
884 /* =================================================================== */
885 /* Calibration sample point support. This allows the successive */
886 /* refinement of our neutral sample points */
887 
888 /* A sample point */
889 typedef struct {
890 	double v;			/* Desired input value */
891 	double rgb[3];		/* Input value through calibration curves */
892 	double tXYZ[3];		/* Target XYZ */
893 	double XYZ[3];		/* Read XYZ */
894 	double deXYZ[3];	/* Delta XYZ wanted to target */
895 	double _de;			/* Non-weighted Delta Lab */
896 	double de;			/* Weightd Delta Lab to neutral target */
897 	double dc;			/* Weightd Delta XYZ to neutral target */
898 	double peqde;		/* Weightd Delta Lab to last pass equivalent point value */
899 	double hde;			/* Weightd Hybrid de composed of de and peqde */
900 
901 	double prgb[3];		/* Previous measured RGB */
902 	double pXYZ[3];		/* Previous measured XYZ */
903 	double pdXYZ[3];	/* Delta XYZ intended from previous measure */
904 	double pdrgb[3];	/* Delta rgb made to previous to acorrect XYZ */
905 
906 	double dXYZ[3];		/* Actual delta XYZ resulting from previous delta rgb */
907 
908 	double j[3][3];		/* Aproximate Jacobian (del RGB -> XYZ) */
909 	double ij[3][3];	/* Aproximate inverse Jacobian (del XYZ-> del RGB) */
910 	double fb_ij[3][3];	/* Copy of initial inverse Jacobian, used as a fallback */
911 } csp;
912 
913 
914 /* All the sample points */
915 typedef struct {
916 	int no;				/* Number of samples */
917 	int _no;			/* Allocation */
918 	csp *s;				/* List of samples */
919 } csamp;
920 
free_alloc_csamp(csamp * p)921 static void free_alloc_csamp(csamp *p) {
922 	if (p->s != NULL)
923 		free(p->s);
924 	p->s = NULL;
925 }
926 
927 /* Initialise v values */
init_csamp_v(csamp * p,calx * x,int psrand)928 static void init_csamp_v(csamp *p, calx *x, int psrand) {
929 	int i, j;
930 	sobol *so = NULL;
931 
932 	if (psrand != 0) {	/* Use pseudo random distribution for verification */
933 		if ((so = new_sobol(1)) == NULL)
934 			error("New sobol failed");
935 	}
936 
937 	/* Generate the sample points */
938 	for (i = 0; i < p->no; i++) {
939 		double vv;
940 
941 #if defined(__APPLE__) && defined(__POWERPC__)
942 		gcc_bug_fix(i);
943 #endif
944 		if (so != NULL) {
945 			if (i == 0)
946 				vv = 1.0;
947 			else if (i == 1)
948 				vv = 0.0;
949 			else
950 				so->next(so, &vv);
951 		} else
952 			vv = i/(p->no - 1.0);
953 		vv = pow(vv, REFN_DIST_POW);	/* Skew sample points to be slightly perceptual */
954 		p->s[i].v = vv;
955 	}
956 
957 	if (so != NULL) {
958 		/* Sort it so white is last */
959 #define HEAP_COMPARE(A,B) (A.v < B.v)
960 	HEAPSORT(csp,p->s,p->no)
961 #undef HEAP_COMPARE
962 		so->del(so);
963 	}
964 }
965 
966 /* Initialise txyz values from v values */
init_csamp_txyz(csamp * p,calx * x,int fixdev,int verb)967 static void init_csamp_txyz(csamp *p, calx *x, int fixdev, int verb) {
968 	int i, j;
969 	double tbL[3];		/* tbk as Lab */
970 
971 	if (verb >= 3)
972 		printf("init_csamp_txyz:\n");
973 
974 	/* Convert target black from XYZ to Lab here, */
975 	/* in case twN has changed at some point. */
976 	icmXYZ2Lab(&x->twN, tbL, x->tbk);
977 
978 	/* Set the sample points targets */
979 	for (i = 0; i < p->no; i++) {
980 		double y, vv;
981 		double XYZ[3];			/* Existing XYZ value */
982 		double Lab[3];
983 		double bl;
984 
985 		vv = p->s[i].v;
986 
987 		/* Compute target relative Y value for this device input. */
988 		/* We allow for any input and/or output offset */
989 		y = x->gooff + dev2Y(x, x->egamma, x->gioff + vv * (1.0 - x->gioff)) * (1.0 - x->gooff);
990 
991 		/* Add viewing environment transform */
992 		y = view_xform(x, y);
993 
994 		/* Convert Y to L* */
995 		Lab[0] = icmY2L(y);
996 		Lab[1] = Lab[2] = 0.0;	/* Target is neutral */
997 
998 		/* Compute blended neutral target a* b* */
999 		bl = pow((1.0 - vv), x->nbrate);		/* Crossover near the black */
1000 		Lab[1] = (1.0 - bl) * 0.0 + bl * tbL[1];
1001 		Lab[2] = (1.0 - bl) * 0.0 + bl * tbL[2];
1002 
1003 		icmAry2Ary(XYZ, p->s[i].tXYZ);				/* Save the existing values */
1004 		icmLab2XYZ(&x->twN, p->s[i].tXYZ, Lab);		/* New XYZ Value to aim for */
1005 
1006 		if (verb >= 3) {
1007 			printf("%d: target XYZ %.4f %.4f %.4f, Lab %.3f %.3f %.3f\n",i, p->s[i].tXYZ[0],p->s[i].tXYZ[1],p->s[i].tXYZ[2], Lab[0],Lab[1],Lab[2]);
1008 		}
1009 	}
1010 }
1011 
1012 
1013 /* Allocate the sample points and initialise them with the */
1014 /* target device and XYZ values, and first cut device values. */
init_csamp(csamp * p,calx * x,int doupdate,int verify,int psrand,int no,int verb)1015 static void init_csamp(csamp *p, calx *x, int doupdate, int verify, int psrand, int no, int verb) {
1016 	int i, j;
1017 
1018 	p->_no = p->no = no;
1019 
1020 	if ((p->s = (csp *)calloc(p->_no, sizeof(csp))) == NULL)
1021 		error("csamp malloc failed");
1022 
1023 	/* Compute v and txyz */
1024 	init_csamp_v(p, x, psrand);
1025 	init_csamp_txyz(p, x, 0, verb);
1026 
1027 	/* Generate the sample points */
1028 	for (i = 0; i < no; i++) {
1029 		double dd, vv;
1030 
1031 #if defined(__APPLE__) && defined(__POWERPC__)
1032 		gcc_bug_fix(i);
1033 #endif
1034 		vv = p->s[i].v;
1035 
1036 		if (verify == 2) {		/* Verifying through installed curve */
1037 			/* Make RGB values the input value */
1038 			p->s[i].rgb[0] = p->s[i].rgb[1] = p->s[i].rgb[2] = vv;
1039 
1040 		} else if (doupdate) {	/* Start or verify through current cal curves */
1041 			for (j = 0; j < 3; j++) {
1042 				p->s[i].rgb[j] = x->rdac[j]->interp(x->rdac[j], vv);
1043 #ifdef CLIP
1044 				if (p->s[i].rgb[j] < 0.0)
1045 					p->s[i].rgb[j] = 0.0;
1046 				else if (p->s[i].rgb[j] > 1.0)
1047 					p->s[i].rgb[j] = 1.0;
1048 #endif
1049 			}
1050 		} else {		/* we have model */
1051 			/* Lookup an initial device RGB for that target by inverting */
1052 			/* the approximate forward device model */
1053 			p->s[i].rgb[0] = p->s[i].rgb[1] = p->s[i].rgb[2] = vv;
1054 			invdev(x, p->s[i].rgb, p->s[i].tXYZ);
1055 		}
1056 		/* Force white to be native if native flag set */
1057 		if (x->nat && i == (no-1)) {
1058 //printf("~1 Forcing white rgb to be 1,1,1\n");
1059 			p->s[i].rgb[0] = p->s[i].rgb[1] = p->s[i].rgb[2] = 1.0;
1060 		}
1061 
1062 //printf("~1 Inital point %d rgb %f %f %f\n",i,p->s[i].rgb[0],p->s[i].rgb[1],p->s[i].rgb[2]);
1063 
1064 		/* Compute the approximate inverse Jacobian at this point */
1065 		/* by taking the partial derivatives wrt to each device */
1066 		/* channel of our aproximate forward model */
1067 		if (verify != 2) {
1068 			double refXYZ[3], delXYZ[3];
1069 			fwddev(x, refXYZ, p->s[i].rgb);
1070 			if (vv < 0.5)
1071 				dd = 0.02;
1072 			else
1073 				dd = -0.02;
1074 			/* Matrix organization is J[XYZ][RGB] for del RGB->del XYZ*/
1075 			for (j = 0; j < 3; j++) {
1076 				p->s[i].rgb[j] += dd;
1077 				fwddev(x, delXYZ, p->s[i].rgb);
1078 				p->s[i].j[0][j] = (delXYZ[0] - refXYZ[0]) / dd;
1079 				p->s[i].j[1][j] = (delXYZ[1] - refXYZ[1]) / dd;
1080 				p->s[i].j[2][j] = (delXYZ[2] - refXYZ[2]) / dd;
1081 				p->s[i].rgb[j] -= dd;
1082 			}
1083 			if (icmInverse3x3(p->s[i].ij, p->s[i].j)) {
1084 				error("dispcal: inverting Jacobian failed (1)");
1085 			}
1086 			/* Make a copy of this Jacobian in case we get an invert failure later */
1087 			icmCpy3x3(p->s[i].fb_ij, p->s[i].ij);
1088 		}
1089 	}
1090 }
1091 
1092 /* Return a linear XYZ interpolation */
csamp_interp(csamp * p,double xyz[3],double v)1093 static void csamp_interp(csamp *p, double xyz[3], double v) {
1094 	int i, j;
1095 	double b;
1096 
1097 	if (p->no < 2)
1098 		error("Calling csamp_interp with less than two existing samples");
1099 
1100 	/* Locate the pair surrounding our input value */
1101 	for (i = 0; i < (p->no-1); i++) {
1102 		if (v >= p->s[i].v && v <= p->s[i+1].v)
1103 			break;
1104 	}
1105 	if (i >= (p->no-1))
1106 		error("csamp_interp out of range");
1107 
1108 	b = (v - p->s[i].v)/(p->s[i+1].v - p->s[i].v);
1109 
1110 	for (j = 0; j < 3; j++) {
1111 		xyz[j] = b * p->s[i+1].XYZ[j] + (1.0 - b) * p->s[i].XYZ[j];
1112 	}
1113 }
1114 
1115 /* Re-initialise a CSP with a new number of points. */
1116 /* Interpolate the device values and jacobian. */
1117 /* Set the current rgb from the current RAMDAC curves if not verifying */
reinit_csamp(csamp * p,calx * x,int verify,int psrand,int no,int verb)1118 static void reinit_csamp(csamp *p, calx *x, int verify, int psrand, int no, int verb) {
1119 	csp *os;			/* Old list of samples */
1120 	int ono;			/* Old number of samples */
1121 	int i, j, k, m;
1122 
1123 	if (no == p->no)
1124 		return;			/* Nothing has changed */
1125 
1126 	os = p->s;			/* Save the existing per point information */
1127 	ono = p->no;
1128 
1129 	init_csamp(p, x, 0, 2, psrand, no, verb);
1130 
1131 	p->_no = p->no = no;
1132 
1133 	/* Interpolate the current device values */
1134 	for (i = 0; i < no; i++) {
1135 		double vv, b;
1136 
1137 		vv = p->s[i].v;
1138 
1139 		/* Locate the pair surrounding our target value */
1140 		for (j = 0; j < ono-1; j++) {
1141 			if (vv >= os[j].v && vv <= os[j+1].v)
1142 				break;
1143 		}
1144 		if (j >= (ono-1))
1145 			error("csamp interp. out of range");
1146 
1147 		b = (vv - os[j].v)/(os[j+1].v - os[j].v);
1148 
1149 		for (k = 0; k < 3; k++) {
1150 			if (verify == 2) {
1151 
1152 				p->s[i].rgb[k] = b * os[j+1].rgb[k] + (1.0 - b) * os[j].rgb[k];
1153 
1154 			} else {	/* Lookup rgb from current calibration curves */
1155 				for (m = 0; m < 3; m++) {
1156 					p->s[i].rgb[m] = x->rdac[m]->interp(x->rdac[m], vv);
1157 #ifdef CLIP
1158 					if (p->s[i].rgb[m] < 0.0)
1159 						p->s[i].rgb[m] = 0.0;
1160 					else if (p->s[i].rgb[m] > 1.0)
1161 						p->s[i].rgb[m] = 1.0;
1162 #endif
1163 				}
1164 			}
1165 			p->s[i].XYZ[k] = b * os[j+1].XYZ[k] + (1.0 - b) * os[j].XYZ[k];
1166 			p->s[i].deXYZ[k] = b * os[j+1].deXYZ[k] + (1.0 - b) * os[j].deXYZ[k];
1167 			p->s[i].pXYZ[k] = b * os[j+1].pXYZ[k] + (1.0 - b) * os[j].pXYZ[k];
1168 			p->s[i].pdrgb[k] = b * os[j+1].pdrgb[k] + (1.0 - b) * os[j].pdrgb[k];
1169 			p->s[i].dXYZ[k] = b * os[j+1].dXYZ[k] + (1.0 - b) * os[j].dXYZ[k];
1170 #ifdef INTERP_JAC
1171 			for (m = 0; m < 3; m++)
1172 				p->s[i].j[k][m] = b * os[j+1].j[k][m] + (1.0 - b) * os[j].j[k][m];
1173 #endif
1174 
1175 		}
1176 #ifndef INTERP_JAC
1177 		/* Create a Jacobian at this location from our forward model */
1178 		{
1179 			double dd, refXYZ[3], delXYZ[3];
1180 			fwddev(x, refXYZ, p->s[i].rgb);
1181 			if (vv < 0.5)
1182 				dd = 0.02;
1183 			else
1184 				dd = -0.02;
1185 			/* Matrix organization is J[XYZ][RGB] for del RGB->del XYZ*/
1186 			for (j = 0; j < 3; j++) {
1187 				p->s[i].rgb[j] += dd;
1188 				fwddev(x, delXYZ, p->s[i].rgb);
1189 				p->s[i].j[0][j] = (delXYZ[0] - refXYZ[0]) / dd;
1190 				p->s[i].j[1][j] = (delXYZ[1] - refXYZ[1]) / dd;
1191 				p->s[i].j[2][j] = (delXYZ[2] - refXYZ[2]) / dd;
1192 				p->s[i].rgb[j] -= dd;
1193 			}
1194 		}
1195 #endif
1196 		if (icmInverse3x3(p->s[i].ij, p->s[i].j)) {
1197 			error("dispcal: inverting Jacobian failed (2)");
1198 		}
1199 		/* Make a copy of this Jacobian in case we get an invert failure later */
1200 		icmCpy3x3(p->s[i].fb_ij, p->s[i].ij);
1201 
1202 		/* Compute expected delta XYZ using new Jacobian */
1203 		icmMulBy3x3(p->s[i].pdXYZ, p->s[i].j, p->s[i].pdrgb);
1204 
1205 		p->s[i]._de = p->s[i].de = b * os[j+1].de + (1.0 - b) * os[j].de;
1206 		p->s[i].dc = b * os[j+1].dc + (1.0 - b) * os[j].dc;
1207 	}
1208 
1209 	free(os);
1210 }
1211 
1212 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
1213 
1214 #ifdef NEVER
1215 /* Do a linear interp of the ramdac */
interp_ramdac(double cal[CAL_RES][3],double drgb[3],double rgb[3])1216 static void interp_ramdac(double cal[CAL_RES][3], double drgb[3], double rgb[3]) {
1217 	int i, j;
1218 	int gres = CAL_RES;
1219 	double w;
1220 
1221 	/* For r,g & b */
1222 	for (j = 0; j < 3; j++) {
1223 		int mi, gres_1 = gres-1;
1224 		double t, vv = rgb[j];
1225 		t = gres * vv;
1226 		mi = (int)floor(t);			/* Grid coordinate */
1227 		if (mi < 0)					/* Limit to valid cube base index range */
1228 			mi = 0;
1229 		else if (mi >= gres_1)
1230 			mi = gres_1-1;
1231 		w = t - (double)mi;	 		/* 1.0 - weight */
1232 
1233 		drgb[j] = (1.0 - w) * cal[mi][j] + w * cal[mi+1][j];
1234 	}
1235 }
1236 #endif	/* NEVER */
1237 
1238 /* Given an XYZ, compute the color temperature and the delta E 2K to the locus */
comp_ct(double * de,double lxyz[3],int plank,int dovct,icxObserverType obType,double xyz[3])1239 static double comp_ct(
1240 	double *de,		/* If non-NULL, return CIEDE2000 to locus */
1241 	double lxyz[3],	/* If non-NULL, return normalised XYZ on locus */
1242 	int plank,		/* NZ if Plankian locus, 0 if Daylight locus */
1243 	int dovct,		/* NZ if visual match, 0 if traditional correlation */
1244 	icxObserverType obType,		/* If not default, set a custom observer */
1245 	double xyz[3]	/* Color to match */
1246 ) {
1247 	double ct_xyz[3];		/* XYZ on locus */
1248 	double nxyz[3];			/* Normalised input color */
1249 	double ct, ctde;		/* Color temperature & delta E to Black Body locus */
1250 	icmXYZNumber wN;
1251 
1252 
1253 	if (obType == icxOT_default)
1254 		obType = icxOT_CIE_1931_2;
1255 
1256 	if ((ct = icx_XYZ2ill_ct(ct_xyz, plank != 0 ? icxIT_Ptemp : icxIT_Dtemp,
1257 	                         obType, NULL, xyz, NULL, dovct)) < 0)
1258 		error("Got bad color temperature conversion\n");
1259 
1260 	if (de != NULL) {
1261 		icmAry2XYZ(wN, ct_xyz);
1262 		icmAry2Ary(nxyz, xyz);
1263 		nxyz[0] /= xyz[1];
1264 		nxyz[2] /= xyz[1];
1265 		nxyz[1] /= xyz[1];
1266 		ctde = icmXYZCIE2K(&wN, nxyz, ct_xyz);
1267 		*de = ctde;
1268 	}
1269 	if (lxyz != NULL) {
1270 		icmAry2Ary(lxyz, ct_xyz);
1271 	}
1272 	return ct;
1273 }
1274 
1275 /* =================================================================== */
1276 
1277 /* Return the normal Delta E given two XYZ values, but */
1278 /* exagerate the L* error if act L* > targ L* by a factor of fact */
bwXYZLabDE(icmXYZNumber * w,double * targ,double * act,double fact)1279 extern ICCLIB_API double bwXYZLabDE(icmXYZNumber *w, double *targ, double *act, double fact) {
1280 	double targlab[3], actlab[3], rv;
1281 
1282 	icmXYZ2Lab(w, targlab, targ);
1283 	icmXYZ2Lab(w, actlab, act);
1284 	if (actlab[0] > targlab[0])
1285 		actlab[0] = targlab[0] + fact * (actlab[0] - targlab[0]);
1286 	rv = icmLabDE(targlab, actlab);
1287 	return rv;
1288 }
1289 /* =================================================================== */
1290 
1291 #ifdef MEAS_RES
1292 
1293 #define NVAL 240			/* Nominal measurement value */
1294 
1295 /* return the estimated RAMDAC precision. Return 0 if not certain */
comp_ramdac_prec(int base,col * ttt)1296 static int comp_ramdac_prec(
1297 	int base,		/* Base quantization to test for */
1298 	col *ttt		/* Measurement values */
1299 ) {
1300 	int i, j;
1301 	double min, max;
1302 	double val[17];
1303 	double meas[17];
1304 
1305 	double scale;
1306 	double targ[17];
1307 	double score[5];
1308 	double bits[5];
1309 
1310 	double bcor, bcor2;
1311 	int bbits = 0, rbits;
1312 
1313 	int verb = 0;
1314 
1315 	/* Extract the measurements */
1316 	for (i = 0; i < 17; i++) {
1317 		val[i] = (double)i;
1318 		meas[i] = ttt[i].XYZ[1];
1319 	}
1320 #ifdef DEBUG
1321 	fprintf(dbgo,"raw measurements:\n");
1322 	do_plot(val, meas, NULL, NULL, 17);
1323 #endif
1324 
1325 	/* Determine min & max, and normalize the values */
1326 	min = 1e9, max = -1e9;
1327 	for (i = 0; i < 17; i++) {
1328 		if (meas[i] < min)
1329 			min = meas[i];
1330 		if (meas[i] > max)
1331 			max = meas[i];
1332 	}
1333 	for (i = 0; i < 17; i++) {
1334 		meas[i] = (meas[i] - min)/(max - min);
1335 //printf("meas[%d] = %f\n",i,meas[i]);
1336 	}
1337 
1338 	/* Create score for each hypothesis */
1339 	scale = 1.0;
1340 	for (j = 0; j < 5; j++) {
1341 		int k;
1342 		int step = 1 << (4 - j);
1343 		double v = 0.0;
1344 		double merr;
1345 
1346 		bits[j] = 8.0 + j;
1347 
1348 		/* Create the target response */
1349 		for (i = 0; i < 17;) {
1350 			for (k = 0; k < step && (i+k) < 17; k++) {
1351 				targ[i + k] = v;
1352 //printf("j %d: targ[%d] = %f\n",j,i+k,v);
1353 			}
1354 			v += step/16.0;
1355 			i += k;
1356 		}
1357 
1358 		/* Tweak it for typical display non-linearity */
1359 		min = 1e9, max = -1e9;
1360 		for (i = 0; i < 17; i++) {
1361 			targ[i] = pow((NVAL + targ[i]/16.0)/255.0, 2.2);
1362 			if (targ[i] < min)
1363 				min = targ[i];
1364 			if (targ[i] > max)
1365 				max = targ[i];
1366 		}
1367 		for (i = 0; i < 17; i++)
1368 			targ[i] = (targ[i] - min)/(max - min);
1369 
1370 		/* Try and make fit a little better */
1371 		/* with a crude optimisation */
1372 		for (k = 0; k < 50; k++) {
1373 
1374 			merr = 0.0;
1375 			for (i = 0; i < 17; i++)
1376 				merr += targ[i] - meas[i];
1377 			merr /= 17.0;
1378 
1379 			for (i = 0; i < 17; i++) {
1380 				targ[i] *= (1.0 + 0.5 * merr);
1381 				targ[i] -= 0.5 * merr;
1382 //				targ[i] -= merr;
1383 			}
1384 		}
1385 
1386 		score[j] = 0.0;
1387 		for (i = 0; i < 17; i++) {
1388 			double tt = targ[i] - meas[i];
1389 			score[j] += tt * tt;
1390 		}
1391 		score[j] *= scale;
1392 		scale *= 1.1;
1393 
1394 #ifdef DEBUG
1395 		printf("%d bits score %f\n",8+j,score[j]);
1396 		do_plot(val, meas, targ, NULL, 17);
1397 #endif
1398 	}
1399 
1400 	/* Pick the best score */
1401 	bcor = bcor2 = 1e8;
1402 	bbits = 0;
1403 
1404 	for (j = 0; j < 5; j++) {
1405 		if (score[j] < bcor) {
1406 			bcor2 = bcor;
1407 			bcor = score[j];
1408 			bbits = 8+j;
1409 		} else if (score[j] < bcor2)
1410 			bcor2 = score[j];
1411 	}
1412 
1413 	rbits = bbits;
1414 
1415 	/* Don't pick anything if it's not reasonably certain */
1416 	if (bcor2/bcor < 1.3
1417 	 || (bcor2/bcor < 2.1 && bcor > 0.15)
1418 	) rbits = 0;
1419 
1420 #ifdef DEBUG
1421 	printf("Win score %f by cor %f, ratio %f\n",bcor, bcor2 - bcor, bcor2/bcor);
1422 	printf("Best %d, returning %d bits\n",bbits,rbits);
1423 	do_plot10(bits, score, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 5, 1);
1424 #endif
1425 
1426 	return rbits;
1427 }
1428 
meas_ramdac_prec(int base,disprd * dr)1429 static int meas_ramdac_prec(int base, disprd *dr) {
1430 	col ttt[17];
1431 	int rv, n, rbits;
1432 
1433 	/* setup the measurements */
1434 	for (n = 0; n < 17; n++) {
1435 		int ii;
1436 		ii = (NVAL * 256);
1437 		ii += n * 16;
1438 		ttt[n].r = ttt[n].g = ttt[n].b = ii/(double)(65535);
1439 	}
1440 
1441 	if ((rv = dr->read(dr, ttt, 17, 1, 17, 1, 0, instNoClamp)) != 0) {
1442 		warning("display read failed with '%s'\n",disprd_err(rv));
1443 		return 0;
1444 	}
1445 
1446 	rbits = comp_ramdac_prec(base, ttt);
1447 
1448 	/* If it failed, try the average to two sets of measurements */
1449 	if (rbits == 0) {
1450 		col t2[17];
1451 		for (n = 0; n < 17; n++)
1452 			t2[n] = ttt[n];
1453 
1454 		if ((rv = dr->read(dr, t2, 17, 1, 17, 1, 0, instNoClamp)) != 0) {
1455 			warning("display read failed with '%s'\n",disprd_err(rv));
1456 			return 0;
1457 		}
1458 
1459 		for (n = 0; n < 17; n++)
1460 			ttt[n].XYZ[1] += t2[n].XYZ[1];
1461 
1462 		rbits = comp_ramdac_prec(base, ttt);
1463 
1464 		/* As a last resort, try just the second measurement */
1465 		if (rbits == 0)
1466 			rbits = comp_ramdac_prec(base, t2);
1467 	}
1468 
1469 	return rbits;
1470 }
1471 
1472 #undef NVAL
1473 
1474 #endif /* MEAS_RES */
1475 
1476 /* =================================================================== */
1477 
1478 /* Default gamma */
1479 double g_def_gamma = 2.4;
1480 
1481 /*
1482 
1483   Flags used:
1484 
1485          ABCDEFGHIJKLMNOPQRSTUVWXYZ
1486   upper  .......... ....... . ....
1487   lower  .......   . ...... .... ..
1488 
1489 */
1490 
1491 /* Flag = 0x0000 = default */
1492 /* Flag & 0x0001 = list ChromCast's */
usage(int flag,char * diag,...)1493 void usage(int flag, char *diag, ...) {
1494 	int i;
1495 	disppath **dp;
1496 	icompaths *icmps;
1497 	inst2_capability cap2 = inst2_none;
1498 
1499 	fprintf(stderr,"Calibrate a Display, Version %s\n",ARGYLL_VERSION_STR);
1500 	fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
1501 	if (diag != NULL) {
1502 		va_list args;
1503 		fprintf(stderr,"Diagnostic: ");
1504 		va_start(args, diag);
1505 		vfprintf(stderr, diag, args);
1506 		va_end(args);
1507 		fprintf(stderr,"\n");
1508 	}
1509 	fprintf(stderr,"usage: dispcal [options] outfile\n");
1510 	fprintf(stderr," -v [n]               Verbose mode\n");
1511 #if defined(UNIX_X11)
1512 	fprintf(stderr," -display displayname Choose X11 display name\n");
1513 	fprintf(stderr," -d n[,m]             Choose the display n from the following list (default 1)\n");
1514 	fprintf(stderr,"                      Optionally choose different display m for VideoLUT access\n");
1515 #else
1516 	fprintf(stderr," -d n                 Choose the display from the following list (default 1)\n");
1517 #endif
1518 	dp = get_displays();
1519 	if (dp == NULL || dp[0] == NULL)
1520 		fprintf(stderr,"    ** No displays found **\n");
1521 	else {
1522 		int i;
1523 		for (i = 0; ; i++) {
1524 			if (dp[i] == NULL)
1525 				break;
1526 			fprintf(stderr,"    %d = '%s'\n",i+1,dp[i]->description);
1527 		}
1528 	}
1529 	free_disppaths(dp);
1530 	fprintf(stderr," -dweb[:port]         Display via a web server at port (default 8080)\n");
1531 	fprintf(stderr," -dcc[:n]             Display via n'th ChromeCast (default 1, ? for list)\n");
1532 	if (flag & 0x001) {
1533 		ccast_id **ids;
1534 		if ((ids = get_ccids()) == NULL) {
1535 			fprintf(stderr,"    ** Error discovering ChromCasts **\n");
1536 		} else {
1537 			if (ids[0] == NULL)
1538 				fprintf(stderr,"    ** No ChromCasts found **\n");
1539 			else {
1540 				int i;
1541 				for (i = 0; ids[i] != NULL; i++)
1542 					fprintf(stderr,"    %d = '%s'\n",i+1,ids[i]->name);
1543 				free_ccids(ids);
1544 			}
1545 		}
1546 	}
1547 #ifdef NT
1548 	fprintf(stderr," -dmadvr              Display via MadVR Video Renderer\n");
1549 #endif
1550 //	fprintf(stderr," -d fake              Use a fake display device for testing, fake%s if present\n",ICC_FILE_EXT);
1551 	fprintf(stderr," -c listno            Set communication port from the following list (default %d)\n",COMPORT);
1552 	if ((icmps = new_icompaths(g_log)) != NULL) {
1553 		icompath **paths;
1554 		if ((paths = icmps->paths) != NULL) {
1555 			int i;
1556 			for (i = 0; ; i++) {
1557 				if (paths[i] == NULL)
1558 					break;
1559 				if ((paths[i]->itype == instSpyder1 && setup_spyd2(0) == 0)
1560 				 || (paths[i]->itype == instSpyder2 && setup_spyd2(1) == 0))
1561 					fprintf(stderr,"    %d = '%s' !! Disabled - no firmware !!\n",i+1,paths[i]->name);
1562 				else
1563 					fprintf(stderr,"    %d = '%s'\n",i+1,paths[i]->name);
1564 			}
1565 		} else
1566 			fprintf(stderr,"    ** No ports found **\n");
1567 	}
1568 	fprintf(stderr," -r                   Report on the calibrated display then exit\n");
1569 	fprintf(stderr," -R                   Report on the uncalibrated display then exit\n");
1570 	fprintf(stderr," -m                   Skip adjustment of the monitor controls\n");
1571 	fprintf(stderr," -o [profile%s]     Create fast matrix/shaper profile [different filename to outfile%s]\n",ICC_FILE_EXT,ICC_FILE_EXT);
1572 	fprintf(stderr," -O \"description\"     Fast ICC Profile Description string (Default \"outfile\")\n");
1573 	fprintf(stderr," -u                   Update previous calibration and (if -o used) ICC profile VideoLUTs\n");
1574 	fprintf(stderr," -q [vlmh]            Quality - Very Low, Low, Medium (def), High\n");
1575 //	fprintf(stderr," -q [vfmsu]           Speed - Very Fast, Fast, Medium (def), Slow, Ultra Slow\n");
1576 	fprintf(stderr," -p                   Use telephoto mode (ie. for a projector) (if available)\n");
1577 	cap2 = inst_show_disptype_options(stderr, " -y                   ", icmps, 0);
1578 	fprintf(stderr," -t [temp]            White Daylight locus target, optional target temperaturee in deg. K (deflt.)\n");
1579 	fprintf(stderr," -T [temp]            White Black Body locus target, optional target temperaturee in deg. K\n");
1580 	fprintf(stderr," -w x,y        	      Set the target white point as chromaticity coordinates\n");
1581 #ifdef NEVER	/* Not worth confusing people about this ? */
1582 	fprintf(stderr," -L                   Show CCT/CDT rather than VCT/VDT during native white point adjustment\n");
1583 #endif
1584 	fprintf(stderr," -b bright            Set the target white brightness in cd/m^2\n");
1585 	fprintf(stderr," -g gamma             Set the target response curve advertised gamma (Def. %3.1f)\n",g_def_gamma);
1586 	fprintf(stderr,"                      Use \"-gl\" for L*a*b* curve\n");
1587 	fprintf(stderr,"                      Use \"-gs\" for sRGB curve\n");
1588 	fprintf(stderr,"                      Use \"-g709\" for REC 709 curve (should use -a as well!)\n");
1589 	fprintf(stderr,"                      Use \"-g240\" for SMPTE 240M curve (should use -a as well!)\n");
1590 	fprintf(stderr,"                      Use \"-G2.4 -f0\" for BT.1886\n");
1591 	fprintf(stderr," -G gamma             Set the target response curve actual technical gamma\n");
1592 	fprintf(stderr," -f [degree]          Amount of black level accounted for with output offset (default all output offset)\n");
1593 	fprintf(stderr," -a ambient           Use viewing condition adjustment for ambient in Lux\n");
1594 	fprintf(stderr," -k factor            Amount to correct black hue, 0 = none, 1 = full, Default = Automatic\n");
1595 	fprintf(stderr," -A rate              Rate of blending from neutral to black point. Default %.1f\n",NEUTRAL_BLEND_RATE);
1596 	fprintf(stderr," -b                   Use forced black point hack\n");
1597 	fprintf(stderr," -B blkbright         Set the target black brightness in cd/m^2\n");
1598 	fprintf(stderr," -e [n]               Run n verify passes on final curves\n");
1599 	fprintf(stderr," -z                   Run only verify pass on installed calibration curves\n");
1600 	fprintf(stderr," -P ho,vo,ss[,vs]     Position test window and scale it\n");
1601 	fprintf(stderr,"                      ho,vi: 0.0 = left/top, 0.5 = center, 1.0 = right/bottom etc.\n");
1602 	fprintf(stderr,"                      ss: 0.5 = half, 1.0 = normal, 2.0 = double etc.\n");
1603 	fprintf(stderr," -F                   Fill whole screen with black background\n");
1604 #if defined(UNIX_X11)
1605 	fprintf(stderr," -n                   Don't set override redirect on test window\n");
1606 #endif
1607 	fprintf(stderr," -E                   Encode the test values for video range 16..235/255\n");
1608 	fprintf(stderr," -J                   Run instrument calibration first (used rarely)\n");
1609 	fprintf(stderr," -N                   Disable initial calibration of instrument if possible\n");
1610 	fprintf(stderr," -H                   Use high resolution spectrum mode (if available)\n");
1611 //	fprintf(stderr," -V                   Use adaptive measurement mode (if available)\n");
1612 	if (cap2 & inst2_ccmx)
1613 		fprintf(stderr," -X file.ccmx         Apply Colorimeter Correction Matrix\n");
1614 	if (cap2 & inst2_ccss) {
1615 		fprintf(stderr," -X file.ccss         Use Colorimeter Calibration Spectral Samples for calibration\n");
1616 		fprintf(stderr," -Q observ            Choose CIE Observer for spectrometer or CCSS colorimeter data:\n");
1617 		fprintf(stderr,"                      1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2, 1964_10c\n");
1618 	}
1619 	fprintf(stderr," -I b|w               Drift compensation, Black: -Ib, White: -Iw, Both: -Ibw\n");
1620 	fprintf(stderr," -Y R:rate            Override measured refresh rate with rate Hz\n");
1621 	fprintf(stderr," -Y A                 Use non-adaptive integration time mode (if available).\n");
1622 	fprintf(stderr," -Y p                 Don't wait for the instrument to be placed on the display\n");
1623 	fprintf(stderr," -C \"command\"         Invoke shell \"command\" each time a color is set\n");
1624 	fprintf(stderr," -M \"command\"         Invoke shell \"command\" each time a color is measured\n");
1625 	fprintf(stderr," -W n|h|x             Override serial port flow control: n = none, h = HW, x = Xon/Xoff\n");
1626 	fprintf(stderr," -D [level]           Print debug diagnostics to stderr\n");
1627 	fprintf(stderr," inoutfile            Base name for created or updated .cal and %s output files\n",ICC_FILE_EXT);
1628 	if (icmps != NULL)
1629 		icmps->del(icmps);
1630 	exit(1);
1631 }
1632 
main(int argc,char * argv[])1633 int main(int argc, char *argv[]) {
1634 	int i, j, k;
1635 	int fa, nfa, mfa;					/* current argument we're looking at */
1636 	disppath *disp = NULL;				/* Display being used */
1637 	double hpatscale = 1.0, vpatscale = 1.0;	/* scale factor for test patch size */
1638 	double ho = 0.0, vo = 0.0;			/* Test window offsets, -1.0 to 1.0 */
1639 	int out_tvenc = 0;					/* 1 to use RGB Video Level encoding */
1640 	int fullscreen = 0;            		/* NZ if whole screen should be filled with black */
1641 	int verb = 0;
1642 	int debug = 0;
1643 	int fake = 0;						/* Use the fake device for testing */
1644 	int override = 1;					/* Override redirect on X11 */
1645 	int docalib = 0;					/* Do a manual instrument calibration */
1646 	int doreport = 0;					/* 1 = Report the current uncalibrated display response */
1647 										/* 2 = Report the current calibrated display response */
1648 	int docontrols = 1;					/* Do adjustment of the display controls */
1649 	int doprofile = 0;					/* Create/update ICC profile */
1650 	char *profDesc = NULL;				/* Created profile description string */
1651 	char *copyright = NULL;				/* Copyright string */
1652 	char *deviceMfgDesc = NULL;			/* Device manufacturer string */
1653 	char *modelDesc = NULL;				/* Device model description string */
1654 	int doupdate = 0;				    /* Do an update rather than a fresh calbration */
1655 	int comport = COMPORT;				/* COM port used */
1656 	icompaths *icmps = NULL;
1657 	icompath *ipath = NULL;
1658 	flow_control fc = fc_nc;			/* Default flow control */
1659 	int dtype = 0;						/* Display type selection charater */
1660 	int tele  = 0;						/* nz if telephoto mode */
1661 	int nocal = 0;						/* Disable auto calibration */
1662 	int noplace = 0;					/* Disable initial user placement check */
1663 	int highres = 0;					/* Use high res mode if available */
1664 	double refrate = 0.0;				/* 0.0 = default, > 0.0 = override refresh rate */
1665 	int nadaptive = 0;					/* Use non-adaptive mode if available */
1666 	int bdrift = 0;						/* Flag, nz for black drift compensation */
1667 	int wdrift = 0;						/* Flag, nz for white drift compensation */
1668 	double temp = 0.0;					/* Color temperature (0 = native) */
1669 	int planckian = 0;					/* 0 = Daylight, 1 = Planckian color locus */
1670 	int dovct = 1;						/* Show VXT rather than CXT for adjusting white point */
1671 	double wpx = 0.0, wpy = 0.0;		/* White point xy (native) */
1672 	double tbright = 0.0;				/* Target white brightness ( 0.0 == max)  */
1673 	double gamma = 0.0;					/* Advertised Gamma target */
1674 	double egamma = 0.0;				/* Effective Gamma target, NZ if set */
1675 	double ambient = 0.0;				/* NZ if viewing cond. adjustment to be used (Lux) */
1676 	double bkcorrect = -1.0;			/* Level of black point correction, < 0 = auto */
1677 	int bkhack = 0;
1678 	double bkbright = 0.0;				/* Target black brightness ( 0.0 == min)  */
1679 	int quality = -99;					/* Quality level, -2 = v, -1 = l, 0 = m, 1 = h, 2 = u */
1680 	int isteps = 22;					/* Initial measurement steps/3 (medium) */
1681 	int rsteps = 64;					/* Refinement measurement steps (medium) */
1682 	double errthr = 1.5;				/* Error threshold for refinement steps (medium) */
1683 	int thrfail = 0;					/* Set to NZ if failed to meet threshold target */
1684 	double failerr = 0.0;				/* Delta E of worst failed target */
1685 	int mxits = 3;						/* maximum iterations (medium) */
1686 	int mxrpts = 12;					/* maximum repeats (medium) */
1687 	int verify = 0;						/* Do a verify after last refinement, 2 = do only verify. */
1688 	int nver = 0;						/* Number of verify passes after refinement */
1689 	int webdisp = 0;					/* NZ for web display, == port number */
1690 	int ccdisp = 0;			 			/* NZ for ChromeCast, == list index */
1691 	ccast_id **ccids = NULL;
1692 	ccast_id *ccid = NULL;
1693 #ifdef NT
1694 	int madvrdisp = 0;					/* NZ for madvr display */
1695 #endif
1696 	char *ccallout = NULL;				/* Change color Shell callout */
1697 	char *mcallout = NULL;				/* Measure color Shell callout */
1698 	char outname[MAXNAMEL+1] = { 0 };	/* Output cgats file base name */
1699 	char iccoutname[MAXNAMEL+1] = { 0 };/* Output icc file base name */
1700 	char ccxxname[MAXNAMEL+1] = "\000";  /* CCMX or CCSS file name */
1701 	ccmx *cmx = NULL;					/* Colorimeter Correction Matrix */
1702 	ccss *ccs = NULL;					/* Colorimeter Calibration Spectral Samples */
1703 	int spec = 0;						/* Want spectral data from instrument */
1704 	icxObserverType obType = icxOT_default;
1705 	disprd *dr = NULL;					/* Display patch read object */
1706 	csamp asgrey;						/* Main calibration loop test points */
1707 	double dispLum = 0.0;				/* Display luminence reading */
1708 	int it;								/* verify & refine iteration */
1709 	int rv;
1710 	int fitord = 30;					/* More seems to make curves smoother */
1711 	int native = 3;						/* X0 = use current per channel calibration curve */
1712 										/* X1 = set native linear output and use ramdac high prec */
1713 										/* 0X = use current color management cLut (MadVR) */
1714 										/* 1X = disable color management cLUT (MadVR) */
1715 	int noramdac = 0;					/* Will be set to nz if can't set ramdac */
1716 	int nocm = 0;						/* Will be set to nz if can't set color managament */
1717 	int errc;							/* Return value from new_disprd() */
1718 	calx x;								/* Context for calibration solution */
1719 
1720 	set_exe_path(argv[0]);				/* Set global exe_path and error_program */
1721 	check_if_not_interactive();
1722 
1723 #if defined(UNIX_APPLE)
1724 	{
1725 		SInt32 MacMajVers, MacMinVers, MacBFVers;
1726 
1727 		/* Hmm. Maybe this should actually be 1.72 ?? */
1728 		g_def_gamma = 1.8;
1729 
1730 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1040
1731 		/* gestaltSystemVersionMajor etc. isn't supported on older systems, */
1732 		/* although "Gestalt(gestaltSystemVersion, &MacVers)" is, but this */
1733 		/* causes warning messages in 10.10. */
1734 
1735 		/* OS X 10.6+ uses a nominal gamma of 2.2 */
1736 		if (Gestalt(gestaltSystemVersionMajor,  &MacMajVers) == noErr
1737 		 && Gestalt(gestaltSystemVersionMinor,  &MacMinVers) == noErr
1738 		 && Gestalt(gestaltSystemVersionBugFix, &MacBFVers) == noErr) {
1739 			if (MacMajVers >= 10 && MacMinVers >= 6) {
1740 				g_def_gamma = 2.4;
1741 			}
1742 		}
1743 #endif	/* >= 1040 */
1744 	}
1745 #else
1746 	g_def_gamma = 2.4;		/* Typical CRT gamma */
1747 #endif
1748 	gamma = g_def_gamma;
1749 
1750 	x.gammat = gt_power ;				/* Default gamma type */
1751 	x.egamma = 0.0;						/* Default effective gamma none */
1752 	x.oofff = 1.0;						/* Default is all output ofset */
1753 	x.vc = 0;							/* No viewing conditions adjustment */
1754 	x.svc = NULL;
1755 	x.dvc = NULL;
1756 	x.nbrate = NEUTRAL_BLEND_RATE;		/* Rate of blending from black point to neutral axis */
1757 
1758 #ifdef DEBUG_OFFSET
1759 	ho = 0.8;
1760 	vo = -0.8;
1761 #endif
1762 
1763 #if defined(DEBUG) || defined(DEBUG_OFFSET) || defined(DEBUG_PLOT)
1764 	printf("!!!!!! Debug turned on !!!!!!\n");
1765 #endif
1766 
1767 	if (argc <= 1)
1768 		usage(0,"Too few arguments");
1769 
1770 	/* Process the arguments */
1771 	mfa = 1;        /* Minimum final arguments */
1772 	for(fa = 1;fa < argc;fa++) {
1773 		nfa = fa;					/* skip to nfa if next argument is used */
1774 		if (argv[fa][0] == '-')		/* Look for any flags */
1775 			{
1776 			char *na = NULL;		/* next argument after flag, null if none */
1777 
1778 			if (argv[fa][2] != '\000')
1779 				na = &argv[fa][2];		/* next is directly after flag */
1780 			else {
1781 				if ((fa+1+mfa) < argc) {
1782 					if (argv[fa+1][0] != '-')
1783 						{
1784 						nfa = fa + 1;
1785 						na = argv[nfa];		/* next is seperate non-flag argument */
1786 					}
1787 				}
1788 			}
1789 
1790 			if (argv[fa][1] == '?' || argv[fa][1] == '-') {
1791 				usage(0,"Usage requested");
1792 
1793 			} else if (argv[fa][1] == 'v') {
1794 				verb = 1;
1795 				if (na != NULL && na[0] >= '0' && na[0] <= '9') {
1796 					verb = atoi(na);
1797 					fa = nfa;
1798 				}
1799 				g_log->verb = verb;
1800 
1801 			/* Display number */
1802 			} else if (argv[fa][1] == 'd') {
1803 				if (strncmp(na,"web",3) == 0
1804 				 || strncmp(na,"WEB",3) == 0) {
1805 					webdisp = 8080;
1806 					if (na[3] == ':') {
1807 						webdisp = atoi(na+4);
1808 						if (webdisp == 0 || webdisp > 65535)
1809 							usage(0,"Web port number must be in range 1..65535");
1810 					}
1811 					fa = nfa;
1812 				} else if (strncmp(na,"cc",2) == 0
1813 				 || strncmp(na,"CC",2) == 0) {
1814 					ccdisp = 1;
1815 					if (na[2] == ':') {
1816 						if (na[3] < '0' || na[3] > '9')
1817 							usage(0x0001,"Available ChromeCasts");
1818 
1819 						ccdisp = atoi(na+3);
1820 						if (ccdisp <= 0)
1821 							usage(0,"ChromCast number must be in range 1..N");
1822 					}
1823 					fa = nfa;
1824 #ifdef NT
1825 				} else if (strncmp(na,"madvr",5) == 0
1826 				 || strncmp(na,"MADVR",5) == 0) {
1827 					madvrdisp = 1;
1828 					fa = nfa;
1829 #endif
1830 				} else {
1831 #if defined(UNIX_X11)
1832 					int ix, iv;
1833 
1834 					if (strcmp(&argv[fa][2], "isplay") == 0 || strcmp(&argv[fa][2], "ISPLAY") == 0) {
1835 						if (++fa >= argc || argv[fa][0] == '-') usage(0,"Parameter expected following -display");
1836 						setenv("DISPLAY", argv[fa], 1);
1837 					} else {
1838 						if (na == NULL) usage(0,"Parameter expected following -d");
1839 						fa = nfa;
1840 						if (strcmp(na,"fake") == 0) {
1841 							fake = 1;
1842 						} else {
1843 							if (sscanf(na, "%d,%d",&ix,&iv) != 2) {
1844 								ix = atoi(na);
1845 								iv = 0;
1846 							}
1847 							if (disp != NULL)
1848 								free_a_disppath(disp);
1849 							if ((disp = get_a_display(ix-1)) == NULL)
1850 								usage(0,"-d parameter %d out of range",ix);
1851 							if (iv > 0)
1852 								disp->rscreen = iv-1;
1853 						}
1854 					}
1855 #else
1856 					int ix;
1857 					if (na == NULL) usage(0,"Parameter expected following -d");
1858 					fa = nfa;
1859 					if (strcmp(na,"fake") == 0) {
1860 						fake = 1;
1861 					} else {
1862 						ix = atoi(na);
1863 						if (disp != NULL)
1864 							free_a_disppath(disp);
1865 						if ((disp = get_a_display(ix-1)) == NULL)
1866 							usage(0,"-d parameter %d out of range",ix);
1867 					}
1868 #endif
1869 				}
1870 
1871 			} else if (argv[fa][1] == 'E') {
1872 				out_tvenc = 1;
1873 
1874 			} else if (argv[fa][1] == 'J') {
1875 				docalib = 1;
1876 
1877 			} else if (argv[fa][1] == 'N') {
1878 				nocal = 1;
1879 
1880 			/* High res mode */
1881 			} else if (argv[fa][1] == 'H') {
1882 				highres = 1;
1883 
1884 			/* Adaptive mode - now default, so flag is deprecated */
1885 			} else if (argv[fa][1] == 'V') {
1886 				warning("dispcal -V flag is deprecated");
1887 
1888 			/* Colorimeter Correction Matrix */
1889 			/* or Colorimeter Calibration Spectral Samples */
1890 			} else if (argv[fa][1] == 'X') {
1891 				int ix;
1892 				fa = nfa;
1893 				if (na == NULL) usage(0,"Parameter expected following -X");
1894 				strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
1895 
1896 			/* Drift Compensation */
1897 			} else if (argv[fa][1] == 'I') {
1898 				fa = nfa;
1899 				if (na == NULL || na[0] == '\000') usage(0,"Parameter expected after -I");
1900 				for (i=0; ; i++) {
1901 					if (na[i] == '\000')
1902 						break;
1903 					if (na[i] == 'b' || na[i] == 'B')
1904 						bdrift = 1;
1905 					else if (na[i] == 'w' || na[i] == 'W')
1906 						wdrift = 1;
1907 					else
1908 						usage(0,"-I parameter '%c' not recognised",na[i]);
1909 				}
1910 
1911 			/* Spectral Observer type */
1912 			} else if (argv[fa][1] == 'Q') {
1913 				fa = nfa;
1914 				if (na == NULL) usage(0,"Parameter expecte after -Q");
1915 				if (strcmp(na, "1931_2") == 0) {			/* Classic 2 degree */
1916 					obType = icxOT_CIE_1931_2;
1917 				} else if (strcmp(na, "1964_10") == 0) {	/* Classic 10 degree */
1918 					obType = icxOT_CIE_1964_10;
1919 				} else if (strcmp(na, "1964_10c") == 0) {	/* 10 degree corrected */
1920 					obType = icxOT_CIE_1964_10c;
1921 				} else if (strcmp(na, "1955_2") == 0) {		/* Stiles and Burch 1955 2 degree */
1922 					obType = icxOT_Stiles_Burch_2;
1923 				} else if (strcmp(na, "1978_2") == 0) {		/* Judd and Voss 1978 2 degree */
1924 					obType = icxOT_Judd_Voss_2;
1925 				} else if (strcmp(na, "shaw") == 0) {		/* Shaw and Fairchilds 1997 2 degree */
1926 					obType = icxOT_Shaw_Fairchild_2;
1927 				} else
1928 					usage(0,"-Q parameter '%s' not recognised",na);
1929 
1930 			/* Change color callout */
1931 			} else if (argv[fa][1] == 'C') {
1932 				fa = nfa;
1933 				if (na == NULL) usage(0,"Parameter expected after -C");
1934 				ccallout = na;
1935 
1936 			/* Measure color callout */
1937 			} else if (argv[fa][1] == 'M') {
1938 				fa = nfa;
1939 				if (na == NULL) usage(0,"Parameter expected after -M");
1940 				mcallout = na;
1941 
1942 			/* Serial port flow control */
1943 			} else if (argv[fa][1] == 'W') {
1944 				fa = nfa;
1945 				if (na == NULL) usage(0,"Paramater expected following -W");
1946 				if (na[0] == 'n' || na[0] == 'N')
1947 					fc = fc_None;
1948 				else if (na[0] == 'h' || na[0] == 'H')
1949 					fc = fc_Hardware;
1950 				else if (na[0] == 'x' || na[0] == 'X')
1951 					fc = fc_XonXOff;
1952 				else
1953 					usage(0,"-W parameter '%c' not recognised",na[0]);
1954 
1955 			/* Debug coms */
1956 			} else if (argv[fa][1] == 'D') {
1957 				debug = 1;
1958 				if (na != NULL && na[0] >= '0' && na[0] <= '9') {
1959 					debug = atoi(na);
1960 					fa = nfa;
1961 				}
1962 				g_log->debug = debug;
1963 				callback_ddebug = 1;		/* dispwin global */
1964 
1965 			/* Black point correction amount */
1966 			} else if (argv[fa][1] == 'k') {
1967 				fa = nfa;
1968 				if (na == NULL) usage(0,"Paramater expected following -k");
1969 				bkcorrect = atof(na);
1970 				if (bkcorrect < 0.0 || bkcorrect > 1.0) usage(0,"-k parameter must be between 0.0 and 1.0");
1971 			/* Neutral blend rate (power) */
1972 			} else if (argv[fa][1] == 'A') {
1973 				fa = nfa;
1974 				if (na == NULL) usage(0,"Paramater expected following -A");
1975 				x.nbrate = atof(na);
1976 				if (x.nbrate < 0.05 || x.nbrate > 20.0) usage(0,"-A parameter must be between 0.05 and 20.0");
1977 			/* Black brightness */
1978 			} else if (argv[fa][1] == 'B') {
1979 				fa = nfa;
1980 				if (na == NULL) usage(0,"Parameter expected after -B");
1981 				bkbright = atof(na);
1982 				if (bkbright <= 0.0 || bkbright > 100000.0) usage(0,"-B parameter %f out of range",bkbright);
1983 
1984 			/* Number of verify passes */
1985 			} else if (argv[fa][1] == 'e') {
1986 				if (verify == 0)
1987 					verify = 1;
1988 				nver = 1;
1989 				if (na != NULL && na[0] >= '0' && na[0] <= '9') {
1990 					nver = atoi(na);
1991 					fa = nfa;
1992 				}
1993 
1994 			} else if (argv[fa][1] == 'z') {
1995 				verify = 2;
1996 				if (nver == 0)
1997 					nver = 1;
1998 				mfa = 0;
1999 
2000 #if defined(UNIX_X11)
2001 			} else if (argv[fa][1] == 'n') {
2002 				override = 0;
2003 #endif /* UNIX */
2004 			/* COM port  */
2005 			} else if (argv[fa][1] == 'c') {
2006 				fa = nfa;
2007 				if (na == NULL) usage(0,"Paramater expected following -c");
2008 				comport = atoi(na);
2009 				if (comport < 1 || comport > 50) usage(0,"-c parameter %d out of range",comport);
2010 
2011 			/* Telephoto */
2012 			} else if (argv[fa][1] == 'p') {
2013 				tele = 1;
2014 
2015 			} else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') {
2016 				if (argv[fa][1] == 'R')
2017 					doreport = 1;		/* raw */
2018 				else
2019 					doreport = 2;		/* Calibrated */
2020 				mfa = 0;
2021 
2022 			} else if (argv[fa][1] == 'm') {
2023 				docontrols = 0;
2024 
2025 			/* Output/update ICC profile [optional different name] */
2026 			} else if (argv[fa][1] == 'o') {
2027 				doprofile = 1;
2028 
2029 				if (na != NULL) {	/* Found an optional icc profile name */
2030 					fa = nfa;
2031 					strncpy(iccoutname,na,MAXNAMEL); iccoutname[MAXNAMEL] = '\000';
2032 				}
2033 
2034 			/* Fast Profile Description */
2035 			} else if (argv[fa][1] == 'O') {
2036 				fa = nfa;
2037 				if (na == NULL) usage(0,"Expect argument to profile description flag -O");
2038 				profDesc = na;
2039 
2040 			/* Update calibration and (optionally) profile */
2041 			} else if (argv[fa][1] == 'u') {
2042 				doupdate = 1;
2043 				docontrols = 0;
2044 
2045 			/* Speed/Quality */
2046 			} else if (argv[fa][1] == 'q') {
2047 				fa = nfa;
2048 				if (na == NULL) usage(0,"Parameter expected following -q");
2049     			switch (na[0]) {
2050 					case 'L':			/* Test value */
2051 						quality = -3;
2052 						break;
2053 					case 'v':			/* very fast */
2054 						quality = -2;
2055 						break;
2056 					case 'f':			/* fast */
2057 					case 'l':
2058 						quality = -1;
2059 						break;
2060 					case 'm':			/* medium */
2061 					case 'M':
2062 						quality = 0;
2063 						break;
2064 					case 's':			/* slow */
2065 					case 'h':
2066 					case 'H':
2067 						quality = 1;
2068 						break;
2069 					case 'u':			/* ultra slow */
2070 					case 'U':
2071 						quality = 2;
2072 						break;
2073 					default:
2074 						usage(0,"-q parameter '%c' not recognised",na[0]);
2075 				}
2076 
2077 			/* Display type */
2078 			} else if (argv[fa][1] == 'y') {
2079 				fa = nfa;
2080 				if (na == NULL) usage(0,"Parameter expected after -y");
2081 				dtype = na[0];
2082 
2083 			/* Daylight color temperature */
2084 			} else if (argv[fa][1] == 't' || argv[fa][1] == 'T') {
2085 				if (argv[fa][1] == 'T')
2086 					planckian = 1;
2087 				else
2088 					planckian = 0;
2089 				if (na != NULL) {
2090 					fa = nfa;
2091 					temp = atof(na);
2092 					if (temp < 1000.0 || temp > 15000.0) usage(0,"-%c parameter %f out of range",argv[fa][1], temp);
2093 				}
2094 
2095 			/* White point as x, y */
2096 			} else if (argv[fa][1] == 'w') {
2097 				fa = nfa;
2098 				if (na == NULL) usage(0,"Parameter expected after -w");
2099 				if (sscanf(na, " %lf,%lf ", &wpx, &wpy) != 2)
2100 					usage(0,"-w parameter '%s' not recognised",na);
2101 
2102 			/* Show CXT rather than VXT when adjusting native white point */
2103 			} else if (argv[fa][1] == 'L') {
2104 				dovct = 0;
2105 
2106 			/* Black point hack/White brightness */
2107 			} else if (argv[fa][1] == 'b') {
2108 				if (na == NULL) {
2109 					bkhack = 1;
2110 				} else {
2111 					fa = nfa;
2112 					/* if (na == NULL) usage(0,"Parameter expected after -b"); */
2113 					tbright = atof(na);
2114 					if (tbright <= 0.0 || tbright > 100000.0) usage(0,"-b parameter %f out of range",tbright);
2115 				}
2116 
2117 			/* Target transfer curve */
2118 			} else if (argv[fa][1] == 'g') {
2119 				fa = nfa;
2120 				if (na == NULL) usage(0,"Parameter expected after -g");
2121 				if ((na[0] == 'l' || na[0] == 'L') && na[1] == '\000')
2122 					x.gammat = gt_Lab;
2123 				else if ((na[0] == 's' || na[0] == 'S') && na[1] == '\000')
2124 					x.gammat = gt_sRGB;
2125 				else if (strcmp(na, "709") == 0)
2126 					x.gammat = gt_Rec709;
2127 				else if (strcmp(na, "240") == 0)
2128 					x.gammat = gt_SMPTE240M;
2129 				else {
2130 					gamma = atof(na);
2131 					if (gamma <= 0.0 || gamma > 10.0) usage(0,"-g parameter %f out of range",gamma);
2132 					x.gammat = gt_power;
2133 				}
2134 
2135 			/* Effective gamma power */
2136 			} else if (argv[fa][1] == 'G') {
2137 				fa = nfa;
2138 				if (na == NULL) usage(0,"Parameter expected after -G");
2139 				egamma = atof(na);
2140 				if (egamma <= 0.0 || egamma > 10.0) usage(0,"-G parameter %f out of range",egamma);
2141 				x.gammat = gt_power;
2142 
2143 			/* Degree of output offset */
2144 			} else if (argv[fa][1] == 'f') {
2145 				fa = nfa;
2146 				if (na == NULL) {
2147 					x.oofff = 0.0;
2148 				} else {
2149 					x.oofff = atof(na);
2150 					if (x.oofff < 0.0 || x.oofff > 1.0)
2151 						usage(0,"-f parameter %f out of range",x.oofff);
2152 				}
2153 
2154 			/* Ambient light level */
2155 			} else if (argv[fa][1] == 'a') {
2156 				fa = nfa;
2157 				if (na == NULL) usage(0,"Parameter expected after -a");
2158 				ambient = atof(na);
2159 				if (ambient < 0.0)
2160 					usage(0,"-a parameter %f out of range",ambient);
2161 
2162 			/* Test patch offset and size */
2163 			} else if (argv[fa][1] == 'P') {
2164 				fa = nfa;
2165 				if (na == NULL) usage(0,"Parameter expected after -P");
2166 				if (sscanf(na, " %lf,%lf,%lf,%lf ", &ho, &vo, &hpatscale, &vpatscale) == 4) {
2167 					;
2168 				} else if (sscanf(na, " %lf,%lf,%lf ", &ho, &vo, &hpatscale) == 3) {
2169 					vpatscale = hpatscale;
2170 				} else {
2171 					usage(0,"-P parameter '%s' not recognised",na);
2172 				}
2173 				if (ho < 0.0 || ho > 1.0
2174 				 || vo < 0.0 || vo > 1.0
2175 				 || hpatscale <= 0.0 || hpatscale > 50.0
2176 				 || vpatscale <= 0.0 || vpatscale > 50.0)
2177 					usage(0,"-P parameters %f %f %f %f out of range",ho,vo,hpatscale,vpatscale);
2178 				ho = 2.0 * ho - 1.0;
2179 				vo = 2.0 * vo - 1.0;
2180 
2181 			/* Full screen black background */
2182 			} else if (argv[fa][1] == 'F') {
2183 				fullscreen = 1;
2184 
2185 			/* Extra flags */
2186 			} else if (argv[fa][1] == 'Y') {
2187 				if (na == NULL)
2188 					usage(0,"Flag '-Y' expects extra flag");
2189 
2190 				if (na[0] == 'R') {
2191 					if (na[1] != ':')
2192 						usage(0,"-Y R:rate syntax incorrect");
2193 					refrate = atof(na+2);
2194 					if (refrate < 5.0 || refrate > 150.0)
2195 						usage(0,"-Y R:rate %f Hz not in valid range",refrate);
2196 				} else if (na[0] == 'A') {
2197 					nadaptive = 1;
2198 				} else if (na[0] == 'p') {
2199 					noplace = 1;
2200 				} else {
2201 					usage(0,"Flag '-Y %c' not recognised",na[0]);
2202 				}
2203 				fa = nfa;
2204 
2205 			} else
2206 				usage(0,"Flag '-%c' not recognised",argv[fa][1]);
2207 		} else
2208 			break;
2209 	}
2210 
2211 	if (bkhack && bkbright > 0.0) {
2212 		error("Can't use -b black point hack and set target black brightness");
2213 	}
2214 
2215 	if (bkhack && bkcorrect != 0.0) {
2216 		if (bkcorrect > 0.0)
2217 			warning("Due to -b flag, -k factor will be set to 0.0");
2218 		bkcorrect = 0.0;
2219 	}
2220 
2221 	/* No explicit display has been set */
2222 	if (
2223 #ifndef SHOW_WINDOW_ONFAKE
2224 	!fake
2225 #endif
2226 #ifdef NT
2227 	 && madvrdisp == 0
2228 #endif
2229 	 && webdisp == 0
2230 	 && ccdisp == 0
2231 	 && disp == NULL) {
2232 		int ix = 0;
2233 #if defined(UNIX_X11)
2234 		char *dn, *pp;
2235 
2236 		if ((dn = getenv("DISPLAY")) != NULL) {
2237 			if ((pp = strrchr(dn, ':')) != NULL) {
2238 				if ((pp = strchr(pp, '.')) != NULL) {
2239 					if (pp[1] != '\000')
2240 						ix = atoi(pp+1);
2241 				}
2242 			}
2243 		}
2244 #endif
2245 		if ((disp = get_a_display(ix)) == NULL)
2246 			error("Unable to open the default display");
2247 	}
2248 
2249 	/* See if there is an environment variable ccxx */
2250 	if (ccxxname[0] == '\000') {
2251 		char *na;
2252 		if ((na = getenv("ARGYLL_COLMTER_CAL_SPEC_SET")) != NULL) {
2253 			strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
2254 
2255 		} else if ((na = getenv("ARGYLL_COLMTER_COR_MATRIX")) != NULL) {
2256 			strncpy(ccxxname,na,MAXNAMEL-1); ccxxname[MAXNAMEL-1] = '\000';
2257 		}
2258 	}
2259 
2260 	/* Load up CCMX or CCSS */
2261 	if (ccxxname[0] != '\000') {
2262 		if ((cmx = new_ccmx()) == NULL
2263 		  || cmx->read_ccmx(cmx, ccxxname)) {
2264 			if (cmx != NULL) {
2265 				cmx->del(cmx);
2266 				cmx = NULL;
2267 			}
2268 
2269 			/* CCMX failed, try CCSS */
2270 			if ((ccs = new_ccss()) == NULL
2271 			  || ccs->read_ccss(ccs, ccxxname)) {
2272 				if (ccs != NULL) {
2273 					ccs->del(ccs);
2274 					ccs = NULL;
2275 					error("Reading CCMX/CCSS File '%s' failed\n", ccxxname);
2276 				}
2277 			}
2278 		}
2279 	}
2280 
2281 	if (fake)
2282 		comport = FAKE_DEVICE_PORT;
2283 	if ((icmps = new_icompaths(g_log)) == NULL)
2284 		error("Finding instrument paths failed");
2285 	if ((ipath = icmps->get_path(icmps, comport)) == NULL)
2286 		error("No instrument at port %d",comport);
2287 
2288 	/* If we've requested ChromeCast, look it up */
2289 	if (ccdisp) {
2290 		if ((ccids = get_ccids()) == NULL)
2291 			error("discovering ChromCasts failed");
2292 		if (ccids[0] == NULL)
2293 			error("There are no ChromCasts to use\n");
2294 		for (i = 0; ccids[i] != NULL; i++)
2295 			;
2296 		if (ccdisp < 1 || ccdisp > i)
2297 			error("Chosen ChromCasts (%d) is outside list (1..%d)\n",ccdisp,i);
2298 		ccid = ccids[ccdisp-1];
2299 	}
2300 
2301 	if (docalib) {
2302 		if ((rv = disprd_calibration(ipath, fc, dtype, -1, 0, tele, nadaptive, nocal, disp,
2303 		                             webdisp, ccid,
2304 #ifdef NT
2305 			                         madvrdisp,
2306 #endif
2307 			                         out_tvenc, fullscreen, override,
2308 			                         100.0 * hpatscale, 100.0 * vpatscale,
2309 			                         ho, vo, g_log)) != 0) {
2310 			error("docalibration failed with return value %d\n",rv);
2311 		}
2312 	}
2313 
2314 	if (verify != 2 && doreport == 0) {
2315 		/* Get the file name argument */
2316 		if (fa >= argc || argv[fa][0] == '-') usage(0,"Output filname parameter not found");
2317 		strncpy(outname,argv[fa],MAXNAMEL-4); outname[MAXNAMEL-4] = '\000';
2318 		strcat(outname,".cal");
2319 		if (iccoutname[0] == '\000') {
2320 			strncpy(iccoutname,argv[fa++],MAXNAMEL-4); iccoutname[MAXNAMEL-4] = '\000';
2321 			strcat(iccoutname,ICC_FILE_EXT);
2322 		}
2323 	}
2324 
2325 	if (verify == 2) {
2326 		if (doupdate)
2327 			warning("Update flag ignored because we're doing a verify only");
2328 		doupdate = 0;
2329 		docontrols = 0;
2330 	}
2331 
2332 	if (doreport != 0) {
2333 		if (verify == 2)
2334 			warning("Verify flag ignored because we're doing a report only");
2335 		verify = 0;
2336 		nver = 0;
2337 	}
2338 
2339 	/* Normally calibrate against native response */
2340 	if (verify == 2 || doreport == 2)
2341 		native = 0;	/* But measure current calibrated & CM response for verify or report calibrated */
2342 
2343 	/* Get ready to do some readings */
2344 	if ((dr = new_disprd(&errc, ipath, fc, dtype, -1, 0, tele, nadaptive, nocal, noplace,
2345 	                     highres, refrate, native, &noramdac, &nocm, NULL, 0,
2346 		                 disp, out_tvenc, fullscreen, override, webdisp, ccid,
2347 #ifdef NT
2348 		                 madvrdisp,
2349 #endif
2350 		                 ccallout, mcallout, 0,
2351 	                     100.0 * hpatscale, 100.0 * vpatscale, ho, vo,
2352 	                     ccs != NULL ? ccs->dtech : cmx != NULL ? cmx->dtech : disptech_unknown,
2353 	                     cmx != NULL ? cmx->cc_cbid : 0,
2354 	                     cmx != NULL ? cmx->matrix : NULL,
2355 	                     ccs != NULL ? ccs->samples : NULL, ccs != NULL ? ccs->no_samp : 0,
2356 	                     spec, obType, NULL, bdrift, wdrift,
2357 	                     "fake" ICC_FILE_EXT, g_log)) == NULL)
2358 		error("new_disprd() failed with '%s'\n",disprd_err(errc));
2359 
2360 	if ((native & 1) && noramdac) {
2361 		warning("Unable to access to VideoLUTs so can't be sure colors are native");
2362 		if (doprofile)
2363 			warning("Profile will reflect the as-is display response and not contain a 'vcgt' tag");
2364 		native &= ~1;
2365 
2366 		if (doupdate && doprofile)
2367 			error("Can't update a profile that doesn't use the 'vcgt' tag for calibration");
2368 	}
2369 
2370 	if (icmps != NULL) {
2371 		icmps->del(icmps);
2372 		icmps = NULL;
2373 	}
2374 	if (cmx != NULL) {
2375 		cmx->del(cmx);
2376 		cmx = NULL;
2377 	}
2378 	if (ccs != NULL) {
2379 		ccs->del(ccs);
2380 		ccs = NULL;
2381 	}
2382 
2383 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2384 	if (doreport) {
2385 		col tcols[3] = {	/* Base set of test colors */
2386 			{ 0.0, 0.0, 0.0 },
2387 			{ 0.5, 0.5, 0.5 },
2388 			{ 1.0, 1.0, 1.0 }
2389 		};
2390 		double cct, cct_de;		/* Color temperatures and DE 2K */
2391 		double cdt, cdt_de;
2392 		double vct, vct_de;
2393 		double vdt, vdt_de;
2394 		double cgamma, w[3], wp[2];
2395 		int sigbits = 0;	/* Number of significant bits in VideoLUT/display/instrument */
2396 
2397 		if ((rv = dr->read(dr, tcols, 3, 1, 3, 1, 0, instClamp)) != 0) {
2398 			dr->del(dr);
2399 			error("display read failed with '%s'\n",disprd_err(rv));
2400 		}
2401 
2402 //printf("~1 Got black = %f, half = %f, white = %f\n",tcols[0].XYZ[1],tcols[1].XYZ[1],tcols[2].XYZ[1]);
2403 		/* Normalised XYZ white point */
2404 		w[0] = tcols[2].XYZ[0]/tcols[2].XYZ[1];
2405 		w[1] = tcols[2].XYZ[1]/tcols[2].XYZ[1];
2406 		w[2] = tcols[2].XYZ[2]/tcols[2].XYZ[1];
2407 
2408 		/* White point chromaticity coordinates */
2409 		wp[0] = w[0]/(w[0] + w[1] + w[2]);
2410 		wp[1] = w[1]/(w[0] + w[1] + w[2]);
2411 
2412 		cct = comp_ct(&cct_de, NULL, 1, 0, obType, w);	/* Compute CCT */
2413 		cdt = comp_ct(&cdt_de, NULL, 0, 0, obType, w);	/* Compute CDT */
2414 		vct = comp_ct(&vct_de, NULL, 1, 1, obType, w);	/* Compute VCT */
2415 		vdt = comp_ct(&vdt_de, NULL, 0, 1, obType, w);	/* Compute VDT */
2416 
2417 		/* Compute advertised current gamma - use the gross curve shape for robustness */
2418 		cgamma = pop_gamma(tcols[0].XYZ[1], tcols[1].XYZ[1], tcols[2].XYZ[1]);
2419 
2420 #ifdef MEAS_RES
2421 #ifdef NEVER		// Use new code
2422 		/* See if we can detect what sort of precision the LUT entries */
2423 		/* have. Our ability to detect this may be limited by the instrument */
2424 		/* (ie. Huey and Spyder 2) */
2425 		if (doreport == 1) {
2426 #define MAX_RES_SAMPS 24
2427 			col ttt[MAX_RES_SAMPS];
2428 			int res_samps = 9;
2429 			double a0, a1, a2, dd;
2430 			int n;
2431 			int issig = 0;
2432 
2433 			if (verb)
2434 				printf("Measuring VideoLUT table entry precision.\n");
2435 
2436 			/* Run a small state machine until we come to a conclusion */
2437 			sigbits = 8;
2438 			for (issig = 0; issig < 2; ) {
2439 
2440 				DBG((dbgo,"Trying %d bits\n",sigbits));
2441 				/* Do the test */
2442 				for (n = 0; n < res_samps; n++) {
2443 					double v;
2444 #if defined(__APPLE__) && defined(__POWERPC__)
2445 					gcc_bug_fix(sigbits);
2446 #endif
2447 					/* Notional test value */
2448 					v = (7 << (sigbits-3))/((1 << sigbits) - 1.0);
2449 					/* And -1, 0 , +1 bit test values */
2450 					if ((n % 3) == 2)
2451 						v += 1.0/((1 << sigbits) - 1.0);
2452 					else if ((n % 3) == 1)
2453 						v += 0.0/((1 << sigbits) - 1.0);
2454 					else
2455 						v += -1.0/((1 << sigbits) - 1.0);
2456 					ttt[n].r = ttt[n].g = ttt[n].b = v;
2457 				}
2458 				if ((rv = dr->read(dr, ttt, res_samps, 1, res_samps, 1, 0, instNoClamp)) != 0) {
2459 					dr->del(dr);
2460 					error("display read failed with '%s'\n",disprd_err(rv));
2461 				}
2462 				/* Average the readings for each test value */
2463 				a0 = a1 = a2 = 0.0;
2464 				for (n = 0; n < res_samps; n++) {
2465 					double v = ttt[n].XYZ[1];
2466 					if ((n % 3) == 2) {
2467 						a2 += v;
2468 					} else if ((n % 3) == 1) {
2469 						a1 += v;
2470 					} else {
2471 						a0 += v;
2472 					}
2473 				}
2474 				a0 /= (res_samps / 3.0);
2475 				a1 /= (res_samps / 3.0);
2476 				a2 /= (res_samps / 3.0);
2477 				DBG((dbgo,"Bits %d: -1: %f 0: %f +1 %f\n",sigbits, a0, a1, a2));
2478 				/* Judge significance of any differences */
2479 				dd = 0.0;
2480 				for (n = 0; n < res_samps; n++) {
2481 					double tt;
2482 					if ((n % 3) == 2)
2483 						tt = fabs(a2 - ttt[n].XYZ[1]);
2484 					else if ((n % 3) == 1)
2485 						tt = fabs(a1 - ttt[n].XYZ[1]);
2486 					else
2487 						tt = fabs(a0 - ttt[n].XYZ[1]);
2488 					dd += tt * tt;
2489 				}
2490 				dd /= res_samps;
2491 				dd = sqrt(dd);
2492 				if (fabs(a1 - a0) > (2.0 * dd) && fabs(a2 - a1) > (2.0 * dd))
2493 					issig = 1;		/* Noticable difference */
2494 				else
2495 					issig = 0;		/* No noticable difference */
2496 				DBG((dbgo,"Bits %d: Between = %f, %f within = %f, sig = %s\n",sigbits, fabs(a1 - a0), fabs(a2 - a1), dd, issig ? "yes" : "no"));
2497 
2498 				switch(sigbits) {
2499 					case 8:				/* Do another trial */
2500 						if (issig) {
2501 							sigbits = 10;
2502 							res_samps = 9;
2503 						} else {
2504 							sigbits = 6;
2505 						}
2506 						break;
2507 					case 6:				/* Do another trial or give up */
2508 						if (issig) {
2509 							sigbits = 7;
2510 							res_samps = 6;
2511 						} else {
2512 							sigbits = 0;
2513 							issig = 2;	/* Give up */
2514 						}
2515 						break;
2516 					case 7:				/* Terminal */
2517 						if (!issig)
2518 							sigbits = 6;
2519 						issig = 2;		/* Stop here */
2520 						break;
2521 					case 10:			/* Do another trial */
2522 						if (issig) {
2523 							sigbits = 12;
2524 							res_samps = 12;
2525 						} else {
2526 							sigbits = 9;
2527 						}
2528 						break;
2529 					case 12:			/* Do another trial or give up */
2530 						if (issig) {
2531 							issig = 2;	/* Stop here */
2532 						} else {
2533 							sigbits = 11;
2534 						}
2535 						break;
2536 					case 11:			/* Terminal */
2537 						if (!issig)
2538 							sigbits = 10;
2539 						issig = 2;		/* Stop here */
2540 						break;
2541 					case 9:				/* Terminal */
2542 						if (!issig)
2543 							sigbits = 8;
2544 						issig = 2;		/* Stop here */
2545 						break;
2546 
2547 					default:
2548 						error("Unexpected number of bits in depth test (bits)",sigbits);
2549 				}
2550 			}
2551 		}
2552 # else	/* ! NEVER */
2553 		/* See if we can deternine what sort of precision the LUT entries */
2554 		/* have. Our ability to detect this may be limited by the instrument */
2555 		/* (ie. Huey and Spyder 2) */
2556 		/* We assume that we need to just detect 8 to 12 bits */
2557 		if (doreport == 1) {
2558 			sigbits = meas_ramdac_prec(8, dr);
2559 		}
2560 #endif /* NEVER */
2561 #endif	/* MEAS_RES */
2562 
2563 		if (doreport == 2)
2564 			printf("Current calibration response:\n");
2565 		else
2566 			printf("Uncalibrated response:\n");
2567 		printf("Black level = %.4f cd/m^2\n",tcols[0].XYZ[1]);
2568 		printf("50%%   level = %.2f cd/m^2\n",tcols[1].XYZ[1]);
2569 		printf("White level = %.2f cd/m^2\n",tcols[2].XYZ[1]);
2570 		printf("Aprox. gamma = %.2f\n",cgamma);
2571 		printf("Contrast ratio = %.0f:1\n",tcols[2].XYZ[1]/tcols[0].XYZ[1]);
2572 		printf("White chromaticity coordinates %.4f, %.4f\n",wp[0],wp[1]);
2573 		printf("White    Correlated Color Temperature = %.0fK, DE 2K to locus = %4.1f\n",cct,cct_de);
2574 		printf("White Correlated Daylight Temperature = %.0fK, DE 2K to locus = %4.1f\n",cdt,cdt_de);
2575 		printf("White        Visual Color Temperature = %.0fK, DE 2K to locus = %4.1f\n",vct,vct_de);
2576 		printf("White     Visual Daylight Temperature = %.0fK, DE 2K to locus = %4.1f\n",vdt,vdt_de);
2577 #ifdef	MEAS_RES
2578 		if (doreport == 1) {
2579 			if (sigbits == 0) {
2580 				warning("Unable to determine effective Video LUT entry bit depth");
2581 			} else {
2582 				printf("Effective Video LUT entry depth seems to be %d bits\n",sigbits);
2583 			}
2584 		}
2585 #endif	/* MEAS_RES */
2586 		dr->del(dr);
2587 		exit(0);
2588 	}
2589 
2590 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2591 	/* If we're updating, retrieve the previously used settings, */
2592 	/* and device model */
2593 	if (doupdate) {
2594 		cgats *icg;						/* output cgats structure */
2595 		int nsamp;
2596 		mcvco *rdv[3];					/* Scattered data for ramdac curves */
2597 		int fi;							/* Field index */
2598 		int si[4];						/* Set fields */
2599 
2600 		if (verb) {
2601 			if (doprofile)
2602 				printf("Updating previous calibration and profile\n");
2603 			else
2604 				printf("Updating previous calibration\n");
2605 		}
2606 
2607 		icg = new_cgats();				/* Create a CGATS structure */
2608 		icg->add_other(icg, "CAL"); 	/* our special type is Calibration file */
2609 
2610 		if (icg->read_name(icg, outname)) {
2611 			dr->del(dr);
2612 			error("Can't update '%s' - read error : %s",outname, icg->err);
2613 		}
2614 
2615 		if (icg->ntables == 0
2616 		 || icg->t[0].tt != tt_other || icg->t[0].oi != 0
2617 		 || icg->t[1].tt != tt_other || icg->t[1].oi != 0) {
2618 			dr->del(dr);
2619 			error("Can't update '%s' - wrong type of file",outname);
2620 		}
2621 		if (icg->ntables < 2) {
2622 			dr->del(dr);
2623 			error("Can't update '%s' - there aren't two tables",outname);
2624 		}
2625 
2626 		out_tvenc = 0;
2627 		if ((fi = icg->find_kword(icg, 0, "TV_OUTPUT_ENCODING")) >= 0) {
2628 			if (strcmp(icg->t[0].kdata[fi], "YES") == 0
2629 			 || strcmp(icg->t[0].kdata[fi], "yes") == 0)
2630 				out_tvenc = 1;
2631 		}
2632 
2633 //printf("~1 reading previous cal, got 2 tables\n");
2634 
2635 		/* Read in the setup, user and model values */
2636 
2637 		if (dtype == 0) {	/* If the use hasn't set anything */
2638 			if ((fi = icg->find_kword(icg, 0, "DEVICE_TYPE")) >= 0) {
2639 				if (strcmp(icg->t[0].kdata[fi], "CRT") == 0)
2640 					dtype = 'c';
2641 				else if (strcmp(icg->t[0].kdata[fi], "LCD") == 0)
2642 					dtype = 'l';
2643 				else
2644 					dtype = icg->t[0].kdata[fi][0];
2645 			}
2646 		}
2647 //printf("~1 dealt with device type\n");
2648 
2649 		if ((fi = icg->find_kword(icg, 0, "TARGET_WHITE_XYZ")) < 0) {
2650 			dr->del(dr);
2651 			error("Can't update '%s' - can't find field 'TARGET_WHITE_XYZ'",outname);
2652 		}
2653 		if (sscanf(icg->t[0].kdata[fi], "%lf %lf %lf", &x.twh[0], &x.twh[1], &x.twh[2]) != 3) {
2654 			dr->del(dr);
2655 			error("Can't update '%s' - reading field 'TARGET_WHITE_XYZ' failed",outname);
2656 		}
2657 		x.nwh[0] = x.twh[0] / x.twh[1];
2658 		x.nwh[1] = x.twh[1] / x.twh[1];
2659 		x.nwh[2] = x.twh[2] / x.twh[1];
2660 
2661 		if ((fi = icg->find_kword(icg, 0, "NATIVE_TARGET_WHITE")) >= 0) {
2662 			wpx = wpy = 0.0;
2663 			temp = 0.0;
2664 			tbright = 0.0;
2665 		} else {
2666 			wpx = wpy = 0.0001;
2667 			temp = 1.0;
2668 			tbright = 1.0;
2669 		}
2670 //printf("~1 dealt with target white\n");
2671 
2672 		if ((fi = icg->find_kword(icg, 0, "TARGET_GAMMA")) < 0) {
2673 			dr->del(dr);
2674 			error("Can't update '%s' - can't find field 'TARGET_GAMMA'",outname);
2675 		}
2676 		if (strcmp(icg->t[0].kdata[fi], "L_STAR") == 0)
2677 			x.gammat = gt_Lab;
2678 		else if (strcmp(icg->t[0].kdata[fi], "sRGB") == 0)
2679 			x.gammat = gt_sRGB;
2680 		else if (strcmp(icg->t[0].kdata[fi], "REC709") == 0)
2681 			x.gammat = gt_Rec709;
2682 		else if (strcmp(icg->t[0].kdata[fi], "SMPTE240M") == 0)
2683 			x.gammat = gt_SMPTE240M;
2684 		else {
2685 			x.gammat = 0;
2686 			gamma = atof(icg->t[0].kdata[fi]);
2687 
2688 			if (fabs(gamma) < 0.1 || fabs(gamma) > 5.0) {
2689 				dr->del(dr);
2690 				error("Can't update '%s' - field 'TARGET_GAMMA' has bad value %f",outname,fabs(gamma));
2691 			}
2692 			if (gamma < 0.0) {	/* Effective gamma = actual power value */
2693 				egamma = -gamma;
2694 			}
2695 		}
2696 		if ((fi = icg->find_kword(icg, 0, "DEGREE_OF_BLACK_OUTPUT_OFFSET")) < 0) {
2697 			/* Backward compatibility if value is not present */
2698 			if (x.gammat == gt_Lab || x.gammat == gt_sRGB)
2699 				x.oofff = 1.0;
2700 			else
2701 				x.oofff = 0.0;
2702 		} else {
2703 			x.oofff = atof(icg->t[0].kdata[fi]);
2704 		}
2705 
2706 		if ((fi = icg->find_kword(icg, 0, "BLACK_POINT_HACK")) < 0) {
2707 			bkhack = 0;
2708 		} else {
2709 			if (strcmp(icg->t[0].kdata[fi], "YES") == 0
2710 			 || strcmp(icg->t[0].kdata[fi], "yes") == 0) {
2711 				bkhack = 1;
2712 			} else {
2713 				bkhack = 0;
2714 			}
2715 		}
2716 
2717 		if ((fi = icg->find_kword(icg, 0, "TARGET_BLACK_BRIGHTNESS")) < 0) {
2718 			bkbright = 0.0;		/* Native */
2719 		} else {
2720 			bkbright = atof(icg->t[0].kdata[fi]);
2721 		}
2722 		if (bkhack && bkbright > 0.0) {
2723 			error("Can't update '%s' - BLACK_POINT_HACK and TARGET_BLACK_BRIGHTNESS conflict",outname);
2724 		}
2725 		if ((fi = icg->find_kword(icg, 0, "BLACK_POINT_CORRECTION")) < 0) {
2726 			dr->del(dr);
2727 			error("Can't update '%s' - can't find field 'BLACK_POINT_CORRECTION'",outname);
2728 		}
2729 		bkcorrect = atof(icg->t[0].kdata[fi]);
2730 		if (bkcorrect < 0.0 || bkcorrect > 1.0) {
2731 			dr->del(dr);
2732 			error("Can't update '%s' - field 'BLACK_POINT_CORRECTION' has bad value %f",outname,bkcorrect);
2733 		}
2734 
2735 		if (bkhack && bkcorrect != 0.0) {
2736 			if (bkcorrect > 0.0)
2737 				warning("Due to -b flag, -k factor will be set to 0.0");
2738 			bkcorrect = 0.0;
2739 		}
2740 
2741 		if ((fi = icg->find_kword(icg, 0, "BLACK_NEUTRAL_BLEND_RATE")) < 0) {
2742 			x.nbrate = 8.0;		/* Backwards compatibility value */
2743 		} else {
2744 			x.nbrate = atof(icg->t[0].kdata[fi]);
2745 			if (x.nbrate < 0.05 || x.nbrate > 20.0) {
2746 				dr->del(dr);
2747 				error("Can't update '%s' - field 'BLACK_NEUTRAL_BLEND_RATE' has bad value %f",outname,x.nbrate);
2748 			}
2749 		}
2750 
2751 		if ((fi = icg->find_kword(icg, 0, "QUALITY")) < 0) {
2752 			dr->del(dr);
2753 			error("Can't update '%s' - can't find field 'QUALITY'",outname);
2754 		}
2755 		if (quality < -50) {	/* User hasn't overridden quality */
2756 			if (strcmp(icg->t[0].kdata[fi], "ultra low") == 0)
2757 				quality = -3;
2758 			else if (strcmp(icg->t[0].kdata[fi], "very low") == 0)
2759 				quality = -2;
2760 			else if (strcmp(icg->t[0].kdata[fi], "low") == 0)
2761 				quality = -1;
2762 			else if (strcmp(icg->t[0].kdata[fi], "medium") == 0)
2763 				quality = 0;
2764 			else if (strcmp(icg->t[0].kdata[fi], "high") == 0)
2765 				quality = 1;
2766 			else if (strcmp(icg->t[0].kdata[fi], "ultra high") == 0)
2767 				quality = 2;
2768 			else {
2769 				dr->del(dr);
2770 				error("Can't update '%s' - field 'QUALITY' has unrecognised value '%s'",
2771 				        outname,icg->t[0].kdata[fi]);
2772 			}
2773 		}
2774 //printf("~1 dealt with quality\n");
2775 
2776 		/* Read in the last set of calibration curves used */
2777 		if ((nsamp = icg->t[0].nsets) < 2) {
2778 			dr->del(dr);
2779 			error("Can't update '%s' - %d not enough data points in calibration curves",
2780 			        outname,nsamp);
2781 		}
2782 //printf("~1 got %d points in calibration curves\n",nsamp);
2783 
2784 		for (k = 0; k < 3; k++) {
2785 			if ((x.rdac[k] = new_mcv()) == NULL) {
2786 				dr->del(dr);
2787 				error("new_mcv x.rdac[%d] failed",k);
2788 			}
2789 			if ((rdv[k] = malloc(sizeof(mcvco) * nsamp)) == NULL) {
2790 				dr->del(dr);
2791 				error("Malloc of scattered data points failed");
2792 			}
2793 		}
2794 //printf("~1 allocated calibration curve objects\n");
2795 
2796 		/* Read the current calibration curve points (usually CAL_RES of them) */
2797 		for (k = 0; k < 4; k++) {
2798 			char *fnames[4] = { "RGB_I", "RGB_R", "RGB_G", "RGB_B" };
2799 
2800 			if ((si[k] = icg->find_field(icg, 0, fnames[k])) < 0) {
2801 				dr->del(dr);
2802 				error("Can't updata '%s' - can't find field '%s'",outname,fnames[k]);
2803 			}
2804 			if (icg->t[0].ftype[si[k]] != r_t) {
2805 				dr->del(dr);
2806 				error("Can't updata '%s' - field '%s' is wrong type",outname,fnames[k]);
2807 			}
2808 		}
2809 //printf("~1 Found calibration curve fields\n");
2810 
2811 		for (i = 0; i < nsamp; i++) {
2812 			rdv[0][i].p =
2813 			rdv[1][i].p =
2814 			rdv[2][i].p =
2815 			                *((double *)icg->t[0].fdata[i][si[0]]);
2816 			for (k = 0; k < 3; k++) {		/* RGB */
2817 				rdv[k][i].v = *((double *)icg->t[0].fdata[i][si[k + 1]]);
2818 			}
2819 			rdv[0][i].w = rdv[1][i].w = rdv[2][i].w = 1.0;
2820 		}
2821 //printf("~1 Read calibration curve data points\n");
2822 		for (k = 0; k < 3; k++) {
2823 			x.rdac[k]->fit(x.rdac[k], 0, fitord, rdv[k], nsamp, RDAC_SMOOTH);
2824 			free (rdv[k]);
2825 		}
2826 //printf("~1 Fitted calibration curves\n");
2827 
2828 		/* Read in the per channel forward model curves */
2829 		for (k = 0; k < 3; k++) {
2830 			char *fnames[3] = { "R_P", "G_P", "B_P" };
2831 			double *pp;
2832 
2833 //printf("~1 Reading device curve channel %d\n",k);
2834 			if ((si[k] = icg->find_field(icg, 1, fnames[k])) < 0) {
2835 				dr->del(dr);
2836 				error("Can't updata '%s' - can't find field '%s'",outname,fnames[k]);
2837 			}
2838 			if (icg->t[1].ftype[si[k]] != r_t) {
2839 				dr->del(dr);
2840 				error("Can't updata '%s' - field '%s' is wrong type",outname,fnames[k]);
2841 			}
2842 			/* Create the model curves */
2843 			if ((pp = (double *)malloc(icg->t[1].nsets * sizeof(double))) == NULL) {
2844 				dr->del(dr);
2845 				error("Malloc of device curve parameters");
2846 			}
2847 			for (i = 0; i < icg->t[1].nsets; i++)
2848 				pp[i] = *((double *)icg->t[1].fdata[i][si[k]]);
2849 
2850 			if ((x.dcvs[k] = new_mcv_p(pp, icg->t[1].nsets)) == NULL) {
2851 				dr->del(dr);
2852 				error("new_mcv x.dcvs[%d] failed",k);
2853 			}
2854 			free(pp);
2855 		}
2856 
2857 		icg->del(icg);
2858 //printf("~1 read in previous settings and device model\n");
2859 	}
2860 
2861 	/* Be nice - check we can read the iccprofile before calibrating the display */
2862 	if (verify != 2 && doupdate && doprofile) {
2863 		icmFile *ic_fp;
2864 		icc *icco;
2865 
2866 		if ((icco = new_icc()) == NULL) {
2867 			dr->del(dr);
2868 			error("Creation of ICC object to read profile '%s' failed",iccoutname);
2869 		}
2870 
2871 		/* Open up the profile for reading */
2872 		if ((ic_fp = new_icmFileStd_name(iccoutname,"r")) == NULL) {
2873 			dr->del(dr);
2874 			error("Can't open file '%s'",iccoutname);
2875 		}
2876 
2877 		/* Read header etc. */
2878 		if ((rv = icco->read(icco,ic_fp,0)) != 0) {
2879 			dr->del(dr);
2880 			error("Reading profile '%s' failed with %d, %s",iccoutname, rv,icco->err);
2881 		}
2882 
2883 		ic_fp->del(ic_fp);
2884 
2885 		if (icco->find_tag(icco, icSigVideoCardGammaTag) != 0) {
2886 			dr->del(dr);
2887 			error("Can't find VideoCardGamma tag in file '%s': %d, %s",
2888 			      iccoutname, icco->errc,icco->err);
2889 		}
2890 		icco->del(icco);
2891 	}
2892 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2893 	/* Convert quality level to iterations etc. */
2894 	/* Note that final tolerance is often double the */
2895 	/* final errth, because one more corrections is always */
2896 	/* performed after the last reading. */
2897     switch (quality) {
2898 		case -3:				/* Test value */
2899 			isteps = 3;
2900 			rsteps = 9;
2901 			mxits = 1;
2902 			mxrpts = 8;
2903 			errthr = 2.0;
2904 			break;
2905 		case -2:				/* Very low */
2906 			isteps = 10;
2907 			rsteps = 16;
2908 			errthr = 1.5;
2909 			if (doupdate)
2910 				mxits = 1;
2911 			else
2912 				mxits = 1;
2913 			mxrpts = 10;
2914 			break;
2915 		case -1:				/* Low */
2916 			if (verify != 2 && doprofile && !doupdate)
2917 				isteps = 24;	/* Use more steps if we're creating a profile */
2918 			else
2919 				isteps = 12;
2920 			rsteps = 32;
2921 			errthr = 0.9;
2922 			if (doupdate)
2923 				mxits = 1;
2924 			else
2925 				mxits = 2;
2926 			mxrpts = 10;
2927 			break;
2928 		default:
2929 		case 0:					/* Medum */
2930 			quality = 0;		/* In case it wasn't set */
2931 			if (verify != 2 && doprofile && !doupdate)
2932 				isteps = 32;	/* Use more steps if we're creating a profile */
2933 			else
2934 				isteps = 16;
2935 			rsteps = 64;
2936 			errthr = 0.6;
2937 			if (doupdate)
2938 				mxits = 1;
2939 			else
2940 				mxits = 3;
2941 			mxrpts = 12;
2942 			break;
2943 		case 1:					/* High */
2944 			if (verify != 2 && doprofile && !doupdate)
2945 				isteps = 40;	/* Use more steps if we're creating a profile */
2946 			else
2947 				isteps = 20;
2948 			rsteps = 96;
2949 			errthr = 0.4;
2950 			if (doupdate)
2951 				mxits = 1;
2952 			else
2953 				mxits = 4;
2954 			mxrpts = 16;
2955 			break;
2956 		case 2:					/* Ultra */
2957 			if (verify != 2 && doprofile && !doupdate)
2958 				isteps = 48;	/* Use more steps if we're creating a profile */
2959 			else
2960 				isteps = 24;
2961 			rsteps = 128;
2962 			errthr = 0.25;
2963 			if (doupdate)
2964 				mxits = 1;
2965 			else
2966 				mxits = 5;
2967 			mxrpts = 24;
2968 			break;
2969 	}
2970 
2971 	/* Set native white target flag in calx so that other things can play the game.. */
2972 	if (wpx == 0.0 && wpy == 0.0 && temp == 0.0 && tbright == 0.0)
2973 		x.nat = 1;
2974 	else
2975 		x.nat = 0;
2976 
2977 	x.bkhack = bkhack;
2978 
2979 	/* Say something about what we're doing */
2980 	if (verb) {
2981 		if (out_tvenc)
2982 			printf("Using TV encoding range of (16-235)/255\n");
2983 
2984 		if (dtype > 0)
2985 			printf("Display type is '%c'\n",dtype);
2986 
2987 		if (doupdate) {
2988 			if (x.nat)
2989 				printf("Target white = native white point & brightness\n");
2990 			else
2991 				printf("Target white = XYZ %f %f %f\n",
2992 				       x.twh[0], x.twh[1], x.twh[2]);
2993 		} else {
2994 			if (wpx > 0.0 || wpy > 0.0)
2995 				printf("Target white = xy %f %f\n",wpx,wpy);
2996 			else if (temp > 0.0) {
2997 				if (planckian)
2998 					printf("Target white = %f degrees kelvin Planckian (black body) spectrum\n",temp);
2999 				else
3000 					printf("Target white = %f degrees kelvin Daylight spectrum\n",temp);
3001 			} else
3002 				printf("Target white = native white point\n");
3003 
3004 			if (tbright > 0.0)
3005 				printf("Target white brightness = %f cd/m^2\n",tbright);
3006 			else
3007 				printf("Target white brightness = native brightness\n");
3008 			if (bkbright > 0.0)
3009 				printf("Target black brightness = %f cd/m^2\n",bkbright);
3010 			else
3011 				printf("Target black brightness = native brightness\n");
3012 			if (bkhack)
3013 				printf("Black point device hack is enabled\n");
3014 		}
3015 
3016 		switch(x.gammat) {
3017 			case gt_power:
3018 				if (egamma > 0.0)
3019 					printf("Target effective gamma = %f\n",egamma);
3020 				else
3021 					printf("Target advertised gamma = %f\n",gamma);
3022 				break;
3023 			case gt_Lab:
3024 				printf("Target gamma = L* curve\n");
3025 				break;
3026 			case gt_sRGB:
3027 				printf("Target gamma = sRGB curve\n");
3028 				break;
3029 			case gt_Rec709:
3030 				printf("Target gamma = REC 709 curve\n");
3031 				break;
3032 			case gt_SMPTE240M:
3033 				printf("Target gamma = SMPTE 240M curve\n");
3034 				break;
3035 			default:
3036 				error("Unknown gamma type");
3037 		}
3038 	}
3039 
3040 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
3041 	/* Go through the procedure of adjusting monitor controls */
3042 	if (docontrols) {
3043 		int rgbch = 0;			/* Got RBG Yxy ? */
3044 		double rgbXYZ[3][3];	/* The RGB XYZ */
3045 
3046 		/* Make sure drift comp. is off for interactive adjustment */
3047 		dr->change_drift_comp(dr, 0, 0);
3048 
3049 		/* Until the user is done */
3050 		printf("\nDisplay adjustment menu:");
3051 		for (;;) {
3052 			int c;
3053 
3054 			/* Print the menue of adjustments */
3055 			printf("\nPress 1 .. 7\n");
3056 			printf("1) Black level (CRT: Offset/Brightness)\n");
3057 			printf("2) White point (Color temperature, R,G,B, Gain/Contrast)\n");
3058 			printf("3) White level (CRT: Gain/Contrast, LCD: Brightness/Backlight)\n");
3059 			printf("4) Black point (R,G,B, Offset/Brightness)\n");
3060 			printf("5) Check all\n");
3061 			printf("6) Measure and set ambient for viewing condition adjustment\n");
3062 			printf("7) Continue on to calibration\n");
3063 			printf("8) Exit\n");
3064 
3065 			empty_con_chars();
3066 			c = next_con_char();
3067 
3068 			/* Black level adjustment */
3069 			/* Due to the possibility of the channel offsets not being even, */
3070 			/* we use the largest of the XYZ values after they have been */
3071 			/* scaled to be even acording to the white XYZ balance. */
3072 			/* It's safer to set the black level a bit low, and then the */
3073 			/* calibration curves can bump the low ones up. */
3074 			if (c == '1') {
3075 				col tcols[3] = {	/* Base set of test colors */
3076 					{ 0.0, 0.0, 0.0 },
3077 					{ 0.5, 0.5, 0.5 },		/* And 1% values */
3078 					{ 1.0, 1.0, 1.0 }
3079 				};
3080 				int ff;
3081 				double mgamma, tar1, dev1;
3082 
3083 				printf("Doing some initial measurements\n");
3084 				/* Do an initial set of readings to set 1% output mark */
3085 				if ((rv = dr->read(dr, tcols, 3, 0, 0, 1, 0, instClamp)) != 0) {
3086 					dr->del(dr);
3087 					error("display read failed with '%s'\n",disprd_err(rv));
3088 				}
3089 
3090 				if (verb) {
3091 					printf("Black = XYZ %6.4f %6.4f %6.4f\n",tcols[0].XYZ[0],
3092 					                                         tcols[0].XYZ[1], tcols[0].XYZ[2]);
3093 					printf("Grey  = XYZ %6.3f %6.3f %6.3f\n",tcols[1].XYZ[0],
3094 					                                         tcols[1].XYZ[1], tcols[1].XYZ[2]);
3095 					printf("White = XYZ %6.3f %6.3f %6.3f\n",tcols[2].XYZ[0],
3096 					                                         tcols[2].XYZ[1], tcols[2].XYZ[2]);
3097 				}
3098 
3099 				/* Advertised Gamma - Gross curve shape */
3100 				mgamma = pop_gamma(tcols[0].XYZ[1], tcols[1].XYZ[1], tcols[2].XYZ[1]);
3101 
3102 				dev1 = pow(0.01, 1.0/mgamma);
3103 //printf("~1 device level for 1%% output = %f\n",dev1);
3104 				tcols[1].r = tcols[1].g = tcols[1].b = dev1;
3105 				tar1 = 0.01 * tcols[2].XYZ[1];
3106 
3107 				printf("\nAdjust CRT brightness to get target level. Press space when done.\n");
3108 				printf("   Target %.2f\n",tar1);
3109 				for (ff = 0;; ff ^= 1) {
3110 					double dir;			/* Direction to adjust brightness */
3111 					double sv[3], val1;				/* Scaled values */
3112 					if ((rv = dr->read(dr, tcols+1, 1, 0, 0, 1, ' ',instClamp)) != 0) {
3113 						if (rv == 4)
3114 							break;			/* User is done with this adjustment */
3115 						dr->del(dr);
3116 						error("display read failed with '%s'\n",disprd_err(rv));
3117 					}
3118 					/* Scale 1% values by ratio of Y to white XYZ */
3119 					sv[0] = tcols[1].XYZ[0] * tcols[2].XYZ[1]/tcols[2].XYZ[0];
3120 					sv[1] = tcols[1].XYZ[1];
3121 					sv[2] = tcols[1].XYZ[2] * tcols[2].XYZ[1]/tcols[2].XYZ[2];
3122 //printf("~1 scaled readings = %f %f %f\n",sv[0],sv[1],sv[2]);
3123 					val1 = sv[1];
3124 					if (sv[0] > val1)
3125 						val1 = sv[0];
3126 					if (sv[2] > val1)
3127 						val1 = sv[2];
3128 					dir = tar1 - val1;
3129 					if (fabs(dir) < 0.01)
3130 						dir = 0.0;
3131 					printf("%c%c Current %.2f  %c",
3132 					       cr_char,
3133 					       ff == 0 ? '/' : '\\',
3134 					       val1,
3135 					       dir < 0.0 ? '-' : dir > 0.0 ? '+' : '=');
3136 					fflush(stdout);
3137 				}
3138 				printf("\n");
3139 
3140 			/* White point adjustment */
3141 			} else if (c == '2') {
3142 				int nat = 0;		/* NZ if using native white as target */
3143 				col tcols[1] = {	/* Base set of test colors */
3144 					{ 1.0, 1.0, 1.0 }
3145 				};
3146 				int ff;
3147 				double tYxy[3];				/* Target white chromaticities */
3148 				icmXYZNumber tXYZ;			/* Target white as XYZ */
3149 				double tLab[3];				/* Target white as Lab or UCS */
3150 				double tarw;				/* Target brightness */
3151 				double Lab[3];				/* Last measured point Lab or UCS */
3152 				double ct = 0.0, ct_de;		/* Color temperature & delta E to white locus */
3153 
3154 				printf("Doing some initial measurements\n");
3155 
3156 				if (rgbch == 0) {	/* Figure the RGB chromaticities */
3157 					col ccols[3] = {
3158 						{ 1.0, 0.0, 0.0 },
3159 						{ 0.0, 1.0, 0.0 },
3160 						{ 0.0, 0.0, 1.0 }
3161 					};
3162 					if ((rv = dr->read(dr, ccols, 3, 0, 0, 1, 0, instClamp)) != 0) {
3163 						dr->del(dr);
3164 						error("display read failed with '%s'\n",disprd_err(rv));
3165 					}
3166 					if (verb) {
3167 						printf("Red   = XYZ %6.3f %6.3f %6.3f\n",ccols[0].XYZ[0],
3168 						                       ccols[0].XYZ[1], ccols[0].XYZ[2]);
3169 						printf("Green = XYZ %6.3f %6.3f %6.3f\n",ccols[1].XYZ[0],
3170 						                       ccols[1].XYZ[1], ccols[1].XYZ[2]);
3171 						printf("Blue  = XYZ %6.3f %6.3f %6.3f\n",ccols[2].XYZ[0],
3172 						                       ccols[2].XYZ[1], ccols[2].XYZ[2]);
3173 					}
3174 					for (i = 0; i < 3; i++)
3175 						icmAry2Ary(rgbXYZ[i], ccols[i].XYZ);
3176 					rgbch = 1;
3177 				}
3178 				/* Do an initial set of readings to set full output mark */
3179 				if ((rv = dr->read(dr, tcols, 1, 0, 0, 1, 0, instClamp)) != 0) {
3180 					dr->del(dr);
3181 					error("display read failed with '%s'\n",disprd_err(rv));
3182 				}
3183 				if (verb) {
3184 					printf("White = XYZ %6.3f %6.3f %6.3f\n",tcols[0].XYZ[0],
3185 					                       tcols[0].XYZ[1], tcols[0].XYZ[2]);
3186 				}
3187 
3188 				/* Figure out the target white chromaticity */
3189 				if (wpx > 0.0 || wpy > 0.0) {	/* xy coordinates */
3190 					tYxy[0] = 1.0;
3191 					tYxy[1] = wpx;
3192 					tYxy[2] = wpy;
3193 				} else if (temp > 0.0) {		/* Daylight color temperature */
3194 					double XYZ[3];
3195 					if (planckian)
3196 						rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Ptemp, temp, NULL);
3197 					else
3198 						rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Dtemp, temp, NULL);
3199 					if (rv != 0)
3200 						error("Failed to compute XYZ of target color temperature %f\n",temp);
3201 					icmXYZ2Yxy(tYxy, XYZ);
3202 				} else {						/* Native white */
3203 					icmXYZ2Yxy(tYxy, tcols[0].XYZ);
3204 					nat = 1;
3205 				}
3206 
3207 				/* Figure out the target white brightness */
3208 				/* Note we're not taking the device gamut into account here */
3209 				if (tbright > 0.0) {			/* Given brightness */
3210 					tarw = tbright;
3211 //printf("~1 set tarw %f from tbright\n",tarw);
3212 				} else {						/* Native/maximum brightness */
3213 					tarw = tcols[0].XYZ[1];
3214 //printf("~1 set tarw %f from tcols[1]\n",tarw);
3215 				}
3216 
3217 				if (!nat) {	/* Target is a specified white */
3218 					printf("\nAdjust R,G & B gain to get target x,y. Press space when done.\n");
3219 					printf("   Target Br %.2f, x %.4f , y %.4f \n",
3220 					        tarw, tYxy[1],tYxy[2]);
3221 
3222 				} else {	/* Target is native white */
3223 					printf("\nAdjust R,G & B gain to desired white point. Press space when done.\n");
3224 					/* Compute the CT and delta E to white locus of target */
3225 					ct = comp_ct(&ct_de, NULL, planckian, dovct, obType, tcols[0].XYZ);
3226 					printf("  Initial Br %.2f, x %.4f , y %.4f , %c%cT %4.0fK DE 2K %4.1f\n",
3227 					        tarw, tYxy[1],tYxy[2],
3228 				            dovct ? 'V' : 'C', planckian ? 'C' : 'D', ct,ct_de);
3229 				}
3230 				for (ff = 0;; ff ^= 1) {
3231 					double dir;			/* Direction to adjust brightness */
3232 					double Yxy[3];		/* Yxy of current reading */
3233 					double rgbdir[3];	/* Direction to adjust RGB */
3234 					double rgbxdir[3];	/* Biggest to move */
3235 					double bdir, terr;
3236 					int bx = 0;
3237 
3238 					if ((rv = dr->read(dr, tcols, 1, 0, 0, 1, ' ', instClamp)) != 0) {
3239 						if (rv == 4)
3240 							break;			/* User is done with this adjustment */
3241 						dr->del(dr);
3242 						error("display read failed with '%s'\n",disprd_err(rv));
3243 					}
3244 					dir = tarw - tcols[0].XYZ[1];
3245 					if (fabs(dir) < 0.01)
3246 						dir = 0.0;
3247 
3248 					icmXYZ2Yxy(Yxy, tcols[0].XYZ);
3249 
3250 					if (!nat) {	/* Target is a specified white */
3251 						/* Compute values we need for delta E and RGB direction */
3252 						icmYxy2XYZ(tLab, tYxy);
3253 						tLab[0] /= tLab[1];
3254 						tLab[2] /= tLab[1];
3255 						tLab[1] /= tLab[1];
3256 						icmAry2XYZ(tXYZ, tLab);					/* Lab white reference */
3257 						icmXYZ2Lab(&tXYZ, tLab, tLab);			/* Target Lab */
3258 
3259 						icmAry2Ary(Lab, tcols[0].XYZ);
3260 						Lab[0] /= Lab[1];
3261 						Lab[2] /= Lab[1];
3262 						Lab[1] /= Lab[1];
3263 						icmXYZ2Lab(&tXYZ, Lab, Lab);			/* Current Lab */
3264 
3265 					} else {	/* Target is native white */
3266 						double lxyz[3];	/* Locus XYZ */
3267 						ct = comp_ct(&ct_de, lxyz, planckian, dovct, obType, tcols[0].XYZ);
3268 
3269 						icmXYZ2Yxy(tYxy, lxyz);
3270 						/* lxyz is already normalised */
3271 						icmAry2XYZ(tXYZ, lxyz);					/* Lab white reference */
3272 						if (dovct)
3273 							icmXYZ2Lab(&tXYZ, tLab, lxyz);		/* Target Lab */
3274 						else
3275 							icmXYZ21960UCS(tLab, lxyz);			/* Target UCS */
3276 
3277 						icmAry2Ary(Lab, tcols[0].XYZ);
3278 						Lab[0] /= Lab[1];
3279 						Lab[2] /= Lab[1];
3280 						Lab[1] /= Lab[1];
3281 						if (dovct)
3282 							icmXYZ2Lab(&tXYZ, Lab, Lab);		/* Current Lab */
3283 						else
3284 							icmXYZ21960UCS(Lab, Lab);			/* Current UCS */
3285 					}
3286 
3287 					/* Compute dot products */
3288 					bdir = 0.0;
3289 					for (i = 0; i < 3; i++) {
3290 						double rgbLab[3];
3291 
3292 						if (dovct)
3293 							icmXYZ2Lab(&tXYZ, rgbLab, rgbXYZ[i]);
3294 						else
3295 							icmXYZ21960UCS(rgbLab, rgbXYZ[i]);
3296 						rgbdir[i] = (tLab[1] - Lab[1]) * (rgbLab[1] - Lab[1])
3297 						          + (tLab[2] - Lab[2]) * (rgbLab[2] - Lab[2]);
3298 						rgbxdir[i] = 0.0;
3299 						if (fabs(rgbdir[i]) > fabs(bdir)) {
3300 							bdir = rgbdir[i];
3301 							bx = i;
3302 						}
3303 					}
3304 
3305 					/* See how close to the target we are */
3306 					terr = sqrt((tLab[1] - Lab[1]) * (tLab[1] - Lab[1])
3307 					          + (tLab[2] - Lab[2]) * (tLab[2] - Lab[2]));
3308 					if (terr < 0.1)
3309 						rgbdir[0] = rgbdir[1] = rgbdir[2] = 0.0;
3310 					rgbxdir[bx] = rgbdir[bx];
3311 
3312 
3313 					if (!nat) {
3314 						printf("%c%c Current Br %.2f, x %.4f%c, y %.4f%c  DE %4.1f  R%c%c G%c%c B%c%c ",
3315 						   cr_char,
3316 					       ff == 0 ? '/' : '\\',
3317 					       tcols[0].XYZ[1],
3318 					       Yxy[1],
3319 						   Yxy[1] > tYxy[1] ? '-' : Yxy[1] < tYxy[1] ? '+' : '=',
3320 					       Yxy[2],
3321 						   Yxy[2] > tYxy[2] ? '-' : Yxy[2] < tYxy[2] ? '+' : '=',
3322 					       icmCIE2K(tLab, Lab),
3323 					       rgbdir[0] < 0.0 ? '-' : rgbdir[0] > 0.0 ? '+' : '=',
3324 					       rgbxdir[0] < 0.0 ? '-' : rgbxdir[0] > 0.0 ? '+' : ' ',
3325 					       rgbdir[1] < 0.0 ? '-' : rgbdir[1] > 0.0 ? '+' : '=',
3326 					       rgbxdir[1] < 0.0 ? '-' : rgbxdir[1] > 0.0 ? '+' : ' ',
3327 					       rgbdir[2] < 0.0 ? '-' : rgbdir[2] > 0.0 ? '+' : '=',
3328 					       rgbxdir[2] < 0.0 ? '-' : rgbxdir[2] > 0.0 ? '+' : ' ');
3329 					} else {
3330 						printf("%c%c Current Br %.2f, x %.4f%c, y %.4f%c  %c%cT %4.0fK DE 2K %4.1f  R%c%c G%c%c B%c%c ",
3331 						   cr_char,
3332 					       ff == 0 ? '/' : '\\',
3333 					       tcols[0].XYZ[1],
3334 					       Yxy[1],
3335 						   Yxy[1] > tYxy[1] ? '-': Yxy[1] < tYxy[1] ? '+' : '=',
3336 					       Yxy[2],
3337 						   Yxy[2] > tYxy[2] ? '-': Yxy[2] < tYxy[2] ? '+' : '=',
3338 				           dovct ? 'V' : 'C', planckian ? 'C' : 'D', ct,ct_de,
3339 					       rgbdir[0] < 0.0 ? '-' : rgbdir[0] > 0.0 ? '+' : '=',
3340 					       rgbxdir[0] < 0.0 ? '-' : rgbxdir[0] > 0.0 ? '+' : ' ',
3341 					       rgbdir[1] < 0.0 ? '-' : rgbdir[1] > 0.0 ? '+' : '=',
3342 					       rgbxdir[1] < 0.0 ? '-' : rgbxdir[1] > 0.0 ? '+' : ' ',
3343 					       rgbdir[2] < 0.0 ? '-' : rgbdir[2] > 0.0 ? '+' : '=',
3344 					       rgbxdir[2] < 0.0 ? '-' : rgbxdir[2] > 0.0 ? '+' : ' ');
3345 					}
3346 					fflush(stdout);
3347 				}
3348 				printf("\n");
3349 
3350 			/* White level adjustment */
3351 			} else if (c == '3') {
3352 				col tcols[1] = {	/* Base set of test colors */
3353 					{ 1.0, 1.0, 1.0 }
3354 				};
3355 				int ff;
3356 				double tarw;
3357 
3358 				printf("Doing some initial measurements\n");
3359 				/* Do an initial set of readings to set full output mark */
3360 				if ((rv = dr->read(dr, tcols, 1, 0, 0, 1, 0, instClamp)) != 0) {
3361 					dr->del(dr);
3362 					error("display read failed with '%s'\n",disprd_err(rv));
3363 				}
3364 				if (verb) {
3365 					printf("White = XYZ %6.3f %6.3f %6.3f\n",tcols[0].XYZ[0],
3366 					                       tcols[0].XYZ[1], tcols[0].XYZ[2]);
3367 				}
3368 
3369 				/* Figure out the target white brightness */
3370 				/* Note we're not taking the device gamut into account here */
3371 				if (tbright > 0.0)			/* Given brightness */
3372 					tarw = tbright;
3373 				else						/* Native/maximum brightness */
3374 					tarw = tcols[0].XYZ[1];
3375 
3376 				if (tbright > 0.0) {
3377 					printf("\nAdjust CRT Contrast or LCD Brightness to get target level. Press space when done.\n");
3378 					printf("   Target %.2f\n", tarw);
3379 				} else {
3380 					printf("\nAdjust CRT Contrast or LCD Brightness to desired level. Press space when done.\n");
3381 					printf("  Initial %.2f\n", tarw);
3382 				}
3383 				for (ff = 0;; ff ^= 1) {
3384 					double dir;			/* Direction to adjust brightness */
3385 
3386 					if ((rv = dr->read(dr, tcols, 1, 0, 0, 1, ' ', instClamp)) != 0) {
3387 						if (rv == 4)
3388 							break;			/* User is done with this adjustment */
3389 						dr->del(dr);
3390 						error("display read failed with '%s'\n",disprd_err(rv));
3391 					}
3392 					dir = tarw - tcols[0].XYZ[1];
3393 					if (fabs(dir) < 0.01)
3394 						dir = 0.0;
3395 
3396 					if (tbright > 0.0)
3397 						printf("%c%c Current %.2f  %c",
3398 						   cr_char,
3399 					       ff == 0 ? '/' : '\\',
3400 					       tcols[0].XYZ[1],
3401 					       dir < 0.0 ? '-' : dir > 0.0 ? '+' : '=');
3402 					else
3403 						printf("%c%c Current %.2f   ",
3404 						   cr_char,
3405 					       ff == 0 ? '/' : '\\',
3406 					       tcols[0].XYZ[1]);
3407 					fflush(stdout);
3408 				}
3409 				printf("\n");
3410 
3411 			/* Black point adjustment */
3412 			} else if (c == '4') {
3413 				col tcols[3] = {	/* Base set of test colors */
3414 					{ 0.0, 0.0, 0.0 },
3415 					{ 0.5, 0.5, 0.5 },		/* And 1% values */
3416 					{ 1.0, 1.0, 1.0 }
3417 				};
3418 				int ff;
3419 				double tYxy[3];				/* Target white chromaticities */
3420 				icmXYZNumber tXYZ;			/* Target white as XYZ */
3421 				double tLab[3];				/* Target white as Lab or UCS */
3422 				double mgamma, tar1, dev1;
3423 				double Lab[3];				/* Last measured point Lab */
3424 
3425 				printf("Doing some initial measurements\n");
3426 
3427 				if (rgbch == 0) {	/* Figure the RGB chromaticities */
3428 					col ccols[3] = {
3429 						{ 1.0, 0.0, 0.0 },
3430 						{ 0.0, 1.0, 0.0 },
3431 						{ 0.0, 0.0, 1.0 }
3432 					};
3433 					if ((rv = dr->read(dr, ccols, 3, 0, 0, 1, 0, instClamp)) != 0) {
3434 						dr->del(dr);
3435 						error("display read failed with '%s'\n",disprd_err(rv));
3436 					}
3437 					if (verb) {
3438 						printf("Red   = XYZ %6.3f %6.3f %6.3f\n",ccols[0].XYZ[0],
3439 						                       ccols[0].XYZ[1], ccols[0].XYZ[2]);
3440 						printf("Green = XYZ %6.3f %6.3f %6.3f\n",ccols[1].XYZ[0],
3441 						                       ccols[1].XYZ[1], ccols[1].XYZ[2]);
3442 						printf("Blue  = XYZ %6.3f %6.3f %6.3f\n",ccols[2].XYZ[0],
3443 						                       ccols[2].XYZ[1], ccols[2].XYZ[2]);
3444 					}
3445 					for (i = 0; i < 3; i++)
3446 						icmAry2Ary(rgbXYZ[i], ccols[i].XYZ);
3447 					rgbch = 1;
3448 				}
3449 				/* Do an initial set of readings to set 1% output mark */
3450 				if ((rv = dr->read(dr, tcols, 3, 0, 0, 1, 0, instClamp)) != 0) {
3451 					dr->del(dr);
3452 					error("display read failed with '%s'\n",disprd_err(rv));
3453 				}
3454 				if (verb) {
3455 					printf("Black = XYZ %6.4f %6.4f %6.4f\n",tcols[0].XYZ[0],
3456 					                                         tcols[0].XYZ[1], tcols[0].XYZ[2]);
3457 					printf("Grey  = XYZ %6.3f %6.3f %6.3f\n",tcols[1].XYZ[0],
3458 					                                         tcols[1].XYZ[1], tcols[1].XYZ[2]);
3459 					printf("White = XYZ %6.3f %6.3f %6.3f\n",tcols[2].XYZ[0],
3460 					                                         tcols[2].XYZ[1], tcols[2].XYZ[2]);
3461 				}
3462 
3463 				/* Advertised Gamma - Gross curve shape */
3464 				mgamma = pop_gamma(tcols[0].XYZ[1], tcols[1].XYZ[1], tcols[2].XYZ[1]);
3465 
3466 				dev1 = pow(0.01, 1.0/mgamma);
3467 				tcols[1].r = tcols[1].g = tcols[1].b = dev1;
3468 				tar1 = 0.01 * tcols[2].XYZ[1];
3469 
3470 				/* Figure out the target white chromaticity */
3471 				if (wpx > 0.0 || wpy > 0.0) {	/* xy coordinates */
3472 					tYxy[0] = 1.0;
3473 					tYxy[1] = wpx;
3474 					tYxy[2] = wpy;
3475 
3476 				} else if (temp > 0.0) {		/* Daylight color temperature */
3477 					double XYZ[3];
3478 					if (planckian)
3479 						rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Ptemp, temp, NULL);
3480 					else
3481 						rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Dtemp, temp, NULL);
3482 					if (rv != 0)
3483 						error("Failed to compute XYZ of target color temperature %f\n",temp);
3484 					icmXYZ2Yxy(tYxy, XYZ);
3485 				} else {						/* Native white */
3486 					icmXYZ2Yxy(tYxy, tcols[2].XYZ);
3487 				}
3488 
3489 				printf("\nAdjust R,G & B offsets to get target x,y. Press space when done.\n");
3490 				printf("   Target Br %.4f, x %.4f , y %.4f \n", tar1, tYxy[1],tYxy[2]);
3491 				for (ff = 0;; ff ^= 1) {
3492 					double dir;			/* Direction to adjust brightness */
3493 					double sv[3], val1;				/* Scaled values */
3494 					double Yxy[3];		/* Yxy of current reading */
3495 					double rgbdir[3];	/* Direction to adjust RGB */
3496 					double rgbxdir[3];	/* Biggest to move */
3497 					double bdir, terr;
3498 					int bx = 0;
3499 
3500 					if ((rv = dr->read(dr, tcols+1, 1, 0, 0, 1, ' ', instClamp)) != 0) {
3501 						if (rv == 4)
3502 							break;			/* User is done with this adjustment */
3503 						dr->del(dr);
3504 						error("display read failed with '%s'\n",disprd_err(rv));
3505 					}
3506 
3507 					/* Scale 1% values by ratio of Y to white XYZ */
3508 					sv[0] = tcols[1].XYZ[0] * tcols[2].XYZ[1]/tcols[2].XYZ[0];
3509 					sv[1] = tcols[1].XYZ[1];
3510 					sv[2] = tcols[1].XYZ[2] * tcols[2].XYZ[1]/tcols[2].XYZ[2];
3511 					val1 = sv[1];
3512 					if (sv[0] > val1)
3513 						val1 = sv[0];
3514 					if (sv[2] > val1)
3515 						val1 = sv[2];
3516 
3517 					/* Compute 1% direction */
3518 					dir = tar1 - val1;
3519 					if (fabs(dir) < 0.01)
3520 						dir = 0.0;
3521 
3522 					/* Compute numbers for black point error and direction */
3523 					icmYxy2XYZ(tLab, tYxy);
3524 					tLab[0] /= tLab[1];
3525 					tLab[2] /= tLab[1];
3526 					tLab[1] /= tLab[1];
3527 					icmAry2XYZ(tXYZ, tLab);				/* Lab white reference */
3528 					icmXYZ2Lab(&tXYZ, tLab, tLab);
3529 
3530 					icmXYZ2Yxy(Yxy, tcols[1].XYZ);
3531 					icmAry2Ary(Lab, tcols[1].XYZ);
3532 					Lab[0] /= Lab[1];
3533 					Lab[2] /= Lab[1];
3534 					Lab[1] /= Lab[1];
3535 					icmXYZ2Lab(&tXYZ, Lab, Lab);
3536 
3537 					/* Compute dot products */
3538 					bdir = 0.0;
3539 					for (i = 0; i < 3; i++) {
3540 						double rgbLab[3];
3541 
3542 						icmXYZ2Lab(&tXYZ, rgbLab, rgbXYZ[i]);
3543 						rgbdir[i] = (tLab[1] - Lab[1]) * (rgbLab[1] - Lab[1])
3544 						          + (tLab[2] - Lab[2]) * (rgbLab[2] - Lab[2]);
3545 						rgbxdir[i] = 0.0;
3546 						if (fabs(rgbdir[i]) > fabs(bdir)) {
3547 							bdir = rgbdir[i];
3548 							bx = i;
3549 						}
3550 					}
3551 
3552 					/* See how close to the target we are */
3553 					terr = sqrt((tLab[1] - Lab[1]) * (tLab[1] - Lab[1])
3554 					          + (tLab[2] - Lab[2]) * (tLab[2] - Lab[2]));
3555 					if (terr < 0.1)
3556 						rgbdir[0] = rgbdir[1] = rgbdir[2] = 0.0;
3557 					rgbxdir[bx] = rgbdir[bx];
3558 
3559 			 		printf("%c%c Current Br %.4f, x %.4f%c, y %.4f%c  DE %4.1f  R%c%c G%c%c B%c%c ",
3560 					       cr_char,
3561 					       ff == 0 ? '/' : '\\',
3562 					       val1,
3563 					       Yxy[1],
3564 						   Yxy[1] > tYxy[1] ? '-': Yxy[1] < tYxy[1] ? '+' : '=',
3565 					       Yxy[2],
3566 						   Yxy[2] > tYxy[2] ? '-': Yxy[2] < tYxy[2] ? '+' : '=',
3567 					       icmCIE2K(tLab, Lab),
3568 					       rgbdir[0] < 0.0 ? '-' : rgbdir[0] > 0.0 ? '+' : '=',
3569 					       rgbxdir[0] < 0.0 ? '-' : rgbxdir[0] > 0.0 ? '+' : ' ',
3570 					       rgbdir[1] < 0.0 ? '-' : rgbdir[1] > 0.0 ? '+' : '=',
3571 					       rgbxdir[1] < 0.0 ? '-' : rgbxdir[1] > 0.0 ? '+' : ' ',
3572 					       rgbdir[2] < 0.0 ? '-' : rgbdir[2] > 0.0 ? '+' : '=',
3573 					       rgbxdir[2] < 0.0 ? '-' : rgbxdir[2] > 0.0 ? '+' : ' ');
3574 					fflush(stdout);
3575 				}
3576 				printf("\n");
3577 
3578 			/* Report on how well we current meet the targets */
3579 			} else if (c == '5') {
3580 				int nat = 0;		/* NZ if using native white as target */
3581 				col tcols[4] = {	/* Set of test colors */
3582 					{ 0.0, 0.0, 0.0 },
3583 					{ 0.5, 0.5, 0.5 },
3584 					{ 1.0, 1.0, 1.0 },
3585 					{ 0.0, 0.0, 0.0 }	/* 1% test value */
3586 				};
3587 				double tYxy[3];				/* Target white chromaticities */
3588 				double sv[3], val1;			/* Scaled values */
3589 				double mgamma, tarw, tar1, dev1, tarh;
3590 				double gooff;				/* Aproximate output offset needed */
3591 				icmXYZNumber tXYZ;
3592 				double tLab[3], wYxy[3], wLab[3], bYxy[3], bLab[3];
3593 				double ct, ct_de;			/* Color temperature & delta E to white locus */
3594 
3595 				printf("Doing check measurements\n");
3596 
3597 				/* Do an initial set of readings to set 1% output mark */
3598 				if ((rv = dr->read(dr, tcols, 3, 0, 0, 1, 0, instClamp)) != 0) {
3599 					dr->del(dr);
3600 					error("display read failed with '%s'\n",disprd_err(rv));
3601 				}
3602 				if (verb) {
3603 					printf("Black = XYZ %6.4f %6.4f %6.4f\n",tcols[0].XYZ[0],
3604 					                                         tcols[0].XYZ[1], tcols[0].XYZ[2]);
3605 					printf("Grey  = XYZ %6.3f %6.3f %6.3f\n",tcols[1].XYZ[0],
3606 					                                         tcols[1].XYZ[1], tcols[1].XYZ[2]);
3607 					printf("White = XYZ %6.3f %6.3f %6.3f\n",tcols[2].XYZ[0],
3608 					                                         tcols[2].XYZ[1], tcols[2].XYZ[2]);
3609 				}
3610 
3611 				/* Approximate Gamma - use the gross curve shape for robustness */
3612 				mgamma = pop_gamma(tcols[0].XYZ[1], tcols[1].XYZ[1], tcols[2].XYZ[1]);
3613 
3614 				dev1 = pow(0.01, 1.0/mgamma);
3615 				tcols[3].r = tcols[3].g = tcols[3].b = dev1;
3616 				tar1 = 0.01 * tcols[2].XYZ[1];
3617 
3618 				/* Read the 1% value */
3619 				if ((rv = dr->read(dr, tcols+3, 1, 0, 0, 1, 0, instClamp)) != 0) {
3620 					dr->del(dr);
3621 					error("display read failed with '%s'\n",disprd_err(rv));
3622 				}
3623 				if (verb) {
3624 					printf("1%%    = XYZ %6.3f %6.3f %6.3f\n",tcols[3].XYZ[0],
3625 					                                         tcols[3].XYZ[1], tcols[3].XYZ[2]);
3626 				}
3627 
3628 				/* Scale 1% values by ratio of Y to white XYZ */
3629 				/* (Note we're assuming -k1 here, which may not be true...) */
3630 				sv[0] = tcols[3].XYZ[0] * tcols[2].XYZ[1]/tcols[2].XYZ[0];
3631 				sv[1] = tcols[3].XYZ[1];
3632 				sv[2] = tcols[3].XYZ[2] * tcols[2].XYZ[1]/tcols[2].XYZ[2];
3633 				val1 = sv[1];
3634 				if (sv[0] > val1)
3635 					val1 = sv[0];
3636 				if (sv[2] > val1)
3637 					val1 = sv[2];
3638 
3639 				/* Figure out the target white brightness */
3640 				/* Note we're not taking the device gamut into account here */
3641 				if (tbright > 0.0)			/* Given brightness */
3642 					tarw = tbright;
3643 				else						/* Native/maximum brightness */
3644 					tarw = tcols[2].XYZ[1];
3645 
3646 				/* Figure out the target white chromaticity */
3647 				if (wpx > 0.0 || wpy > 0.0) {	/* xy coordinates */
3648 					tYxy[0] = 1.0;
3649 					tYxy[1] = wpx;
3650 					tYxy[2] = wpy;
3651 
3652 				} else if (temp > 0.0) {		/* Daylight color temperature */
3653 					double XYZ[3];
3654 					if (planckian)
3655 						rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Ptemp, temp, NULL);
3656 					else
3657 						rv = icx_ill_sp2XYZ(XYZ, icxOT_default, NULL, icxIT_Dtemp, temp, NULL);
3658 					if (rv != 0)
3659 						error("Failed to compute XYZ of target color temperature %f\n",temp);
3660 					icmXYZ2Yxy(tYxy, XYZ);
3661 				} else {						/* Native white */
3662 					icmXYZ2Yxy(tYxy, tcols[2].XYZ);
3663 					nat = 1;
3664 				}
3665 
3666 				/* Figure out the target 50% device output value */
3667 				gooff = tcols[0].XYZ[1]/tcols[2].XYZ[1];	/* Aprox. normed black output offset */
3668 
3669 				/* Use tech_gamma() to do the hard work */
3670 				tarh = tech_gamma(&x, NULL, NULL, NULL, egamma, gamma, gooff);
3671 
3672 				/* Convert from Y fraction to absolute Y */
3673 				tarh = tarh * tcols[2].XYZ[1];
3674 
3675 				/* Compute various white point values */
3676 				icmYxy2XYZ(tLab, tYxy);
3677 				tLab[0] /= tLab[1];
3678 				tLab[2] /= tLab[1];
3679 				tLab[1] /= tLab[1];
3680 				icmAry2XYZ(tXYZ, tLab);
3681 				icmXYZ2Lab(&tXYZ, tLab, tLab);
3682 
3683 				icmXYZ2Yxy(wYxy, tcols[2].XYZ);
3684 				icmAry2Ary(wLab, tcols[2].XYZ);
3685 				wLab[0] /= wLab[1];
3686 				wLab[2] /= wLab[1];
3687 				wLab[1] /= wLab[1];
3688 				icmXYZ2Lab(&tXYZ, wLab, wLab);
3689 
3690 				icmXYZ2Yxy(bYxy, tcols[3].XYZ);
3691 				icmAry2Ary(bLab, tcols[3].XYZ);
3692 				bLab[0] /= bLab[1];
3693 				bLab[2] /= bLab[1];
3694 				bLab[1] /= bLab[1];
3695 				icmXYZ2Lab(&tXYZ, bLab, bLab);
3696 
3697 				/* And color temperature */
3698 				ct = comp_ct(&ct_de, NULL, planckian, dovct, obType, tcols[2].XYZ);
3699 
3700 				printf("\n");
3701 
3702 				if (tbright > 0.0)			/* Given brightness */
3703 					printf("  Target Brightness = %.3f, Current = %.3f, error = % .1f%%\n",
3704 				       tarw, tcols[2].XYZ[1],
3705 				       100.0 * (tcols[2].XYZ[1] - tarw)/tarw);
3706 				else
3707 					printf("  Current Brightness = %.2f\n", tcols[2].XYZ[1]);
3708 
3709 				printf("  Target 50%% Level  = %.3f, Current = %.3f (Aprox. Gamma %.2f), error = % .1f%%\n",
3710 				       tarh, tcols[1].XYZ[1],
3711 				       mgamma,
3712 				       100.0 * (tcols[1].XYZ[1] - tarh)/tarw);
3713 
3714 				printf("  Target Near Black = %.4f, Current = %.4f, error = % .1f%%\n",
3715 				       tar1, val1,
3716 				       100.0 * (val1 - tar1)/tarw);
3717 
3718 				if (!nat)
3719 					printf("  Target white = x %.4f, y %.4f, Current = x %.4f, y %.4f, error = %5.2f DE\n",
3720 				       tYxy[1], tYxy[2], wYxy[1], wYxy[2], icmCIE2K(tLab, wLab));
3721 				else
3722 					printf("  Current white = x %.4f, y %.4f, %c%cT %4.0fK DE 2K %4.1f\n",
3723 				       wYxy[1], wYxy[2], dovct ? 'V' : 'C', planckian ? 'C' : 'D', ct,ct_de);
3724 
3725 				printf("  Target black = x %.4f, y %.4f, Current = x %.4f, y %.4f, error = %5.2f DE\n",
3726 				       tYxy[1], tYxy[2], bYxy[1], bYxy[2], icmCIE2K(tLab, bLab));
3727 
3728 
3729 			/* Measure and set ambient for viewing condition adjustment */
3730 			} else if (c == '6') {
3731 				if ((rv = dr->ambient(dr, &ambient, 1)) != 0) {
3732 					if (rv == 8) {
3733 						printf("Instrument doesn't have an ambient reading capability\n");
3734 					} else {
3735 						dr->del(dr);
3736 						error("ambient measure failed with '%s'\n",disprd_err(rv));
3737 					}
3738 				} else {
3739 					printf("Measured ambient level = %.1f Lux\n",ambient);
3740 				}
3741 
3742 			} else if (c == '7') {
3743 				if (!verb) {		/* Tell user command has been accepted */
3744 					if (verify == 2)
3745 						printf("Commencing display verification\n");
3746 					else
3747 						printf("Commencing display calibration\n");
3748 				}
3749 				break;
3750 			} else if (c == '8' || c == 0x03 || c == 0x1b) {
3751 				printf("Exiting\n");
3752 				dr->del(dr);
3753 				exit(0);
3754 			}
3755 		}
3756 
3757 		/* Make sure drift comp. is set to the command line options */
3758 		dr->change_drift_comp(dr, bdrift, wdrift);
3759 	}
3760 
3761 	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
3762 
3763 	/* Take a small number of readings, and compute basic */
3764 	/* informations such as black & white, white target, */
3765 	/* aproximate matrix based display forward and reverse model. */
3766 	/* If bkcorrect is auto, determine a level. */
3767 
3768 	/* Read the base test set */
3769 	{
3770 		double mrd[3];		/* Number for matrix */
3771 		double mgn[3];
3772 		double mbl[3];
3773 		double mwh[3];
3774 		ramdac *or = NULL;
3775 
3776 		col base[9] = {	/* Base set of test colors */
3777 			{ 0.0, 0.0, 0.0 },		/* 0 - Black */
3778 			{ 1.0, 0.0, 0.0 },		/* 1 - Red */
3779 			{ 1.0, 1.0, 1.0 },		/* 2 - White */
3780 			{ 0.0, 0.0, 0.0 },		/* 3 - Black */
3781 			{ 0.0, 1.0, 0.0 },		/* 4 - Green */
3782 			{ 0.0, 0.0, 0.0 },		/* 5 - Black */
3783 			{ 0.0, 0.0, 1.0 },		/* 6 - Blue */
3784 			{ 1.0, 1.0, 1.0 },		/* 7 - White */
3785 			{ 0.0, 0.0, 0.0 }		/* 8 - Black */
3786 		};
3787 		int ix_k1 = 0;
3788 		int ix_r  = 1;
3789 		int ix_w1 = 2;
3790 		int ix_k2 = 3;
3791 		int ix_g  = 4;
3792 		int ix_k3 = 5;
3793 		int ix_b  = 6;
3794 		int ix_w2 = 7;
3795 		int ix_k4 = 8;
3796 
3797 		if (verb) {
3798 			if (verify == 2)
3799 				printf("Commencing display verification\n");
3800 			else
3801 				printf("Commencing display calibration\n");
3802 		}
3803 
3804 		/* Switch to native for this, so the black calc is realistic. */
3805 		/* (Should we really get black aim from previous .cal though ???) */
3806 		if (verify == 2) {
3807 			if (fake)
3808 				error("Can't verify against current curves using fake device");
3809 
3810 			if ((or = dr->dw->get_ramdac(dr->dw)) != NULL) {
3811 				ramdac *r;
3812 				if (verb) printf("Switching to native response for base measurements\n");
3813 				r = or->clone(or);
3814 				r->setlin(r);
3815 				dr->dw->set_ramdac(dr->dw, r, 0);
3816 				r->del(r);
3817 			}
3818 		}
3819 
3820 		/* Read the patches without clamping */
3821 		if ((rv = dr->read(dr, base, 9, 1, 9, 1, 0, instNoClamp)) != 0) {
3822 			dr->del(dr);
3823 			error("display read failed with '%s'\n",disprd_err(rv));
3824 		}
3825 
3826 		/* Restore the cal we're verifying */
3827 		if (verify == 2 && or != NULL) {
3828 			if (verb) printf("Switching back to calibration being verified\n");
3829 			dr->dw->set_ramdac(dr->dw, or, 0);
3830 			or->del(or);
3831 		}
3832 
3833 		if (base[ix_k1].XYZ_v == 0) {
3834 			dr->del(dr);
3835 			error("Failed to get an XYZ value from the instrument!\n");
3836 		}
3837 
3838 		if (verb >= 3) {
3839 			for (i = 0; i < 9; i++)
3840 				printf("Meas %d XYZ = %f %f %f\n",i,base[i].XYZ[0], base[i].XYZ[1], base[i].XYZ[2]);
3841 		}
3842 
3843 		/* Average black relative from 4 readings */
3844 		x.bk[0] = 0.25 * (base[ix_k1].XYZ[0] + base[ix_k2].XYZ[0]
3845 		                + base[ix_k3].XYZ[0] + base[ix_k4].XYZ[0]);
3846 		x.bk[1] = 0.25 * (base[ix_k1].XYZ[1] + base[ix_k2].XYZ[1]
3847 		                + base[ix_k3].XYZ[1] + base[ix_k4].XYZ[1]);
3848 		x.bk[2] = 0.25 * (base[ix_k1].XYZ[2] + base[ix_k2].XYZ[2]
3849 		                + base[ix_k3].XYZ[2] + base[ix_k4].XYZ[2]);
3850 		icmClamp3(x.bk, x.bk);	/* And clamp them */
3851 
3852 		/* Average white reading from 2 readings */
3853 		base[ix_w1].XYZ[0] = 0.5 * (base[ix_w1].XYZ[0] + base[ix_w2].XYZ[0]);
3854 		base[ix_w1].XYZ[1] = 0.5 * (base[ix_w1].XYZ[1] + base[ix_w2].XYZ[1]);
3855 		base[ix_w1].XYZ[2] = 0.5 * (base[ix_w1].XYZ[2] + base[ix_w2].XYZ[2]);
3856 
3857 		for (i = 0; i < 9; i++)
3858 			icmClamp3(base[i].XYZ, base[i].XYZ);
3859 
3860 		/* Copy other readings into place */
3861 		dispLum = base[ix_w1].XYZ[1];				/* White Y */
3862 		icmAry2Ary(x.wh, base[ix_w1].XYZ);
3863 		icmAry2XYZ(x.twN, x.wh);	/* Use this as Lab reference white until we establish target */
3864 
3865 		icmAry2Ary(mrd, base[ix_r].XYZ);
3866 		icmAry2Ary(mgn, base[ix_g].XYZ);
3867 		icmAry2Ary(mbl, base[ix_b].XYZ);
3868 		icmAry2Ary(mwh, base[ix_w1].XYZ);
3869 
3870 		if (verb) {
3871 			printf("Black = XYZ %6.4f %6.4f %6.4f\n",x.bk[0],x.bk[1],x.bk[2]);
3872 			printf("Red   = XYZ %6.3f %6.3f %6.3f\n",base[ix_r].XYZ[0], base[ix_r].XYZ[1], base[ix_r].XYZ[2]);
3873 			printf("Green = XYZ %6.3f %6.3f %6.3f\n",base[ix_g].XYZ[0], base[ix_g].XYZ[1], base[ix_g].XYZ[2]);
3874 			printf("Blue  = XYZ %6.3f %6.3f %6.3f\n",base[ix_b].XYZ[0], base[ix_b].XYZ[1], base[ix_b].XYZ[2]);
3875 			printf("White = XYZ %6.3f %6.3f %6.3f\n",base[ix_w1].XYZ[0], base[ix_w1].XYZ[1], base[ix_w1].XYZ[2]);
3876 		}
3877 
3878 		/* Setup forward matrix */
3879 		if (icmRGBXYZprim2matrix(mrd, mgn, mbl, mwh, x.fm)) {
3880 			dr->del(dr);
3881 			error("Aprox. fwd matrix unexpectedly singular\n");
3882 		}
3883 
3884 #ifdef DEBUG
3885 		if (verb) {
3886 			printf("Forward matrix is:\n");
3887 			printf("%f %f %f\n", x.fm[0][0], x.fm[0][1], x.fm[0][2]);
3888 			printf("%f %f %f\n", x.fm[1][0], x.fm[1][1], x.fm[1][2]);
3889 			printf("%f %f %f\n", x.fm[2][0], x.fm[2][1], x.fm[2][2]);
3890 		}
3891 #endif
3892 
3893 		/* Compute bwd matrix */
3894 		if (icmInverse3x3(x.bm, x.fm)) {
3895 			dr->del(dr);
3896 			error("Inverting aprox. fwd matrix failed");
3897 		}
3898 
3899 		/* Decide on the level of black correction. */
3900 		if (bkcorrect < 0.0) {
3901 			double rat;
3902 
3903 			/* rat is 0 for displays with a good black, */
3904 			/* and 1 for displays with a bad black level. */
3905 			/* (Not sure if this should be scaled by the white, */
3906 			/*  making it contrast ratio sensitive?) */
3907 			rat = (x.bk[1] - 0.02)/(0.3 - 0.02);
3908 			if (rat < 0.0)
3909 				rat = 0.0;
3910 			else if (rat > 1.0)
3911 				rat = 1.0;
3912 			/* Make transition more perceptual */
3913 			rat = sqrt(rat);
3914 			bkcorrect = 1.0 - rat;
3915 			if (verb)
3916 				printf("Automatic black point hue correction level = %1.2f\n", bkcorrect);
3917 		}
3918 	}
3919 
3920 	/* Now do some more readings, to compute the basic per channel */
3921 	/* transfer characteristics, and then a device model. */
3922 	if (verify != 2 && !doupdate) {
3923 		col *cols;				/* Read 4 x isteps patches from display */
3924 		sxyz *asrgb[4];			/* samples for r, g, b & w */
3925 
3926 		if ((cols = (col *)malloc(isteps * 4 * sizeof(col))) == NULL) {
3927 			dr->del(dr);
3928 			error("Malloc of array of readings failed");
3929 		}
3930 		for (j = 0; j < 4; j++) {
3931 			if ((asrgb[j] = (sxyz *)malloc(isteps * sizeof(sxyz))) == NULL) {
3932 				free(cols);
3933 				dr->del(dr);
3934 				error("Malloc of array of readings failed");
3935 			}
3936 		}
3937 
3938 		/* Set the device colors to read */
3939 		for (i = 0; i < isteps; i++) {
3940 			double vv;
3941 
3942 #if defined(__APPLE__) && defined(__POWERPC__)
3943 			gcc_bug_fix(i);
3944 #endif
3945 			vv = i/(isteps - 1.0);
3946 			vv = pow(vv, MOD_DIST_POW);
3947 			for (j = 0; j < 4; j++) {
3948 				cols[i * 4 + j].r = cols[i * 4 + j].g = cols[i * 4 + j].b = 0.0;
3949 				if (j == 0)
3950 					cols[i * 4 + j].r = vv;
3951 				else if (j == 1)
3952 					cols[i * 4 + j].g = vv;
3953 				else if (j == 2)
3954 					cols[i * 4 + j].b = vv;
3955 				else
3956 					cols[i * 4 + j].r = cols[i * 4 + j].g = cols[i * 4 + j].b = vv;
3957 			}
3958 		}
3959 
3960 		/* Read the patches */
3961 		if ((rv = dr->read(dr, cols, isteps * 4, 1, isteps * 4, 1, 0, instClamp)) != 0) {
3962 			free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
3963 			dr->del(dr);
3964 			error("display read failed with '%s'\n",disprd_err(rv));
3965 		}
3966 
3967 		/* Transfer readings to asrgb[] */
3968 		for (i = 0; i < isteps; i++) {
3969 			double vv =  cols[i * 4 + 0].r;
3970 			for (j = 0; j < 4; j++) {
3971 //printf("~1 R = %f, G = %f, B = %f, XYZ = %f %f %f\n",
3972 //cols[i * 4 + j].r, cols[i * 4 + j].g, cols[i * 4 + j].b, cols[i * 4 + j].XYZ[0], cols[i * 4 + j].XYZ[1], cols[i * 4 + j].XYZ[2]);
3973 				asrgb[j][i].v = vv;
3974 				asrgb[j][i].xyz[0] = cols[i * 4 + j].XYZ[0];
3975 				asrgb[j][i].xyz[1] = cols[i * 4 + j].XYZ[1];
3976 				asrgb[j][i].xyz[2] = cols[i * 4 + j].XYZ[2];
3977 			}
3978 		}
3979 
3980 		/* Convert RGB channel samples to curves */
3981 		{
3982 			mcvco *sdv;		/* Points used to create cvs[], RGB */
3983 			double blrgb[3];
3984 			double *op;		/* Parameters to optimise */
3985 			double *sa;		/* Search area */
3986 			double re;		/* Residual error */
3987 
3988 			/* Transform measured black back to linearised RGB values */
3989 			icmMulBy3x3(blrgb, x.bm, x.bk);
3990 //printf("~1 model black should be %f %f %f\n", x.bk[0], x.bk[1], x.bk[2]);
3991 //printf("~1 linearised RGB should be %f %f %f\n", blrgb[0], blrgb[1], blrgb[2]);
3992 
3993 			if ((sdv = malloc(sizeof(mcvco) * isteps)) == NULL) {
3994 				free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
3995 				dr->del(dr);
3996 				error("Malloc of scattered data points failed");
3997 			}
3998 			for (k = 0; k < 3; k++) {	/* Create the model curves */
3999 				for (i = 0; i < isteps; i++) {
4000 					sdv[i].p = asrgb[k][i].v;
4001 					sdv[i].v = ICMNORM3(asrgb[k][i].xyz);
4002 					sdv[i].w = 1.0;
4003 //printf("~1 chan %d, entry %d, p = %f, v = %f from XYZ %f %f %f\n",
4004 //k,i,x.sdv[k][i].p,x.sdv[k][i].v, asrgb[k][i].xyz[0], asrgb[k][i].xyz[1], asrgb[k][i].xyz[2]);
4005 				}
4006 				if ((x.dcvs[k] = new_mcv()) == NULL) {
4007 					free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
4008 					dr->del(dr);
4009 					error("new_mcv x.dcvs[%d] failed",k);
4010 				}
4011 				x.dcvs[k]->fit(x.dcvs[k], 0, fitord, sdv, isteps, 5.0);
4012 
4013 				/* Scale the whole curve so the output is scaled to 1.0 */
4014 				x.dcvs[k]->force_scale(x.dcvs[k], 1.0);
4015 
4016 				/* Force curves to produce this lrgb for 0.0 */
4017 				x.dcvs[k]->force_0(x.dcvs[k], blrgb[k]);
4018 			}
4019 			free(sdv);
4020 
4021 #ifdef OPTIMIZE_MODEL
4022 			/* Setup list of reference points ready for optimisation */
4023 			x.nrp = 4 * isteps;
4024 			if ((x.rp = (optref *)malloc(sizeof(optref) * x.nrp)) == NULL) {
4025 				free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
4026 				dr->del(dr);
4027 				error("Malloc of measurement reference points failed");
4028 			}
4029 			for (k = 0; k < 4; k++) {
4030 				for (i = 0; i < isteps; i++) {
4031 					int ii = k * isteps + i;
4032 					double v[3];
4033 
4034 					v[0] = v[1] = v[2] = 0.0;
4035 					if (k == 0)
4036 						v[k] = asrgb[k][i].v;
4037 					else if (k == 1)
4038 						v[k] = asrgb[k][i].v;
4039 					else if (k == 2)
4040 						v[k] = asrgb[k][i].v;
4041 					else
4042 						v[0] = v[1] = v[2] = asrgb[k][i].v;
4043 					icmAry2Ary(x.rp[ii].dev, v);
4044 					icmXYZ2Lab(&x.twN, x.rp[ii].lab, asrgb[k][i].xyz);
4045 					if (k == 3)		/* White */
4046 						x.rp[ii].w = 0.5;
4047 					else
4048 						x.rp[ii].w = 0.16667;
4049 				}
4050 			}
4051 
4052 			/* Get parameters and setup for optimisation */
4053 			op = dev_get_params(&x);
4054 			if ((sa = malloc(x.np * sizeof(double))) == NULL) {
4055 				free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
4056 				dr->del(dr);
4057 				error("Malloc of scattered data points failed");
4058 			}
4059 
4060 			for (i = 0; i < x.np; i++)
4061 				sa[i] = 0.1;
4062 
4063 			/* Do optimisation */
4064 #ifdef NEVER
4065 			if (powell(&re, x.np, op, sa, 1e-5, 3000, dev_opt_func, (void *)&x, NULL, NULL) != 0) {
4066 				free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
4067 				dr->del(dr);
4068 				error("Model powell failed, re = %f",re);
4069 			}
4070 #else
4071 			if (conjgrad(&re, x.np, op, sa, 1e-5, 3000,
4072 			                         dev_opt_func, dev_dopt_func, (void *)&x, NULL, NULL) != 0) {
4073 				if (re > 1e-2) {
4074 					free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
4075 					dr->del(dr);
4076 					error("Model conjgrad failed, residual error = %f",re);
4077 				} else
4078 					warning("Model conjgrad failed, residual error = %f",re);
4079 			}
4080 #endif
4081 
4082 			/* Put optimised parameters in place */
4083 			dev_put_params(&x, op);
4084 
4085 			free(x.rp);
4086 			x.rp = NULL;
4087 			x.nrp = 0;
4088 			free(x.dtin_iv);		/* Free temporary arrays */
4089 			x.dtin_iv = NULL;
4090 			free(sa);
4091 			free(op);
4092 #endif /* OPTIMIZE_MODEL */
4093 		}
4094 
4095 #ifdef DEBUG_PLOT
4096 		/* Plot the current calc curves */
4097 		{
4098 			#define	XRES 256
4099 			double xx[XRES];
4100 			double yy[3][XRES];
4101 			double xyz[3];
4102 			for (i = 0; i < XRES; i++) {
4103 				xx[i] = i/(XRES-1.0);
4104 				for (j = 0; j < 3; j++)
4105 					yy[j][i] = x.dcvs[j]->interp(x.dcvs[j], xx[i]);
4106 			}
4107 			printf("Channel curves\n");
4108 			do_plot(xx,yy[0],yy[1],yy[2],XRES);
4109 			#undef XRES
4110 		}
4111 #endif
4112 
4113 		/* We're done with cols[] and asrgb[] */
4114 		free(cols); free(asrgb[0]); free(asrgb[1]); free(asrgb[2]); free(asrgb[3]);
4115 	}
4116 
4117 #ifdef CHECK_MODEL
4118 	/* Check how well our fwd model agrees with the device */
4119 	if (verify != 2) {
4120 		col set[3];					/* Variable to read up to 3 values from the display */
4121 		int nn = 27;
4122 		double alab[3], mxyz[3], mlab[3];	/* Actual and model Lab */
4123 		double mnerr;		/* Maximum neutral error */
4124 		double mnv;			/* Value where maximum error is */
4125 		double anerr;		/* Average neutral error */
4126 
4127 		mnerr = anerr = 0.0;
4128 		/* !!! Should change this to single batch to work better with drift comp. !!! */
4129 		for (i = 0; i < (nn + 3); i++) {
4130 			double vv, v[3];
4131 			double de;
4132 
4133 			if (i < nn) {
4134 				vv = i/(nn - 1.0);
4135 				vv = pow(vv, CHECK_DIST_POW);
4136 				v[0] = v[1] = v[2] = vv;
4137 				set[0].r = set[0].g = set[0].b = vv;
4138 
4139 			} else {	/* Do R, G, B */
4140 				v[0] = v[1] = v[2] = 0.0;
4141 				v[i - nn] = 1.0;
4142 				set[0].r = v[0];
4143 				set[0].g = v[1];
4144 				set[0].b = v[2];
4145 			}
4146 
4147 			if ((rv = dr->read(dr, set, 1, i+1, nn+3, 1, 0, instClamp)) != 0) {
4148 				dr->del(dr);
4149 				error("display read failed with '%s'\n",disprd_err(rv));
4150 			}
4151 			icmXYZ2Lab(&x.twN, alab, set[0].XYZ);
4152 
4153 			fwddev(&x, mxyz, v);
4154 			icmXYZ2Lab(&x.twN, mlab, mxyz);
4155 
4156 			de = icmCIE2K(mlab, alab);
4157 			if (de > mnerr) {
4158 				mnerr = de;
4159 				mnv = vv;
4160 			}
4161 			anerr += de;
4162 
4163 			printf("RGB %.3f %.3f %.3f -> XYZ %.2f %.2f %.2f, model %.2f %.2f %.2f\n",
4164 			set[0].r, set[0].g, set[0].b,
4165 			set[0].XYZ[0], set[0].XYZ[1],
4166 			set[0].XYZ[2], mxyz[0], mxyz[1], mxyz[2]);
4167 
4168 			printf("RGB %.3f %.3f %.3f -> Lab %.2f %.2f %.2f, model %.2f %.2f %.2f, DE %f\n",
4169 			set[0].r, set[0].g, set[0].b, alab[0], alab[1], alab[2], mlab[0], mlab[1], mlab[2],de);
4170 		}
4171 		anerr /= (double)(nn+3);
4172 		printf("Model maximum error(@ %f) = %f deltaE\n",mnv, mnerr);
4173 		printf("Model average error = %f deltaE\n",anerr);
4174 	}
4175 #endif /* CHECK_MODEL */
4176 
4177 	/* Figure out our calibration curve parameter targets */
4178 	if (!doupdate) {
4179 
4180 		/* Figure out the target white point */
4181 		if (wpx > 0.0 || wpy > 0.0) {	/* xy coordinates */
4182 			double Yxy[3];
4183 			Yxy[0] = 1.0;
4184 			Yxy[1] = wpx;
4185 			Yxy[2] = wpy;
4186 			icmYxy2XYZ(x.twh, Yxy);
4187 
4188 		} else if (temp > 0.0) {		/* Daylight color temperature */
4189 			if (planckian)
4190 				rv = icx_ill_sp2XYZ(x.twh, icxOT_default, NULL, icxIT_Ptemp, temp, NULL);
4191 			else
4192 				rv = icx_ill_sp2XYZ(x.twh, icxOT_default, NULL, icxIT_Dtemp, temp, NULL);
4193 			if (rv != 0)
4194 				error("Failed to compute XYZ of target color temperature %f\n",temp);
4195 //printf("~1 Raw target from temp %f XYZ = %f %f %f\n",temp,x.twh[0],x.twh[1],x.twh[2]);
4196 		} else {						/* Native white */
4197 			x.twh[0] = x.wh[0]/x.wh[1];
4198 			x.twh[1] = x.wh[1]/x.wh[1];
4199 			x.twh[2] = x.wh[2]/x.wh[1];
4200 		}
4201 		x.nwh[0] = x.twh[0];
4202 		x.nwh[1] = x.twh[1];
4203 		x.nwh[2] = x.twh[2];
4204 
4205 		/* Convert it to absolute white target */
4206 		if (tbright > 0.0) {			/* Given brightness */
4207 			x.twh[0] *= tbright;
4208 			x.twh[1] *= tbright;
4209 			x.twh[2] *= tbright;
4210 		} else {						/* Native/maximum brightness */
4211 			x.twh[0] *= x.wh[1];
4212 			x.twh[1] *= x.wh[1];
4213 			x.twh[2] *= x.wh[1];
4214 			if (verb)
4215 				printf("\nInitial native brightness target = %f cd/m^2\n", x.twh[1]);
4216 		}
4217 
4218 		/* Now make sure the target white will fit in gamut. */
4219 		if (verify != 2 &&
4220 		   ((tbright > 0.0 && invlindev(&x, NULL, x.twh) > 0.0) /* Defined brightness and clips */
4221 		 || (tbright <= 0.0 && x.nat == 0))) {		/* Max non-native white */
4222 			double rgb[3];
4223 			double scale = 0.5;
4224 			double sa = 0.1;
4225 
4226 			if (powell(NULL, 1, &scale, &sa, 1e-7, 500, wp_opt_func, (void *)&x, NULL, NULL) != 0)
4227 				error("WP scale powell failed");
4228 
4229 			x.twh[0] *= scale;
4230 			x.twh[1] *= scale;
4231 			x.twh[2] *= scale;
4232 			invdev(&x, rgb, x.twh);
4233 			if (verb) {
4234 				printf("Had to scale brightness from %f to %f to fit within gamut,\n",x.twh[1]/scale, x.twh[1]);
4235 				printf("corresponding to aprox. RGB %f %f %f\n",rgb[0],rgb[1],rgb[2]);
4236 			}
4237 		}
4238 
4239 		icmXYZ2Yxy(x.twYxy, x.twh);		/* For information */
4240 
4241 		if (verb)
4242 			printf("Target white value is XYZ %f %f %f [xy %f %f]\n",x.twh[0],x.twh[1],x.twh[2],
4243 			                                                          x.twYxy[1], x.twYxy[2]);
4244 	}
4245 
4246 	/* Need this for Lab conversions */
4247 	icmAry2XYZ(x.twN, x.twh);
4248 
4249 	/* Figure out the black point target */
4250 	{
4251 		double tbL[3];
4252 		double tbkLab[3];
4253 
4254 		icmXYZ2Lab(&x.twN, tbkLab, x.bk);	/* Convert measured black to Lab */
4255 
4256 //printf("~1 black point Lab = %f %f %f\n", tbkLab[0], tbkLab[1], tbkLab[2]);
4257 
4258 		/* Now blend the a* b* with that of the target white point */
4259 		/* according to how much to try and correct the hue. */
4260 		tbL[0] = tbkLab[0];
4261 		tbL[1] = bkcorrect * 0.0 + (1.0 - bkcorrect) * tbkLab[1];
4262 		tbL[2] = bkcorrect * 0.0 + (1.0 - bkcorrect) * tbkLab[2];
4263 
4264 //printf("~1 blended black Lab = %f %f %f\n", tbL[0], tbL[1], tbL[2]);
4265 
4266 		if (bkbright > 0.0 && (bkbright <= x.bk[1] || (2.0 * bkbright) >= x.twh[1]))
4267 			warning("Black brigtness %f ignored because it is out of range",bkbright);
4268 		else if (bkbright > 0.0) {
4269 			double bkbxyz[3], bkbLab[3], vv, bl;
4270 			/* Figure out the L value of the target brightness */
4271 			bkbxyz[0] = 0.0;
4272 			bkbxyz[1] = bkbright;
4273 			bkbxyz[2] = 0.0;
4274 			icmXYZ2Lab(&x.twN, bkbLab, bkbxyz);
4275 
4276 			/* Do crossover to white neutral */
4277 			vv = bkbLab[0] / (100.0 - tbL[0]);
4278 			bl = pow((1.0 - vv), x.nbrate);		/* Crossover near the black */
4279 			tbL[0] = bkbLab[0];
4280 			tbL[1] = (1.0 - bl) * 0.0 + bl * tbL[1];
4281 			tbL[2] = (1.0 - bl) * 0.0 + bl * tbL[2];
4282 //printf("~1 brighted black Lab = %f %f %f\n", tbL[0], tbL[1], tbL[2]);
4283 		}
4284 
4285 		/* And make this the black hue to aim for */
4286 		icmLab2XYZ(&x.twN, x.tbk, tbL);
4287 		icmAry2XYZ(x.tbN, x.tbk);
4288 		if (verb)
4289 			printf("Adjusted target black XYZ %.4f %.4f %.4f, Lab %.3f %.3f %.3f\n",
4290 	        x.tbk[0], x.tbk[1], x.tbk[2], tbL[0], tbL[1], tbL[2]);
4291 	}
4292 
4293 	/* Figure out the gamma curve black offset value */
4294 	/* that will give us the black level we actually have. */
4295 	{
4296 		double yy, tby;			/* Target black y */
4297 
4298 		/* Make target black Y as high as necessary */
4299 		/* to get the black point hue */
4300 		/* ????? should do this by increasing L* until XYZ > x.bk ????? */
4301 		tby = x.bk[1];
4302 //printf("Target Y from Y = %f\n",tby);
4303 		yy = x.bk[0] * x.tbk[1]/x.tbk[0];
4304 //printf("Target Y from X = %f\n",yy);
4305 		if (yy > tby)
4306 			tby = yy;
4307 		yy = x.bk[2] * x.tbk[1]/x.tbk[2];
4308 //printf("Target Y from Z = %f\n",yy);
4309 		if (yy > tby)
4310 			tby = yy;
4311 
4312 		if (x.tbk[1] > tby)		/* If target is already high enough */
4313 			tby = x.tbk[1];
4314 
4315 		if (verb) {
4316 			double tbp[3], tbplab[3];
4317 			if (fabs(x.tbk[1]) > 1e-9)
4318 		        tbp[0] = x.tbk[0] * tby/x.tbk[1];
4319 			else
4320 		        tbp[0] = x.tbk[0];
4321 			tbp[1] = tby;
4322 			if (fabs(x.tbk[1]) > 1e-9)
4323 				tbp[2] = x.tbk[2] * tby/x.tbk[1];
4324 			else
4325 				tbp[2] = x.tbk[2];
4326 			icmXYZ2Lab(&x.twN, tbplab, tbp);
4327 			printf("Target black after min adjust: XYZ %.4f %.4f %.4f, Lab %.3f %.3f %.3f\n",
4328 			        tbp[0], tbp[1], tbp[2], tbplab[0], tbplab[1], tbplab[2]);
4329 		}
4330 
4331 		/* Figure out the x.gioff and egamma needed to get this x.gooff and gamma */
4332 		x.gooff = tby / x.twh[1];			/* Convert to relative */
4333 
4334 		/* tech_gamma() does the hard work */
4335 		tech_gamma(&x, &x.egamma, &x.gooff, &x.gioff, egamma, gamma, x.gooff);
4336 	}
4337 
4338 	if (verb)
4339 		printf("Gamma curve input offset = %f, output offset = %f, power = %f\n",x.gioff,x.gooff,x.egamma);
4340 
4341 	/* For ambient light compensation, we make use of CIECAM02 */
4342 	if (ambient > 0.0) {
4343 		double xyz[3], Jab[3];
4344 		double t1, t0, a1, a0;
4345 
4346 		/* Setup default source viewing conditions */
4347 		if ((x.svc = new_icxcam(cam_default)) == NULL
4348 		 || (x.dvc = new_icxcam(cam_default)) == NULL) {
4349 			error("Failed to create source and destination CAMs");
4350 		}
4351 
4352 		switch(x.gammat) {
4353 			case gt_power:	/* There's nothing obvious for these cases, */
4354 			case gt_Lab:	/* So default to a computerish source viewing condition */
4355 
4356 			case gt_sRGB:	/* sRGB standard viewing conditions */
4357 				x.svc->set_view(x.svc, vc_none,
4358 					x.nwh,					/* Display normalised white point */
4359 					0.2 * 80.0,				/* Adapting luminence, 20% of display 80 cd/m^2 */
4360 					0.2,					/* Background relative to reference white */
4361 					80.0,					/* Display is 80 cd/m^2 */
4362 			        0.0, 0.01, x.nwh,		/* 0% flare and 1% glare same white point */
4363 					0, 1.0);
4364 				break;
4365 
4366 			case gt_Rec709:
4367 			case gt_SMPTE240M:	/* Television studio conditions */
4368 				x.svc->set_view(x.svc, vc_none,
4369 					x.nwh,					/* Display normalised white point */
4370 					0.2 * 1000.0/3.1415,	/* Adapting luminence, 20% of 1000 lux in cd/m^2 */
4371 					0.2,					/* Background relative to reference white */
4372 					1000.0/3.1415,			/* Luminance of white in the Image field (cd/m^2) */
4373 			        0.0, 0.01, x.nwh,		/* 0% flare and 1% glare same white point */
4374 					0, 1.0);
4375 				break;
4376 
4377 			default:
4378 				error("Unknown gamma type");
4379 		}
4380 		/* The display we're calibratings situation */
4381 		x.dvc->set_view(x.dvc, vc_none,
4382 			x.nwh,				/* Display normalised white point */
4383 			0.2 * ambient/3.1415,	/* Adapting luminence, 20% of ambient in cd/m^2 */
4384 			0.2,				/* Background relative to reference white */
4385 			x.twh[1],			/* Target white level (cd/m^2) */
4386 	        0.0, 0.01, x.nwh,	/* 0% flare and 1% glare same white point */
4387 			0, 1.0);
4388 
4389 		/* Compute the normalisation values */
4390 		x.svc->XYZ_to_cam(x.svc, Jab, x.nwh);		/* Relative white point */
4391 		x.dvc->cam_to_XYZ(x.dvc, xyz, Jab);
4392 		t1 = x.nwh[1];
4393 		a1 = xyz[1];
4394 
4395 		xyz[0] = x.tbk[1]/x.twh[1] * x.nwh[0];
4396 		xyz[1] = x.tbk[1]/x.twh[1] * x.nwh[1];
4397 		xyz[2] = x.tbk[1]/x.twh[1] * x.nwh[2];
4398 		t0 = xyz[1];
4399 		x.svc->XYZ_to_cam(x.svc, Jab, xyz);		/* Relative black Y */
4400 		x.dvc->cam_to_XYZ(x.dvc, xyz, Jab);
4401 		a0 = xyz[1];
4402 
4403 //printf("~1 t1 = %f, t0 = %f\n",t1,t0);
4404 //printf("~1 a1 = %f, a0 = %f\n",a1,a0);
4405 		x.vn1 = (t1 - t0)/(a1 - a0);		/* Scale factor */
4406 		x.vn0 = t0 - (a0 * x.vn1);			/* Then offset */
4407 //printf("~1 vn1 = %f, vn0 = %f\n",x.vn1, x.vn0);
4408 //printf("~1 fix a1 = %f, should be = %f\n",a1 * x.vn1 + x.vn0, t1);
4409 //printf("~1 fix a0 = %f, should be = %f\n",a0 * x.vn1 + x.vn0, t0);
4410 
4411 		x.vc = 1;
4412 
4413 		/* Compute aproximate power of viewing transform */
4414 		if (verb) {
4415 			double v;
4416 			v = view_xform(&x, 0.5);
4417 			v = log(v) / log(0.5);
4418 			printf("Viewing conditions adjustment aprox. power = %f\n",v);
4419 		}
4420 #ifdef NEVER
4421 {
4422 	int i;
4423 
4424 	printf("~1 viewing xtranform:\n");
4425 	for (i = 0; i <= 10; i++) {
4426 		double w, v = i/10.0;
4427 
4428 		w = view_xform(&x, v);
4429 		printf("~1 in %f -> %f\n",v,w);
4430 	}
4431 }
4432 #endif /* NEVER */
4433 	}
4434 
4435 	/* - - - - - - - - - - - - - - - - - - - - - */
4436 	/* Make sure nver has a sane value */
4437 	if (verify == 0)
4438 		nver = 0;			/* 0 verify count if no verify */
4439 	else if (nver == 0)
4440 		nver = 1;			/* min 1 count if verify */
4441 
4442 	/* Start with a scaled down number of test points and refine threshold, */
4443 	/* and double/halve these on each iteration. */
4444 	if (verb && verify != 2)
4445 		printf("Total Iteration %d, Final Samples = %d Final Repeat threshold = %f\n",
4446 		        mxits, rsteps, errthr);
4447 	if (verify == 2) {
4448 		rsteps = VER_RES;
4449 		errthr = 0.0;
4450 	} else {
4451 		rsteps /= (1 << (mxits-1));
4452 		errthr *= pow((double)(1 << (mxits-1)), THRESH_SCALE_POW);
4453 	}
4454 
4455 	/* Setup the initial calibration test point values */
4456 	init_csamp(&asgrey, &x, doupdate, verify, verify == 2 ? 1 : 0, rsteps, verb);
4457 
4458 	/* Calculate the initial calibration curve values */
4459 	if (verify != 2 && !doupdate) {
4460 		int nsamp = 128;
4461 		mcvco *sdv[3];				/* Scattered data for creating mcv */
4462 
4463 		for (j = 0; j < 3; j++) {
4464 			if ((x.rdac[j] = new_mcv()) == NULL) {
4465 				dr->del(dr);
4466 				error("new_mcv x.rdac[%d] failed",j);
4467 			}
4468 		}
4469 
4470 		for (j = 0; j < 3; j++) {
4471 			if ((sdv[j] = malloc(sizeof(mcvco) * rsteps)) == NULL) {
4472 				dr->del(dr);
4473 				error("Malloc of scattered data points failed");
4474 			}
4475 		}
4476 
4477 		if (verb)
4478 			printf("Creating initial calibration curves...\n");
4479 
4480 		/* Copy the sample points */
4481 		for (i = 0; i < rsteps; i++) {
4482 			for (j = 0; j < 3; j++) {
4483 				sdv[j][i].p = asgrey.s[i].v;
4484 				sdv[j][i].v = asgrey.s[i].rgb[j];
4485 				sdv[j][i].w = 1.0;
4486 			}
4487 		}
4488 		if (x.nat)		/* Make curve go thought white if possible by setting a weighting */
4489 			sdv[0][rsteps-1].w = sdv[1][rsteps-1].w = sdv[2][rsteps-1].w = 50.0;
4490 
4491 		if (x.bkhack)	/* Make curve go thought black if possible by setting a weighting */
4492 			sdv[0][0].w = sdv[1][0].w = sdv[2][0].w = 50.0;
4493 
4494 		/* Create an initial set of RAMDAC curves */
4495 		for (j = 0; j < 3; j++)
4496 			x.rdac[j]->fit(x.rdac[j], 0, fitord, sdv[j], rsteps, RDAC_SMOOTH);
4497 
4498 		/* Make sure that if we are using native brightness and white point, */
4499 		/* that the curves go to a perfect 1.0 ... */
4500 		if (x.nat) {
4501 			for (j = 0; j < 3; j++)
4502 				x.rdac[j]->force_1(x.rdac[j], 1.0);
4503 		}
4504 
4505 		/* Make sure that if we are using black point hack, */
4506 		/* that the curves go to a perfect 0.0 ... */
4507 		if (x.bkhack) {
4508 			for (j = 0; j < 3; j++)
4509 				x.rdac[j]->force_0(x.rdac[j], 0.0);
4510 		}
4511 
4512 		for (j = 0; j < 3; j++)
4513 			free (sdv[j]);
4514 	}
4515 
4516 #ifdef DEBUG_PLOT
4517 	/* Plot the initial curves */
4518 	if (verify != 2) {
4519 		#define	XRES 255
4520 		double xx[XRES];
4521 		double y1[XRES];
4522 		double y2[XRES];
4523 		double y3[XRES];
4524 		double rgb[3];
4525 		for (i = 0; i < XRES; i++) {
4526 			double drgb[3], rgb[3];
4527 			xx[i] = i/(XRES-1.0);
4528 			rgb[0] = rgb[1] = rgb[2] = xx[i];
4529 			for (j = 0; j < 3; j++)
4530 				drgb[j] = x.rdac[j]->interp(x.rdac[j], rgb[j]);
4531 			y1[i] = drgb[0];
4532 			y2[i] = drgb[1];
4533 			y3[i] = drgb[2];
4534 		}
4535 		printf("Initial ramdac curves\n");
4536 		do_plot(xx,y1,y2,y3,XRES);
4537 		#undef XRES
4538 	}
4539 #endif
4540 
4541 	/* If native white and white drift compensation enabled, */
4542 	/* reset white drift target at start of main cal. */
4543 	if (x.nat && asgrey.s[0].v == 1.0 && wdrift)
4544 		dr->reset_targ_w(dr);
4545 
4546 	/* Now we go into the main verify & refine loop */
4547 	for (it = (verify == 2) ? mxits : 0;
4548 	     it < (mxits + nver);
4549 	     rsteps *= 2, errthr /= (it < mxits) ? pow(2.0,THRESH_SCALE_POW) : 1.0, it++) {
4550 		int totmeas = 0;		/* Total number of measurements in this pass */
4551 		col set[3];				/* Variable to read one to three values from the display */
4552 
4553 		/* Verify pass ? */
4554 		if (it >= mxits)
4555 			rsteps = VER_RES;	/* Fixed verification resolution */
4556 		else
4557 			thrfail = 0;		/* Not verify pass */
4558 
4559 		/* re-init asgrey if the number of test points has changed */
4560 		reinit_csamp(&asgrey, &x, verify, it >= mxits ? 1 : 0, rsteps, verb);
4561 
4562 		if (verb) {
4563 			if (it >= mxits)
4564 				printf("\nDoing verify pass %d/%d with %d sample points\n",
4565 				                              it - mxits+1, nver, rsteps);
4566 			else
4567 				printf("\nDoing iteration %d/%d with %d sample points and repeat threshold of %f DE\n",
4568 				                                                      it+1,mxits, rsteps, errthr);
4569 		}
4570 
4571 		/* Read and adjust each step */
4572 		/* Do this white to black to track drift in native white point */
4573 		for (i = rsteps-1; i >= 0; i--) {
4574 			int rpt;
4575 			double peqXYZ[3];		/* Previous steps equivalent aim point */
4576 			double bestrgb[3];		/* In case we fail */
4577 			double bestxyz[3];
4578 			double prevde = 1e7;
4579 			double best_de = 1e7;
4580 			double bestde = 1e7;
4581 			double bestdc = 1e7;
4582 			double bestpeqde = 1e7;
4583 			double besthde = 1e7;
4584 			double rgain = REFINE_GAIN;	/* Scale down if lots of repeats */
4585 			int mjac = 0;				/* We measured the Jacobian */
4586 			double ierrth = errthr;		/* This points error threshold */
4587 
4588 			icmCpy3(asgrey.s[i].prgb, asgrey.s[i].rgb);		/* Init previous */
4589 
4590 			/* Setup a second termination threshold criteria based on */
4591 			/* the delta E to the previous step point for the last pass. */
4592 			/* This is to try and steer towards neutral axis consistency ? */
4593 			if (i == (rsteps-1) || it < (mxits-1)) {
4594 				icmAry2Ary(peqXYZ, asgrey.s[i].tXYZ);	/* Its own aim point */
4595 			} else {
4596 				double Lab1[3], Lab2[3], Lab3[3];
4597 				icmXYZ2Lab(&x.twN, Lab1, asgrey.s[i+1].XYZ);
4598 				icmXYZ2Lab(&x.twN, Lab2, asgrey.s[i].tXYZ);
4599 				Lab1[0] = Lab2[0];		/* L of current target with ab of previous as 2nd threshold */
4600 				icmLab2XYZ(&x.twN, peqXYZ, Lab1);		/* Previous equivalent */
4601 			}
4602 
4603 #ifdef ADJ_THRESH
4604 			/* Adjust the termination threshold to make sure it is less than */
4605 			/* half a step */
4606 			{
4607 				double de;
4608 				if (i < (rsteps-1)) {
4609 					de = 0.5 * icmXYZLabDE(&x.twN, asgrey.s[i].tXYZ, asgrey.s[i+1].tXYZ);
4610 					if (de < MIN_THRESH)		/* Don't be silly */
4611 						de = MIN_THRESH;
4612 					if (de < ierrth)
4613 						ierrth = de;
4614 				}
4615 				if (i > 0) {
4616 					de = 0.5 * icmXYZLabDE(&x.twN, asgrey.s[i].tXYZ, asgrey.s[i-1].tXYZ);
4617 					if (de < MIN_THRESH)		/* Don't be silly */
4618 						de = MIN_THRESH;
4619 					if (de < ierrth)
4620 						ierrth = de;
4621 				}
4622 			}
4623 #endif /* ADJ_THRESH */
4624 
4625 			/* Until we meet the necessary accuracy or give up */
4626 			for (rpt = 0; rpt < mxrpts; rpt++) {
4627 				double hlew = 1.0;	/* high L* error weight */
4628 				int gworse = 0;		/* information flag */
4629 				double w_de, wde;	/* informational */
4630 				double pjadj[3][3] = { 0.0 };	/* Previous jacobian adjustment */
4631 
4632 				set[0].r = asgrey.s[i].rgb[0];
4633 				set[0].g = asgrey.s[i].rgb[1];
4634 				set[0].b = asgrey.s[i].rgb[2];
4635 				set[0].id = NULL;
4636 
4637 				/* Read patches (no auto cr in case we repeat last patch) */
4638 				if ((rv = dr->read(dr, set, 1, rsteps-i, rsteps, 0, 0, instClamp)) != 0) {
4639 					dr->del(dr);
4640 					error("display read failed with '%s'\n",disprd_err(rv));
4641 				}
4642 				totmeas++;
4643 
4644 				icmAry2Ary(asgrey.s[i].pXYZ, asgrey.s[i].XYZ);	/* Remember previous XYZ */
4645 				icmAry2Ary(asgrey.s[i].XYZ, set[0].XYZ);		/* Transfer current reading */
4646 
4647 				/* If native white and we've just measured it, */
4648 				/* and we're not doing a verification, */
4649 				/* adjust all the other point targets txyz to track the white. */
4650 				if (x.nat && i == (rsteps-1) && it < mxits && asgrey.s[i].v == 1.0) {
4651 					icmAry2Ary(x.twh, asgrey.s[i].XYZ);		/* Set current white */
4652 					icmAry2XYZ(x.twN, x.twh);				/* Need this for Lab conversions */
4653 					init_csamp_txyz(&asgrey, &x, 1, verb);	/* Recompute txyz's */
4654 					icmAry2Ary(peqXYZ, asgrey.s[i].tXYZ);	/* Fix peqXYZ */
4655 //printf("~1 Just reset target white to native white\n");
4656 					if (wdrift) {	/* Make sure white drift is reset on next read. */
4657 						dr->reset_targ_w(dr);			/* Reset this */
4658 					}
4659 				}
4660 
4661 				/* If black point hack and we've just measured it, */
4662 				/* and we're not doing a verification, */
4663 				if (x.bkhack && i == 0 && it < mxits && asgrey.s[i].v == 0.0) {
4664 					icmAry2Ary(x.tbk, asgrey.s[i].XYZ);		/* Set current black */
4665 					icmAry2XYZ(x.tbN, x.tbk);
4666 					init_csamp_txyz(&asgrey, &x, 1, verb);	/* Recompute txyz's */
4667 					icmAry2Ary(peqXYZ, asgrey.s[i].tXYZ);	/* Fix peqXYZ */
4668 				}
4669 
4670 				/* Compute the next change wanted to hit target */
4671 				icmSub3(asgrey.s[i].deXYZ, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
4672 
4673 				/* For the darkest 5% of targets, weight the L* delta E so that */
4674 				/* we err on the darker side */
4675 				if (asgrey.s[i].v < POWERR_THR)
4676 					hlew = 1.0 + POWERR_WEIGHT * pow((POWERR_THR - asgrey.s[i].v)/POWERR_THR,
4677 					                                                       POWERR_WEIGHT_POW);
4678 				else
4679 					hlew = 1.0;
4680 //printf("~1 i %d, v %f, hlew %f\n",i,asgrey.s[i].v,hlew);
4681 				asgrey.s[i]._de = icmXYZLabDE(&x.twN, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
4682 				asgrey.s[i].de = bwXYZLabDE(&x.twN, asgrey.s[i].tXYZ, asgrey.s[i].XYZ, hlew);
4683 				asgrey.s[i].peqde = bwXYZLabDE(&x.twN, peqXYZ, asgrey.s[i].XYZ, hlew);
4684 				asgrey.s[i].hde = 0.8 * asgrey.s[i].de + 0.2 * asgrey.s[i].peqde;
4685 				/* Eudclidian difference of XYZ, because this doesn't always track Lab */
4686 				asgrey.s[i].dc = icmLabDE(asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
4687 
4688 				/* Compute actual change from last XYZ */
4689 				icmSub3(asgrey.s[i].dXYZ, asgrey.s[i].XYZ, asgrey.s[i].pXYZ);
4690 
4691 				w_de = asgrey.s[i]._de;
4692 				wde = asgrey.s[i].de;
4693 
4694 				if (verb >= 3) {
4695 					printf("\n\nTest point %d, v = %f, rpt %d\n",rsteps - i,asgrey.s[i].v,rpt);
4696 					printf("Current rgb %f %f %f -> XYZ %f %f %f, de %f, dc %f\n",
4697 					asgrey.s[i].rgb[0], asgrey.s[i].rgb[1], asgrey.s[i].rgb[2],
4698 					asgrey.s[i].XYZ[0], asgrey.s[i].XYZ[1], asgrey.s[i].XYZ[2],
4699 					asgrey.s[i]._de, asgrey.s[i].dc);
4700 					printf("Target XYZ %f %f %f, delta needed %f %f %f\n",
4701 					asgrey.s[i].tXYZ[0], asgrey.s[i].tXYZ[1], asgrey.s[i].tXYZ[2],
4702 					asgrey.s[i].deXYZ[0], asgrey.s[i].deXYZ[1], asgrey.s[i].deXYZ[2]);
4703 					if (rpt > 0) {
4704 						printf("Last intended XYZ change %f %f %f, actual change %f %f %f\n",
4705 						asgrey.s[i].pdXYZ[0], asgrey.s[i].pdXYZ[1], asgrey.s[i].pdXYZ[2],
4706 						asgrey.s[i].dXYZ[0], asgrey.s[i].dXYZ[1], asgrey.s[i].dXYZ[2]);
4707 					}
4708 				}
4709 
4710 				if (it < mxits) {		/* Not verify, apply correction */
4711 					int impj = 0;		/* We adjusted the Jacobian */
4712 					int dclip = 0;		/* We clipped the new RGB */
4713 #ifdef ADJ_JACOBIAN
4714 					int isclipped = 0;
4715 
4716 #ifndef CLIP		/* Check for cliping */
4717 					/* Don't try and update the Jacobian if the */
4718 					/* device values are going out of gamut, */
4719 					/* and being clipped without Jac correction being aware. */
4720 					for (j = 0; j < 3; j++) {
4721 						if (asgrey.s[i].rgb[j] <= 0.0 || asgrey.s[i].rgb[j] >= 1.0) {
4722 							isclipped = 1;
4723 							break;
4724 						}
4725 					}
4726 #endif /* !CLIP */
4727 
4728 #ifdef REMEAS_JACOBIAN
4729 					/* If the de hasn't improved, try and measure the Jacobian */
4730 //					if (it < (rsteps-1) && mjac == 0 && asgrey.s[i].de > (0.8 * prevde))
4731 					if (mjac == 0 && asgrey.s[i].de > (0.8 * prevde))
4732 					{
4733 						double dd;
4734 						if (asgrey.s[i].v < 0.5)
4735 							dd = 0.05;
4736 						else
4737 							dd= -0.05;
4738 						set[0].r = asgrey.s[i].rgb[0] + dd;
4739 						set[0].g = asgrey.s[i].rgb[1];
4740 						set[0].b = asgrey.s[i].rgb[2];
4741 						set[0].id = NULL;
4742 						set[1].r = asgrey.s[i].rgb[0];
4743 						set[1].g = asgrey.s[i].rgb[1] + dd;
4744 						set[1].b = asgrey.s[i].rgb[2];
4745 						set[1].id = NULL;
4746 						set[2].r = asgrey.s[i].rgb[0];
4747 						set[2].g = asgrey.s[i].rgb[1];
4748 						set[2].b = asgrey.s[i].rgb[2] + dd;
4749 						set[2].id = NULL;
4750 
4751 						if ((rv = dr->read(dr, set, 1, rsteps-i, rsteps, 0, 0, instClamp)) != 0
4752 						 || (rv = dr->read(dr, set+1, 1, rsteps-i, rsteps, 0, 0, instClamp)) != 0
4753 						 || (rv = dr->read(dr, set+2, 1, rsteps-i, rsteps, 0, 0, instClamp)) != 0) {
4754 							dr->del(dr);
4755 							error("display read failed with '%s'\n",disprd_err(rv));
4756 						}
4757 						totmeas += 3;
4758 
4759 //printf("\n~1 remeasured jacobian\n");
4760 						/* Matrix organization is J[XYZ][RGB] for del RGB->del XYZ*/
4761 						for (j = 0; j < 3; j++) {
4762 							asgrey.s[i].j[0][j] = (set[j].XYZ[0] - asgrey.s[i].XYZ[0]) / dd;
4763 							asgrey.s[i].j[1][j] = (set[j].XYZ[1] - asgrey.s[i].XYZ[1]) / dd;
4764 							asgrey.s[i].j[2][j] = (set[j].XYZ[2] - asgrey.s[i].XYZ[2]) / dd;
4765 						}
4766 
4767 						/* Clear pjadj */
4768 						for (j = 0; j < 3; j++)
4769 							pjadj[3][0] = pjadj[3][1] = pjadj[3][2] = 0.0;
4770 
4771 						if (icmInverse3x3(asgrey.s[i].ij, asgrey.s[i].j)) {
4772 							/* Should repeat with bigger dd ? */
4773 							if (verb)
4774 								printf("dispcal: inverting Jacobian failed (3) - falling back\n");
4775 
4776 							/* Revert to the initial Jacobian */
4777 							icmCpy3x3(asgrey.s[i].ij, asgrey.s[i].fb_ij);
4778 						}
4779 						/* Restart at the best we've had */
4780 						if (asgrey.s[i].hde > besthde) {
4781 							asgrey.s[i]._de = best_de;
4782 							asgrey.s[i].de = bestde;
4783 							asgrey.s[i].dc = bestdc;
4784 							asgrey.s[i].peqde = bestpeqde;
4785 							asgrey.s[i].hde = besthde;
4786 							asgrey.s[i].rgb[0] = bestrgb[0];
4787 							asgrey.s[i].rgb[1] = bestrgb[1];
4788 							asgrey.s[i].rgb[2] = bestrgb[2];
4789 							asgrey.s[i].XYZ[0] = bestxyz[0];
4790 							asgrey.s[i].XYZ[1] = bestxyz[1];
4791 							asgrey.s[i].XYZ[2] = bestxyz[2];
4792 							icmSub3(asgrey.s[i].deXYZ, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
4793 						}
4794 						mjac = 1;
4795 						impj = 1;		/* Have remeasured */
4796 					}
4797 #endif /* REMEAS_JACOBIAN */
4798 
4799 					/* Compute a correction to the Jacobian if we can. */
4800 					/* (Don't do this unless we have a solid previous */
4801 					/* reading for this patch, and we haven't remeasured it) */
4802 					if (impj == 0 && rpt > 0 && isclipped == 0) {
4803 						double nsdrgb;			/* Norm squared of pdrgb */
4804 						double spdrgb[3];		/* Scaled previous delta rgb */
4805 						double dXYZerr[3];		/* Error in previous prediction */
4806 						double jadj[3][3];		/* Adjustment to Jacobian */
4807 						double tj[3][3];		/* Temp Jacobian */
4808 						double itj[3][3];		/* Temp inverse Jacobian */
4809 
4810 //printf("~1 Jacobian was: %f %f %f\n", asgrey.s[i].j[0][0], asgrey.s[i].j[0][1], asgrey.s[i].j[0][2]);
4811 //printf("~1               %f %f %f\n", asgrey.s[i].j[1][0], asgrey.s[i].j[1][1], asgrey.s[i].j[1][2]);
4812 //printf("~1               %f %f %f\n", asgrey.s[i].j[2][0], asgrey.s[i].j[2][1], asgrey.s[i].j[2][2]);
4813 
4814 						/* Use Broyden's Formula */
4815 						icmSub3(dXYZerr, asgrey.s[i].dXYZ, asgrey.s[i].pdXYZ);
4816 //printf("~1 Jacobian error = %f %f %f\n", dXYZerr[0], dXYZerr[1], dXYZerr[2]);
4817 						nsdrgb = icmNorm3sq(asgrey.s[i].pdrgb);
4818 						/* If there was sufficient change in device values */
4819 						/* to be above any noise: */
4820 						if (nsdrgb >= (0.005 * 0.005)) {
4821 							icmScale3(spdrgb, asgrey.s[i].pdrgb, 1.0/nsdrgb);
4822 							icmTensMul3(jadj, dXYZerr, spdrgb);
4823 
4824 #ifdef DEBUG
4825 							/* Check that new Jacobian predicts previous delta XYZ */
4826 							{
4827 								double eXYZ[3];
4828 
4829 //printf("~1 del RGB %f %f %f got del XYZ %f %f %f\n", asgrey.s[i].pdrgb[0], asgrey.s[i].pdrgb[1], asgrey.s[i].pdrgb[2], asgrey.s[i].dXYZ[0], asgrey.s[i].dXYZ[1], asgrey.s[i].dXYZ[2]);
4830 
4831 								/* Make a full adjustment to temporary Jac */
4832 								icmAdd3x3(tj, asgrey.s[i].j, jadj);
4833 
4834 //printf("~1 Full Jacobian: %f %f %f\n", tj[0][0], tj[0][1], tj[0][2]);
4835 //printf("~1                %f %f %f\n", tj[1][0], tj[1][1], tj[1][2]);
4836 //printf("~1                %f %f %f\n", tj[2][0], tj[2][1], tj[2][2]);
4837 
4838 								icmMulBy3x3(eXYZ, tj, asgrey.s[i].pdrgb);
4839 								icmSub3(eXYZ, eXYZ, asgrey.s[i].dXYZ);
4840 								printf("Jac check resid %f %f %f\n", eXYZ[0], eXYZ[1], eXYZ[2]);
4841 							}
4842 #endif	/* DEBUG */
4843 
4844 							/* Add to portion of previous adjustment */
4845 							/* to counteract undershoot & overshoot */
4846 							icmScale3x3(pjadj, pjadj, JAC_COMP_FACT);
4847 							icmAdd3x3(jadj, jadj, pjadj);
4848 							icmCpy3x3(pjadj, jadj);
4849 
4850 							/* Add part of our correction to actual Jacobian */
4851 							/* to smooth out correction to counteract noise */
4852 							icmScale3x3(jadj, jadj, JAC_COR_FACT);
4853 							icmAdd3x3(tj, asgrey.s[i].j, jadj);
4854 
4855 							if (icmInverse3x3(itj, tj) == 0) {		/* Invert OK */
4856 								icmCpy3x3(asgrey.s[i].j, tj);		/* Use adjusted */
4857 								icmCpy3x3(asgrey.s[i].ij, itj);
4858 								impj = 1;
4859 
4860 #ifdef NEVER
4861 					/* Check how close new Jacobian predicts previous delta XYZ */
4862 					{
4863 						double eXYZ[3];
4864 						double ergb[3];
4865 
4866 						icmMulBy3x3(eXYZ, asgrey.s[i].j, asgrey.s[i].pdrgb);
4867 						icmSub3(eXYZ, eXYZ, asgrey.s[i].dXYZ);
4868 						printf("Jac check2 resid %f %f %f\n", eXYZ[0], eXYZ[1], eXYZ[2]);
4869 
4870 						icmMulBy3x3(ergb, asgrey.s[i].ij, asgrey.s[i].pdXYZ);
4871 						printf("Jac check2 drgb would have been %f %f %f\n", ergb[0], ergb[1], ergb[2]);
4872 						icmAdd3(ergb, ergb, asgrey.s[i].prgb);
4873 						printf("Jac check2 rgb would have been %f %f %f\n", ergb[0], ergb[1], ergb[2]);
4874 					}
4875 #endif
4876 							}
4877 //else printf("~1 ij failed - reverted\n");
4878 						}
4879 //else printf("~1 nsdrgb was below threshold\n");
4880 					}
4881 //else if (isclipped) printf("~1 no j update: rgb is clipped\n");
4882 //printf("~1 Jacobian now: %f %f %f\n", asgrey.s[i].j[0][0], asgrey.s[i].j[0][1], asgrey.s[i].j[0][2]);
4883 //printf("~1               %f %f %f\n", asgrey.s[i].j[1][0], asgrey.s[i].j[1][1], asgrey.s[i].j[1][2]);
4884 //printf("~1               %f %f %f\n", asgrey.s[i].j[2][0], asgrey.s[i].j[2][1], asgrey.s[i].j[2][2]);
4885 
4886 #endif	/* ADJ_JACOBIAN */
4887 
4888 					/* Track the best solution we've found */
4889 					if (asgrey.s[i].hde <= besthde) {
4890 						best_de = asgrey.s[i]._de;
4891 						bestde = asgrey.s[i].de;
4892 						bestdc = asgrey.s[i].dc;
4893 						bestpeqde = asgrey.s[i].peqde;
4894 						besthde = asgrey.s[i].hde;
4895 						bestrgb[0] = asgrey.s[i].rgb[0];
4896 						bestrgb[1] = asgrey.s[i].rgb[1];
4897 						bestrgb[2] = asgrey.s[i].rgb[2];
4898 						bestxyz[0] = asgrey.s[i].XYZ[0];
4899 						bestxyz[1] = asgrey.s[i].XYZ[1];
4900 						bestxyz[2] = asgrey.s[i].XYZ[2];
4901 
4902 //printf("~1 new best\n");
4903 					} else if (asgrey.s[i].dc > bestdc) {
4904 						/* we got worse in Lab and XYZ ! */
4905 
4906 						/* If we've wandered too far, return to best we found */
4907 						if (asgrey.s[i].hde > (3.0 * besthde)) {
4908 //printf("~1 resetting to last best\n");
4909 							asgrey.s[i]._de = best_de;
4910 							asgrey.s[i].de = bestde;
4911 							asgrey.s[i].dc = bestdc;
4912 							asgrey.s[i].peqde = bestpeqde;
4913 							asgrey.s[i].hde = besthde;
4914 							asgrey.s[i].rgb[0] = bestrgb[0];
4915 							asgrey.s[i].rgb[1] = bestrgb[1];
4916 							asgrey.s[i].rgb[2] = bestrgb[2];
4917 							asgrey.s[i].XYZ[0] = bestxyz[0];
4918 							asgrey.s[i].XYZ[1] = bestxyz[1];
4919 							asgrey.s[i].XYZ[2] = bestxyz[2];
4920 							icmSub3(asgrey.s[i].deXYZ, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
4921 						}
4922 
4923 						/* If the Jacobian hasn't changed, moderate the gain */
4924 						if (impj == 0) {
4925 							rgain *= 0.8;		/* We might be overshooting */
4926 //printf("~1 reducing rgain to %f\n",rgain);
4927 						}
4928 						gworse = 1;
4929 					}
4930 
4931 					/* See if we need to repeat */
4932 					if (asgrey.s[i].de <= ierrth && asgrey.s[i].peqde < ierrth) {
4933 						if (verb > 1) {
4934 							if (it < (mxits-1))
4935 								printf("Point %d DE %f, W.DE %f, OK ( < %f)\n",rsteps - i,asgrey.s[i]._de, asgrey.s[i].de, ierrth);
4936 							else
4937 								printf("Point %d DE %f, W.DE %f, W.peqDE %f, OK ( < %f)\n",rsteps - i,asgrey.s[i]._de,asgrey.s[i].de, asgrey.s[i].peqde, ierrth);
4938 						}
4939 						break;	/* No more retries */
4940 					}
4941 					if ((rpt+1) >= mxrpts) {
4942 						asgrey.s[i]._de = best_de;			/* Restore to best we found */
4943 						asgrey.s[i].de = bestde;
4944 						asgrey.s[i].dc = bestdc;
4945 						asgrey.s[i].peqde = bestpeqde;		/* Restore to best we found */
4946 						asgrey.s[i].hde = besthde;			/* Restore to best we found */
4947 						asgrey.s[i].rgb[0] = bestrgb[0];
4948 						asgrey.s[i].rgb[1] = bestrgb[1];
4949 						asgrey.s[i].rgb[2] = bestrgb[2];
4950 						asgrey.s[i].XYZ[0] = bestxyz[0];
4951 						asgrey.s[i].XYZ[1] = bestxyz[1];
4952 						asgrey.s[i].XYZ[2] = bestxyz[2];
4953 						if (verb > 1) {
4954 							if (it < (mxits-1))
4955 								printf("Point %d DE %f, W.DE %f, Fail ( > %f)\n",rsteps - i,asgrey.s[i]._de, asgrey.s[i].de, ierrth);
4956 							else
4957 								printf("Point %d DE %f, W.DE %f, W.peqDE %f, Fail ( > %f)\n",rsteps - i,asgrey.s[i]._de,asgrey.s[i].de,asgrey.s[i].peqde,ierrth);
4958 						}
4959 						thrfail = 1;						/* Failed to meet target */
4960 						if (bestde > failerr)
4961 							failerr = bestde;				/* Worst failed delta E */
4962 						break;	/* No more retries */
4963 					}
4964 					if (verb > 1) {
4965 						if (gworse)
4966 							if (it < (mxits-1))
4967 								printf("Point %d DE %f, W.DE %f, Repeat (got worse)\n", rsteps - i, w_de, wde);
4968 							else
4969 								printf("Point %d DE %f, W.DE %f, peqDE %f, Repeat (got worse)\n", rsteps - i, w_de, wde,asgrey.s[i].peqde);
4970 						else
4971 							if (it < (mxits-1))
4972 								printf("Point %d DE %f, W.DE %f, Repeat\n", rsteps - i,asgrey.s[i]._de,asgrey.s[i].de);
4973 							else
4974 								printf("Point %d DE %f, W.DE %f, peqDE %f, Repeat\n", rsteps - i,asgrey.s[i]._de,asgrey.s[i].de,asgrey.s[i].peqde);
4975 					}
4976 
4977 //printf("~1 RGB Jacobian: %f %f %f\n", asgrey.s[i].j[0][0], asgrey.s[i].j[0][1], asgrey.s[i].j[0][2]);
4978 //printf("~1               %f %f %f\n", asgrey.s[i].j[1][0], asgrey.s[i].j[1][1], asgrey.s[i].j[1][2]);
4979 //printf("~1               %f %f %f\n", asgrey.s[i].j[2][0], asgrey.s[i].j[2][1], asgrey.s[i].j[2][2]);
4980 					/* Compute refinement of rgb */
4981 					icmMulBy3x3(asgrey.s[i].pdrgb, asgrey.s[i].ij, asgrey.s[i].deXYZ);
4982 //printf("~1 XYZ delta needed %f %f %f -> delta RGB %f %f %f\n",
4983 //asgrey.s[i].deXYZ[0], asgrey.s[i].deXYZ[1], asgrey.s[i].deXYZ[2],
4984 //asgrey.s[i].pdrgb[0], asgrey.s[i].pdrgb[1], asgrey.s[i].pdrgb[2]);
4985 
4986 					/* Gain scale */
4987 					icmScale3(asgrey.s[i].pdrgb, asgrey.s[i].pdrgb, rgain);
4988 //printf("~1 delta RGB after gain scale %f %f %f\n", asgrey.s[i].pdrgb[0], asgrey.s[i].pdrgb[1], asgrey.s[i].pdrgb[2]);
4989 
4990 #ifdef CLIP
4991 					/* Component wise clip */
4992 					for (j = 0; j < 3; j++) {		/* Check for clip */
4993 						if ((-asgrey.s[i].pdrgb[j]) > asgrey.s[i].rgb[j]) {
4994 							asgrey.s[i].pdrgb[j] = -asgrey.s[i].rgb[j];
4995 							dclip = 1;
4996 						}
4997 						if (asgrey.s[i].pdrgb[j] > (1.0 - asgrey.s[i].rgb[j])) {
4998 							asgrey.s[i].pdrgb[j] = (1.0 - asgrey.s[i].rgb[j]);
4999 							dclip = 1;
5000 						}
5001 					}
5002 					if (verb >= 3 && dclip) printf("delta RGB after clip %f %f %f\n",
5003 					       asgrey.s[i].pdrgb[0], asgrey.s[i].pdrgb[1], asgrey.s[i].pdrgb[2]);
5004 #endif	/* CLIP */
5005 					/* Compute next on the basis of this one RGB */
5006 					icmCpy3(asgrey.s[i].prgb, asgrey.s[i].rgb);		/* Save previous */
5007 					icmAdd3(asgrey.s[i].rgb, asgrey.s[i].rgb, asgrey.s[i].pdrgb);
5008 
5009 					/* Save expected change in XYZ */
5010 					icmMulBy3x3(asgrey.s[i].pdXYZ, asgrey.s[i].j, asgrey.s[i].pdrgb);
5011 					if (verb >= 3) {
5012 						printf("New rgb %f %f %f from expected del XYZ %f %f %f\n",
5013 						       asgrey.s[i].rgb[0], asgrey.s[i].rgb[1], asgrey.s[i].rgb[2],
5014 						       asgrey.s[i].pdXYZ[0], asgrey.s[i].pdXYZ[1], asgrey.s[i].pdXYZ[2]);
5015 					}
5016 				} else {	/* Verification, so no repeat */
5017 					break;
5018 				}
5019 
5020 				prevde = asgrey.s[i].de;
5021 			}	/* Next repeat */
5022 
5023 			if (verb >= 3) {
5024 				printf("After adjustment:\n");
5025 				printf("Current rgb %f %f %f -> XYZ %f %f %f, de %f, dc %f\n",
5026 				asgrey.s[i].rgb[0], asgrey.s[i].rgb[1], asgrey.s[i].rgb[2],
5027 				asgrey.s[i].XYZ[0], asgrey.s[i].XYZ[1], asgrey.s[i].XYZ[2],
5028 				asgrey.s[i].de, asgrey.s[i].dc);
5029 				printf("Target XYZ %f %f %f, delta needed %f %f %f\n",
5030 				asgrey.s[i].tXYZ[0], asgrey.s[i].tXYZ[1], asgrey.s[i].tXYZ[2],
5031 				asgrey.s[i].deXYZ[0], asgrey.s[i].deXYZ[1], asgrey.s[i].deXYZ[2]);
5032 			}
5033 
5034 		}		/* Next patch/step */
5035 		if (verb)
5036 			printf("\n");			/* Final return for patch count */
5037 
5038 #ifdef DEBUG_PLOT
5039 		/* Plot the measured response XYZ */
5040 		{
5041 			#define	XRES 256
5042 			double xx[XRES];
5043 			double yy[3][XRES];
5044 			double xyz[3];
5045 			for (i = 0; i < XRES; i++) {
5046 				xx[i] = i/(XRES-1.0);
5047 				csamp_interp(&asgrey, xyz, xx[i]);
5048 				for (j = 0; j < 3; j++)
5049 					yy[j][i] = xyz[j];
5050 			}
5051 			printf("Measured neutral axis XYZ\n",k);
5052 			do_plot(xx,yy[0],yy[1],yy[2],XRES);
5053 			#undef XRES
5054 		}
5055 #endif
5056 
5057 		/* Check out the accuracy of the results: */
5058 		{
5059 			double ctwh[3];		/* Current target white */
5060 			icmXYZNumber ctwN;	/* Same as above as XYZNumber */
5061 			double brerr;		/* Brightness error */
5062 			double cterr;		/* Color temperature delta E */
5063 			double mnerr;		/* Maximum neutral error */
5064 			double mnv = 0.0;	/* Value where maximum error is */
5065 			double anerr;		/* Average neutral error */
5066 			double lab1[3], lab2[3];
5067 
5068 			/* Brightness */
5069 			brerr = asgrey.s[asgrey.no-1].XYZ[1] - x.twh[1];
5070 
5071 			/* Compensate for brightness error */
5072 			for (j = 0; j < 3; j++)
5073 				ctwh[j] = x.twh[j] * asgrey.s[asgrey.no-1].XYZ[1]/x.twh[1];
5074 			icmAry2XYZ(ctwN, ctwh);		/* Need this for Lab conversions */
5075 
5076 			/* Color temperature error */
5077 			icmXYZ2Lab(&ctwN, lab1, ctwh);		/* Should be 100,0,0 */
5078 			icmXYZ2Lab(&ctwN, lab2, asgrey.s[asgrey.no-1].XYZ);
5079 			cterr = icmLabDE(lab1, lab2);
5080 
5081 			/* check delta E of all the sample points */
5082 			/* We're checking against our given brightness and */
5083 			/* white point target. */
5084 			mnerr = anerr = 0.0;
5085 			init_csamp_txyz(&asgrey, &x, 0, verb);	/* In case the targets were tweaked */
5086 			for (i = 0; i < asgrey.no; i++) {
5087 				double err;
5088 
5089 				/* Re-compute de in case last pass had tweaked targets */
5090 				asgrey.s[i].de = icmXYZLabDE(&x.twN, asgrey.s[i].tXYZ, asgrey.s[i].XYZ);
5091 				err = asgrey.s[i].de;
5092 //printf("RGB %.3f -> Lab %.2f %.2f %.2f, target %.2f %.2f %.2f, DE %f\n",
5093 //asgrey.s[i].v, lab2[0], lab2[1], lab2[2], lab1[0], lab1[1], lab1[2], err);
5094 				if (err > mnerr) {
5095 					mnerr = err;
5096 					mnv = asgrey.s[i].v;
5097 				}
5098 				anerr += err;
5099 			}
5100 			anerr /= (double)asgrey.no;
5101 
5102 			if (verb || it >= mxits) {
5103 				if (it >= mxits)
5104 					printf("Verification results:\n");
5105 				printf("Brightness error = %f cd/m^2 (is %f, should be %f)\n",brerr,asgrey.s[asgrey.no-1].XYZ[1],x.twh[1]);
5106 				printf("White point error = %f deltaE\n",cterr);
5107 				printf("Maximum neutral error (@ %f) = %f deltaE\n",mnv, mnerr);
5108 				printf("Average neutral error = %f deltaE\n",anerr);
5109 				if (it < mxits && thrfail)
5110 					printf("Failed to meet target %f delta E, got worst case %f\n",errthr,failerr);
5111 				printf("Number of measurements taken = %d\n",totmeas);
5112 			}
5113 		}
5114 
5115 		/* Convert our test points into calibration curves. */
5116 		/* The call to reinit_csamp() will then convert the */
5117 		/* curves back to current test point values. */
5118 		/* This applies some level of cohesion between the test points, */
5119 		/* as well as forcing monotomicity */
5120 		if (it < mxits) {		/* If not verify pass */
5121 			mcvco *sdv[3];				/* Scattered data for mcv */
5122 
5123 			for (j = 0; j < 3; j++) {
5124 				if ((sdv[j] = malloc(sizeof(mcvco) * asgrey.no)) == NULL) {
5125 					dr->del(dr);
5126 					error("Malloc of scattered data points failed");
5127 				}
5128 			}
5129 
5130 			if (verb)
5131 				printf("Computing update to calibration curves...\n");
5132 
5133 			/* Use fixed rgb's */
5134 			for (j = 0; j < 3; j++) {
5135 				for (i = 0; i < asgrey.no; i++) {
5136 					sdv[j][i].p = asgrey.s[i].v;
5137 					sdv[j][i].v = asgrey.s[i].rgb[j];
5138 					sdv[j][i].w = 1.0;
5139 #ifdef NEVER
5140 					printf("rdac %d point %d = %f, %f\n",j,i,sdv[j][i].p,sdv[j][i].v);
5141 #endif
5142 				}
5143 			}
5144 			if (x.nat)		/* Make curve go thought white if possible */
5145 				sdv[0][rsteps-1].w = sdv[1][rsteps-1].w = sdv[2][rsteps-1].w = 10.0;
5146 
5147 			if (x.bkhack)		/* Make curve go thought black if possible */
5148 				sdv[0][0].w = sdv[1][0].w = sdv[2][0].w = 10.0;
5149 
5150 			for (j = 0; j < 3; j++)
5151 				x.rdac[j]->fit(x.rdac[j], 0, fitord, sdv[j], asgrey.no, RDAC_SMOOTH);
5152 
5153 			/* Make sure that if we are using native brightness and white point, */
5154 			/* that the curves go to a perfect 1.0 ... */
5155 			if (x.nat) {
5156 				for (j = 0; j < 3; j++)
5157 					x.rdac[j]->force_1(x.rdac[j], 1.0);
5158 			}
5159 
5160 			/* Make sure that if we are using black hack black point, */
5161 			/* that the curves go to a perfect 0.0 ... */
5162 			if (x.bkhack) {
5163 				for (j = 0; j < 3; j++)
5164 					x.rdac[j]->force_0(x.rdac[j], 0.0);
5165 			}
5166 
5167 			for (j = 0; j < 3; j++)
5168 				free(sdv[j]);
5169 #ifdef DEBUG_PLOT
5170 			/* Plot the current curves */
5171 			{
5172 				#define	XRES 255
5173 				double xx[XRES];
5174 				double y1[XRES];
5175 				double y2[XRES];
5176 				double y3[XRES];
5177 				double rgb[3];
5178 				for (i = 0; i < XRES; i++) {
5179 					double drgb[3], rgb[3];
5180 					xx[i] = i/(XRES-1.0);
5181 					rgb[0] = rgb[1] = rgb[2] = xx[i];
5182 					for (j = 0; j < 3; j++)
5183 						drgb[j] = x.rdac[j]->interp(x.rdac[j], rgb[j]);
5184 					y1[i] = drgb[0];
5185 					y2[i] = drgb[1];
5186 					y3[i] = drgb[2];
5187 				}
5188 				printf("Current ramdac curves\n");
5189 				do_plot(xx,y1,y2,y3,XRES);
5190 				#undef XRES
5191 			}
5192 #endif
5193 		}
5194 	}	/* Next refine/verify loop */
5195 
5196 	free_alloc_csamp(&asgrey);		/* We're done with test points */
5197 	dr->del(dr);	/* Now we're done with test window */
5198 
5199 	/* Write out the resulting calibration file */
5200 	if (verify != 2) {
5201 		int calres = CAL_RES;			/* steps in calibration table saved */
5202 		cgats *ocg;						/* output cgats structure */
5203 		time_t clk = time(0);
5204 		struct tm *tsp = localtime(&clk);
5205 		char *atm = asctime(tsp);		/* Ascii time */
5206 		cgats_set_elem *setel;			/* Array of set value elements */
5207 		int ncps;						/* Number of curve parameters */
5208 		double *cps[3];					/* Arrays of curve parameters */
5209 		char *bp = NULL, buf[100];		/* Buffer to sprintf into */
5210 
5211 		ocg = new_cgats();				/* Create a CGATS structure */
5212 		ocg->add_other(ocg, "CAL"); 	/* our special type is Calibration file */
5213 
5214 		ocg->add_table(ocg, tt_other, 0);	/* Add a table for RAMDAC values */
5215 		ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Device Calibration Curves",NULL);
5216 		ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll dispcal", NULL);
5217 		atm[strlen(atm)-1] = '\000';	/* Remove \n from end */
5218 		ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
5219 
5220 		ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
5221 		ocg->add_kword(ocg, 0, "COLOR_REP","RGB", NULL);
5222 		/* Tell downstream whether they can expect that this calibration */
5223 		/* will be applied in hardware or not. */
5224 		ocg->add_kword(ocg, 0, "VIDEO_LUT_CALIBRATION_POSSIBLE",noramdac ? "NO" : "YES", NULL);
5225 		/* Tell downstream whether the device range was actually (16-235)/255 */
5226 		ocg->add_kword(ocg, 0, "TV_OUTPUT_ENCODING",out_tvenc ? "YES" : "NO", NULL);
5227 
5228 		/* Put the target parameters in the CGATS file too */
5229 		if (dtype != 0) {
5230 			sprintf(buf,"%c",dtype);
5231 			ocg->add_kword(ocg, 0, "DEVICE_TYPE", buf, NULL);
5232 		}
5233 
5234 		if (wpx == 0.0 && wpy == 0.0 && temp == 0.0 && tbright == 0.0)
5235 			ocg->add_kword(ocg, 0, "NATIVE_TARGET_WHITE","", NULL);
5236 
5237 		sprintf(buf,"%f %f %f", x.twh[0], x.twh[1], x.twh[2]);
5238 		ocg->add_kword(ocg, 0, "TARGET_WHITE_XYZ",buf, NULL);
5239 
5240 		switch(x.gammat) {
5241 			case gt_power:
5242 				if (egamma > 0.0)
5243 					sprintf(buf,"%f", -egamma);
5244 				else
5245 					sprintf(buf,"%f", gamma);
5246 				break;
5247 			case gt_Lab:
5248 				strcpy(buf,"L_STAR");
5249 				break;
5250 			case gt_sRGB:
5251 				strcpy(buf,"sRGB");
5252 				break;
5253 			case gt_Rec709:
5254 				strcpy(buf,"REC709");
5255 				break;
5256 			case gt_SMPTE240M:
5257 				strcpy(buf,"SMPTE240M");
5258 				break;
5259 			default:
5260 				error("Unknown gamma type");
5261 		}
5262 		ocg->add_kword(ocg, 0, "TARGET_GAMMA",buf, NULL);
5263 
5264 		sprintf(buf,"%f", x.oofff);
5265 		ocg->add_kword(ocg, 0, "DEGREE_OF_BLACK_OUTPUT_OFFSET",buf, NULL);
5266 
5267 		sprintf(buf,"%f", bkcorrect);
5268 		ocg->add_kword(ocg, 0, "BLACK_POINT_CORRECTION", buf, NULL);
5269 
5270 		sprintf(buf,"%f", x.nbrate);
5271 		ocg->add_kword(ocg, 0, "BLACK_NEUTRAL_BLEND_RATE", buf, NULL);
5272 
5273 		if (bkbright > 0.0) {
5274 			sprintf(buf,"%f", bkbright);
5275 			ocg->add_kword(ocg, 0, "TARGET_BLACK_BRIGHTNESS",buf, NULL);
5276 		}
5277 
5278 		if (bkhack) {
5279 			ocg->add_kword(ocg, 0, "BLACK_POINT_HACK","YES", NULL);
5280 		}
5281 
5282 		/* Write rest of setup */
5283 	    switch (quality) {
5284 			case -3:				/* Test value */
5285 				bp = "ultra low";
5286 				break;
5287 			case -2:				/* Very low */
5288 				bp = "very low";
5289 				break;
5290 			case -1:				/* Low */
5291 				bp = "low";
5292 				break;
5293 			case 0:					/* Medum */
5294 				bp = "medium";
5295 				break;
5296 			case 1:					/* High */
5297 				bp = "high";
5298 				break;
5299 			case 2:					/* Ultra */
5300 				bp = "ultra high";
5301 				break;
5302 			default:
5303 				error("unknown quality level %d",quality);
5304 		}
5305 		ocg->add_kword(ocg, 0, "QUALITY",bp, NULL);
5306 
5307 		ocg->add_field(ocg, 0, "RGB_I", r_t);
5308 		ocg->add_field(ocg, 0, "RGB_R", r_t);
5309 		ocg->add_field(ocg, 0, "RGB_G", r_t);
5310 		ocg->add_field(ocg, 0, "RGB_B", r_t);
5311 
5312 		if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * 4)) == NULL)
5313 			error("Malloc failed!");
5314 
5315 		/* Write the video lut curve values */
5316 		for (i = 0; i < calres; i++) {
5317 			double vv, rgb[3];
5318 
5319 #if defined(__APPLE__) && defined(__POWERPC__)
5320 			gcc_bug_fix(i);
5321 #endif
5322 			vv = i/(calres-1.0);
5323 			for (j = 0; j < 3; j++) {
5324 				double cc;
5325 				cc = x.rdac[j]->interp(x.rdac[j], vv);
5326 				if (cc < 0.0)
5327 					cc = 0.0;
5328 				else if (cc > 1.0)
5329 					cc = 1.0;
5330 				rgb[j] = cc;
5331 			}
5332 
5333 			setel[0].d = vv;
5334 			setel[1].d = rgb[0];
5335 			setel[2].d = rgb[1];
5336 			setel[3].d = rgb[2];
5337 
5338 			ocg->add_setarr(ocg, 0, setel);
5339 		}
5340 
5341 		free(setel);
5342 
5343 		/* Write some of the device model information to a second */
5344 		/* table, so that we can update the calibration latter on without */
5345 		/* having to read R,G & B curves. */
5346 
5347 		ocg->add_table(ocg, tt_other, 0);	/* Add a second table for setup and model */
5348 		ocg->add_kword(ocg, 1, "DESCRIPTOR", "Argyll Calibration options and model",NULL);
5349 		ocg->add_kword(ocg, 1, "ORIGINATOR", "Argyll dispcal", NULL);
5350 		atm[strlen(atm)-1] = '\000';	/* Remove \n from end */
5351 		ocg->add_kword(ocg, 1, "CREATED",atm, NULL);
5352 
5353 
5354 		/* Write device model curves */
5355 		ocg->add_field(ocg, 1, "R_P", r_t);
5356 		ocg->add_field(ocg, 1, "G_P", r_t);
5357 		ocg->add_field(ocg, 1, "B_P", r_t);
5358 
5359 		if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * 3)) == NULL)
5360 			error("Malloc failed!");
5361 
5362 		ncps = -1;
5363 		for (i = 0; i < 3; i++) {
5364 			int nn;
5365 			nn = x.dcvs[i]->get_params(x.dcvs[i], &cps[i]);
5366 			if (ncps != -1 && ncps != nn)
5367 				error("Expect device model linearisation curves to have the same order");
5368 			ncps = nn;
5369 		}
5370 
5371 		for (i = 0; i < ncps; i++) {
5372 			setel[0].d = cps[0][i];
5373 			setel[1].d = cps[1][i];
5374 			setel[2].d = cps[2][i];
5375 			ocg->add_setarr(ocg, 1, setel);
5376 		}
5377 
5378 		for (i = 0; i < 3; i++)
5379 			free(cps[i]);
5380 		free(setel);
5381 
5382 		if (ocg->write_name(ocg, outname))
5383 			error("Write error : %s",ocg->err);
5384 
5385 		if (verb)
5386 			printf("Written calibration file '%s'\n",outname);
5387 
5388 		ocg->del(ocg);		/* Clean up */
5389 
5390 	}
5391 
5392 	/* Update the ICC file with the new 'vcgt' curves */
5393 	if (verify != 2 && doupdate && doprofile) {
5394 		icmFile *ic_fp;
5395 		icc *icco;
5396 		int j, i;
5397 		icmVideoCardGamma *wo;
5398 
5399 		if ((icco = new_icc()) == NULL)
5400 			error("Creation of ICC object to read profile '%s' failed",iccoutname);
5401 
5402 		/* Open up the profile for reading */
5403 		if ((ic_fp = new_icmFileStd_name(iccoutname,"r")) == NULL)
5404 			error("Can't open file '%s'",iccoutname);
5405 
5406 		/* Read header etc. */
5407 		if ((rv = icco->read(icco,ic_fp,0)) != 0)
5408 			error("Reading profile '%s' failed with %d, %s",iccoutname, rv,icco->err);
5409 
5410 		/* Read every tag */
5411 		if (icco->read_all_tags(icco) != 0) {
5412 			error("Unable to read all tags from '%s': %d, %s",iccoutname, icco->errc,icco->err);
5413 		}
5414 
5415 		ic_fp->del(ic_fp);
5416 
5417 		wo = (icmVideoCardGamma *)icco->read_tag(icco, icSigVideoCardGammaTag);
5418 		if (wo == NULL)
5419 			error("Can't find VideoCardGamma tag in file '%s': %d, %s",
5420 			      iccoutname, icco->errc,icco->err);
5421 
5422 		wo->tagType = icmVideoCardGammaTableType;
5423 		wo->u.table.channels = 3;			/* rgb */
5424 		wo->u.table.entryCount = CAL_RES;	/* full lut */
5425 		wo->u.table.entrySize = 2;			/* 16 bits */
5426 		wo->allocate((icmBase*)wo);
5427 		for (j = 0; j < 3; j++) {
5428 			for (i = 0; i < CAL_RES; i++) {
5429 				double cc, vv;
5430 #if defined(__APPLE__) && defined(__POWERPC__)
5431 				gcc_bug_fix(i);
5432 #endif
5433 				vv = i/(CAL_RES-1.0);
5434 
5435 				cc = x.rdac[j]->interp(x.rdac[j], vv);
5436 
5437 				if (cc < 0.0)
5438 					cc = 0.0;
5439 				else if (cc > 1.0)
5440 					cc = 1.0;
5441 				if (out_tvenc) {
5442 					cc = (cc * (235.0-16.0) + 16.0)/255.0;
5443 
5444 					/* For video encoding the extra bits of precision are created by bit shifting */
5445 					/* rather than scaling, so we need to scale the fp value to account for this. */
5446 					/* We assume the precision is the vcgt table size = 16 */
5447 					/* ~~99 ideally we should tag the fact that this is video encoded, so that */
5448 					/* the vcgt loaded can adjust for a different bit precision ~~~~ */
5449 					cc = (cc * 255 * (1 << (16 - 8)))/((1 << 16) - 1.0);
5450 				}
5451 				((unsigned short*)wo->u.table.data)[CAL_RES * j + i] = (int)(cc * 65535.0 + 0.5);
5452 			}
5453 		}
5454 
5455 		/* Open up the profile again writing */
5456 		if ((ic_fp = new_icmFileStd_name(iccoutname,"w")) == NULL)
5457 			error("Can't open file '%s' for writing",iccoutname);
5458 
5459 		if ((rv = icco->write(icco,ic_fp,0)) != 0)
5460 			error("Write to file '%s' failed: %d, %s",iccoutname, rv,icco->err);
5461 
5462 		if (verb)
5463 			printf("Updated profile '%s'\n",iccoutname);
5464 
5465 		ic_fp->del(ic_fp);
5466 		icco->del(icco);
5467 
5468 	/* Create a fast matrix/shaper profile */
5469 	/*
5470 	     [ Another way of doing this would be to run all the
5471 	     measured points through the calibration curves, and
5472 	     then re-fit the curve/matrix to the calibrated points.
5473 	     This might be more accurate ?]
5474 
5475 		 Ideally we should also re-measure primaries through calibration
5476 		 rather than computing the calibrated values ?
5477 
5478 	 */
5479 
5480 	} else if (verify != 2 && doprofile) {
5481 		icmFile *wr_fp;
5482 		icc *wr_icco;
5483 		double uwp[3];		/* Absolute Uncalibrated White point in XYZ */
5484 		double wp[3];		/* Absolute White point in XYZ */
5485 		double bp[3];		/* Absolute Black point in XYZ */
5486 		double mat[3][3];	/* Device to XYZ matrix */
5487 		double calrgb[3];	/* 1.0 through calibration curves */
5488 		double clrgb[3];	/* 1.0 through calibration and linearization */
5489 
5490 		/* Open up the file for writing */
5491 		if ((wr_fp = new_icmFileStd_name(iccoutname,"w")) == NULL)
5492 			error("Write: Can't open file '%s'",iccoutname);
5493 
5494 		if ((wr_icco = new_icc()) == NULL)
5495 			error("Write: Creation of ICC object failed");
5496 
5497 		/* Set the header: */
5498 		{
5499 			icmHeader *wh = wr_icco->header;
5500 
5501 			/* Values that must be set before writing */
5502 			wh->deviceClass     = icSigDisplayClass;
5503 			wh->colorSpace      = icSigRgbData;				/* Display is RGB */
5504 			wh->pcs         = icSigXYZData;					/* XYZ for matrix based profile */
5505 			wh->renderingIntent = icRelativeColorimetric;	/* For want of something */
5506 
5507 			wh->manufacturer = icmSigUnknownType;
5508 	    	wh->model        = icmSigUnknownType;
5509 #ifdef NT
5510 			wh->platform = icSigMicrosoft;
5511 #endif
5512 #ifdef UNIX_APPLE
5513 			wh->platform = icSigMacintosh;
5514 #endif
5515 #if defined(UNIX_X11)
5516 			wh->platform = icmSig_nix;
5517 #endif
5518 		}
5519 
5520 		/* Lookup white and black points */
5521 		{
5522 			int j;
5523 			double rgb[3];
5524 
5525 			calrgb[0] = calrgb[1] = calrgb[2] = 1.0;
5526 
5527 			fwddev(&x, uwp, calrgb);		/* absolute uncalibrated WP (native white point) */
5528 
5529 //printf("~1 native abs white point XYZ %f %f %f\n", uwp[0], uwp[1], uwp[2]);
5530 
5531 			/* RGB 1.0 Through calibration */
5532 			for (j = 0; j < 3; j++) {
5533 				calrgb[j] = x.rdac[j]->interp(x.rdac[j], calrgb[j]);
5534 				if (calrgb[j] < 0.0)
5535 					calrgb[j] = 0.0;
5536 				else if (calrgb[j] > 1.0)
5537 					calrgb[j] = 1.0;
5538 			}
5539 			fwddev(&x, wp, calrgb);		/* absolute calibrated WP */
5540 //printf("~1 calibrated rgb = %f %f %f\n", calrgb[0], calrgb[1], calrgb[2]);
5541 //printf("~1 calibrated abs white point XYZ %f %f %f\n", wp[0], wp[1], wp[2]);
5542 
5543 			for (j = 0; j < 3; j++)
5544 				clrgb[j] = x.dcvs[j]->interp(x.dcvs[j], calrgb[j]);
5545 //printf("~1 cal & lin rgb = %f %f %f\n", clrgb[0], clrgb[1], clrgb[2]);
5546 
5547 			rgb[0] = rgb[1] = rgb[2] = 0.0;
5548 
5549 			/* RGB 0.0 through calibration */
5550 			for (j = 0; j < 3; j++) {
5551 				rgb[j] = x.rdac[j]->interp(x.rdac[j], rgb[j]);
5552 				if (rgb[j] < 0.0)
5553 					rgb[j] = 0.0;
5554 				else if (rgb[j] > 1.0)
5555 					rgb[j] = 1.0;
5556 			}
5557 			fwddev(&x, bp, rgb);		/* Absolute calibrated BP */
5558 		}
5559 
5560 		/* Apply calibration to matrix, and then adjust it to be */
5561 		/* relative to D50 white point, rather than absolute. */
5562 		{
5563 			double rgb[3];
5564 			icmXYZNumber swp;
5565 
5566 			/* Transfer from parameter to matrix */
5567 			icmCpy3x3(mat, x.fm);
5568 
5569 			/* Compute the calibrated matrix values so that the curves */
5570 			/* device curves end at 1.0. */
5571 
5572 			/* In the HW calibrated case this represents the lower XYZ due to */
5573 			/* the HW calibrated lower RGB values of white compared to the raw */
5574 			/* model response, so that the calibration curve concatentation with the */
5575 			/* device curves can be scaled up to end at 1.0. */
5576 			if (noramdac == 0) {
5577 				for (j = 0; j < 3; j++) {
5578 					for (i = 0; i < 3; i++)
5579 						rgb[i] = 0.0;
5580 					rgb[j] = clrgb[j];
5581 					icmMulBy3x3(rgb, x.fm, rgb);	/* clrgb -> matrix -> RGB */
5582 					for (i = 0; i < 3; i++)
5583 						mat[i][j] = rgb[i];
5584 				}
5585 #ifdef NEVER
5586 				{
5587 					double rgb[3], xyz[3], lab[3];
5588 
5589 					rgb[0] = rgb[1] = rgb[2] = 1.0;
5590 					icmMulBy3x3(xyz, mat, rgb);
5591 					icmXYZ2Lab(&icmD50, lab, xyz);
5592 
5593 					printf("RGB 1 through matrix = XYZ %f %f %f, Lab %f %f %f\n", xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
5594 				}
5595 #endif
5596 				/* Chromatic Adaptation matrix */
5597 				icmAry2XYZ(swp, wp);
5598 				wr_icco->chromAdaptMatrix(wr_icco, ICM_CAM_MULMATRIX, NULL, mat, icmD50, swp);
5599 #ifdef NEVER
5600 				{
5601 					double rgb[3], xyz[3], lab[3];
5602 
5603 					rgb[0] = rgb[1] = rgb[2] = 1.0;
5604 					icmMulBy3x3(xyz, mat, rgb);
5605 					icmXYZ2Lab(&icmD50, lab, xyz);
5606 
5607 					printf("RGB 1 through chrom matrix = XYZ %f %f %f, Lab %f %f %f\n", xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
5608 				}
5609 #endif
5610 
5611 			/* For the calibration incororated in profile case, we should boost the */
5612 			/* XYZ by 1/calrgb[] so that the lower calibrated RGB values results in the native */
5613 			/* white point, but we want to reduce it by callinrgb[] to move from the native */
5614 			/* white point to the calibrated white point. */
5615 			} else {
5616 				icmCpy3x3(mat, x.fm);
5617 
5618 				for (j = 0; j < 3; j++) {
5619 					for (i = 0; i < 3; i++)
5620 						rgb[i] = 0.0;
5621 					rgb[j] = clrgb[j]/calrgb[j];
5622 					icmMulBy3x3(rgb, x.fm, rgb);	/* 1/calrgb -> matrix -> RGB */
5623 					for (i = 0; i < 3; i++)
5624 						mat[i][j] = rgb[i];
5625 				}
5626 #ifdef NEVER
5627 				{
5628 					double rgb[3], xyz[3], lab[3];
5629 
5630 					for (j = 0; j < 3; j++)
5631 						rgb[j] = calrgb[j];
5632 					icmMulBy3x3(xyz, mat, rgb);
5633 					icmXYZ2Lab(&icmD50, lab, xyz);
5634 
5635 					printf("RGB cal through matrix = XYZ %f %f %f, Lab %f %f %f\n", xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
5636 				}
5637 #endif
5638 				/* Chromatic Adaptation matrix */
5639 				icmAry2XYZ(swp, wp);
5640 				wr_icco->chromAdaptMatrix(wr_icco, ICM_CAM_MULMATRIX, NULL, mat, icmD50, swp);
5641 #ifdef NEVER
5642 				{
5643 					double rgb[3], xyz[3], lab[3];
5644 
5645 					icmMulBy3x3(xyz, mat, calrgb);
5646 					icmXYZ2Lab(&icmD50, lab, xyz);
5647 
5648 					printf("RGB cal through chrom matrix = XYZ %f %f %f, Lab %f %f %f\n", xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
5649 				}
5650 #endif
5651 			}
5652 		}
5653 
5654 		/* Add all the other tags required */
5655 
5656 		/* Profile Description Tag: */
5657 		{
5658 			icmTextDescription *wo;
5659 			char *dst, dstm[200];			/* description */
5660 
5661 			if (profDesc != NULL)
5662 				dst = profDesc;
5663 			else {
5664 				dst = iccoutname;
5665 			}
5666 
5667 			if ((wo = (icmTextDescription *)wr_icco->add_tag(
5668 			           wr_icco, icSigProfileDescriptionTag,	icSigTextDescriptionType)) == NULL)
5669 				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
5670 
5671 			wo->size = strlen(dst)+1; 	/* Allocated and used size of desc, inc null */
5672 			wo->allocate((icmBase *)wo);/* Allocate space */
5673 			strcpy(wo->desc, dst);		/* Copy the string in */
5674 		}
5675 		/* Copyright Tag: */
5676 		{
5677 			icmText *wo;
5678 			char *crt;
5679 
5680 			if (copyright != NULL)
5681 				crt = copyright;
5682 			else
5683 				crt = "Copyright, the creator of this profile";
5684 
5685 			if ((wo = (icmText *)wr_icco->add_tag(
5686 			           wr_icco, icSigCopyrightTag,	icSigTextType)) == NULL)
5687 				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
5688 
5689 			wo->size = strlen(crt)+1; 	/* Allocated and used size of text, inc null */
5690 			wo->allocate((icmBase *)wo);/* Allocate space */
5691 			strcpy(wo->data, crt);		/* Copy the text in */
5692 		}
5693 		/* Device Manufacturers Description Tag: */
5694 		if (deviceMfgDesc != NULL) {
5695 			icmTextDescription *wo;
5696 			char *dst = deviceMfgDesc;
5697 
5698 			if ((wo = (icmTextDescription *)wr_icco->add_tag(
5699 			           wr_icco, icSigDeviceMfgDescTag,	icSigTextDescriptionType)) == NULL)
5700 				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
5701 
5702 			wo->size = strlen(dst)+1; 	/* Allocated and used size of desc, inc null */
5703 			wo->allocate((icmBase *)wo);/* Allocate space */
5704 			strcpy(wo->desc, dst);		/* Copy the string in */
5705 		}
5706 		/* Model Description Tag: */
5707 		if (modelDesc != NULL) {
5708 			icmTextDescription *wo;
5709 			char *dst = modelDesc;
5710 
5711 			if ((wo = (icmTextDescription *)wr_icco->add_tag(
5712 			           wr_icco, icSigDeviceModelDescTag,	icSigTextDescriptionType)) == NULL)
5713 				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
5714 
5715 			wo->size = strlen(dst)+1; 	/* Allocated and used size of desc, inc null */
5716 			wo->allocate((icmBase *)wo);/* Allocate space */
5717 			strcpy(wo->desc, dst);		/* Copy the string in */
5718 		}
5719 		/* Luminance tag */
5720 		{
5721 			icmXYZArray *wo;;
5722 
5723 			if ((wo = (icmXYZArray *)wr_icco->add_tag(
5724 			           wr_icco, icSigLuminanceTag, icSigXYZArrayType)) == NULL)
5725 				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
5726 
5727 			/* (Only Y is used according to the ICC spec.) */
5728 			wo->size = 1;
5729 			wo->allocate((icmBase *)wo);	/* Allocate space */
5730 			wo->data[0].X = 0.0;
5731 			wo->data[0].Y = dispLum * wp[1]/uwp[1];	/* Adjust for effect of calibration */
5732 			wo->data[0].Z = 0.0;
5733 
5734 			if (verb)
5735 				printf("Luminance XYZ = %f %f %f\n", wo->data[0].X, wo->data[0].Y, wo->data[0].Z);
5736 		}
5737 		/* White Point Tag: */
5738 		{
5739 			icmXYZArray *wo;
5740 			/* Note that tag types icSigXYZType and icSigXYZArrayType are identical */
5741 			if ((wo = (icmXYZArray *)wr_icco->add_tag(
5742 			           wr_icco, icSigMediaWhitePointTag, icSigXYZArrayType)) == NULL)
5743 				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
5744 
5745 			wo->size = 1;
5746 			wo->allocate((icmBase *)wo);	/* Allocate space */
5747 			wo->data[0].X = wp[0] * 1.0/wp[1];
5748 			wo->data[0].Y = wp[1] * 1.0/wp[1];
5749 			wo->data[0].Z = wp[2] * 1.0/wp[1];
5750 
5751 			if (verb)
5752 				printf("White point XYZ = %f %f %f\n", wo->data[0].X, wo->data[0].Y, wo->data[0].Z);
5753 		}
5754 		/* Black Point Tag: */
5755 		{
5756 			icmXYZArray *wo;
5757 			if ((wo = (icmXYZArray *)wr_icco->add_tag(
5758 			           wr_icco, icSigMediaBlackPointTag, icSigXYZArrayType)) == NULL)
5759 				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
5760 
5761 			wo->size = 1;
5762 			wo->allocate((icmBase *)wo);	/* Allocate space */
5763 			wo->data[0].X = bp[0] * 1.0/wp[1];
5764 			wo->data[0].Y = bp[1] * 1.0/wp[1];
5765 			wo->data[0].Z = bp[2] * 1.0/wp[1];
5766 
5767 			if (verb)
5768 				printf("Black point XYZ = %f %f %f\n", wo->data[0].X, wo->data[0].Y, wo->data[0].Z);
5769 		}
5770 
5771 		/* vcgt tag, if the display has an accessible VideoLUT */
5772 		if (noramdac == 0) {
5773 			int j, i;
5774 			icmVideoCardGamma *wo;
5775 			wo = (icmVideoCardGamma *)wr_icco->add_tag(wr_icco,
5776 			                           icSigVideoCardGammaTag, icSigVideoCardGammaType);
5777 			if (wo == NULL)
5778 				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
5779 
5780 			wo->tagType = icmVideoCardGammaTableType;
5781 			wo->u.table.channels = 3;				/* rgb */
5782 			wo->u.table.entryCount = CAL_RES;		/* full lut */
5783 			wo->u.table.entrySize = 2;				/* 16 bits */
5784 			wo->allocate((icmBase*)wo);
5785 			for (j = 0; j < 3; j++) {
5786 				for (i = 0; i < CAL_RES; i++) {
5787 					double cc, vv = i/(CAL_RES-1.0);
5788 					cc = x.rdac[j]->interp(x.rdac[j], vv);
5789 					if (cc < 0.0)
5790 						cc = 0.0;
5791 					else if (cc > 1.0)
5792 						cc = 1.0;
5793 					if (out_tvenc) {
5794 						cc = (cc * (235.0-16.0) + 16.0)/255.0;
5795 						/* For video encoding the extra bits of precision are created by bit */
5796 						/* shifting rather than scaling, so we need to scale the fp value to */
5797 						/* account for this. We assume the precision is the vcgt table size = 16 */
5798 						/* ~~99 ideally we should tag the fact that this is video encoded, so */
5799 						/* that the vcgt loaded can adjust for a different bit precision ~~~~ */
5800 						cc = (cc * 255 * (1 << (16 - 8)))/((1 << 16) - 1.0);
5801 					}
5802 					((unsigned short*)wo->u.table.data)[CAL_RES * j + i] = (int)(cc * 65535.0 + 0.5);
5803 				}
5804 			}
5805 		}
5806 
5807 		/* Red, Green and Blue Colorant Tags: */
5808 		{
5809 			icmXYZArray *wor, *wog, *wob;
5810 			if ((wor = (icmXYZArray *)wr_icco->add_tag(
5811 			           wr_icco, icSigRedColorantTag, icSigXYZArrayType)) == NULL)
5812 				error("add_tag failed: %d, %s",rv,wr_icco->err);
5813 			if ((wog = (icmXYZArray *)wr_icco->add_tag(
5814 			           wr_icco, icSigGreenColorantTag, icSigXYZArrayType)) == NULL)
5815 				error("add_tag failed: %d, %s",rv,wr_icco->err);
5816 			if ((wob = (icmXYZArray *)wr_icco->add_tag(
5817 			           wr_icco, icSigBlueColorantTag, icSigXYZArrayType)) == NULL)
5818 				error("add_tag failed: %d, %s",rv,wr_icco->err);
5819 
5820 			wor->size = wog->size = wob->size = 1;
5821 			wor->allocate((icmBase *)wor);	/* Allocate space */
5822 			wog->allocate((icmBase *)wog);
5823 			wob->allocate((icmBase *)wob);
5824 
5825 			/* Make sure rounding doesn't wreck white point */
5826 			icmTranspose3x3(mat, mat);	/* Convert [XYZ][RGB] to [RGB][XYZ] */
5827 			quantizeRGBprimsS15Fixed16(mat);
5828 
5829 			wor->data[0].X = mat[0][0]; wor->data[0].Y = mat[0][1]; wor->data[0].Z = mat[0][2];
5830 			wog->data[0].X = mat[1][0]; wog->data[0].Y = mat[1][1]; wog->data[0].Z = mat[1][2];
5831 			wob->data[0].X = mat[2][0]; wob->data[0].Y = mat[2][1]; wob->data[0].Z = mat[2][2];
5832 		}
5833 
5834 		/* Red, Green and Blue Tone Reproduction Curve Tags: */
5835 		{
5836 			icmCurve *wor, *wog, *wob;
5837 			int ui;
5838 
5839 			if ((wor = (icmCurve *)wr_icco->add_tag(
5840 			           wr_icco, icSigRedTRCTag, icSigCurveType)) == NULL)
5841 				error("add_tag failed: %d, %s",rv,wr_icco->err);
5842 			if ((wog = (icmCurve *)wr_icco->add_tag(
5843 			           wr_icco, icSigGreenTRCTag, icSigCurveType)) == NULL)
5844 				error("add_tag failed: %d, %s",rv,wr_icco->err);
5845 			if ((wob = (icmCurve *)wr_icco->add_tag(
5846 			           wr_icco, icSigBlueTRCTag, icSigCurveType)) == NULL)
5847 				error("add_tag failed: %d, %s",rv,wr_icco->err);
5848 
5849 			wor->flag = wog->flag = wob->flag = icmCurveSpec;
5850 			wor->size = wog->size = wob->size = 256;			/* Number of entries */
5851 			wor->allocate((icmBase *)wor);	/* Allocate space */
5852 			wog->allocate((icmBase *)wog);
5853 			wob->allocate((icmBase *)wob);
5854 
5855 			/* For the HW calibrated case, we have lowered the matrix */
5856 			/* values to reflect the calibrated RGB through the native */
5857 			/* device model, so now we can scale up the comcatenation */
5858 			/* of the calibration and linearisation curves so that */
5859 			/* 1.0 in maps to 1.0 out. */
5860 			if (noramdac == 0) {
5861 
5862 				for (ui = 0; ui < wor->size; ui++) {
5863 					double in, rgb[3];
5864 
5865 					for (j = 0; j < 3; j++) {
5866 #if defined(__APPLE__) && defined(__POWERPC__)
5867 						gcc_bug_fix(ui);
5868 #endif
5869 						in = (double)ui / (wor->size - 1.0);
5870 
5871 						/* Transform through calibration curve */
5872 						in = x.rdac[j]->interp(x.rdac[j], in);
5873 
5874 						if (in < 0.0)
5875 							in = 0.0;
5876 						else if (in > 1.0)
5877 							in = 1.0;
5878 
5879 						/* Trandform though device model linearisation */
5880 						in = x.dcvs[j]->interp(x.dcvs[j], in);
5881 
5882 						/* Scale back so that 1.0 in gets 1.0 out */
5883 						in /= clrgb[j];
5884 
5885 
5886 						if (in < 0.0)
5887 							in = 0.0;
5888 						else if (in > 1.0)
5889 							in = 1.0;
5890 						rgb[j] = in;
5891 //printf("Step %d, Chan %d, %f -> %f\n",ui,j,(double)ui / (wor->size - 1.0),in);
5892 					}
5893 					wor->data[ui] = rgb[0];	/* Curve values 0.0 - 1.0 */
5894 					wog->data[ui] = rgb[1];
5895 					wob->data[ui] = rgb[2];
5896 				}
5897 
5898 			/* For the calibration incororated in profile case, */
5899 			/* we bypass the inverse calibration curve if it would */
5900 			/* result in saturation, and then scale the overall output */
5901 			/* back by the calrgb[] value so that the overall curve */
5902 			/* maps 1.0 to 1.0. The scaled up values in the matrix */
5903 			/* then result in a calibrated RGB input mapping to the */
5904 			/* PCS white point. */
5905 			} else {
5906 
5907 				for (ui = 0; ui < wor->size; ui++) {
5908 					double in, rgb[3];
5909 
5910 					for (j = 0; j < 3; j++) {
5911 #if defined(__APPLE__) && defined(__POWERPC__)
5912 						gcc_bug_fix(ui);
5913 #endif
5914 						in = (double)ui / (wor->size - 1.0);
5915 
5916 						/* If within the inversion range, */
5917 						/* transform through the inverse calibration curve. */
5918 						if (in < calrgb[j]) {
5919 							in = x.rdac[j]->inv_interp(x.rdac[j], in);
5920 							if (in < 0.0)
5921 								in = 0.0;
5922 							else if (in > 1.0)
5923 								in = 1.0;
5924 							/* Pass through device model linearisation. */
5925 							in = x.dcvs[j]->interp(x.dcvs[j], in);
5926 
5927 						/* Linearly extrapolate when outside inv range */
5928 						} else {
5929 							in /= calrgb[j];
5930 						}
5931 
5932 						/* Scale it back again to 0.0 to 1.0, */
5933 						/* which is compensated for by matrix scale. */
5934 						in *= calrgb[j];
5935 
5936 						if (in < 0.0)
5937 							in = 0.0;
5938 						else if (in > 1.0)
5939 							in = 1.0;
5940 						rgb[j] = in;
5941 //printf("Step %d, Chan %d, %f -> %f\n",ui,j,(double)ui / (wor->size - 1.0),in);
5942 					}
5943 					wor->data[ui] = rgb[0];	/* Curve values 0.0 - 1.0 */
5944 					wog->data[ui] = rgb[1];
5945 					wob->data[ui] = rgb[2];
5946 				}
5947 			}
5948 		}
5949 
5950 		/* Write the file (including all tags) out */
5951 		if ((rv = wr_icco->write(wr_icco,wr_fp,0)) != 0) {
5952 			error("Write file: %d, %s",rv,wr_icco->err);
5953 		}
5954 
5955 		if (verb)
5956 			printf("Created fast shaper/matrix profile '%s'\n",iccoutname);
5957 
5958 		/* Close the file */
5959 		wr_icco->del(wr_icco);
5960 		wr_fp->del(wr_fp);
5961 	}
5962 
5963 	if (verify != 2) {
5964 		for (j = 0; j < 3; j++)
5965 			x.rdac[j]->del(x.rdac[j]);
5966 
5967 		for (k = 0; k < 3; k++)
5968 			x.dcvs[k]->del(x.dcvs[k]);
5969 	}
5970 
5971 	if (x.svc != NULL) {
5972 		x.svc->del(x.svc);
5973 		x.svc = NULL;
5974 	}
5975 	if (x.dvc != NULL) {
5976 		x.dvc->del(x.svc);
5977 		x.dvc = NULL;
5978 	}
5979 
5980 	free_a_disppath(disp);
5981 	free_ccids(ccids);
5982 
5983 	return 0;
5984 }
5985 
5986 
5987