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