1 
2  /* Standardized display types */
3 
4  /* Standardized display types for use with libinst */
5 
6 /*
7  * Argyll Color Correction System
8  *
9  * Author: Graeme W. Gill
10  * Date:   14/5/2014
11  *
12  * Copyright 2014 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  */
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <ctype.h>
23 #include <string.h>
24 #include <time.h>
25 #include "numsup.h"
26 #ifndef SALONEINSTLIB
27 #include "copyright.h"
28 #include "aconfig.h"
29 #include "icc.h"
30 #else
31 #include "sa_config.h"
32 #include "sa_conv.h"
33 #endif /* !SALONEINSTLIB */
34 #include "conv.h"
35 #include "disptechs.h"
36 
37 /* Other selection characters used,
38    that shouldn't be used in the disptech_info_array[] entries :
39 
40 	"n"		Non-refresh (Generic)
41 	"r"		Refresh (Generic)
42 	"F"		Factory base calibration
43 	"R"		Raw sensor values
44 	"g"		Generic
45 
46   oemarch:
47 	"C"		CMF
48 	"U"		Custom
49   kleink10:
50 	"P"		DLP projector using ambient
51 	"E"		SMPTE C
52 	"P"		Klein DLP Lux
53 	"d"		Klein LED Bk LCD
54 	"O"		Sony EL OLED
55 	"z"		Eizo CG LCD
56  */
57 
58 /* We deliberately duplicate the selection characters, */
59 /* because it's not usual to offer the whole list, just */
60 /* a sub-set which may not clash. */
61 /* disptechs_set_sel() should be used to present */
62 /* unique selectors. */
63 static disptech_info disptech_info_array[] = {
64 	{
65 		disptech_none,				/* Not applicable entry. Must be first */
66 		"None",						/* because disptech_get_list() assumes so */
67 		"None",
68 		0,
69 		0.001,
70 		0.001,
71 		NULL
72 	},
73 
74 	{
75 		disptech_crt,
76 		"CRT",
77 		"CRT",
78 		1,
79 		DISPTECH_CRT_RISE,
80 		DISPTECH_CRT_FALL,
81 		"c"
82 	},
83 	{
84 		disptech_plasma,
85 		"Plasma",
86 		"Plasma",
87 		1,
88 		DISPTECH_CRT_RISE,
89 		DISPTECH_CRT_FALL,
90 		"m"
91 	},
92 
93 	{
94 		disptech_lcd,
95 		"LCD",
96 		"LCD",
97 		0,
98 		DISPTECH_LCD_RISE,
99 		DISPTECH_LCD_FALL,
100 		"l"
101 	},
102 	{
103 		disptech_lcd_ccfl,
104 		"LCD CCFL",
105 		"LCD CCFL",
106 		0,
107 		DISPTECH_LCD_RISE,
108 		DISPTECH_LCD_FALL,
109 		"l"
110 	},
111 	{
112 		disptech_lcd_ccfl_ips,
113 		"LCD CCFL IPS",
114 		"LCD CCFL IPS",
115 		0,
116 		DISPTECH_LCD_RISE,
117 		DISPTECH_LCD_FALL,
118 		"l"
119 	},
120 	{	disptech_lcd_ccfl_vpa,
121 		"LCD CCFL VPA",
122 		"LCD CCFL VPA",
123 		0,
124 		DISPTECH_LCD_RISE,
125 		DISPTECH_LCD_FALL,
126 		"l"
127 	},
128 	{	disptech_lcd_ccfl_tft,
129 		"LCD CCFL TFT",
130 		"LCD CCFL TFT",
131 		0,
132 		DISPTECH_LCD_RISE,
133 		DISPTECH_LCD_FALL,
134 		"l"
135 
136 	},
137 	{	disptech_lcd_ccfl_wg,
138 		"LCD CCFL Wide Gamut",
139 		"LCD CCFL Wide Gamut",
140 		0,
141 		DISPTECH_LCD_RISE,
142 		DISPTECH_LCD_FALL,
143 		"L"
144 	},
145 	{	disptech_lcd_ccfl_wg_ips,
146 		"LCD CCFL Wide Gamut IPS",
147 		"LCD CCFL Wide Gamut IPS",
148 		0,
149 		DISPTECH_LCD_RISE,
150 		DISPTECH_LCD_FALL,
151 		"L"
152 	},
153 	{	disptech_lcd_ccfl_wg_vpa,
154 		"LCD CCFL Wide Gamut VPA",
155 		"LCD CCFL Wide Gamut VPA",
156 		0,
157 		DISPTECH_LCD_RISE,
158 		DISPTECH_LCD_FALL,
159 		"L"
160 	},
161 	{	disptech_lcd_ccfl_wg_tft,
162 		"LCD CCFL Wide Gamut TFT",
163 		"LCD CCFL Wide Gamut TFT",
164 		0,
165 		DISPTECH_LCD_RISE,
166 		DISPTECH_LCD_FALL,
167 		"L"
168 	},
169 	{	disptech_lcd_wled,
170 		"LCD White LED",
171 		"LCD White LED",
172 		0,
173 		DISPTECH_LCD_RISE,
174 		DISPTECH_LCD_FALL,
175 		"e"
176 	},
177 	{	disptech_lcd_wled_ips,
178 		"LCD White LED IPS",
179 		"LCD White LED IPS",
180 		0,
181 		DISPTECH_LCD_RISE,
182 		DISPTECH_LCD_FALL,
183 		"e"
184 	},
185 	{	disptech_lcd_wled_vpa,
186 		"LCD White LED VPA",
187 		"LCD White LED VPA",
188 		0,
189 		DISPTECH_LCD_RISE,
190 		DISPTECH_LCD_FALL,
191 		"e"
192 	},
193 	{	disptech_lcd_wled_tft,
194 		"LCD White LED TFT",
195 		"LCD White LED TFT",
196 		0,
197 		DISPTECH_LCD_RISE,
198 		DISPTECH_LCD_FALL,
199 		"e"
200 	},
201 	{	disptech_lcd_rgbled,
202 		"LCD RGB LED",
203 		"LCD RGB LED",
204 		0,
205 		DISPTECH_LCD_RISE,
206 		DISPTECH_LCD_FALL,
207 		"b"
208 	},
209 	{	disptech_lcd_rgbled_ips,
210 		"LCD RGB LED IPS",
211 		"LCD RGB LED IPS",
212 		0,
213 		DISPTECH_LCD_RISE,
214 		DISPTECH_LCD_FALL,
215 		"b"
216 	},
217 	{	disptech_lcd_rgbled_vpa,
218 		"LCD RGB LED VPA",
219 		"LCD RGB LED VPA",
220 		0,
221 		DISPTECH_LCD_RISE,
222 		DISPTECH_LCD_FALL,
223 		"b"
224 	},
225 	{	disptech_lcd_rgbled_tft,
226 		"LCD RGB LED TFT",
227 		"LCD RGB LED TFT",
228 		0,
229 		DISPTECH_LCD_RISE,
230 		DISPTECH_LCD_FALL,
231 		"b"
232 
233 	},
234 	{	disptech_lcd_rgledp,
235 		"LCD RG Phosphor",
236 		"LCD RG Phosphor",
237 		0,
238 		DISPTECH_LCD_RISE,
239 		DISPTECH_LCD_FALL,
240 		"h"
241 	},
242 	{	disptech_lcd_rgledp_ips,
243 		"LCD RG Phosphor IPS",
244 		"LCD RG Phosphor IPS",
245 		0,
246 		DISPTECH_LCD_RISE,
247 		DISPTECH_LCD_FALL,
248 		"h"
249 	},
250 	{	disptech_lcd_rgledp_vpa,
251 		"LCD RG Phosphor VPA",
252 		"LCD RG Phosphor VPA",
253 		0,
254 		DISPTECH_LCD_RISE,
255 		DISPTECH_LCD_FALL,
256 		"h"
257 	},
258 	{	disptech_lcd_rgledp_tft,
259 		"LCD RG Phosphor TFT",
260 		"LCD RG Phosphor TFT",
261 		0,
262 		DISPTECH_LCD_RISE,
263 		DISPTECH_LCD_FALL,
264 		"h"
265 	},
266 
267 	{	disptech_oled,
268 		"LED OLED",
269 		"LED OLED",
270 		0,
271 		DISPTECH_LED_RISE,
272 		DISPTECH_LED_FALL,
273 		"o"
274 	},
275 	{	disptech_amoled,
276 		"LED AMOLED",
277 		"LED AMOLED",
278 		0,
279 		DISPTECH_LED_RISE,
280 		DISPTECH_LED_FALL,
281 		"a"
282 	},
283 
284 	{	disptech_dlp,
285 		"Projector",
286 		"DLP Projector",
287 		1,
288 		DISPTECH_DLP_RISE,
289 		DISPTECH_DLP_FALL,
290 		"p"
291 	},
292 	{	disptech_dlp_rgb,
293 		"Projector RGB Filter Wheel",
294 		"DLP Projector RGB Filter Wheel",
295 		1,
296 		DISPTECH_DLP_RISE,
297 		DISPTECH_DLP_FALL,
298 		"p"
299 	},
300 	{	disptech_dlp_rgbw,
301 		"Projector RGBW Filter Wheel",
302 		"DPL Projector RGBW Filter Wheel",
303 		1,
304 		DISPTECH_DLP_RISE,
305 		DISPTECH_DLP_FALL,
306 		"p"
307 	},
308 	{	disptech_dlp_rgbcmy,
309 		"Projector RGBCMY Filter Wheel",
310 		"DLP Projector RGBCMY Filter Wheel",
311 		1,
312 		DISPTECH_DLP_RISE,
313 		DISPTECH_DLP_FALL,
314 		"p"
315 	},
316 
317 	{
318 		disptech_unknown,
319 		"Unknown",
320 		"Unknown",
321 		1,
322 		DISPTECH_WORST_RISE,
323 		DISPTECH_WORST_FALL,
324 		"u"
325 	},
326 
327 	{
328 		disptech_end				/* End marker */
329 	}
330 };
331 
332 
333 static int unknown_ix = -1;
334 
find_unknown()335 static void find_unknown() {
336 	int i;
337 
338 	for (i = 0; disptech_info_array[i].dtech != disptech_end; i++) {
339 		if (disptech_info_array[i].dtech == disptech_unknown) {
340 			unknown_ix = i;
341 			break;
342 		}
343 	}
344 }
345 
346 /* Given the enum id, return the matching disptech_info entry */
347 /* Return the disptech_unknown entry if not matched */
disptech_get_id(disptech id)348 disptech_info *disptech_get_id(disptech id) {
349 	int i;
350 
351 	for (i = 0; disptech_info_array[i].dtech != disptech_end; i++) {
352 		if (disptech_info_array[i].dtech == id)
353 			return &disptech_info_array[i];
354 	}
355 
356 	if (unknown_ix < 0)
357 		find_unknown();
358 	return &disptech_info_array[unknown_ix];
359 }
360 
361 /* Given the string id, return the matching disptech_info entry */
362 /* Return the disptech_unknown entry if not matched */
disptech_get_strid(char * strid)363 disptech_info *disptech_get_strid(char *strid) {
364 	int i;
365 
366 	for (i = 0; disptech_info_array[i].dtech != disptech_end; i++) {
367 		if (strcmp(disptech_info_array[i].strid, strid) == 0)
368 			return &disptech_info_array[i];
369 	}
370 
371 	if (unknown_ix < 0)
372 		find_unknown();
373 	return &disptech_info_array[unknown_ix];
374 }
375 
376 /* For each selector we need to:
377 
378 	check each selector char
379 	if already used,
380 		remove it.
381 	if no selector remain,
382 		allocate a free one from the fallback list.
383 	mark all used selectors
384 
385 	We treat the first selector as more important
386 	than any aliases that come after it, and the
387 	aliases as more important than the fallback list,
388 	so we need to do three passes through all the selections.
389 */
390 
391 /* Append the selection characters. */
392 /* If a selector is set, its index will be set in usels[]. */
393 /* Remove any used selectors from isel[]. */
394 /* If flag == 0, set from just first suggested selector. */
395 /* If flag == 1, set from just suggested selectors. */
396 /* If flag == 2, set from suggested and fallback selectors */
397 /* If flag == 3, append from suggested selectors */
disptechs_set_sel(int flag,int ix,char * osel,char * isel,char * usels,int * k,char * asels)398 void disptechs_set_sel(
399 	int flag,			/* See above */
400 	int ix,				/* Index of entry being set */
401 	char *osel,			/* Append unique selectors to this string. */
402 	char *isel,			/* Pointer to string list of suggested selectors, */
403 	char *usels,		/* char[256] initially -1, to track used selector entry index */
404 	int *k,				/* Index of next available selector in asels */
405 	char *asels			/* String list of fallback selectors to choose from, in order. */
406 ) {
407 	char *iisel = isel, i;
408 
409 //a1logd(g_log, 1,"disptechs_set_sel: flag %d, ix %d, osel '%s', isel '%s', k %d\n",flag, ix,osel,isel,*k);
410 
411 	if (flag != 3) {
412 		/* See if we already have a selecor character */
413 		if (osel[0] != '\000') {
414 //a1logd(g_log, 1," already set OK\n");
415 			return;
416 		}
417 	} else {
418 		if (isel[0] == '\000') {
419 //a1logd(g_log, 1," nothing to set from\n");
420 			return;				/* Nothing to set from */
421 		}
422 
423 		/* Get ready to append */
424 		osel += strlen(osel);
425 	}
426 
427 	/* Set or add from the first unsed suggested selectors */
428 	for (i = 0; *isel != '\000'; isel++, i++) {
429 		if (flag == 0 && i > 0) {
430 //a1logd(g_log, 1," run out of primaries\n");
431 			break;					/* Looked at primary */
432 		}
433 		if (usels[*isel] == ((char)-1)) {		/* If this selector is not currently used */
434 //a1logd(g_log, 1," added to '%c' from %d\n", *isel, i);
435 			osel[0] = *isel;			/* Use it */
436 			osel[1] = '\000';
437 			usels[osel[0]] = ix;
438 
439 			/* Remove all used/discarded from isel, in case we are called again. */
440 			for (isel++; ;isel++, iisel++) {
441 				*iisel = *isel;
442 				if (*isel == '\000')
443 					break;
444 			}
445 			return;
446 		}
447 //a1logd(g_log, 1," sel '%c' at %d is used by ix %d\n", *isel, i, usels[*isel]);
448 	}
449 
450 	/* If we get here, we haven't managed to add anything from the remaining */
451 	/* selectors, so mark the candidate list as empty: */
452 	iisel[0] = '\000';
453 
454 	if (flag != 2) {
455 //a1logd(g_log, 1," returning without add\n");
456 		return;
457 	}
458 
459 	/* Get the next unused char in fallback list */
460 	for (;asels[*k] != '\000'; (*k)++) {
461 		if (usels[asels[*k]] == ((char)-1))	/* Unused */
462 			break;
463 	}
464 	if (asels[*k] != '\000') {
465 //a1logd(g_log, 1," set int to fallback '%c' at %d\n", asels[*k], *k);
466 		osel[0] = asels[*k];
467 		osel[1] = '\000';
468 		usels[osel[0]] = ix;
469 		(*k)++;
470 		return;
471 	}
472 
473 //a1logd(g_log, 1," returning after fallback without add\n");
474 	/* If we got here, we failed to add a selector */
475 	return;
476 }
477 
478 /* Return the display tech list with unique lsel lectors */
disptech_get_list()479 disptech_info *disptech_get_list() {
480 	disptech_info *list = &disptech_info_array[1];	/* skip disptech_none entry */
481 
482 	int i, k;
483 	char usels[256];			/* Used selectors */
484 	static char *asels = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
485 
486 	for (i = 0; i < 256; i++)
487 		usels[i] = ((char)-1);
488 	k = 0;		/* Next selector index */
489 
490 	/* Add entries from the static list and their primary selectors */
491 	for (i = 0; list[i].dtech != disptech_end; i++) {
492 //a1logd(g_log,1,"tech[%d] '%s' sels = '%s'\n",i,list[i].desc,list[i].sel);
493 		strcpy(list[i].isel, list[i].sel);
494 		list[i].lsel[0] = '\000';
495 		disptechs_set_sel(0, i, list[i].lsel, list[i].isel, usels, &k, asels);
496 	}
497 
498 	/* Set selectors from secondary */
499 	for (i = 0; list[i].dtech != disptech_end; i++)
500 		disptechs_set_sel(1, i, list[i].lsel, list[i].isel, usels, &k, asels);
501 
502 	/* Set remainder from fallback */
503 	for (i = 0; list[i].dtech != disptech_end; i++)
504 		disptechs_set_sel(2, i, list[i].lsel, list[i].isel, usels, &k, asels);
505 
506 	return list;
507 }
508 
509 /* Locate the display list item that matches the given selector. */
510 /* Return NULL if not found */
disptech_select(disptech_info * list,char c)511 disptech_info *disptech_select(disptech_info *list, char c) {
512 	int i;
513 
514 	for (i = 0; list[i].dtech != disptech_end; i++) {
515 		if (c == list[i].lsel[0])
516 			return &list[i];
517 	}
518 
519 	return NULL;
520 }
521 
522 
523 /* ------------------------------------------- */
524 
525 /*
526 	Display settling time model. This is primarily tailored
527 	to phosphor type response (ie. CRT or Plasma), but LCD
528 	should be somewhat similar
529 
530 	Outline:
531 
532 	Use sRGB as the device model.
533 
534 	For the target RGB, compute the partial derivative of
535 	delta E with respect to R, G & B, and then multiply
536 	that by desired dE accuracy to get the target R, G & B
537 	delta from the target, and then put that into
538 	the exponential rise/fall model to compute settling time.
539 	Choose the worst of the 3.
540 
541 	Should really change the code to compute partial derivative
542 	directly, to speed code up.
543 
544 	We assume the phosphor stimulus is the proportional to the
545 	light output required (ie. that the CRT/encoding non-linearity
546 	is in the electron gun, not the electron->phoshor->light mechanism. )
547 */
548 
549 /* Convert gamma encoded rgb to linear light rgb */
rgb2rgbl(double * rgbl,double * rgb)550 static void rgb2rgbl(double *rgbl, double *rgb) {
551 	int i;
552 	for (i = 0; i < 3; i++) {
553 		if (rgb[i] < 0.04045)
554 			rgbl[i] = rgb[i]/12.92;
555 		else
556 			rgbl[i] = pow((rgb[i] + 0.055)/1.055, 2.4);
557 	}
558 }
559 
560 /* Convert linear light rgb to L*a*b* */
rgbl2lab(double * lab,double * rgbl)561 static void rgbl2lab(double *lab, double *rgbl) {
562 	int i;
563 	double xyz[3];
564 	static double mat[3][3] = {
565 		{ 0.412391, 0.212639, 0.019331 },	/* Red */
566 		{ 0.357584, 0.715169, 0.119195 },	/* Green */
567 		{ 0.180481, 0.072192, 0.950532 }	/* Blue */
568 	};
569 
570 	icmMulBy3x3(xyz, mat, rgbl);
571 	icmXYZ2Lab(&icmD65, lab, xyz);
572 }
573 
574 #ifdef NEVER
575 /* Convert linear light rgb to L*a*b* with partial derivatives */
ddrgbl2lab(double * lab,double dout[3][3],double * rgbl)576 static void ddrgbl2lab(double *lab, double dout[3][3], double *rgbl) {
577 	int i, j, k;
578 	double xyz[3];
579 	static double mat[9] = {
580 		0.412391, 0.212639, 0.019331,	/* Red */
581 		0.357584, 0.715169, 0.119195,	/* Green */
582 		0.180481, 0.072192, 0.950532	/* Blue */
583 	};
584     double dxyzlab[3][3];	/* Part. Deriv of [xyz] with respect to [rgb] */
585 	double dlabxyz[3][3];	/* Part. Deriv of [lab] from [xyz] */
586 
587 	icxdpdiiMulBy3x3Parm(xyz, dxyzlab, mat, rgbl);
588 	icxdXYZ2Lab(&icmD65, lab, dlabxyz, xyz);
589 
590 	/* Compute the partial derivative of Lab from rgb */
591 	for (k = 0; k < 3; k++) {
592 		for (j = 0; j < 3; j++) {
593 			dout[k][j] = 0.0;
594 			for (i = 0; i < 3; i++) {
595 				dout[k][j] += dlabxyz[k][i] * dxyzlab[i][j];
596 			}
597 		}
598 	}
599 }
600 #endif /* NEVER */
601 
602 /* Convert linear light rgb to L*a*b* delta E partial derivatives */
drgbl2lab(double deout[3],double * rgbl)603 static void drgbl2lab(/* double *lab, */double deout[3], double *rgbl) {
604 	int i, j, k;
605 	static double mat[3][3] = {
606 		{ 0.412391, 0.212639, 0.019331 },	/* Red */
607 		{ 0.357584, 0.715169, 0.119195 },	/* Green */
608 		{ 0.180481, 0.072192, 0.950532 }	/* Blue */
609 	};
610 	double xyz[3];
611 	double wp[3], tin[3], dtin[3];
612 	double dlabxyz[3][3];	/* Part. Deriv of [lab] from [xyz] */
613 	double dout[3][3];		/* Part. Deriv of [lab] from [rgb] */
614 
615 	/* rgb to XYZ */
616 	for (i = 0; i < 3; i++) {
617 		xyz[i] = 0.0;
618 		for (j = 0; j < 3; j++) {
619 			xyz[i] += mat[i][j] * rgbl[j];
620 		}
621 	}
622 
623 	/* XYZ to perceptual Lab with partial derivatives. */
624 	wp[0] = icmD65.X, wp[1] = icmD65.Y, wp[2] = icmD65.Z;
625 
626 	for (i = 0; i < 3; i++) {
627 		tin[i] = xyz[i]/wp[i];
628 		dtin[i] = 1.0/wp[i];
629 
630 		if (tin[i] > 0.008856451586) {
631 			dtin[i] *= pow(tin[i], -2.0/3.0) / 3.0;
632 			tin[i] = pow(tin[i],1.0/3.0);
633 		} else {
634 			dtin[i] *= 7.787036979;
635 			tin[i] = 7.787036979 * tin[i] + 16.0/116.0;
636 		}
637 	}
638 
639 /*	lab[0] = 116.0 * tin[1] - 16.0; */
640 	dlabxyz[0][0] = 0.0;
641 	dlabxyz[0][1] = 116.0 * dtin[1];
642 	dlabxyz[0][2] = 0.0;
643 
644 /*	lab[1] = 500.0 * (tin[0] - tin[1]); */
645 	dlabxyz[1][0] = 500.0 * dtin[0];
646 	dlabxyz[1][1] = 500.0 * -dtin[1];
647 	dlabxyz[1][2] = 0.0;
648 
649 /*	lab[2] = 200.0 * (tin[1] - tin[2]); */
650 	dlabxyz[2][0] = 0.0 * mat[0][1];
651 	dlabxyz[2][1] = 200.0 * dtin[1];
652 	dlabxyz[2][2] = 200.0 * -dtin[2];
653 
654 	/* Compute the partial derivative of delta E from rgb */
655 	for (j = 0; j < 3; j++) {
656 		deout[j] = 0.0;
657 		for (k = 0; k < 3; k++) {
658 			dout[k][j] = 0.0;
659 			for (i = 0; i < 3; i++) {
660 				dout[k][j] += dlabxyz[k][i] * mat[i][j];
661 			}
662 			deout[j] += dout[k][j] * dout[k][j];
663 		}
664 		deout[j] = sqrt(deout[j]);
665 	}
666 }
667 
disp_settle_time(double * orgb,double * nrgb,double rise,double fall,double dE)668 double disp_settle_time(double *orgb, double *nrgb, double rise, double fall, double dE) {
669 	int i, j;
670 	double orgbl[3], nrgbl[3];		/* Linear light RGB */
671 	double drgb[3];		/* Partial derivative of RGB wrt to dE at new rgb */
672 	double argbl[3];	/* Acceptable RGB */
673 	double stime[3];	/* Settling time */
674 	double kr, kf;
675 	double xtime = 0.0;
676 
677 	/* Convert rgb's to linear light rgb */
678 	rgb2rgbl(orgbl, orgb);
679 	rgb2rgbl(nrgbl, nrgb);
680 
681 //printf("orgb = %f %f %f\n", orgb[0], orgb[1], orgb[2]);
682 //printf("nrgb = %f %f %f\n", nrgb[0], nrgb[1], nrgb[2]);
683 //printf("orgbl = %f %f %f\n", orgbl[0], orgbl[1], orgbl[2]);
684 //printf("nrgbl = %f %f %f\n", nrgbl[0], nrgbl[1], nrgbl[2]);
685 //printf("dE = %f\n", dE);
686 
687 	/* Compute partial derivative */
688 	drgbl2lab(drgb, nrgbl);
689 //printf("drgb = %f %f %f\n", drgb[0], drgb[1], drgb[2]);
690 
691 #ifdef NEVER
692 	/* Calculate partial derivative explicitely to check */
693 	{
694 		double rlab[3], lab[3];
695 		double xdrgb[3];		/* Partial derivative of RGB wrt to dE at new rgb */
696 
697 		/* Reference Lab */
698 		rgbl2lab(rlab, nrgbl);
699 //printf("rlab = %f %f %f\n", rlab[0], rlab[1], rlab[2]);
700 
701 		for (j = 0; j < 3; j++) {
702 			double del;
703 
704 			if (nrgbl[j] > 0.5)
705 				del = -1e-6;
706 			else
707 				del = 1e-6;
708 
709 			nrgbl[j] += del;
710 			rgbl2lab(lab, nrgbl);
711 			nrgbl[j] -= del;
712 //printf("check pde of in %d = lab %f, %f, %f\n",j, (lab[0] - rlab[0])/del, (lab[1] - rlab[1])/del, (lab[2] - rlab[2])/del);
713 			xdrgb[j] = icmLabDE(rlab, lab)/fabs(del);
714 		}
715 printf("chk drgb = %f %f %f\n", xdrgb[0], xdrgb[1], xdrgb[2]);
716 	}
717 #endif /* NEVER */
718 
719 	/* Compute rgb value that would give targ delta E */
720 	for (j = 0; j < 3; j++) {
721 		double del;
722 
723 		del = dE/drgb[j];
724 
725 		if (orgbl[j] < nrgbl[j]) {
726 			argbl[j] = nrgbl[j] - del;
727 			if (argbl[j] < orgbl[j])
728 				argbl[j] = orgbl[j];
729 		} else {
730 			argbl[j] = nrgbl[j] + del;
731 			if (argbl[j] > orgbl[j])
732 				argbl[j] = orgbl[j];
733 		}
734 	}
735 
736 //{ double rlab[3], lab[3];
737 //rgbl2lab(lab, argbl);
738 //rgbl2lab(rlab, nrgbl);
739 //printf("argbl = %f %f %f\n", argbl[0], argbl[1], argbl[2]);
740 //printf("dE for argbl = %f\n",icmLabDE(rlab, lab));
741 //}
742 
743 	/* Compute the modelled time from orgbl to argbl */
744 	kr = rise/log(1.0 - 0.9);	/* Exponent constant for 90% change*/
745 	kf = fall/log(1.0 - 0.9);	/* Exponent constant for 90% change*/
746 
747 	for (j = 0; j < 3; j++) {
748 		double el, dl, n, t;
749 
750 		dl = (argbl[j] - orgbl[j])/(nrgbl[j] - orgbl[j]);
751 
752 		if (fabs(dl) < 1e-6) {
753 			stime[j] = 0.0;
754 			continue;
755 		}
756 
757 		if (nrgbl[j] > orgbl[j])
758 			stime[j] = kr * log(1.0 - dl);
759 		else
760 			stime[j] = kf * log(1.0 - dl);
761 
762 		if (stime[j] > xtime && stime[j] < 5.0)
763 			xtime = stime[j];
764 	}
765 //printf("stime = %f %f %f\n", stime[0], stime[1], stime[2]);
766 //printf("returning = %f\n",xtime);
767 
768 	return xtime;
769 }
770 
771 
772 
773 
774 
775