1 //--------------------------------------------------------------------------------- 2 // 3 // Little Color Management System 4 // Copyright (c) 1998-2011 Marti Maria Saguer 5 // 6 // Permission is hereby granted, free of charge, to any person obtaining 7 // a copy of this software and associated documentation files (the "Software"), 8 // to deal in the Software without restriction, including without limitation 9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 // and/or sell copies of the Software, and to permit persons to whom the Software 11 // is furnished to do so, subject to the following conditions: 12 // 13 // The above copyright notice and this permission notice shall be included in 14 // all copies or substantial portions of the Software. 15 // 16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 // 24 //--------------------------------------------------------------------------------- 25 // 26 27 #include "lcms2_internal.h" 28 29 30 // ------------------------------------------------------------------------ 31 32 // Gamut boundary description by using Jan Morovic's Segment maxima method 33 // Many thanks to Jan for allowing me to use his algorithm. 34 35 // r = C* 36 // alpha = Hab 37 // theta = L* 38 39 #define SECTORS 16 // number of divisions in alpha and theta 40 41 // Spherical coordinates 42 typedef struct { 43 44 cmsFloat64Number r; 45 cmsFloat64Number alpha; 46 cmsFloat64Number theta; 47 48 } cmsSpherical; 49 50 typedef enum { 51 GP_EMPTY, 52 GP_SPECIFIED, 53 GP_MODELED 54 55 } GDBPointType; 56 57 58 typedef struct { 59 60 GDBPointType Type; 61 cmsSpherical p; // Keep also alpha & theta of maximum 62 63 } cmsGDBPoint; 64 65 66 typedef struct { 67 68 cmsContext ContextID; 69 cmsGDBPoint Gamut[SECTORS][SECTORS]; 70 71 } cmsGDB; 72 73 74 // A line using the parametric form 75 // P = a + t*u 76 typedef struct { 77 78 cmsVEC3 a; 79 cmsVEC3 u; 80 81 } cmsLine; 82 83 84 // A plane using the parametric form 85 // Q = b + r*v + s*w 86 typedef struct { 87 88 cmsVEC3 b; 89 cmsVEC3 v; 90 cmsVEC3 w; 91 92 } cmsPlane; 93 94 95 96 // -------------------------------------------------------------------------------------------- 97 98 // ATAN2() which always returns degree positive numbers 99 100 static 101 cmsFloat64Number _cmsAtan2(cmsFloat64Number y, cmsFloat64Number x) 102 { 103 cmsFloat64Number a; 104 105 // Deal with undefined case 106 if (x == 0.0 && y == 0.0) return 0; 107 108 a = (atan2(y, x) * 180.0) / M_PI; 109 110 while (a < 0) { 111 a += 360; 112 } 113 114 return a; 115 } 116 117 // Convert to spherical coordinates 118 static 119 void ToSpherical(cmsSpherical* sp, const cmsVEC3* v) 120 { 121 122 cmsFloat64Number L, a, b; 123 124 L = v ->n[VX]; 125 a = v ->n[VY]; 126 b = v ->n[VZ]; 127 128 sp ->r = sqrt( L*L + a*a + b*b ); 129 130 if (sp ->r == 0) { 131 sp ->alpha = sp ->theta = 0; 132 return; 133 } 134 135 sp ->alpha = _cmsAtan2(a, b); 136 sp ->theta = _cmsAtan2(sqrt(a*a + b*b), L); 137 } 138 139 140 // Convert to cartesian from spherical 141 static 142 void ToCartesian(cmsVEC3* v, const cmsSpherical* sp) 143 { 144 cmsFloat64Number sin_alpha; 145 cmsFloat64Number cos_alpha; 146 cmsFloat64Number sin_theta; 147 cmsFloat64Number cos_theta; 148 cmsFloat64Number L, a, b; 149 150 sin_alpha = sin((M_PI * sp ->alpha) / 180.0); 151 cos_alpha = cos((M_PI * sp ->alpha) / 180.0); 152 sin_theta = sin((M_PI * sp ->theta) / 180.0); 153 cos_theta = cos((M_PI * sp ->theta) / 180.0); 154 155 a = sp ->r * sin_theta * sin_alpha; 156 b = sp ->r * sin_theta * cos_alpha; 157 L = sp ->r * cos_theta; 158 159 v ->n[VX] = L; 160 v ->n[VY] = a; 161 v ->n[VZ] = b; 162 } 163 164 165 // Quantize sector of a spherical coordinate. Saturate 360, 180 to last sector 166 // The limits are the centers of each sector, so 167 static 168 void QuantizeToSector(const cmsSpherical* sp, int* alpha, int* theta) 169 { 170 *alpha = (int) floor(((sp->alpha * (SECTORS)) / 360.0) ); 171 *theta = (int) floor(((sp->theta * (SECTORS)) / 180.0) ); 172 173 if (*alpha >= SECTORS) 174 *alpha = SECTORS-1; 175 if (*theta >= SECTORS) 176 *theta = SECTORS-1; 177 } 178 179 180 // Line determined by 2 points 181 static 182 void LineOf2Points(cmsLine* line, cmsVEC3* a, cmsVEC3* b) 183 { 184 185 _cmsVEC3init(&line ->a, a ->n[VX], a ->n[VY], a ->n[VZ]); 186 _cmsVEC3init(&line ->u, b ->n[VX] - a ->n[VX], 187 b ->n[VY] - a ->n[VY], 188 b ->n[VZ] - a ->n[VZ]); 189 } 190 191 192 // Evaluate parametric line 193 static 194 void GetPointOfLine(cmsVEC3* p, const cmsLine* line, cmsFloat64Number t) 195 { 196 p ->n[VX] = line ->a.n[VX] + t * line->u.n[VX]; 197 p ->n[VY] = line ->a.n[VY] + t * line->u.n[VY]; 198 p ->n[VZ] = line ->a.n[VZ] + t * line->u.n[VZ]; 199 } 200 201 202 203 /* 204 Closest point in sector line1 to sector line2 (both are defined as 0 <=t <= 1) 205 http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm 206 207 Copyright 2001, softSurfer (www.softsurfer.com) 208 This code may be freely used and modified for any purpose 209 providing that this copyright notice is included with it. 210 SoftSurfer makes no warranty for this code, and cannot be held 211 liable for any real or imagined damage resulting from its use. 212 Users of this code must verify correctness for their application. 213 214 */ 215 216 static 217 cmsBool ClosestLineToLine(cmsVEC3* r, const cmsLine* line1, const cmsLine* line2) 218 { 219 cmsFloat64Number a, b, c, d, e, D; 220 cmsFloat64Number sc, sN, sD; 221 cmsFloat64Number tc, tN, tD; 222 cmsVEC3 w0; 223 224 _cmsVEC3minus(&w0, &line1 ->a, &line2 ->a); 225 226 a = _cmsVEC3dot(&line1 ->u, &line1 ->u); 227 b = _cmsVEC3dot(&line1 ->u, &line2 ->u); 228 c = _cmsVEC3dot(&line2 ->u, &line2 ->u); 229 d = _cmsVEC3dot(&line1 ->u, &w0); 230 e = _cmsVEC3dot(&line2 ->u, &w0); 231 232 D = a*c - b * b; // Denominator 233 sD = tD = D; // default sD = D >= 0 234 235 if (D < MATRIX_DET_TOLERANCE) { // the lines are almost parallel 236 237 sN = 0.0; // force using point P0 on segment S1 238 sD = 1.0; // to prevent possible division by 0.0 later 239 tN = e; 240 tD = c; 241 } 242 else { // get the closest points on the infinite lines 243 244 sN = (b*e - c*d); 245 tN = (a*e - b*d); 246 247 if (sN < 0.0) { // sc < 0 => the s=0 edge is visible 248 249 sN = 0.0; 250 tN = e; 251 tD = c; 252 } 253 else if (sN > sD) { // sc > 1 => the s=1 edge is visible 254 sN = sD; 255 tN = e + b; 256 tD = c; 257 } 258 } 259 260 if (tN < 0.0) { // tc < 0 => the t=0 edge is visible 261 262 tN = 0.0; 263 // recompute sc for this edge 264 if (-d < 0.0) 265 sN = 0.0; 266 else if (-d > a) 267 sN = sD; 268 else { 269 sN = -d; 270 sD = a; 271 } 272 } 273 else if (tN > tD) { // tc > 1 => the t=1 edge is visible 274 275 tN = tD; 276 277 // recompute sc for this edge 278 if ((-d + b) < 0.0) 279 sN = 0; 280 else if ((-d + b) > a) 281 sN = sD; 282 else { 283 sN = (-d + b); 284 sD = a; 285 } 286 } 287 // finally do the division to get sc and tc 288 sc = (fabs(sN) < MATRIX_DET_TOLERANCE ? 0.0 : sN / sD); 289 tc = (fabs(tN) < MATRIX_DET_TOLERANCE ? 0.0 : tN / tD); 290 291 GetPointOfLine(r, line1, sc); 292 return TRUE; 293 } 294 295 296 297 // ------------------------------------------------------------------ Wrapper 298 299 300 // Allocate & free structure 301 cmsHANDLE CMSEXPORT cmsGBDAlloc(cmsContext ContextID) 302 { 303 cmsGDB* gbd = (cmsGDB*) _cmsMallocZero(ContextID, sizeof(cmsGDB)); 304 if (gbd == NULL) return NULL; 305 306 gbd -> ContextID = ContextID; 307 308 return (cmsHANDLE) gbd; 309 } 310 311 312 void CMSEXPORT cmsGBDFree(cmsHANDLE hGBD) 313 { 314 cmsGDB* gbd = (cmsGDB*) hGBD; 315 if (hGBD != NULL) 316 _cmsFree(gbd->ContextID, (void*) gbd); 317 } 318 319 320 // Auxiliar to retrieve a pointer to the segmentr containing the Lab value 321 static 322 cmsGDBPoint* GetPoint(cmsGDB* gbd, const cmsCIELab* Lab, cmsSpherical* sp) 323 { 324 cmsVEC3 v; 325 int alpha, theta; 326 327 // Housekeeping 328 _cmsAssert(gbd != NULL); 329 _cmsAssert(Lab != NULL); 330 _cmsAssert(sp != NULL); 331 332 // Center L* by substracting half of its domain, that's 50 333 _cmsVEC3init(&v, Lab ->L - 50.0, Lab ->a, Lab ->b); 334 335 // Convert to spherical coordinates 336 ToSpherical(sp, &v); 337 338 if (sp ->r < 0 || sp ->alpha < 0 || sp->theta < 0) { 339 cmsSignalError(gbd ->ContextID, cmsERROR_RANGE, "spherical value out of range"); 340 return NULL; 341 } 342 343 // On which sector it falls? 344 QuantizeToSector(sp, &alpha, &theta); 345 346 if (alpha < 0 || theta < 0 || alpha >= SECTORS || theta >= SECTORS) { 347 cmsSignalError(gbd ->ContextID, cmsERROR_RANGE, " quadrant out of range"); 348 return NULL; 349 } 350 351 // Get pointer to the sector 352 return &gbd ->Gamut[theta][alpha]; 353 } 354 355 // Add a point to gamut descriptor. Point to add is in Lab color space. 356 // GBD is centered on a=b=0 and L*=50 357 cmsBool CMSEXPORT cmsGDBAddPoint(cmsHANDLE hGBD, const cmsCIELab* Lab) 358 { 359 cmsGDB* gbd = (cmsGDB*) hGBD; 360 cmsGDBPoint* ptr; 361 cmsSpherical sp; 362 363 364 // Get pointer to the sector 365 ptr = GetPoint(gbd, Lab, &sp); 366 if (ptr == NULL) return FALSE; 367 368 // If no samples at this sector, add it 369 if (ptr ->Type == GP_EMPTY) { 370 371 ptr -> Type = GP_SPECIFIED; 372 ptr -> p = sp; 373 } 374 else { 375 376 377 // Substitute only if radius is greater 378 if (sp.r > ptr -> p.r) { 379 380 ptr -> Type = GP_SPECIFIED; 381 ptr -> p = sp; 382 } 383 } 384 385 return TRUE; 386 } 387 388 // Check if a given point falls inside gamut 389 cmsBool CMSEXPORT cmsGDBCheckPoint(cmsHANDLE hGBD, const cmsCIELab* Lab) 390 { 391 cmsGDB* gbd = (cmsGDB*) hGBD; 392 cmsGDBPoint* ptr; 393 cmsSpherical sp; 394 395 // Get pointer to the sector 396 ptr = GetPoint(gbd, Lab, &sp); 397 if (ptr == NULL) return FALSE; 398 399 // If no samples at this sector, return no data 400 if (ptr ->Type == GP_EMPTY) return FALSE; 401 402 // In gamut only if radius is greater 403 404 return (sp.r <= ptr -> p.r); 405 } 406 407 // ----------------------------------------------------------------------------------------------------------------------- 408 409 // Find near sectors. The list of sectors found is returned on Close[]. 410 // The function returns the number of sectors as well. 411 412 // 24 9 10 11 12 413 // 23 8 1 2 13 414 // 22 7 * 3 14 415 // 21 6 5 4 15 416 // 20 19 18 17 16 417 // 418 // Those are the relative movements 419 // {-2,-2}, {-1, -2}, {0, -2}, {+1, -2}, {+2, -2}, 420 // {-2,-1}, {-1, -1}, {0, -1}, {+1, -1}, {+2, -1}, 421 // {-2, 0}, {-1, 0}, {0, 0}, {+1, 0}, {+2, 0}, 422 // {-2,+1}, {-1, +1}, {0, +1}, {+1, +1}, {+2, +1}, 423 // {-2,+2}, {-1, +2}, {0, +2}, {+1, +2}, {+2, +2}}; 424 425 426 static 427 const struct _spiral { 428 429 int AdvX, AdvY; 430 431 } Spiral[] = { {0, -1}, {+1, -1}, {+1, 0}, {+1, +1}, {0, +1}, {-1, +1}, 432 {-1, 0}, {-1, -1}, {-1, -2}, {0, -2}, {+1, -2}, {+2, -2}, 433 {+2, -1}, {+2, 0}, {+2, +1}, {+2, +2}, {+1, +2}, {0, +2}, 434 {-1, +2}, {-2, +2}, {-2, +1}, {-2, 0}, {-2, -1}, {-2, -2} }; 435 436 #define NSTEPS (sizeof(Spiral) / sizeof(struct _spiral)) 437 438 static 439 int FindNearSectors(cmsGDB* gbd, int alpha, int theta, cmsGDBPoint* Close[]) 440 { 441 int nSectors = 0; 442 int a, t; 443 cmsUInt32Number i; 444 cmsGDBPoint* pt; 445 446 for (i=0; i < NSTEPS; i++) { 447 448 a = alpha + Spiral[i].AdvX; 449 t = theta + Spiral[i].AdvY; 450 451 // Cycle at the end 452 a %= SECTORS; 453 t %= SECTORS; 454 455 // Cycle at the begin 456 if (a < 0) a = SECTORS + a; 457 if (t < 0) t = SECTORS + t; 458 459 pt = &gbd ->Gamut[t][a]; 460 461 if (pt -> Type != GP_EMPTY) { 462 463 Close[nSectors++] = pt; 464 } 465 } 466 467 return nSectors; 468 } 469 470 471 // Interpolate a missing sector. Method identifies whatever this is top, bottom or mid 472 static 473 cmsBool InterpolateMissingSector(cmsGDB* gbd, int alpha, int theta) 474 { 475 cmsSpherical sp; 476 cmsVEC3 Lab; 477 cmsVEC3 Centre; 478 cmsLine ray; 479 int nCloseSectors; 480 cmsGDBPoint* Close[NSTEPS + 1]; 481 cmsSpherical closel, templ; 482 cmsLine edge; 483 int k, m; 484 485 // Is that point already specified? 486 if (gbd ->Gamut[theta][alpha].Type != GP_EMPTY) return TRUE; 487 488 // Fill close points 489 nCloseSectors = FindNearSectors(gbd, alpha, theta, Close); 490 491 492 // Find a central point on the sector 493 sp.alpha = (cmsFloat64Number) ((alpha + 0.5) * 360.0) / (SECTORS); 494 sp.theta = (cmsFloat64Number) ((theta + 0.5) * 180.0) / (SECTORS); 495 sp.r = 50.0; 496 497 // Convert to Cartesian 498 ToCartesian(&Lab, &sp); 499 500 // Create a ray line from centre to this point 501 _cmsVEC3init(&Centre, 50.0, 0, 0); 502 LineOf2Points(&ray, &Lab, &Centre); 503 504 // For all close sectors 505 closel.r = 0.0; 506 closel.alpha = 0; 507 closel.theta = 0; 508 509 for (k=0; k < nCloseSectors; k++) { 510 511 for(m = k+1; m < nCloseSectors; m++) { 512 513 cmsVEC3 temp, a1, a2; 514 515 // A line from sector to sector 516 ToCartesian(&a1, &Close[k]->p); 517 ToCartesian(&a2, &Close[m]->p); 518 519 LineOf2Points(&edge, &a1, &a2); 520 521 // Find a line 522 ClosestLineToLine(&temp, &ray, &edge); 523 524 // Convert to spherical 525 ToSpherical(&templ, &temp); 526 527 528 if ( templ.r > closel.r && 529 templ.theta >= (theta*180.0/SECTORS) && 530 templ.theta <= ((theta+1)*180.0/SECTORS) && 531 templ.alpha >= (alpha*360.0/SECTORS) && 532 templ.alpha <= ((alpha+1)*360.0/SECTORS)) { 533 534 closel = templ; 535 } 536 } 537 } 538 539 gbd ->Gamut[theta][alpha].p = closel; 540 gbd ->Gamut[theta][alpha].Type = GP_MODELED; 541 542 return TRUE; 543 544 } 545 546 547 // Interpolate missing parts. The algorithm fist computes slices at 548 // theta=0 and theta=Max. 549 cmsBool CMSEXPORT cmsGDBCompute(cmsHANDLE hGBD, cmsUInt32Number dwFlags) 550 { 551 int alpha, theta; 552 cmsGDB* gbd = (cmsGDB*) hGBD; 553 554 _cmsAssert(hGBD != NULL); 555 556 // Interpolate black 557 for (alpha = 0; alpha < SECTORS; alpha++) { 558 559 if (!InterpolateMissingSector(gbd, alpha, 0)) return FALSE; 560 } 561 562 // Interpolate white 563 for (alpha = 0; alpha < SECTORS; alpha++) { 564 565 if (!InterpolateMissingSector(gbd, alpha, SECTORS-1)) return FALSE; 566 } 567 568 569 // Interpolate Mid 570 for (theta = 1; theta < SECTORS; theta++) { 571 for (alpha = 0; alpha < SECTORS; alpha++) { 572 573 if (!InterpolateMissingSector(gbd, alpha, theta)) return FALSE; 574 } 575 } 576 577 // Done 578 return TRUE; 579 580 cmsUNUSED_PARAMETER(dwFlags); 581 } 582 583 584 585 586 // -------------------------------------------------------------------------------------------------------- 587 588 // Great for debug, but not suitable for real use 589 590 #if 0 591 cmsBool cmsGBDdumpVRML(cmsHANDLE hGBD, const char* fname) 592 { 593 FILE* fp; 594 int i, j; 595 cmsGDB* gbd = (cmsGDB*) hGBD; 596 cmsGDBPoint* pt; 597 598 fp = fopen (fname, "wt"); 599 if (fp == NULL) 600 return FALSE; 601 602 fprintf (fp, "#VRML V2.0 utf8\n"); 603 604 // set the viewing orientation and distance 605 fprintf (fp, "DEF CamTest Group {\n"); 606 fprintf (fp, "\tchildren [\n"); 607 fprintf (fp, "\t\tDEF Cameras Group {\n"); 608 fprintf (fp, "\t\t\tchildren [\n"); 609 fprintf (fp, "\t\t\t\tDEF DefaultView Viewpoint {\n"); 610 fprintf (fp, "\t\t\t\t\tposition 0 0 340\n"); 611 fprintf (fp, "\t\t\t\t\torientation 0 0 1 0\n"); 612 fprintf (fp, "\t\t\t\t\tdescription \"default view\"\n"); 613 fprintf (fp, "\t\t\t\t}\n"); 614 fprintf (fp, "\t\t\t]\n"); 615 fprintf (fp, "\t\t},\n"); 616 fprintf (fp, "\t]\n"); 617 fprintf (fp, "}\n"); 618 619 // Output the background stuff 620 fprintf (fp, "Background {\n"); 621 fprintf (fp, "\tskyColor [\n"); 622 fprintf (fp, "\t\t.5 .5 .5\n"); 623 fprintf (fp, "\t]\n"); 624 fprintf (fp, "}\n"); 625 626 // Output the shape stuff 627 fprintf (fp, "Transform {\n"); 628 fprintf (fp, "\tscale .3 .3 .3\n"); 629 fprintf (fp, "\tchildren [\n"); 630 631 // Draw the axes as a shape: 632 fprintf (fp, "\t\tShape {\n"); 633 fprintf (fp, "\t\t\tappearance Appearance {\n"); 634 fprintf (fp, "\t\t\t\tmaterial Material {\n"); 635 fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); 636 fprintf (fp, "\t\t\t\t\temissiveColor 1.0 1.0 1.0\n"); 637 fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); 638 fprintf (fp, "\t\t\t\t}\n"); 639 fprintf (fp, "\t\t\t}\n"); 640 fprintf (fp, "\t\t\tgeometry IndexedLineSet {\n"); 641 fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); 642 fprintf (fp, "\t\t\t\t\tpoint [\n"); 643 fprintf (fp, "\t\t\t\t\t0.0 0.0 0.0,\n"); 644 fprintf (fp, "\t\t\t\t\t%f 0.0 0.0,\n", 255.0); 645 fprintf (fp, "\t\t\t\t\t0.0 %f 0.0,\n", 255.0); 646 fprintf (fp, "\t\t\t\t\t0.0 0.0 %f]\n", 255.0); 647 fprintf (fp, "\t\t\t\t}\n"); 648 fprintf (fp, "\t\t\t\tcoordIndex [\n"); 649 fprintf (fp, "\t\t\t\t\t0, 1, -1\n"); 650 fprintf (fp, "\t\t\t\t\t0, 2, -1\n"); 651 fprintf (fp, "\t\t\t\t\t0, 3, -1]\n"); 652 fprintf (fp, "\t\t\t}\n"); 653 fprintf (fp, "\t\t}\n"); 654 655 656 fprintf (fp, "\t\tShape {\n"); 657 fprintf (fp, "\t\t\tappearance Appearance {\n"); 658 fprintf (fp, "\t\t\t\tmaterial Material {\n"); 659 fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); 660 fprintf (fp, "\t\t\t\t\temissiveColor 1 1 1\n"); 661 fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); 662 fprintf (fp, "\t\t\t\t}\n"); 663 fprintf (fp, "\t\t\t}\n"); 664 fprintf (fp, "\t\t\tgeometry PointSet {\n"); 665 666 // fill in the points here 667 fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); 668 fprintf (fp, "\t\t\t\t\tpoint [\n"); 669 670 // We need to transverse all gamut hull. 671 for (i=0; i < SECTORS; i++) 672 for (j=0; j < SECTORS; j++) { 673 674 cmsVEC3 v; 675 676 pt = &gbd ->Gamut[i][j]; 677 ToCartesian(&v, &pt ->p); 678 679 fprintf (fp, "\t\t\t\t\t%g %g %g", v.n[0]+50, v.n[1], v.n[2]); 680 681 if ((j == SECTORS - 1) && (i == SECTORS - 1)) 682 fprintf (fp, "]\n"); 683 else 684 fprintf (fp, ",\n"); 685 686 } 687 688 fprintf (fp, "\t\t\t\t}\n"); 689 690 691 692 // fill in the face colors 693 fprintf (fp, "\t\t\t\tcolor Color {\n"); 694 fprintf (fp, "\t\t\t\t\tcolor [\n"); 695 696 for (i=0; i < SECTORS; i++) 697 for (j=0; j < SECTORS; j++) { 698 699 cmsVEC3 v; 700 701 pt = &gbd ->Gamut[i][j]; 702 703 704 ToCartesian(&v, &pt ->p); 705 706 707 if (pt ->Type == GP_EMPTY) 708 fprintf (fp, "\t\t\t\t\t%g %g %g", 0.0, 0.0, 0.0); 709 else 710 if (pt ->Type == GP_MODELED) 711 fprintf (fp, "\t\t\t\t\t%g %g %g", 1.0, .5, .5); 712 else { 713 fprintf (fp, "\t\t\t\t\t%g %g %g", 1.0, 1.0, 1.0); 714 715 } 716 717 if ((j == SECTORS - 1) && (i == SECTORS - 1)) 718 fprintf (fp, "]\n"); 719 else 720 fprintf (fp, ",\n"); 721 } 722 fprintf (fp, "\t\t\t}\n"); 723 724 725 fprintf (fp, "\t\t\t}\n"); 726 fprintf (fp, "\t\t}\n"); 727 fprintf (fp, "\t]\n"); 728 fprintf (fp, "}\n"); 729 730 fclose (fp); 731 732 return TRUE; 733 } 734 #endif 735