| //--------------------------------------------------------------------------------- |
| // |
| // Little Color Management System |
| // Copyright (c) 1998-2020 Marti Maria Saguer |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining |
| // a copy of this software and associated documentation files (the "Software"), |
| // to deal in the Software without restriction, including without limitation |
| // the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| // and/or sell copies of the Software, and to permit persons to whom the Software |
| // is furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO |
| // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| // |
| //--------------------------------------------------------------------------------- |
| // |
| |
| #include "lcms2_internal.h" |
| |
| |
| // ------------------------------------------------------------------------ |
| |
| // Gamut boundary description by using Jan Morovic's Segment maxima method |
| // Many thanks to Jan for allowing me to use his algorithm. |
| |
| // r = C* |
| // alpha = Hab |
| // theta = L* |
| |
| #define SECTORS 16 // number of divisions in alpha and theta |
| |
| // Spherical coordinates |
| typedef struct { |
| |
| cmsFloat64Number r; |
| cmsFloat64Number alpha; |
| cmsFloat64Number theta; |
| |
| } cmsSpherical; |
| |
| typedef enum { |
| GP_EMPTY, |
| GP_SPECIFIED, |
| GP_MODELED |
| |
| } GDBPointType; |
| |
| |
| typedef struct { |
| |
| GDBPointType Type; |
| cmsSpherical p; // Keep also alpha & theta of maximum |
| |
| } cmsGDBPoint; |
| |
| |
| typedef struct { |
| |
| cmsContext ContextID; |
| cmsGDBPoint Gamut[SECTORS][SECTORS]; |
| |
| } cmsGDB; |
| |
| |
| // A line using the parametric form |
| // P = a + t*u |
| typedef struct { |
| |
| cmsVEC3 a; |
| cmsVEC3 u; |
| |
| } cmsLine; |
| |
| |
| // A plane using the parametric form |
| // Q = b + r*v + s*w |
| typedef struct { |
| |
| cmsVEC3 b; |
| cmsVEC3 v; |
| cmsVEC3 w; |
| |
| } cmsPlane; |
| |
| |
| |
| // -------------------------------------------------------------------------------------------- |
| |
| // ATAN2() which always returns degree positive numbers |
| |
| static |
| cmsFloat64Number _cmsAtan2(cmsFloat64Number y, cmsFloat64Number x) |
| { |
| cmsFloat64Number a; |
| |
| // Deal with undefined case |
| if (x == 0.0 && y == 0.0) return 0; |
| |
| a = (atan2(y, x) * 180.0) / M_PI; |
| |
| while (a < 0) { |
| a += 360; |
| } |
| |
| return a; |
| } |
| |
| // Convert to spherical coordinates |
| static |
| void ToSpherical(cmsSpherical* sp, const cmsVEC3* v) |
| { |
| |
| cmsFloat64Number L, a, b; |
| |
| L = v ->n[VX]; |
| a = v ->n[VY]; |
| b = v ->n[VZ]; |
| |
| sp ->r = sqrt( L*L + a*a + b*b ); |
| |
| if (sp ->r == 0) { |
| sp ->alpha = sp ->theta = 0; |
| return; |
| } |
| |
| sp ->alpha = _cmsAtan2(a, b); |
| sp ->theta = _cmsAtan2(sqrt(a*a + b*b), L); |
| } |
| |
| |
| // Convert to cartesian from spherical |
| static |
| void ToCartesian(cmsVEC3* v, const cmsSpherical* sp) |
| { |
| cmsFloat64Number sin_alpha; |
| cmsFloat64Number cos_alpha; |
| cmsFloat64Number sin_theta; |
| cmsFloat64Number cos_theta; |
| cmsFloat64Number L, a, b; |
| |
| sin_alpha = sin((M_PI * sp ->alpha) / 180.0); |
| cos_alpha = cos((M_PI * sp ->alpha) / 180.0); |
| sin_theta = sin((M_PI * sp ->theta) / 180.0); |
| cos_theta = cos((M_PI * sp ->theta) / 180.0); |
| |
| a = sp ->r * sin_theta * sin_alpha; |
| b = sp ->r * sin_theta * cos_alpha; |
| L = sp ->r * cos_theta; |
| |
| v ->n[VX] = L; |
| v ->n[VY] = a; |
| v ->n[VZ] = b; |
| } |
| |
| |
| // Quantize sector of a spherical coordinate. Saturate 360, 180 to last sector |
| // The limits are the centers of each sector, so |
| static |
| void QuantizeToSector(const cmsSpherical* sp, int* alpha, int* theta) |
| { |
| *alpha = (int) floor(((sp->alpha * (SECTORS)) / 360.0) ); |
| *theta = (int) floor(((sp->theta * (SECTORS)) / 180.0) ); |
| |
| if (*alpha >= SECTORS) |
| *alpha = SECTORS-1; |
| if (*theta >= SECTORS) |
| *theta = SECTORS-1; |
| } |
| |
| |
| // Line determined by 2 points |
| static |
| void LineOf2Points(cmsLine* line, cmsVEC3* a, cmsVEC3* b) |
| { |
| |
| _cmsVEC3init(&line ->a, a ->n[VX], a ->n[VY], a ->n[VZ]); |
| _cmsVEC3init(&line ->u, b ->n[VX] - a ->n[VX], |
| b ->n[VY] - a ->n[VY], |
| b ->n[VZ] - a ->n[VZ]); |
| } |
| |
| |
| // Evaluate parametric line |
| static |
| void GetPointOfLine(cmsVEC3* p, const cmsLine* line, cmsFloat64Number t) |
| { |
| p ->n[VX] = line ->a.n[VX] + t * line->u.n[VX]; |
| p ->n[VY] = line ->a.n[VY] + t * line->u.n[VY]; |
| p ->n[VZ] = line ->a.n[VZ] + t * line->u.n[VZ]; |
| } |
| |
| |
| |
| /* |
| Closest point in sector line1 to sector line2 (both are defined as 0 <=t <= 1) |
| http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm |
| |
| Copyright 2001, softSurfer (www.softsurfer.com) |
| This code may be freely used and modified for any purpose |
| providing that this copyright notice is included with it. |
| SoftSurfer makes no warranty for this code, and cannot be held |
| liable for any real or imagined damage resulting from its use. |
| Users of this code must verify correctness for their application. |
| |
| */ |
| |
| static |
| cmsBool ClosestLineToLine(cmsVEC3* r, const cmsLine* line1, const cmsLine* line2) |
| { |
| cmsFloat64Number a, b, c, d, e, D; |
| cmsFloat64Number sc, sN, sD; |
| //cmsFloat64Number tc; // left for future use |
| cmsFloat64Number tN, tD; |
| cmsVEC3 w0; |
| |
| _cmsVEC3minus(&w0, &line1 ->a, &line2 ->a); |
| |
| a = _cmsVEC3dot(&line1 ->u, &line1 ->u); |
| b = _cmsVEC3dot(&line1 ->u, &line2 ->u); |
| c = _cmsVEC3dot(&line2 ->u, &line2 ->u); |
| d = _cmsVEC3dot(&line1 ->u, &w0); |
| e = _cmsVEC3dot(&line2 ->u, &w0); |
| |
| D = a*c - b * b; // Denominator |
| sD = tD = D; // default sD = D >= 0 |
| |
| if (D < MATRIX_DET_TOLERANCE) { // the lines are almost parallel |
| |
| sN = 0.0; // force using point P0 on segment S1 |
| sD = 1.0; // to prevent possible division by 0.0 later |
| tN = e; |
| tD = c; |
| } |
| else { // get the closest points on the infinite lines |
| |
| sN = (b*e - c*d); |
| tN = (a*e - b*d); |
| |
| if (sN < 0.0) { // sc < 0 => the s=0 edge is visible |
| |
| sN = 0.0; |
| tN = e; |
| tD = c; |
| } |
| else if (sN > sD) { // sc > 1 => the s=1 edge is visible |
| sN = sD; |
| tN = e + b; |
| tD = c; |
| } |
| } |
| |
| if (tN < 0.0) { // tc < 0 => the t=0 edge is visible |
| |
| tN = 0.0; |
| // recompute sc for this edge |
| if (-d < 0.0) |
| sN = 0.0; |
| else if (-d > a) |
| sN = sD; |
| else { |
| sN = -d; |
| sD = a; |
| } |
| } |
| else if (tN > tD) { // tc > 1 => the t=1 edge is visible |
| |
| tN = tD; |
| |
| // recompute sc for this edge |
| if ((-d + b) < 0.0) |
| sN = 0; |
| else if ((-d + b) > a) |
| sN = sD; |
| else { |
| sN = (-d + b); |
| sD = a; |
| } |
| } |
| // finally do the division to get sc and tc |
| sc = (fabs(sN) < MATRIX_DET_TOLERANCE ? 0.0 : sN / sD); |
| //tc = (fabs(tN) < MATRIX_DET_TOLERANCE ? 0.0 : tN / tD); // left for future use. |
| |
| GetPointOfLine(r, line1, sc); |
| return TRUE; |
| } |
| |
| |
| |
| // ------------------------------------------------------------------ Wrapper |
| |
| |
| // Allocate & free structure |
| cmsHANDLE CMSEXPORT cmsGBDAlloc(cmsContext ContextID) |
| { |
| cmsGDB* gbd = (cmsGDB*) _cmsMallocZero(ContextID, sizeof(cmsGDB)); |
| if (gbd == NULL) return NULL; |
| |
| gbd -> ContextID = ContextID; |
| |
| return (cmsHANDLE) gbd; |
| } |
| |
| |
| void CMSEXPORT cmsGBDFree(cmsHANDLE hGBD) |
| { |
| cmsGDB* gbd = (cmsGDB*) hGBD; |
| if (hGBD != NULL) |
| _cmsFree(gbd->ContextID, (void*) gbd); |
| } |
| |
| |
| // Auxiliary to retrieve a pointer to the segmentr containing the Lab value |
| static |
| cmsGDBPoint* GetPoint(cmsGDB* gbd, const cmsCIELab* Lab, cmsSpherical* sp) |
| { |
| cmsVEC3 v; |
| int alpha, theta; |
| |
| // Housekeeping |
| _cmsAssert(gbd != NULL); |
| _cmsAssert(Lab != NULL); |
| _cmsAssert(sp != NULL); |
| |
| // Center L* by subtracting half of its domain, that's 50 |
| _cmsVEC3init(&v, Lab ->L - 50.0, Lab ->a, Lab ->b); |
| |
| // Convert to spherical coordinates |
| ToSpherical(sp, &v); |
| |
| if (sp ->r < 0 || sp ->alpha < 0 || sp->theta < 0) { |
| cmsSignalError(gbd ->ContextID, cmsERROR_RANGE, "spherical value out of range"); |
| return NULL; |
| } |
| |
| // On which sector it falls? |
| QuantizeToSector(sp, &alpha, &theta); |
| |
| if (alpha < 0 || theta < 0 || alpha >= SECTORS || theta >= SECTORS) { |
| cmsSignalError(gbd ->ContextID, cmsERROR_RANGE, " quadrant out of range"); |
| return NULL; |
| } |
| |
| // Get pointer to the sector |
| return &gbd ->Gamut[theta][alpha]; |
| } |
| |
| // Add a point to gamut descriptor. Point to add is in Lab color space. |
| // GBD is centered on a=b=0 and L*=50 |
| cmsBool CMSEXPORT cmsGDBAddPoint(cmsHANDLE hGBD, const cmsCIELab* Lab) |
| { |
| cmsGDB* gbd = (cmsGDB*) hGBD; |
| cmsGDBPoint* ptr; |
| cmsSpherical sp; |
| |
| |
| // Get pointer to the sector |
| ptr = GetPoint(gbd, Lab, &sp); |
| if (ptr == NULL) return FALSE; |
| |
| // If no samples at this sector, add it |
| if (ptr ->Type == GP_EMPTY) { |
| |
| ptr -> Type = GP_SPECIFIED; |
| ptr -> p = sp; |
| } |
| else { |
| |
| |
| // Substitute only if radius is greater |
| if (sp.r > ptr -> p.r) { |
| |
| ptr -> Type = GP_SPECIFIED; |
| ptr -> p = sp; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| // Check if a given point falls inside gamut |
| cmsBool CMSEXPORT cmsGDBCheckPoint(cmsHANDLE hGBD, const cmsCIELab* Lab) |
| { |
| cmsGDB* gbd = (cmsGDB*) hGBD; |
| cmsGDBPoint* ptr; |
| cmsSpherical sp; |
| |
| // Get pointer to the sector |
| ptr = GetPoint(gbd, Lab, &sp); |
| if (ptr == NULL) return FALSE; |
| |
| // If no samples at this sector, return no data |
| if (ptr ->Type == GP_EMPTY) return FALSE; |
| |
| // In gamut only if radius is greater |
| |
| return (sp.r <= ptr -> p.r); |
| } |
| |
| // ----------------------------------------------------------------------------------------------------------------------- |
| |
| // Find near sectors. The list of sectors found is returned on Close[]. |
| // The function returns the number of sectors as well. |
| |
| // 24 9 10 11 12 |
| // 23 8 1 2 13 |
| // 22 7 * 3 14 |
| // 21 6 5 4 15 |
| // 20 19 18 17 16 |
| // |
| // Those are the relative movements |
| // {-2,-2}, {-1, -2}, {0, -2}, {+1, -2}, {+2, -2}, |
| // {-2,-1}, {-1, -1}, {0, -1}, {+1, -1}, {+2, -1}, |
| // {-2, 0}, {-1, 0}, {0, 0}, {+1, 0}, {+2, 0}, |
| // {-2,+1}, {-1, +1}, {0, +1}, {+1, +1}, {+2, +1}, |
| // {-2,+2}, {-1, +2}, {0, +2}, {+1, +2}, {+2, +2}}; |
| |
| |
| static |
| const struct _spiral { |
| |
| int AdvX, AdvY; |
| |
| } Spiral[] = { {0, -1}, {+1, -1}, {+1, 0}, {+1, +1}, {0, +1}, {-1, +1}, |
| {-1, 0}, {-1, -1}, {-1, -2}, {0, -2}, {+1, -2}, {+2, -2}, |
| {+2, -1}, {+2, 0}, {+2, +1}, {+2, +2}, {+1, +2}, {0, +2}, |
| {-1, +2}, {-2, +2}, {-2, +1}, {-2, 0}, {-2, -1}, {-2, -2} }; |
| |
| #define NSTEPS (sizeof(Spiral) / sizeof(struct _spiral)) |
| |
| static |
| int FindNearSectors(cmsGDB* gbd, int alpha, int theta, cmsGDBPoint* Close[]) |
| { |
| int nSectors = 0; |
| int a, t; |
| cmsUInt32Number i; |
| cmsGDBPoint* pt; |
| |
| for (i=0; i < NSTEPS; i++) { |
| |
| a = alpha + Spiral[i].AdvX; |
| t = theta + Spiral[i].AdvY; |
| |
| // Cycle at the end |
| a %= SECTORS; |
| t %= SECTORS; |
| |
| // Cycle at the begin |
| if (a < 0) a = SECTORS + a; |
| if (t < 0) t = SECTORS + t; |
| |
| pt = &gbd ->Gamut[t][a]; |
| |
| if (pt -> Type != GP_EMPTY) { |
| |
| Close[nSectors++] = pt; |
| } |
| } |
| |
| return nSectors; |
| } |
| |
| |
| // Interpolate a missing sector. Method identifies whatever this is top, bottom or mid |
| static |
| cmsBool InterpolateMissingSector(cmsGDB* gbd, int alpha, int theta) |
| { |
| cmsSpherical sp; |
| cmsVEC3 Lab; |
| cmsVEC3 Centre; |
| cmsLine ray; |
| int nCloseSectors; |
| cmsGDBPoint* Close[NSTEPS + 1]; |
| cmsSpherical closel, templ; |
| cmsLine edge; |
| int k, m; |
| |
| // Is that point already specified? |
| if (gbd ->Gamut[theta][alpha].Type != GP_EMPTY) return TRUE; |
| |
| // Fill close points |
| nCloseSectors = FindNearSectors(gbd, alpha, theta, Close); |
| |
| |
| // Find a central point on the sector |
| sp.alpha = (cmsFloat64Number) ((alpha + 0.5) * 360.0) / (SECTORS); |
| sp.theta = (cmsFloat64Number) ((theta + 0.5) * 180.0) / (SECTORS); |
| sp.r = 50.0; |
| |
| // Convert to Cartesian |
| ToCartesian(&Lab, &sp); |
| |
| // Create a ray line from centre to this point |
| _cmsVEC3init(&Centre, 50.0, 0, 0); |
| LineOf2Points(&ray, &Lab, &Centre); |
| |
| // For all close sectors |
| closel.r = 0.0; |
| closel.alpha = 0; |
| closel.theta = 0; |
| |
| for (k=0; k < nCloseSectors; k++) { |
| |
| for(m = k+1; m < nCloseSectors; m++) { |
| |
| cmsVEC3 temp, a1, a2; |
| |
| // A line from sector to sector |
| ToCartesian(&a1, &Close[k]->p); |
| ToCartesian(&a2, &Close[m]->p); |
| |
| LineOf2Points(&edge, &a1, &a2); |
| |
| // Find a line |
| ClosestLineToLine(&temp, &ray, &edge); |
| |
| // Convert to spherical |
| ToSpherical(&templ, &temp); |
| |
| |
| if ( templ.r > closel.r && |
| templ.theta >= (theta*180.0/SECTORS) && |
| templ.theta <= ((theta+1)*180.0/SECTORS) && |
| templ.alpha >= (alpha*360.0/SECTORS) && |
| templ.alpha <= ((alpha+1)*360.0/SECTORS)) { |
| |
| closel = templ; |
| } |
| } |
| } |
| |
| gbd ->Gamut[theta][alpha].p = closel; |
| gbd ->Gamut[theta][alpha].Type = GP_MODELED; |
| |
| return TRUE; |
| |
| } |
| |
| |
| // Interpolate missing parts. The algorithm fist computes slices at |
| // theta=0 and theta=Max. |
| cmsBool CMSEXPORT cmsGDBCompute(cmsHANDLE hGBD, cmsUInt32Number dwFlags) |
| { |
| int alpha, theta; |
| cmsGDB* gbd = (cmsGDB*) hGBD; |
| |
| _cmsAssert(hGBD != NULL); |
| |
| // Interpolate black |
| for (alpha = 0; alpha < SECTORS; alpha++) { |
| |
| if (!InterpolateMissingSector(gbd, alpha, 0)) return FALSE; |
| } |
| |
| // Interpolate white |
| for (alpha = 0; alpha < SECTORS; alpha++) { |
| |
| if (!InterpolateMissingSector(gbd, alpha, SECTORS-1)) return FALSE; |
| } |
| |
| |
| // Interpolate Mid |
| for (theta = 1; theta < SECTORS; theta++) { |
| for (alpha = 0; alpha < SECTORS; alpha++) { |
| |
| if (!InterpolateMissingSector(gbd, alpha, theta)) return FALSE; |
| } |
| } |
| |
| // Done |
| return TRUE; |
| |
| cmsUNUSED_PARAMETER(dwFlags); |
| } |
| |
| |
| |
| |
| // -------------------------------------------------------------------------------------------------------- |
| |
| // Great for debug, but not suitable for real use |
| |
| #if 0 |
| cmsBool cmsGBDdumpVRML(cmsHANDLE hGBD, const char* fname) |
| { |
| FILE* fp; |
| int i, j; |
| cmsGDB* gbd = (cmsGDB*) hGBD; |
| cmsGDBPoint* pt; |
| |
| fp = fopen (fname, "wt"); |
| if (fp == NULL) |
| return FALSE; |
| |
| fprintf (fp, "#VRML V2.0 utf8\n"); |
| |
| // set the viewing orientation and distance |
| fprintf (fp, "DEF CamTest Group {\n"); |
| fprintf (fp, "\tchildren [\n"); |
| fprintf (fp, "\t\tDEF Cameras Group {\n"); |
| fprintf (fp, "\t\t\tchildren [\n"); |
| fprintf (fp, "\t\t\t\tDEF DefaultView Viewpoint {\n"); |
| fprintf (fp, "\t\t\t\t\tposition 0 0 340\n"); |
| fprintf (fp, "\t\t\t\t\torientation 0 0 1 0\n"); |
| fprintf (fp, "\t\t\t\t\tdescription \"default view\"\n"); |
| fprintf (fp, "\t\t\t\t}\n"); |
| fprintf (fp, "\t\t\t]\n"); |
| fprintf (fp, "\t\t},\n"); |
| fprintf (fp, "\t]\n"); |
| fprintf (fp, "}\n"); |
| |
| // Output the background stuff |
| fprintf (fp, "Background {\n"); |
| fprintf (fp, "\tskyColor [\n"); |
| fprintf (fp, "\t\t.5 .5 .5\n"); |
| fprintf (fp, "\t]\n"); |
| fprintf (fp, "}\n"); |
| |
| // Output the shape stuff |
| fprintf (fp, "Transform {\n"); |
| fprintf (fp, "\tscale .3 .3 .3\n"); |
| fprintf (fp, "\tchildren [\n"); |
| |
| // Draw the axes as a shape: |
| fprintf (fp, "\t\tShape {\n"); |
| fprintf (fp, "\t\t\tappearance Appearance {\n"); |
| fprintf (fp, "\t\t\t\tmaterial Material {\n"); |
| fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); |
| fprintf (fp, "\t\t\t\t\temissiveColor 1.0 1.0 1.0\n"); |
| fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); |
| fprintf (fp, "\t\t\t\t}\n"); |
| fprintf (fp, "\t\t\t}\n"); |
| fprintf (fp, "\t\t\tgeometry IndexedLineSet {\n"); |
| fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); |
| fprintf (fp, "\t\t\t\t\tpoint [\n"); |
| fprintf (fp, "\t\t\t\t\t0.0 0.0 0.0,\n"); |
| fprintf (fp, "\t\t\t\t\t%f 0.0 0.0,\n", 255.0); |
| fprintf (fp, "\t\t\t\t\t0.0 %f 0.0,\n", 255.0); |
| fprintf (fp, "\t\t\t\t\t0.0 0.0 %f]\n", 255.0); |
| fprintf (fp, "\t\t\t\t}\n"); |
| fprintf (fp, "\t\t\t\tcoordIndex [\n"); |
| fprintf (fp, "\t\t\t\t\t0, 1, -1\n"); |
| fprintf (fp, "\t\t\t\t\t0, 2, -1\n"); |
| fprintf (fp, "\t\t\t\t\t0, 3, -1]\n"); |
| fprintf (fp, "\t\t\t}\n"); |
| fprintf (fp, "\t\t}\n"); |
| |
| |
| fprintf (fp, "\t\tShape {\n"); |
| fprintf (fp, "\t\t\tappearance Appearance {\n"); |
| fprintf (fp, "\t\t\t\tmaterial Material {\n"); |
| fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); |
| fprintf (fp, "\t\t\t\t\temissiveColor 1 1 1\n"); |
| fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); |
| fprintf (fp, "\t\t\t\t}\n"); |
| fprintf (fp, "\t\t\t}\n"); |
| fprintf (fp, "\t\t\tgeometry PointSet {\n"); |
| |
| // fill in the points here |
| fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); |
| fprintf (fp, "\t\t\t\t\tpoint [\n"); |
| |
| // We need to transverse all gamut hull. |
| for (i=0; i < SECTORS; i++) |
| for (j=0; j < SECTORS; j++) { |
| |
| cmsVEC3 v; |
| |
| pt = &gbd ->Gamut[i][j]; |
| ToCartesian(&v, &pt ->p); |
| |
| fprintf (fp, "\t\t\t\t\t%g %g %g", v.n[0]+50, v.n[1], v.n[2]); |
| |
| if ((j == SECTORS - 1) && (i == SECTORS - 1)) |
| fprintf (fp, "]\n"); |
| else |
| fprintf (fp, ",\n"); |
| |
| } |
| |
| fprintf (fp, "\t\t\t\t}\n"); |
| |
| |
| |
| // fill in the face colors |
| fprintf (fp, "\t\t\t\tcolor Color {\n"); |
| fprintf (fp, "\t\t\t\t\tcolor [\n"); |
| |
| for (i=0; i < SECTORS; i++) |
| for (j=0; j < SECTORS; j++) { |
| |
| cmsVEC3 v; |
| |
| pt = &gbd ->Gamut[i][j]; |
| |
| |
| ToCartesian(&v, &pt ->p); |
| |
| |
| if (pt ->Type == GP_EMPTY) |
| fprintf (fp, "\t\t\t\t\t%g %g %g", 0.0, 0.0, 0.0); |
| else |
| if (pt ->Type == GP_MODELED) |
| fprintf (fp, "\t\t\t\t\t%g %g %g", 1.0, .5, .5); |
| else { |
| fprintf (fp, "\t\t\t\t\t%g %g %g", 1.0, 1.0, 1.0); |
| |
| } |
| |
| if ((j == SECTORS - 1) && (i == SECTORS - 1)) |
| fprintf (fp, "]\n"); |
| else |
| fprintf (fp, ",\n"); |
| } |
| fprintf (fp, "\t\t\t}\n"); |
| |
| |
| fprintf (fp, "\t\t\t}\n"); |
| fprintf (fp, "\t\t}\n"); |
| fprintf (fp, "\t]\n"); |
| fprintf (fp, "}\n"); |
| |
| fclose (fp); |
| |
| return TRUE; |
| } |
| #endif |
| |