1 //--------------------------------------------------------------------------------- 2 // 3 // Little Color Management System 4 // Copyright (c) 1998-2016 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; // left for future use 222 cmsFloat64Number tN, tD; 223 cmsVEC3 w0; 224 225 _cmsVEC3minus(&w0, &line1 ->a, &line2 ->a); 226 227 a = _cmsVEC3dot(&line1 ->u, &line1 ->u); 228 b = _cmsVEC3dot(&line1 ->u, &line2 ->u); 229 c = _cmsVEC3dot(&line2 ->u, &line2 ->u); 230 d = _cmsVEC3dot(&line1 ->u, &w0); 231 e = _cmsVEC3dot(&line2 ->u, &w0); 232 233 D = a*c - b * b; // Denominator 234 sD = tD = D; // default sD = D >= 0 235 236 if (D < MATRIX_DET_TOLERANCE) { // the lines are almost parallel 237 238 sN = 0.0; // force using point P0 on segment S1 239 sD = 1.0; // to prevent possible division by 0.0 later 240 tN = e; 241 tD = c; 242 } 243 else { // get the closest points on the infinite lines 244 245 sN = (b*e - c*d); 246 tN = (a*e - b*d); 247 248 if (sN < 0.0) { // sc < 0 => the s=0 edge is visible 249 250 sN = 0.0; 251 tN = e; 252 tD = c; 253 } 254 else if (sN > sD) { // sc > 1 => the s=1 edge is visible 255 sN = sD; 256 tN = e + b; 257 tD = c; 258 } 259 } 260 261 if (tN < 0.0) { // tc < 0 => the t=0 edge is visible 262 263 tN = 0.0; 264 // recompute sc for this edge 265 if (-d < 0.0) 266 sN = 0.0; 267 else if (-d > a) 268 sN = sD; 269 else { 270 sN = -d; 271 sD = a; 272 } 273 } 274 else if (tN > tD) { // tc > 1 => the t=1 edge is visible 275 276 tN = tD; 277 278 // recompute sc for this edge 279 if ((-d + b) < 0.0) 280 sN = 0; 281 else if ((-d + b) > a) 282 sN = sD; 283 else { 284 sN = (-d + b); 285 sD = a; 286 } 287 } 288 // finally do the division to get sc and tc 289 sc = (fabs(sN) < MATRIX_DET_TOLERANCE ? 0.0 : sN / sD); 290 //tc = (fabs(tN) < MATRIX_DET_TOLERANCE ? 0.0 : tN / tD); // left for future use. 291 292 GetPointOfLine(r, line1, sc); 293 return TRUE; 294 } 295 296 297 298 // ------------------------------------------------------------------ Wrapper 299 300 301 // Allocate & free structure 302 cmsHANDLE CMSEXPORT cmsGBDAlloc(cmsContext ContextID) 303 { 304 cmsGDB* gbd = (cmsGDB*) _cmsMallocZero(ContextID, sizeof(cmsGDB)); 305 if (gbd == NULL) return NULL; 306 307 gbd -> ContextID = ContextID; 308 309 return (cmsHANDLE) gbd; 310 } 311 312 313 void CMSEXPORT cmsGBDFree(cmsHANDLE hGBD) 314 { 315 cmsGDB* gbd = (cmsGDB*) hGBD; 316 if (hGBD != NULL) 317 _cmsFree(gbd->ContextID, (void*) gbd); 318 } 319 320 321 // Auxiliary to retrieve a pointer to the segmentr containing the Lab value 322 static 323 cmsGDBPoint* GetPoint(cmsGDB* gbd, const cmsCIELab* Lab, cmsSpherical* sp) 324 { 325 cmsVEC3 v; 326 int alpha, theta; 327 328 // Housekeeping 329 _cmsAssert(gbd != NULL); 330 _cmsAssert(Lab != NULL); 331 _cmsAssert(sp != NULL); 332 333 // Center L* by subtracting half of its domain, that's 50 334 _cmsVEC3init(&v, Lab ->L - 50.0, Lab ->a, Lab ->b); 335 336 // Convert to spherical coordinates 337 ToSpherical(sp, &v); 338 339 if (sp ->r < 0 || sp ->alpha < 0 || sp->theta < 0) { 340 cmsSignalError(gbd ->ContextID, cmsERROR_RANGE, "spherical value out of range"); 341 return NULL; 342 } 343 344 // On which sector it falls? 345 QuantizeToSector(sp, &alpha, &theta); 346 347 if (alpha < 0 || theta < 0 || alpha >= SECTORS || theta >= SECTORS) { 348 cmsSignalError(gbd ->ContextID, cmsERROR_RANGE, " quadrant out of range"); 349 return NULL; 350 } 351 352 // Get pointer to the sector 353 return &gbd ->Gamut[theta][alpha]; 354 } 355 356 // Add a point to gamut descriptor. Point to add is in Lab color space. 357 // GBD is centered on a=b=0 and L*=50 358 cmsBool CMSEXPORT cmsGDBAddPoint(cmsHANDLE hGBD, const cmsCIELab* Lab) 359 { 360 cmsGDB* gbd = (cmsGDB*) hGBD; 361 cmsGDBPoint* ptr; 362 cmsSpherical sp; 363 364 365 // Get pointer to the sector 366 ptr = GetPoint(gbd, Lab, &sp); 367 if (ptr == NULL) return FALSE; 368 369 // If no samples at this sector, add it 370 if (ptr ->Type == GP_EMPTY) { 371 372 ptr -> Type = GP_SPECIFIED; 373 ptr -> p = sp; 374 } 375 else { 376 377 378 // Substitute only if radius is greater 379 if (sp.r > ptr -> p.r) { 380 381 ptr -> Type = GP_SPECIFIED; 382 ptr -> p = sp; 383 } 384 } 385 386 return TRUE; 387 } 388 389 // Check if a given point falls inside gamut 390 cmsBool CMSEXPORT cmsGDBCheckPoint(cmsHANDLE hGBD, const cmsCIELab* Lab) 391 { 392 cmsGDB* gbd = (cmsGDB*) hGBD; 393 cmsGDBPoint* ptr; 394 cmsSpherical sp; 395 396 // Get pointer to the sector 397 ptr = GetPoint(gbd, Lab, &sp); 398 if (ptr == NULL) return FALSE; 399 400 // If no samples at this sector, return no data 401 if (ptr ->Type == GP_EMPTY) return FALSE; 402 403 // In gamut only if radius is greater 404 405 return (sp.r <= ptr -> p.r); 406 } 407 408 // ----------------------------------------------------------------------------------------------------------------------- 409 410 // Find near sectors. The list of sectors found is returned on Close[]. 411 // The function returns the number of sectors as well. 412 413 // 24 9 10 11 12 414 // 23 8 1 2 13 415 // 22 7 * 3 14 416 // 21 6 5 4 15 417 // 20 19 18 17 16 418 // 419 // Those are the relative movements 420 // {-2,-2}, {-1, -2}, {0, -2}, {+1, -2}, {+2, -2}, 421 // {-2,-1}, {-1, -1}, {0, -1}, {+1, -1}, {+2, -1}, 422 // {-2, 0}, {-1, 0}, {0, 0}, {+1, 0}, {+2, 0}, 423 // {-2,+1}, {-1, +1}, {0, +1}, {+1, +1}, {+2, +1}, 424 // {-2,+2}, {-1, +2}, {0, +2}, {+1, +2}, {+2, +2}}; 425 426 427 static 428 const struct _spiral { 429 430 int AdvX, AdvY; 431 432 } Spiral[] = { {0, -1}, {+1, -1}, {+1, 0}, {+1, +1}, {0, +1}, {-1, +1}, 433 {-1, 0}, {-1, -1}, {-1, -2}, {0, -2}, {+1, -2}, {+2, -2}, 434 {+2, -1}, {+2, 0}, {+2, +1}, {+2, +2}, {+1, +2}, {0, +2}, 435 {-1, +2}, {-2, +2}, {-2, +1}, {-2, 0}, {-2, -1}, {-2, -2} }; 436 437 #define NSTEPS (sizeof(Spiral) / sizeof(struct _spiral)) 438 439 static 440 int FindNearSectors(cmsGDB* gbd, int alpha, int theta, cmsGDBPoint* Close[]) 441 { 442 int nSectors = 0; 443 int a, t; 444 cmsUInt32Number i; 445 cmsGDBPoint* pt; 446 447 for (i=0; i < NSTEPS; i++) { 448 449 a = alpha + Spiral[i].AdvX; 450 t = theta + Spiral[i].AdvY; 451 452 // Cycle at the end 453 a %= SECTORS; 454 t %= SECTORS; 455 456 // Cycle at the begin 457 if (a < 0) a = SECTORS + a; 458 if (t < 0) t = SECTORS + t; 459 460 pt = &gbd ->Gamut[t][a]; 461 462 if (pt -> Type != GP_EMPTY) { 463 464 Close[nSectors++] = pt; 465 } 466 } 467 468 return nSectors; 469 } 470 471 472 // Interpolate a missing sector. Method identifies whatever this is top, bottom or mid 473 static 474 cmsBool InterpolateMissingSector(cmsGDB* gbd, int alpha, int theta) 475 { 476 cmsSpherical sp; 477 cmsVEC3 Lab; 478 cmsVEC3 Centre; 479 cmsLine ray; 480 int nCloseSectors; 481 cmsGDBPoint* Close[NSTEPS + 1]; 482 cmsSpherical closel, templ; 483 cmsLine edge; 484 int k, m; 485 486 // Is that point already specified? 487 if (gbd ->Gamut[theta][alpha].Type != GP_EMPTY) return TRUE; 488 489 // Fill close points 490 nCloseSectors = FindNearSectors(gbd, alpha, theta, Close); 491 492 493 // Find a central point on the sector 494 sp.alpha = (cmsFloat64Number) ((alpha + 0.5) * 360.0) / (SECTORS); 495 sp.theta = (cmsFloat64Number) ((theta + 0.5) * 180.0) / (SECTORS); 496 sp.r = 50.0; 497 498 // Convert to Cartesian 499 ToCartesian(&Lab, &sp); 500 501 // Create a ray line from centre to this point 502 _cmsVEC3init(&Centre, 50.0, 0, 0); 503 LineOf2Points(&ray, &Lab, &Centre); 504 505 // For all close sectors 506 closel.r = 0.0; 507 closel.alpha = 0; 508 closel.theta = 0; 509 510 for (k=0; k < nCloseSectors; k++) { 511 512 for(m = k+1; m < nCloseSectors; m++) { 513 514 cmsVEC3 temp, a1, a2; 515 516 // A line from sector to sector 517 ToCartesian(&a1, &Close[k]->p); 518 ToCartesian(&a2, &Close[m]->p); 519 520 LineOf2Points(&edge, &a1, &a2); 521 522 // Find a line 523 ClosestLineToLine(&temp, &ray, &edge); 524 525 // Convert to spherical 526 ToSpherical(&templ, &temp); 527 528 529 if ( templ.r > closel.r && 530 templ.theta >= (theta*180.0/SECTORS) && 531 templ.theta <= ((theta+1)*180.0/SECTORS) && 532 templ.alpha >= (alpha*360.0/SECTORS) && 533 templ.alpha <= ((alpha+1)*360.0/SECTORS)) { 534 535 closel = templ; 536 } 537 } 538 } 539 540 gbd ->Gamut[theta][alpha].p = closel; 541 gbd ->Gamut[theta][alpha].Type = GP_MODELED; 542 543 return TRUE; 544 545 } 546 547 548 // Interpolate missing parts. The algorithm fist computes slices at 549 // theta=0 and theta=Max. 550 cmsBool CMSEXPORT cmsGDBCompute(cmsHANDLE hGBD, cmsUInt32Number dwFlags) 551 { 552 int alpha, theta; 553 cmsGDB* gbd = (cmsGDB*) hGBD; 554 555 _cmsAssert(hGBD != NULL); 556 557 // Interpolate black 558 for (alpha = 0; alpha < SECTORS; alpha++) { 559 560 if (!InterpolateMissingSector(gbd, alpha, 0)) return FALSE; 561 } 562 563 // Interpolate white 564 for (alpha = 0; alpha < SECTORS; alpha++) { 565 566 if (!InterpolateMissingSector(gbd, alpha, SECTORS-1)) return FALSE; 567 } 568 569 570 // Interpolate Mid 571 for (theta = 1; theta < SECTORS; theta++) { 572 for (alpha = 0; alpha < SECTORS; alpha++) { 573 574 if (!InterpolateMissingSector(gbd, alpha, theta)) return FALSE; 575 } 576 } 577 578 // Done 579 return TRUE; 580 581 cmsUNUSED_PARAMETER(dwFlags); 582 } 583 584 585 586 587 // -------------------------------------------------------------------------------------------------------- 588 589 // Great for debug, but not suitable for real use 590 591 #if 0 592 cmsBool cmsGBDdumpVRML(cmsHANDLE hGBD, const char* fname) 593 { 594 FILE* fp; 595 int i, j; 596 cmsGDB* gbd = (cmsGDB*) hGBD; 597 cmsGDBPoint* pt; 598 599 fp = fopen (fname, "wt"); 600 if (fp == NULL) 601 return FALSE; 602 603 fprintf (fp, "#VRML V2.0 utf8\n"); 604 605 // set the viewing orientation and distance 606 fprintf (fp, "DEF CamTest Group {\n"); 607 fprintf (fp, "\tchildren [\n"); 608 fprintf (fp, "\t\tDEF Cameras Group {\n"); 609 fprintf (fp, "\t\t\tchildren [\n"); 610 fprintf (fp, "\t\t\t\tDEF DefaultView Viewpoint {\n"); 611 fprintf (fp, "\t\t\t\t\tposition 0 0 340\n"); 612 fprintf (fp, "\t\t\t\t\torientation 0 0 1 0\n"); 613 fprintf (fp, "\t\t\t\t\tdescription \"default view\"\n"); 614 fprintf (fp, "\t\t\t\t}\n"); 615 fprintf (fp, "\t\t\t]\n"); 616 fprintf (fp, "\t\t},\n"); 617 fprintf (fp, "\t]\n"); 618 fprintf (fp, "}\n"); 619 620 // Output the background stuff 621 fprintf (fp, "Background {\n"); 622 fprintf (fp, "\tskyColor [\n"); 623 fprintf (fp, "\t\t.5 .5 .5\n"); 624 fprintf (fp, "\t]\n"); 625 fprintf (fp, "}\n"); 626 627 // Output the shape stuff 628 fprintf (fp, "Transform {\n"); 629 fprintf (fp, "\tscale .3 .3 .3\n"); 630 fprintf (fp, "\tchildren [\n"); 631 632 // Draw the axes as a shape: 633 fprintf (fp, "\t\tShape {\n"); 634 fprintf (fp, "\t\t\tappearance Appearance {\n"); 635 fprintf (fp, "\t\t\t\tmaterial Material {\n"); 636 fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); 637 fprintf (fp, "\t\t\t\t\temissiveColor 1.0 1.0 1.0\n"); 638 fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); 639 fprintf (fp, "\t\t\t\t}\n"); 640 fprintf (fp, "\t\t\t}\n"); 641 fprintf (fp, "\t\t\tgeometry IndexedLineSet {\n"); 642 fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); 643 fprintf (fp, "\t\t\t\t\tpoint [\n"); 644 fprintf (fp, "\t\t\t\t\t0.0 0.0 0.0,\n"); 645 fprintf (fp, "\t\t\t\t\t%f 0.0 0.0,\n", 255.0); 646 fprintf (fp, "\t\t\t\t\t0.0 %f 0.0,\n", 255.0); 647 fprintf (fp, "\t\t\t\t\t0.0 0.0 %f]\n", 255.0); 648 fprintf (fp, "\t\t\t\t}\n"); 649 fprintf (fp, "\t\t\t\tcoordIndex [\n"); 650 fprintf (fp, "\t\t\t\t\t0, 1, -1\n"); 651 fprintf (fp, "\t\t\t\t\t0, 2, -1\n"); 652 fprintf (fp, "\t\t\t\t\t0, 3, -1]\n"); 653 fprintf (fp, "\t\t\t}\n"); 654 fprintf (fp, "\t\t}\n"); 655 656 657 fprintf (fp, "\t\tShape {\n"); 658 fprintf (fp, "\t\t\tappearance Appearance {\n"); 659 fprintf (fp, "\t\t\t\tmaterial Material {\n"); 660 fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); 661 fprintf (fp, "\t\t\t\t\temissiveColor 1 1 1\n"); 662 fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); 663 fprintf (fp, "\t\t\t\t}\n"); 664 fprintf (fp, "\t\t\t}\n"); 665 fprintf (fp, "\t\t\tgeometry PointSet {\n"); 666 667 // fill in the points here 668 fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); 669 fprintf (fp, "\t\t\t\t\tpoint [\n"); 670 671 // We need to transverse all gamut hull. 672 for (i=0; i < SECTORS; i++) 673 for (j=0; j < SECTORS; j++) { 674 675 cmsVEC3 v; 676 677 pt = &gbd ->Gamut[i][j]; 678 ToCartesian(&v, &pt ->p); 679 680 fprintf (fp, "\t\t\t\t\t%g %g %g", v.n[0]+50, v.n[1], v.n[2]); 681 682 if ((j == SECTORS - 1) && (i == SECTORS - 1)) 683 fprintf (fp, "]\n"); 684 else 685 fprintf (fp, ",\n"); 686 687 } 688 689 fprintf (fp, "\t\t\t\t}\n"); 690 691 692 693 // fill in the face colors 694 fprintf (fp, "\t\t\t\tcolor Color {\n"); 695 fprintf (fp, "\t\t\t\t\tcolor [\n"); 696 697 for (i=0; i < SECTORS; i++) 698 for (j=0; j < SECTORS; j++) { 699 700 cmsVEC3 v; 701 702 pt = &gbd ->Gamut[i][j]; 703 704 705 ToCartesian(&v, &pt ->p); 706 707 708 if (pt ->Type == GP_EMPTY) 709 fprintf (fp, "\t\t\t\t\t%g %g %g", 0.0, 0.0, 0.0); 710 else 711 if (pt ->Type == GP_MODELED) 712 fprintf (fp, "\t\t\t\t\t%g %g %g", 1.0, .5, .5); 713 else { 714 fprintf (fp, "\t\t\t\t\t%g %g %g", 1.0, 1.0, 1.0); 715 716 } 717 718 if ((j == SECTORS - 1) && (i == SECTORS - 1)) 719 fprintf (fp, "]\n"); 720 else 721 fprintf (fp, ",\n"); 722 } 723 fprintf (fp, "\t\t\t}\n"); 724 725 726 fprintf (fp, "\t\t\t}\n"); 727 fprintf (fp, "\t\t}\n"); 728 fprintf (fp, "\t]\n"); 729 fprintf (fp, "}\n"); 730 731 fclose (fp); 732 733 return TRUE; 734 } 735 #endif 736 737