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 */ 100static int TekHVC_ParseString(register char *spec, XcmsColor *pColor); 101static 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 */ 110static 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 */ 119static XcmsConversionProc Fl_CIEXYZ_to_TekHVC[] = { 120 XcmsCIEXYZToCIEuvY, 121 XcmsCIEuvYToTekHVC, 122 NULL 123}; 124 125/* 126 * GLOBALS 127 */ 128 129 /* 130 * TekHVC Color Space 131 */ 132XcmsColorSpace 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 */ 157static int 158TekHVC_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 */ 232static int 233ThetaOffset( 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 */ 281static int 282XcmsTekHVC_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 */ 329Status 330XcmsTekHVCToCIEuvY( 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 */ 450Status 451XcmsCIEuvYToTekHVC( 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 */ 583int 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