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