• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //---------------------------------------------------------------------------------
2 //
3 //  Little Color Management System
4 //  Copyright (c) 1998-2017 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
_cmsAtan2(cmsFloat64Number y,cmsFloat64Number x)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
ToSpherical(cmsSpherical * sp,const cmsVEC3 * v)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
ToCartesian(cmsVEC3 * v,const cmsSpherical * sp)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
QuantizeToSector(const cmsSpherical * sp,int * alpha,int * theta)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
LineOf2Points(cmsLine * line,cmsVEC3 * a,cmsVEC3 * b)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
GetPointOfLine(cmsVEC3 * p,const cmsLine * line,cmsFloat64Number t)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
ClosestLineToLine(cmsVEC3 * r,const cmsLine * line1,const cmsLine * line2)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
cmsGBDAlloc(cmsContext ContextID)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 
cmsGBDFree(cmsHANDLE hGBD)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
GetPoint(cmsGDB * gbd,const cmsCIELab * Lab,cmsSpherical * sp)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
cmsGDBAddPoint(cmsHANDLE hGBD,const cmsCIELab * Lab)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
cmsGDBCheckPoint(cmsHANDLE hGBD,const cmsCIELab * Lab)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
FindNearSectors(cmsGDB * gbd,int alpha,int theta,cmsGDBPoint * Close[])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
InterpolateMissingSector(cmsGDB * gbd,int alpha,int theta)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.
cmsGDBCompute(cmsHANDLE hGBD,cmsUInt32Number dwFlags)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