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