1 
2 /*
3  * Argyll Color Correction System
4  * Colorimeter Correction Matrix
5  *
6  */
7 
8 /*
9  * Author: Graeme W. Gill
10  * Date:   19/8/2010
11  *
12  * Copyright 2010 Graeme W. Gill
13  * All rights reserved.
14  *
15  * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
16  * see the License2.txt file for licencing details.
17  *
18  * NOTE though that if SALONEINSTLIB is not defined, that this file depends
19  * on other libraries that are licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3.
20  */
21 
22 
23 /*
24  * TTBD:
25  */
26 
27 #undef DEBUG
28 
29 #define verbo stdout
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <math.h>
34 #include <sys/types.h>
35 #include <time.h>
36 #ifndef SALONEINSTLIB
37 #include "numlib.h"
38 #include "icc.h"
39 #else
40 #include "numsup.h"
41 #include "sa_conv.h"
42 #endif
43 #include "cgats.h"
44 #include "disptechs.h"
45 #include "ccmx.h"
46 
47 #ifdef NT       /* You'd think there might be some standards.... */
48 # ifndef __BORLANDC__
49 #  define stricmp _stricmp
50 # endif
51 #else
52 # define stricmp strcasecmp
53 #endif
54 
55 /* Forward declarations */
56 
57 /* Utilities */
58 
59 /* Method implimentations */
60 
61 /* Write out the ccmx to a CGATS format .ccmx file */
62 /* Return nz on error */
create_ccmx_cgats(ccmx * p,cgats ** pocg)63 static int create_ccmx_cgats(
64 ccmx *p,			/* This */
65 cgats **pocg        /* return CGATS structure */
66 ) {
67 	int i, j, n;
68 	time_t clk = time(0);
69 	struct tm *tsp = localtime(&clk);
70 	char *atm = asctime(tsp); /* Ascii time */
71 	cgats *ocg;				/* CGATS structure */
72 	char buf[100];
73 
74 	atm[strlen(atm)-1] = '\000';	/* Remove \n from end */
75 
76 	/* Setup output cgats file */
77 	ocg = new_cgats();	/* Create a CGATS structure */
78 	ocg->add_other(ocg, "CCMX"); 		/* our special type is Colorimeter Correction Matrix */
79 	ocg->add_table(ocg, tt_other, 0);	/* Start the first table */
80 
81 	if (p->desc != NULL)
82 		ocg->add_kword(ocg, 0, "DESCRIPTOR", p->desc,NULL);
83 	ocg->add_kword(ocg, 0, "INSTRUMENT",p->inst, NULL);
84 	if (p->disp != NULL)
85 		ocg->add_kword(ocg, 0, "DISPLAY",p->disp, NULL);
86 
87 	ocg->add_kword(ocg, 0, "TECHNOLOGY", disptech_get_id(p->dtech)->strid,NULL);
88 
89 	if (p->cc_cbid != 0) {
90 		sprintf(buf, "%d", p->cc_cbid);
91 		ocg->add_kword(ocg, 0, "DISPLAY_TYPE_BASE_ID", buf, NULL);
92 	}
93 	if (p->refrmode >= 0)
94 		ocg->add_kword(ocg, 0, "DISPLAY_TYPE_REFRESH", p->refrmode ? "YES" : "NO", NULL);
95 	if (p->sel != NULL)
96 		ocg->add_kword(ocg, 0, "UI_SELECTORS", p->sel, NULL);
97 	if (p->ref != NULL)
98 		ocg->add_kword(ocg, 0, "REFERENCE",p->ref, NULL);
99 	if (p->oem != 0)
100 		ocg->add_kword(ocg, 0, "OEM","YES", NULL);
101 
102 	ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll ccmx", NULL);
103 	ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
104 
105 	ocg->add_kword(ocg, 0, "COLOR_REP", "XYZ", NULL);
106 
107 	/* Add fields for the matrix */
108 	ocg->add_field(ocg, 0, "XYZ_X", r_t);
109 	ocg->add_field(ocg, 0, "XYZ_Y", r_t);
110 	ocg->add_field(ocg, 0, "XYZ_Z", r_t);
111 
112 	/* Write out the matrix values */
113 	for (i = 0; i < 3; i++) {
114 		ocg->add_set(ocg, 0, p->matrix[i][0], p->matrix[i][1], p->matrix[i][2]);
115 	}
116 
117     if (pocg != NULL)
118         *pocg = ocg;
119 
120 	return 0;
121 }
122 
123 /* Write out the ccmx to a CGATS format .ccmx file */
124 /* Return nz on error */
write_ccmx(ccmx * p,char * outname)125 static int write_ccmx(
126 ccmx *p,		/* This */
127 char *outname	/* Filename to write to */
128 ) {
129 	int rv;
130 	cgats *ocg;				/* CGATS structure */
131 
132 	/* Create CGATS elements */
133 	if ((rv = create_ccmx_cgats(p, &ocg)) != 0) {
134 		return rv;
135 	}
136 
137 	/* Write it to file */
138 	if (ocg->write_name(ocg, outname)) {
139 		strcpy(p->err, ocg->err);
140 		ocg->del(ocg);		/* Clean up */
141 		return 1;
142 	}
143 	ocg->del(ocg);		/* Clean up */
144 
145 	return 0;
146 }
147 
148 /* write to a CGATS .ccmx file to a memory buffer. */
149 /* return nz on error, with message in err[] */
buf_write_ccmx(ccmx * p,unsigned char ** buf,size_t * len)150 static int buf_write_ccmx(
151 ccmx *p,
152 unsigned char **buf,		/* Return allocated buffer */
153 size_t *len					/* Return length */
154 ) {
155 	int rv;
156 	cgats *ocg;				/* CGATS structure */
157 	cgatsFile *fp;
158 
159 	/* Create CGATS elements */
160 	if ((rv = create_ccmx_cgats(p, &ocg)) != 0) {
161 		return rv;
162 	}
163 
164 	if ((fp = new_cgatsFileMem(NULL, 0)) == NULL) {
165 		strcpy(p->err, "new_cgatsFileMem failed");
166 		return 2;
167 	}
168 
169 	/* Write it to file */
170 	if (ocg->write(ocg, fp)) {
171 		strcpy(p->err, ocg->err);
172 		ocg->del(ocg);		/* Clean up */
173 		fp->del(fp);
174 		return 1;
175 	}
176 
177 	/* Get the buffer the ccmx has been written to */
178 	if (fp->get_buf(fp, buf, (size_t *)len)) {
179 		strcpy(p->err, "cgatsFileMem get_buf failed");
180 		return 2;
181 	}
182 
183 	ocg->del(ocg);		/* Clean up */
184 	fp->del(fp);
185 
186 	return 0;
187 }
188 
189 /* Read in the ccmx CGATS .ccmx file */
190 /* Return nz on error */
read_ccmx_cgats(ccmx * p,cgats * icg)191 static int read_ccmx_cgats(
192 ccmx *p,			/* This */
193 cgats *icg			/* input cgats structure */
194 ) {
195 	int i, j, n, ix;
196 	int ti;				/* Temporary CGATs index */
197 	int  spi[3];		/* CGATS indexes for each band */
198 	char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
199 
200 	if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0) {
201 		sprintf(p->err, "read_ccmx: Input file isn't a CCMX format file");
202 		return 1;
203 	}
204 	if (icg->ntables != 1) {
205 		sprintf(p->err, "Input file doesn't contain exactly one table");
206 		return 1;
207 	}
208 	if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0) {
209 		sprintf(p->err, "read_ccmx: Input file doesn't contain keyword COLOR_REP");
210 		return 1;
211 	}
212 
213 	if (strcmp(icg->t[0].kdata[ti],"XYZ") != 0) {
214 		sprintf(p->err, "read_ccmx: Input file doesn't have COLOR_REP of XYZ");
215 		return 1;
216 	}
217 
218 	if ((ti = icg->find_kword(icg, 0, "DESCRIPTOR")) >= 0) {
219 		if ((p->desc = strdup(icg->t[0].kdata[ti])) == NULL) {
220 			sprintf(p->err, "read_ccmx: malloc failed");
221 			return 2;
222 		}
223 	}
224 
225 	if ((ti = icg->find_kword(icg, 0, "INSTRUMENT")) < 0) {
226 		sprintf(p->err, "read_ccmx: Input file doesn't contain keyword INSTRUMENT");
227 		return 1;
228 	}
229 	if ((p->inst = strdup(icg->t[0].kdata[ti])) == NULL) {
230 		sprintf(p->err, "read_ccmx: malloc failed");
231 		return 2;
232 	}
233 
234 	if ((ti = icg->find_kword(icg, 0, "DISPLAY")) >= 0) {
235 		if ((p->disp = strdup(icg->t[0].kdata[ti])) == NULL) {
236 			sprintf(p->err, "read_ccmx: malloc failed");
237 			return 2;
238 		}
239 	}
240 	if ((ti = icg->find_kword(icg, 0, "TECHNOLOGY")) >= 0) {
241 		if ((p->tech = strdup(icg->t[0].kdata[ti])) == NULL) {
242 			sprintf(p->err, "read_ccmx: malloc failed");
243 			return 2;
244 		}
245 		/* Get disptech enum from standard TECHNOLOGY string */
246 		p->dtech = disptech_get_strid(p->tech)->dtech;
247 	}
248 	if (p->disp == NULL && p->tech == NULL) {
249 		sprintf(p->err, "read_ccmx: Input file doesn't contain keyword DISPLAY or TECHNOLOGY");
250 		return 1;
251 	}
252 	if ((ti = icg->find_kword(icg, 0, "DISPLAY_TYPE_REFRESH")) >= 0) {
253 		if (stricmp(icg->t[0].kdata[ti], "YES") == 0)
254 			p->refrmode = 1;
255 		else if (stricmp(icg->t[0].kdata[ti], "NO") == 0)
256 			p->refrmode = 0;
257 	} else {
258 		p->refrmode = -1;
259 	}
260 	if ((ti = icg->find_kword(icg, 0, "DISPLAY_TYPE_BASE_ID")) >= 0) {
261 		p->cc_cbid = atoi(icg->t[0].kdata[ti]);
262 	} else {
263 		p->cc_cbid = 0;
264 	}
265 
266 	if ((ti = icg->find_kword(icg, 0, "UI_SELECTORS")) >= 0) {
267 		if ((p->sel = strdup(icg->t[0].kdata[ti])) == NULL) {
268 			sprintf(p->err, "read_ccmx: malloc failed");
269 			return 2;
270 		}
271 	}
272 
273 	if ((ti = icg->find_kword(icg, 0, "REFERENCE")) >= 0) {
274 		if ((p->ref = strdup(icg->t[0].kdata[ti])) == NULL) {
275 			sprintf(p->err, "read_ccmx: malloc failed");
276 			return 2;
277 		}
278 	}
279 
280 	if ((ti = icg->find_kword(icg, 0, "OEM")) >= 0) {
281 		if ((p->ref = strdup(icg->t[0].kdata[ti])) == NULL) {
282 			sprintf(p->err, "read_ccmx: malloc failed");
283 			return 2;
284 		}
285 	}
286 
287 	if ((ti = icg->find_kword(icg, 0, "OEM")) >= 0) {
288 		if (stricmp(icg->t[0].kdata[ti], "YES") == 0)
289 			p->oem = 1;
290 		else if (stricmp(icg->t[0].kdata[ti], "NO") == 0)
291 			p->oem = 0;
292 	} else {
293 		p->oem = 0;
294 	}
295 
296 	/* Locate the fields */
297 	for (i = 0; i < 3; i++) {	/* XYZ fields */
298 		if ((spi[i] = icg->find_field(icg, 0, xyzfname[i])) < 0) {
299 			sprintf(p->err, "read_ccmx: Input file doesn't contain field %s", xyzfname[i]);
300 			return 1;
301 		}
302 		if (icg->t[0].ftype[spi[i]] != r_t) {
303 			sprintf(p->err, "read_ccmx: Input file field %s is wrong type", xyzfname[i]);
304 			return 1;
305 		}
306 	}
307 
308 	if (icg->t[0].nsets != 3) {
309 		sprintf(p->err, "read_ccmx: Input file doesn't have exactly 3 sets");
310 		return 1;
311 	}
312 
313 	/* Read the matrix values */
314 	for (ix = 0; ix < icg->t[0].nsets; ix++) {
315 		p->matrix[ix][0] = *((double *)icg->t[0].fdata[ix][spi[0]]);
316 		p->matrix[ix][1] = *((double *)icg->t[0].fdata[ix][spi[1]]);
317 		p->matrix[ix][2] = *((double *)icg->t[0].fdata[ix][spi[2]]);
318 	}
319 
320 	return 0;
321 }
322 
323 /* Read in the ccmx CGATS .ccmx file */
324 /* Return nz on error */
read_ccmx(ccmx * p,char * inname)325 static int read_ccmx(
326 ccmx *p,			/* This */
327 char *inname	/* Filename to read from */
328 ) {
329 	int rv;
330 	cgats *icg;			/* input cgats structure */
331 
332 	/* Open and look at the .ccmx */
333 	if ((icg = new_cgats()) == NULL) {		/* Create a CGATS structure */
334 		sprintf(p->err, "read_ccmx: new_cgats() failed");
335 		return 2;
336 	}
337 	icg->add_other(icg, "CCMX");		/* our special type is Model Printer Profile */
338 
339 	if (icg->read_name(icg, inname)) {
340 		strcpy(p->err, icg->err);
341 		icg->del(icg);
342 		return 1;
343 	}
344 
345 	if ((rv = read_ccmx_cgats(p, icg)) != 0) {
346 		icg->del(icg);
347 		return rv;
348 	}
349 
350 	icg->del(icg);		/* Clean up */
351 
352 	return 0;
353 }
354 
355 /* Read in the ccmx CGATS .ccmx file from a memory buffer */
356 /* Return nz on error */
buf_read_ccmx(ccmx * p,unsigned char * buf,size_t len)357 static int buf_read_ccmx(
358 ccmx *p,		/* This */
359 unsigned char *buf,
360 size_t len
361 ) {
362 	int rv;
363 	cgatsFile *fp;
364 	cgats *icg;			/* input cgats structure */
365 
366 	if ((fp = new_cgatsFileMem(buf, len)) == NULL) {
367 		strcpy(p->err, "new_cgatsFileMem failed");
368 		return 2;
369 	}
370 
371 	/* Open and look at the .ccmx file */
372 	if ((icg = new_cgats()) == NULL) {		/* Create a CGATS structure */
373 		sprintf(p->err, "read_ccmx: new_cgats() failed");
374 		fp->del(fp);
375 		return 2;
376 	}
377 	icg->add_other(icg, "CCMX");		/* our special type is Model Printer Profile */
378 
379 	if (icg->read(icg, fp)) {
380 		strcpy(p->err, icg->err);
381 		icg->del(icg);
382 		fp->del(fp);
383 		return 1;
384 	}
385 	fp->del(fp);
386 
387 	if ((rv = read_ccmx_cgats(p, icg)) != 0) {
388 		icg->del(icg);		/* Clean up */
389 		return rv;
390 	}
391 
392 	icg->del(icg);		/* Clean up */
393 
394 	return 0;
395 }
396 
397 /* Lookup an XYZ or Lab color */
xform(ccmx * p,double * out,double * in)398 static void xform(
399 ccmx *p,						/* This */
400 double *out,				/* Output XYZ */
401 double *in					/* Input XYZ */
402 ) {
403 	icmMulBy3x3(out, p->matrix, in);
404 }
405 
406 
407 /* Set the contents of the ccmx. return nz on error. */
set_ccmx(ccmx * p,char * desc,char * inst,char * disp,disptech dtech,int refrmode,int cbid,char * sel,char * refd,int oem,double mtx[3][3])408 static int set_ccmx(ccmx *p,
409 char *desc,			/* General description (optional) */
410 char *inst,			/* Instrument description to copy from */
411 char *disp,			/* Display make and model (optional) */
412 disptech dtech,		/* Display technology enum */
413 int refrmode,		/* Display refresh mode, -1 = unknown, 0 = n, 1 = yes */
414 int cbid,			/* Display type calibration base ID, 0 = unknown */
415 char *sel,			/* UI selector characters - NULL for none */
416 char *refd,			/* Reference spectrometer description (optional) */
417 int oem,			/* NZ if OEM source */
418 double mtx[3][3]	/* Transform matrix to copy from */
419 ) {
420 	if ((p->desc = desc) != NULL && (p->desc = strdup(desc)) == NULL) {
421 		sprintf(p->err, "set_ccmx: malloc failed");
422 		return 2;
423 	}
424 	if ((p->inst = inst) != NULL && (p->inst = strdup(inst)) == NULL) {
425 		sprintf(p->err, "set_ccmx: malloc failed");
426 		return 2;
427 	}
428 	if ((p->disp = disp) != NULL && (p->disp = strdup(disp)) == NULL) {
429 		sprintf(p->err, "set_ccmx: malloc failed");
430 		return 2;
431 	}
432 	p->dtech = dtech;
433 	p->refrmode = refrmode;
434 	p->cc_cbid = cbid;
435 
436 	if (sel != NULL) {
437 		if ((p->sel = strdup(sel)) == NULL) {
438 			sprintf(p->err, "set_ccmx: malloc sel failed");
439 			return 2;
440 		}
441 	}
442 	if ((p->ref = refd) != NULL && (p->ref = strdup(refd)) == NULL) {
443 		sprintf(p->err, "set_ccmx: malloc failed");
444 		return 2;
445 	}
446 
447 	p->oem = oem;
448 
449 	icmCpy3x3(p->matrix, mtx);
450 
451 	return 0;
452 }
453 
454 #ifndef SALONEINSTLIB
455 
456 /* ------------------------------------------- */
457 /* Modified version that de-weights Luminance errors by 5: */
458 /* Return the CIE94 Delta E color difference measure, squared */
wCIE94sq(double Lab0[3],double Lab1[3])459 static double wCIE94sq(double Lab0[3], double Lab1[3]) {
460 	double desq, dhsq;
461 	double dlsq, dcsq;
462 	double c12;
463 
464 	{
465 		double dl, da, db;
466 		dl = Lab0[0] - Lab1[0];
467 		dlsq = dl * dl;		/* dl squared */
468 		da = Lab0[1] - Lab1[1];
469 		db = Lab0[2] - Lab1[2];
470 
471 		/* Compute normal Lab delta E squared */
472 		desq = dlsq + da * da + db * db;
473 	}
474 
475 	{
476 		double c1, c2, dc;
477 
478 		/* Compute chromanance for the two colors */
479 		c1 = sqrt(Lab0[1] * Lab0[1] + Lab0[2] * Lab0[2]);
480 		c2 = sqrt(Lab1[1] * Lab1[1] + Lab1[2] * Lab1[2]);
481 		c12 = sqrt(c1 * c2);	/* Symetric chromanance */
482 
483 		/* delta chromanance squared */
484 		dc = c2 - c1;
485 		dcsq = dc * dc;
486 	}
487 
488 	/* Compute delta hue squared */
489 	if ((dhsq = desq - dlsq - dcsq) < 0.0)
490 		dhsq = 0.0;
491 	{
492 		double sc, sh;
493 
494 		/* Weighting factors for delta chromanance & delta hue */
495 		sc = 1.0 + 0.048 * c12;
496 		sh = 1.0 + 0.014 * c12;
497 		return 0.2 * 0.2 * dlsq + dcsq/(sc * sc) + dhsq/(sh * sh);
498 	}
499 }
500 
501 /* ------------------------------------------- */
502 
503 /* Context for optimisation callback */
504 typedef struct {
505 	int npat;
506 	double (*refs)[3];	/* Array of XYZ values from spectrometer */
507 	double (*cols)[3];	/* Array of XYZ values from colorimeter */
508 	int wix;			/* White index */
509 	icmXYZNumber wh;	/* White reference */
510 } cntx;
511 
512 
513 /* Optimisation function */
514 /* Compute the sum of delta E's squared for the */
515 /* tp will be the 9 matrix values */
516 /* It's not clear if the white weighting is advantagous or not. */
optf(void * fdata,double * tp)517 double optf(void *fdata, double *tp) {
518 	cntx *cx = (cntx *)fdata;
519 	int i;
520 	double de;
521 	double m[3][3];
522 
523 	m[0][0] = tp[0];
524 	m[0][1] = tp[1];
525 	m[0][2] = tp[2];
526 	m[1][0] = tp[3];
527 	m[1][1] = tp[4];
528 	m[1][2] = tp[5];
529 	m[2][0] = tp[6];
530 	m[2][1] = tp[7];
531 	m[2][2] = tp[8];
532 
533 	for (de = 0.0, i = 0; i < cx->npat; i++) {
534 		double tlab[3], xyz[3], lab[3];
535 		icmXYZ2Lab(&cx->wh, tlab, cx->refs[i]);
536 
537 		icmMulBy3x3(xyz, m, cx->cols[i]);
538 		icmXYZ2Lab(&cx->wh, lab, xyz);
539 
540 		if (i == cx->wix)
541 			de += cx->npat/4.0 * wCIE94sq(tlab, lab);	/* Make white weight = 1/4 all others */
542 		else
543 			de += wCIE94sq(tlab, lab);
544 //printf("~1 %d: txyz %f %f %f, tlab %f %f %f\n", i,cx->refs[i][0], cx->refs[i][1], cx->refs[i][2], tlab[0], tlab[1], tlab[2]);
545 //printf("~1 %d: xyz %f %f %f\n", i,cx->cols[i][0], cx->cols[i][1], cx->cols[i][2]);
546 //printf("~1 %d: mxyz %f %f %f, lab %f %f %f\n", i,xyz[0], xyz[1], xyz[2], lab[0], lab[1], lab[2]);
547 //printf("~1 %d: de %f\n", i,wCIE94(tlab, lab));
548 	}
549 
550 	de /= cx->npat;
551 #ifdef DEBUG
552 //	printf("~1 return values = %f\n",de);
553 #endif
554 
555 	return de;
556 }
557 
558 /* Create a ccmx from measurements. return nz on error. */
create_ccmx(ccmx * p,char * desc,char * inst,char * disp,disptech dtech,int refrmode,int cbid,char * sel,char * refd,int oem,int npat,double (* refs)[3],double (* cols)[3])559 static int create_ccmx(ccmx *p,
560 char *desc,			/* General description (optional) */
561 char *inst,			/* Instrument description to copy from */
562 char *disp,			/* Display make and model (optional) */
563 disptech dtech,		/* Display technology enum */
564 int refrmode,		/* Display refresh mode, -1 = unknown, 0 = n, 1 = yes */
565 int cbid,			/* Display type calibration base index, 0 = unknown */
566 char *sel,			/* UI selector characters - NULL for none */
567 char *refd,			/* Reference spectrometer description (optional) */
568 int oem,			/* NZ if OEM source */
569 int npat,			/* Number of samples in following arrays */
570 double (*refs)[3],	/* Array of XYZ values from spectrometer */
571 double (*cols)[3]		/* Array of XYZ values from colorimeter */
572 ) {
573 	int i, mxix;
574 	double maxy = -1e6;
575 	cntx cx;
576 	double cp[9], sa[9];
577 
578 	if ((p->desc = desc) != NULL && (p->desc = strdup(desc)) == NULL) {
579 		sprintf(p->err, "create_ccmx: malloc failed");
580 		return 2;
581 	}
582 	if ((p->inst = inst) != NULL && (p->inst = strdup(inst)) == NULL) {
583 		sprintf(p->err, "create_ccmx: malloc failed");
584 		return 2;
585 	}
586 	if ((p->disp = disp) != NULL && (p->disp = strdup(disp)) == NULL) {
587 		sprintf(p->err, "create_ccmx: malloc failed");
588 		return 2;
589 	}
590 	p->dtech = dtech;
591 	p->refrmode = refrmode;
592 	p->cc_cbid = cbid;
593 
594 	if (sel != NULL) {
595 		if ((p->sel = strdup(sel)) == NULL) {
596 			sprintf(p->err, "create_ccmx: malloc sel failed");
597 			return 2;
598 		}
599 	}
600 
601 	if ((p->ref = refd) != NULL && (p->ref = strdup(refd)) == NULL) {
602 		sprintf(p->err, "create_ccmx: malloc failed");
603 		return 2;
604 	}
605 
606 	p->oem = oem;
607 
608 	/* Find the white patch */
609 
610 	cx.npat = npat;
611 	cx.refs = refs;
612 	cx.cols = cols;
613 
614 	for (i = 0; i < npat; i++) {
615 		if (refs[i][1] > maxy) {
616 			maxy = refs[i][1];
617 			cx.wix = i;
618 		}
619 	}
620 #ifdef DEBUG
621 	printf("white = %f %f %f\n",refs[cx.wix][0],refs[cx.wix][1],refs[cx.wix][1]);
622 #endif
623 	cx.wh.X = refs[cx.wix][0];
624 	cx.wh.Y = refs[cx.wix][1];
625 	cx.wh.Z = refs[cx.wix][2];
626 
627 	/* Starting matrix */
628 	cp[0] = 1.0; cp[1] = 0.0; cp[2] = 0.0;
629 	cp[3] = 0.0; cp[4] = 1.0; cp[5] = 0.0;
630 	cp[6] = 0.0; cp[7] = 0.0; cp[8] = 1.0;
631 
632 	for (i = 0; i < 9; i++)
633 		sa[i] = 0.1;
634 
635 	if (powell(NULL, 9, cp, sa, 1e-6, 2000, optf, &cx, NULL, NULL) < 0.0) {
636 		sprintf(p->err, "create_ccmx: powell() failed");
637 		return 1;
638 	}
639 
640 	p->matrix[0][0] = cp[0];
641 	p->matrix[0][1] = cp[1];
642 	p->matrix[0][2] = cp[2];
643 	p->matrix[1][0] = cp[3];
644 	p->matrix[1][1] = cp[4];
645 	p->matrix[1][2] = cp[5];
646 	p->matrix[2][0] = cp[6];
647 	p->matrix[2][1] = cp[7];
648 	p->matrix[2][2] = cp[8];
649 
650 	/* Compute the average and max errors */
651 	p->av_err = p->mx_err = 0.0;
652 	for (i = 0; i < npat; i++) {
653 		double tlab[3], xyz[3], lab[3], de;
654 		icmXYZ2Lab(&cx.wh, tlab, cx.refs[i]);
655 		icmMulBy3x3(xyz, p->matrix, cx.cols[i]);
656 		icmXYZ2Lab(&cx.wh, lab, xyz);
657 		de = icmCIE94(tlab, lab);
658 		p->av_err += de;
659 		if (de > p->mx_err) {
660 			p->mx_err = de;
661 			mxix = i;
662 		}
663 //printf("~1 %d: de %f, tlab %f %f %f, lab %f %f %f\n",i,de,tlab[0], tlab[1], tlab[2], lab[0], lab[1], lab[2]);
664 	}
665 	p->av_err /= (double)npat;
666 
667 //printf("~1 max error is index %d\n",mxix);
668 
669 #ifdef DEBUG
670 	printf("Average error %f, max %f\n",p->av_err,p->mx_err);
671 	printf("Correction matrix is:\n");
672 	printf("  %f %f %f\n", cp[0], cp[1], cp[2]);
673 	printf("  %f %f %f\n", cp[3], cp[4], cp[5]);
674 	printf("  %f %f %f\n", cp[6], cp[7], cp[8]);
675 #endif
676 
677 	return 0;
678 }
679 
680 #else /* SALONEINSTLIB */
681 
682 /* Create a ccmx from measurements. return nz on error. */
create_ccmx(ccmx * p,char * desc,char * inst,char * disp,disptech dtech,int refrmode,int cbid,char * sel,char * refd,int oem,int npat,double (* refs)[3],double (* cols)[3])683 static int create_ccmx(ccmx *p,
684 char *desc,			/* General description (optional) */
685 char *inst,			/* Instrument description to copy from */
686 char *disp,			/* Display make and model (optional) */
687 disptech dtech,		/* Display technology enum */
688 int refrmode,		/* Display refresh mode, -1 = unknown, 0 = n, 1 = yes */
689 int cbid,			/* Display type calibration base index, 0 = unknown */
690 char *sel,			/* UI selector characters - NULL for none */
691 char *refd,			/* Reference spectrometer description (optional) */
692 int oem,			/* NZ if OEM source */
693 int npat,			/* Number of samples in following arrays */
694 double (*refs)[3],	/* Array of XYZ values from spectrometer */
695 double (*cols)[3]		/* Array of XYZ values from colorimeter */
696 ) {
697 	sprintf(p->err, "create_ccmx: not implemented in ccmx.c");
698 	return 1;
699 }
700 
701 #endif /* SALONEINSTLIB */
702 
703 /* Delete it */
del_ccmx(ccmx * p)704 static void del_ccmx(ccmx *p) {
705 	if (p != NULL) {
706 		if (p->desc != NULL)
707 			free(p->desc);
708 		if (p->inst != NULL)
709 			free(p->inst);
710 		if (p->disp != NULL)
711 			free(p->disp);
712 		if (p->tech != NULL)
713 			free(p->tech);
714 		if (p->sel != NULL)
715 			free(p->sel);
716 		if (p->ref != NULL)
717 			free(p->ref);
718 		free(p);
719 	}
720 }
721 
722 /* Allocate a new, uninitialised ccmx */
723 /* Note thate black and white points aren't allocated */
new_ccmx(void)724 ccmx *new_ccmx(void) {
725 	ccmx *p;
726 
727 	if ((p = (ccmx *)calloc(1, sizeof(ccmx))) == NULL)
728 		return NULL;
729 
730 	p->refrmode = -1;
731 	p->cc_cbid = 0;
732 
733 	/* Init method pointers */
734 	p->del            = del_ccmx;
735 	p->set_ccmx       = set_ccmx;
736 	p->create_ccmx    = create_ccmx;
737 	p->write_ccmx     = write_ccmx;
738 	p->buf_write_ccmx = buf_write_ccmx;
739 	p->read_ccmx      = read_ccmx;
740 	p->buf_read_ccmx  = buf_read_ccmx;
741 	p->xform          = xform;
742 
743 	return p;
744 }
745 
746 
747 
748 
749 
750 
751 
752 
753 
754 
755 
756 
757 
758 
759 
760 
761 
762 
763 
764 
765 
766 
767 
768 
769 
770 
771