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