Home | History | Annotate | Download | only in src
      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