1 
2 /*
3  * Code and supporting documentation (c) Copyright 1990 1991 Tektronix, Inc.
4  * 	All Rights Reserved
5  *
6  * This file is a component of an X Window System-specific implementation
7  * of Xcms based on the TekColor Color Management System.  TekColor is a
8  * trademark of Tektronix, Inc.  The term "TekHVC" designates a particular
9  * color space that is the subject of U.S. Patent No. 4,985,853 (equivalent
10  * foreign patents pending).  Permission is hereby granted to use, copy,
11  * modify, sell, and otherwise distribute this software and its
12  * documentation for any purpose and without fee, provided that:
13  *
14  * 1. This copyright, permission, and disclaimer notice is reproduced in
15  *    all copies of this software and any modification thereof and in
16  *    supporting documentation;
17  * 2. Any color-handling application which displays TekHVC color
18  *    cooordinates identifies these as TekHVC color coordinates in any
19  *    interface that displays these coordinates and in any associated
20  *    documentation;
21  * 3. The term "TekHVC" is always used, and is only used, in association
22  *    with the mathematical derivations of the TekHVC Color Space,
23  *    including those provided in this file and any equivalent pathways and
24  *    mathematical derivations, regardless of digital (e.g., floating point
25  *    or integer) representation.
26  *
27  * Tektronix makes no representation about the suitability of this software
28  * for any purpose.  It is provided "as is" and with all faults.
29  *
30  * TEKTRONIX DISCLAIMS ALL WARRANTIES APPLICABLE TO THIS SOFTWARE,
31  * INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
32  * PARTICULAR PURPOSE.  IN NO EVENT SHALL TEKTRONIX BE LIABLE FOR ANY
33  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
34  * RESULTING FROM LOSS OF USE, DATA, OR PROFITS, WHETHER IN AN ACTION OF
35  * CONTRACT, NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
36  * CONNECTION WITH THE USE OR THE PERFORMANCE OF THIS SOFTWARE.
37  *
38  *	NAME
39  *		TekHVC.c
40  *
41  *	DESCRIPTION
42  *		This file contains routines that support the TekHVC
43  *		color space to include conversions to and from the CIE
44  *		XYZ space.
45  *
46  *	DOCUMENTATION
47  *		"TekColor Color Management System, System Implementor's Manual"
48  */
49 
50 #ifdef HAVE_CONFIG_H
51 #include <config.h>
52 #endif
53 #include "Xlibint.h"
54 #include "Xcmsint.h"
55 #include <X11/Xos.h>
56 #include <math.h>
57 #include "Cv.h"
58 
59 #include <stdio.h>
60 
61 /*
62  *	DEFINES
63  */
64 #define u_BR    0.7127          /* u' Best Red */
65 #define v_BR    0.4931          /* v' Best Red */
66 #define EPS     0.001
67 #define CHROMA_SCALE_FACTOR   7.50725
68 #ifndef PI
69 #  ifdef M_PI
70 #    define PI	M_PI
71 #  else
72 #    define PI       3.14159265358979323846264338327950
73 #  endif
74 #endif
75 #ifndef degrees
76 #  define degrees(r) ((XcmsFloat)(r) * 180.0 / PI)
77 #endif /* degrees */
78 #ifndef radians
79 #  define radians(d) ((XcmsFloat)(d) * PI / 180.0)
80 #endif /* radians */
81 
82 /*************************************************************************
83  * Note: The DBL_EPSILON for ANSI is 1e-5 so my checks need to take
84  *       this into account.  If your DBL_EPSILON is different then
85  *       adjust this define.
86  *
87  *       Also note that EPS is the error factor in the calculations
88  *       This may need to be the same as XMY_DBL_EPSILON in
89  *       some implementations.
90  **************************************************************************/
91 #ifdef DBL_EPSILON
92 #  define XMY_DBL_EPSILON DBL_EPSILON
93 #else
94 #  define XMY_DBL_EPSILON 0.00001
95 #endif
96 
97 /*
98  *	FORWARD DECLARATIONS
99  */
100 static int TekHVC_ParseString(register char *spec, XcmsColor *pColor);
101 static Status XcmsTekHVC_ValidSpec(XcmsColor *pColor);
102 
103 /*
104  *	LOCAL VARIABLES
105  */
106 
107     /*
108      * NULL terminated list of functions applied to get from TekHVC to CIEXYZ
109      */
110 static XcmsConversionProc Fl_TekHVC_to_CIEXYZ[] = {
111     XcmsTekHVCToCIEuvY,
112     XcmsCIEuvYToCIEXYZ,
113     NULL
114 };
115 
116     /*
117      * NULL terminated list of functions applied to get from CIEXYZ to TekHVC
118      */
119 static XcmsConversionProc Fl_CIEXYZ_to_TekHVC[] = {
120     XcmsCIEXYZToCIEuvY,
121     XcmsCIEuvYToTekHVC,
122     NULL
123 };
124 
125 /*
126  *	GLOBALS
127  */
128 
129     /*
130      * TekHVC Color Space
131      */
132 XcmsColorSpace	XcmsTekHVCColorSpace =
133     {
134 	_XcmsTekHVC_prefix,	/* prefix */
135 	XcmsTekHVCFormat,		/* id */
136 	TekHVC_ParseString,	/* parseString */
137 	Fl_TekHVC_to_CIEXYZ,	/* to_CIEXYZ */
138 	Fl_CIEXYZ_to_TekHVC,	/* from_CIEXYZ */
139 	1
140     };
141 
142 
143 
144 
145 /************************************************************************
146  *									*
147  *			 PRIVATE ROUTINES				*
148  *									*
149  ************************************************************************/
150 
151 /*
152  *	NAME
153  *		TekHVC_ParseString
154  *
155  *	SYNOPSIS
156  */
157 static int
TekHVC_ParseString(register char * spec,XcmsColor * pColor)158 TekHVC_ParseString(
159     register char *spec,
160     XcmsColor *pColor)
161 /*
162  *	DESCRIPTION
163  *		This routines takes a string and attempts to convert
164  *		it into a XcmsColor structure with XcmsTekHVCFormat.
165  *		The assumed TekHVC string syntax is:
166  *		    TekHVC:<H>/<V>/<C>
167  *		Where H, V, and C are in string input format for floats
168  *		consisting of:
169  *		    a. an optional sign
170  *		    b. a string of numbers possibly containing a decimal point,
171  *		    c. an optional exponent field containing an 'E' or 'e'
172  *			followed by a possibly signed integer string.
173  *
174  *	RETURNS
175  *		XcmsFailure if invalid;
176  *		XcmsSuccess if valid.
177  */
178 {
179     size_t n;
180     char *pchar;
181 
182     if ((pchar = strchr(spec, ':')) == NULL) {
183 	return(XcmsFailure);
184     }
185     n = (size_t)(pchar - spec);
186 
187     /*
188      * Check for proper prefix.
189      */
190     if (strncmp(spec, _XcmsTekHVC_prefix, n) != 0) {
191 	return(XcmsFailure);
192     }
193 
194     /*
195      * Attempt to parse the value portion.
196      */
197     if (sscanf(spec + n + 1, "%lf/%lf/%lf",
198 	    &pColor->spec.TekHVC.H,
199 	    &pColor->spec.TekHVC.V,
200 	    &pColor->spec.TekHVC.C) != 3) {
201         char *s; /* Maybe failed due to locale */
202         int f;
203         if ((s = strdup(spec))) {
204             for (f = 0; s[f]; ++f)
205                 if (s[f] == '.')
206                     s[f] = ',';
207                 else if (s[f] == ',')
208                     s[f] = '.';
209 	    if (sscanf(s + n + 1, "%lf/%lf/%lf",
210 		       &pColor->spec.TekHVC.H,
211 		       &pColor->spec.TekHVC.V,
212 		       &pColor->spec.TekHVC.C) != 3) {
213                 free(s);
214                 return(XcmsFailure);
215             }
216             free(s);
217         } else
218 	    return(XcmsFailure);
219     }
220     pColor->format = XcmsTekHVCFormat;
221     pColor->pixel = 0;
222     return(XcmsTekHVC_ValidSpec(pColor));
223 }
224 
225 
226 /*
227  *	NAME
228  *		ThetaOffset -- compute thetaOffset
229  *
230  *	SYNOPSIS
231  */
232 static int
ThetaOffset(XcmsColor * pWhitePt,XcmsFloat * pThetaOffset)233 ThetaOffset(
234     XcmsColor *pWhitePt,
235     XcmsFloat *pThetaOffset)
236 /*
237  *	DESCRIPTION
238  *		This routine computes the theta offset of a given
239  *		white point, i.e. XcmsColor.  It is used in both this
240  *		conversion and the printer conversions.
241  *
242  *	RETURNS
243  *		0 if failed.
244  *		1 if succeeded with no modifications.
245  *
246  *	ASSUMPTIONS
247  *		Assumes:
248  *			pWhitePt != NULL
249  *			pWhitePt->format == XcmsCIEuvYFormat
250  *
251  */
252 {
253     double div, slopeuv;
254 
255     if (pWhitePt == NULL || pWhitePt->format != XcmsCIEuvYFormat) {
256 	return(0);
257     }
258 
259     if ((div = u_BR - pWhitePt->spec.CIEuvY.u_prime) == 0.0) {
260 	return(0);
261     }
262     slopeuv = (v_BR - pWhitePt->spec.CIEuvY.v_prime) / div;
263     *pThetaOffset = degrees(XCMS_ATAN(slopeuv));
264     return(1);
265 }
266 
267 
268 
269 /************************************************************************
270  *									*
271  *			 PUBLIC ROUTINES				*
272  *									*
273  ************************************************************************/
274 
275 /*
276  *	NAME
277  *		XcmsTekHVC_ValidSpec()
278  *
279  *	SYNOPSIS
280  */
281 static int
XcmsTekHVC_ValidSpec(XcmsColor * pColor)282 XcmsTekHVC_ValidSpec(
283     XcmsColor *pColor)
284 /*
285  *	DESCRIPTION
286  *		Checks if values in the color specification are valid.
287  *		Also brings hue into the range 0.0 <= Hue < 360.0
288  *
289  *	RETURNS
290  *		0 if not valid.
291  *		1 if valid.
292  *
293  */
294 {
295     if (pColor->format != XcmsTekHVCFormat) {
296 	return(XcmsFailure);
297     }
298     if (pColor->spec.TekHVC.V < (0.0 - XMY_DBL_EPSILON)
299 	    || pColor->spec.TekHVC.V > (100.0 + XMY_DBL_EPSILON)
300 	    || (pColor->spec.TekHVC.C < 0.0 - XMY_DBL_EPSILON)) {
301 	return(XcmsFailure);
302     }
303 
304     if (pColor->spec.TekHVC.V < 0.0) {
305 	    pColor->spec.TekHVC.V = 0.0 + XMY_DBL_EPSILON;
306     } else if (pColor->spec.TekHVC.V > 100.0) {
307 	pColor->spec.TekHVC.V = 100.0 - XMY_DBL_EPSILON;
308     }
309 
310     if (pColor->spec.TekHVC.C < 0.0) {
311 	pColor->spec.TekHVC.C = 0.0 - XMY_DBL_EPSILON;
312     }
313 
314     while (pColor->spec.TekHVC.H < 0.0) {
315 	pColor->spec.TekHVC.H += 360.0;
316     }
317     while (pColor->spec.TekHVC.H >= 360.0) {
318 	pColor->spec.TekHVC.H -= 360.0;
319     }
320     return(XcmsSuccess);
321 }
322 
323 /*
324  *	NAME
325  *		XcmsTekHVCToCIEuvY - convert TekHVC to CIEuvY
326  *
327  *	SYNOPSIS
328  */
329 Status
XcmsTekHVCToCIEuvY(XcmsCCC ccc,XcmsColor * pHVC_WhitePt,XcmsColor * pColors_in_out,unsigned int nColors)330 XcmsTekHVCToCIEuvY(
331     XcmsCCC ccc,
332     XcmsColor *pHVC_WhitePt,
333     XcmsColor *pColors_in_out,
334     unsigned int nColors)
335 /*
336  *	DESCRIPTION
337  *		Transforms an array of TekHVC color specifications, given
338  *		their associated white point, to CIECIEuvY.color
339  *		specifications.
340  *
341  *	RETURNS
342  *		XcmsFailure if failed, XcmsSuccess otherwise.
343  *
344  */
345 {
346     XcmsFloat	thetaOffset;
347     XcmsColor	*pColor = pColors_in_out;
348     XcmsColor	whitePt;
349     XcmsCIEuvY	uvY_return;
350     XcmsFloat	tempHue, u, v;
351     XcmsFloat	tmpVal;
352     unsigned int i;
353 
354     /*
355      * Check arguments
356      */
357     if (pHVC_WhitePt == NULL || pColors_in_out == NULL) {
358 	return(XcmsFailure);
359     }
360 
361     /*
362      * Make sure white point is in CIEuvY form
363      */
364     if (pHVC_WhitePt->format != XcmsCIEuvYFormat) {
365 	/* Make copy of the white point because we're going to modify it */
366 	memcpy((char *)&whitePt, (char *)pHVC_WhitePt, sizeof(XcmsColor));
367 	if (!_XcmsDIConvertColors(ccc, &whitePt, (XcmsColor *)NULL, 1,
368 		XcmsCIEuvYFormat)) {
369 	    return(XcmsFailure);
370 	}
371 	pHVC_WhitePt = &whitePt;
372     }
373     /* Make sure it is a white point, i.e., Y == 1.0 */
374     if (pHVC_WhitePt->spec.CIEuvY.Y != 1.0) {
375 	return(XcmsFailure);
376     }
377 
378     /* Get the thetaOffset */
379     if (!ThetaOffset(pHVC_WhitePt, &thetaOffset)) {
380 	return(XcmsFailure);
381     }
382 
383     /*
384      * Now convert each XcmsColor structure to CIEXYZ form
385      */
386     for (i = 0; i < nColors; i++, pColor++) {
387 
388 	/* Make sure original format is TekHVC and is valid */
389 	if (!XcmsTekHVC_ValidSpec(pColor)) {
390 	    return(XcmsFailure);
391 	}
392 
393 	if (pColor->spec.TekHVC.V == 0.0 || pColor->spec.TekHVC.V == 100.0) {
394 	    if (pColor->spec.TekHVC.V == 100.0) {
395 		uvY_return.Y = 1.0;
396 	    } else { /* pColor->spec.TekHVC.V == 0.0 */
397 		uvY_return.Y = 0.0;
398 	    }
399 	    uvY_return.u_prime = pHVC_WhitePt->spec.CIEuvY.u_prime;
400 	    uvY_return.v_prime = pHVC_WhitePt->spec.CIEuvY.v_prime;
401 	} else {
402 
403 	    /* Find the hue based on the white point offset */
404 	    tempHue = pColor->spec.TekHVC.H + thetaOffset;
405 
406 	    while (tempHue < 0.0) {
407 		tempHue += 360.0;
408 	    }
409 	    while (tempHue >= 360.0) {
410 		tempHue -= 360.0;
411 	    }
412 
413 	    tempHue = radians(tempHue);
414 
415 	    /* Calculate u'v' for the obtained hue */
416 	    u = (XcmsFloat) ((XCMS_COS(tempHue) * pColor->spec.TekHVC.C) /
417 		    (pColor->spec.TekHVC.V * (double)CHROMA_SCALE_FACTOR));
418 	    v = (XcmsFloat) ((XCMS_SIN(tempHue) * pColor->spec.TekHVC.C) /
419 		    (pColor->spec.TekHVC.V * (double)CHROMA_SCALE_FACTOR));
420 
421 	    /* Based on the white point get the offset from best red */
422 	    uvY_return.u_prime = u + pHVC_WhitePt->spec.CIEuvY.u_prime;
423 	    uvY_return.v_prime = v + pHVC_WhitePt->spec.CIEuvY.v_prime;
424 
425 	    /* Calculate the Y value based on the L* = V. */
426 	    if (pColor->spec.TekHVC.V < 7.99953624) {
427 		uvY_return.Y = pColor->spec.TekHVC.V / 903.29;
428 	    } else {
429 		tmpVal = (pColor->spec.TekHVC.V + 16.0) / 116.0;
430 		uvY_return.Y = tmpVal * tmpVal * tmpVal; /* tmpVal ** 3 */
431 	    }
432 	}
433 
434 	/* Copy result to pColor */
435 	memcpy((char *)&pColor->spec, (char *)&uvY_return, sizeof(XcmsCIEuvY));
436 
437 	/* Identify that the format is now CIEuvY */
438 	pColor->format = XcmsCIEuvYFormat;
439     }
440     return(XcmsSuccess);
441 }
442 
443 
444 /*
445  *	NAME
446  *		XcmsCIEuvYToTekHVC - convert CIEuvY to TekHVC
447  *
448  *	SYNOPSIS
449  */
450 Status
XcmsCIEuvYToTekHVC(XcmsCCC ccc,XcmsColor * pHVC_WhitePt,XcmsColor * pColors_in_out,unsigned int nColors)451 XcmsCIEuvYToTekHVC(
452     XcmsCCC ccc,
453     XcmsColor *pHVC_WhitePt,
454     XcmsColor *pColors_in_out,
455     unsigned int nColors)
456 /*
457  *	DESCRIPTION
458  *		Transforms an array of CIECIEuvY.color specifications, given
459  *		their assiciated white point, to TekHVC specifications.
460  *
461  *	RETURNS
462  *		XcmsFailure if failed, XcmsSuccess otherwise.
463  *
464  */
465 {
466     XcmsFloat	theta, L2, u, v, nThetaLow, nThetaHigh;
467     XcmsFloat	thetaOffset;
468     XcmsColor	*pColor = pColors_in_out;
469     XcmsColor	whitePt;
470     XcmsTekHVC	HVC_return;
471     unsigned int i;
472 
473     /*
474      * Check arguments
475      */
476     if (pHVC_WhitePt == NULL || pColors_in_out == NULL) {
477 	return(XcmsFailure);
478     }
479 
480     /*
481      * Make sure white point is in CIEuvY form
482      */
483     if (pHVC_WhitePt->format != XcmsCIEuvYFormat) {
484 	/* Make copy of the white point because we're going to modify it */
485 	memcpy((char *)&whitePt, (char *)pHVC_WhitePt, sizeof(XcmsColor));
486 	if (!_XcmsDIConvertColors(ccc, &whitePt, (XcmsColor *)NULL, 1,
487 		XcmsCIEuvYFormat)) {
488 	    return(XcmsFailure);
489 	}
490 	pHVC_WhitePt = &whitePt;
491     }
492     /* Make sure it is a white point, i.e., Y == 1.0 */
493     if (pHVC_WhitePt->spec.CIEuvY.Y != 1.0) {
494 	return(XcmsFailure);
495     }
496     if (!ThetaOffset(pHVC_WhitePt, &thetaOffset)) {
497 	return(XcmsFailure);
498     }
499 
500     /*
501      * Now convert each XcmsColor structure to CIEXYZ form
502      */
503     for (i = 0; i < nColors; i++, pColor++) {
504 	if (!_XcmsCIEuvY_ValidSpec(pColor)) {
505 	    return(XcmsFailure);
506 	}
507 
508 	/* Use the white point offset to determine HVC */
509 	u = pColor->spec.CIEuvY.u_prime - pHVC_WhitePt->spec.CIEuvY.u_prime;
510 	v = pColor->spec.CIEuvY.v_prime - pHVC_WhitePt->spec.CIEuvY.v_prime;
511 
512 	/* Calculate the offset */
513 	if (u == 0.0) {
514 	    theta = 0.0;
515 	} else {
516 	    theta = v / u;
517 	    theta = (XcmsFloat) XCMS_ATAN((double)theta);
518 	    theta = degrees(theta);
519 	}
520 
521 	nThetaLow = 0.0;
522 	nThetaHigh = 360.0;
523 	if (u > 0.0 && v > 0.0) {
524 	    nThetaLow = 0.0;
525 	    nThetaHigh = 90.0;
526 	} else if (u < 0.0 && v > 0.0) {
527 	    nThetaLow = 90.0;
528 	    nThetaHigh = 180.0;
529 	} else if (u < 0.0 && v < 0.0) {
530 	    nThetaLow = 180.0;
531 	    nThetaHigh = 270.0;
532 	} else if (u > 0.0 && v < 0.0) {
533 	    nThetaLow = 270.0;
534 	    nThetaHigh = 360.0;
535 	}
536 	while (theta < nThetaLow) {
537 		theta += 90.0;
538 	}
539 	while (theta >= nThetaHigh) {
540 	    theta -= 90.0;
541 	}
542 
543 	/* calculate the L value from the given Y */
544 	L2 = (pColor->spec.CIEuvY.Y < 0.008856)
545 	    ?
546 	    (pColor->spec.CIEuvY.Y * 903.29)
547 	    :
548 	    ((XcmsFloat)(XCMS_CUBEROOT(pColor->spec.CIEuvY.Y) * 116.0) - 16.0);
549 	HVC_return.C = L2 * CHROMA_SCALE_FACTOR * XCMS_SQRT((double) ((u * u) + (v * v)));
550 	if (HVC_return.C < 0.0) {
551 	    theta = 0.0;
552 	}
553 	HVC_return.V = L2;
554 	HVC_return.H = theta - thetaOffset;
555 
556 	/*
557 	 * If this is within the error margin let some other routine later
558 	 * in the chain worry about the slop in the calculations.
559 	 */
560 	while (HVC_return.H < -EPS) {
561 	    HVC_return.H += 360.0;
562 	}
563 	while (HVC_return.H >= 360.0 + EPS) {
564 	    HVC_return.H -= 360.0;
565 	}
566 
567 	/* Copy result to pColor */
568 	memcpy((char *)&pColor->spec, (char *)&HVC_return, sizeof(XcmsTekHVC));
569 
570 	/* Identify that the format is now CIEuvY */
571 	pColor->format = XcmsTekHVCFormat;
572     }
573     return(XcmsSuccess);
574 }
575 
576 
577 /*
578  *	NAME
579  *		_XcmsTekHVC_CheckModify
580  *
581  *	SYNOPSIS
582  */
583 int
_XcmsTekHVC_CheckModify(XcmsColor * pColor)584 _XcmsTekHVC_CheckModify(
585     XcmsColor *pColor)
586 /*
587  *	DESCRIPTION
588  *		Checks if values in the color specification are valid.
589  *		If they are not it modifies the values.
590  *		Also brings hue into the range 0.0 <= Hue < 360.0
591  *
592  *	RETURNS
593  *		0 if not valid.
594  *		1 if valid.
595  *
596  */
597 {
598     int n;
599 
600     /* For now only use the TekHVC numbers as inputs */
601     if (pColor->format != XcmsTekHVCFormat) {
602 	return(0);
603     }
604 
605     if (pColor->spec.TekHVC.V < 0.0) {
606 	pColor->spec.TekHVC.V = 0.0 + XMY_DBL_EPSILON;
607     } else if (pColor->spec.TekHVC.V > 100.0) {
608 	pColor->spec.TekHVC.V = 100.0 - XMY_DBL_EPSILON;
609     }
610 
611     if (pColor->spec.TekHVC.C < 0.0) {
612 	pColor->spec.TekHVC.C = 0.0 - XMY_DBL_EPSILON;
613     }
614 
615     if (pColor->spec.TekHVC.H < 0.0) {
616 	n = -pColor->spec.TekHVC.H / 360.0;
617 	pColor->spec.TekHVC.H += (n + 1) * 360.0;
618 	if (pColor->spec.TekHVC.H >= 360.0)
619 	    pColor->spec.TekHVC.H -= 360.0;
620     } else if (pColor->spec.TekHVC.H >= 360.0) {
621 	n = pColor->spec.TekHVC.H / 360.0;
622 	pColor->spec.TekHVC.H -= n * 360.0;
623     }
624     return(1);
625 }
626