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