/* */
/*  Little cms - profiler construction set */
/*  Copyright (C) 1998-2001 Marti Maria <marti@littlecms.com> */
/* */
/* THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY */
/* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. */
/* */
/* IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL, */
/* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, */
/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
/* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF */
/* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE */
/* OF THIS SOFTWARE. */
/* */
/* This file is free software; you can redistribute it and/or modify it */
/* under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 2 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, but */
/* WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU */
/* General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program; if not, write to the Free Software */
/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
/* */
/* As a special exception to the GNU General Public License, if you */
/* distribute this file as part of a program that contains a */
/* configuration script generated by Autoconf, you may include it under */
/* the same distribution terms that you use for the rest of that program. */
/* */
/* Version 1.08a */


#include "lcmsprf.h"


/* ----------------------------------------------------------------- Patch collections */

BOOL        cdecl cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet);

BOOL        cdecl cmsxPCollBuildMeasurement(LPMEASUREMENT m, 
                                            const char *ReferenceSheet,
                                            const char *MeasurementSheet,
                                            DWORD dwNeededSamplesType);

LPPATCH     cdecl cmsxPCollGetPatch(LPMEASUREMENT m, int n);

LPPATCH     cdecl cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* Name, int* lpPos);
LPPATCH     cdecl cmsxPCollGetPatchByPos(LPMEASUREMENT m, int row, int col);
LPPATCH     cdecl cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name,
                                      double r, double g, double b,
                                      LPcmsCIEXYZ XYZ, LPcmsCIELab Lab);

/* Sets of patches */

SETOFPATCHES cdecl cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault);
int          cdecl cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set);
BOOL         cdecl cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags);


/* Collect "need" patches of the specific kind, return the number of collected (that */
/* could be less if set of patches is exhausted) */


void         cdecl cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result);

int          cdecl cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
                                            double r, double g, double b, int need, SETOFPATCHES Result);

int          cdecl cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids,
                                            int need, SETOFPATCHES Result);

int          cdecl cmsxPCollPatchesNearPrimary(LPMEASUREMENT m, SETOFPATCHES Valids,
                                           int nChannel, int need, SETOFPATCHES Result);

int          cdecl cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids, 
                                           double Lmin, double LMax, double a, double b, SETOFPATCHES Result);

int 	     cdecl cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids, 
											  LPLUT Gamut, SETOFPATCHES Result);

LPPATCH		 cdecl cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance);
LPPATCH		 cdecl cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* Distance);
LPPATCH      cdecl cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* Distance);


/* ------------------------------------------------------------- Implementation */

#define IS(x)   EqualsTo(c, x)

/* A wrapper on stricmp() */

static
BOOL EqualsTo(const char* a, const char *b)
{
        return (stricmp(a, b) == 0);
}


/* Does return a bitwise mask holding the measurements contained in a Sheet */

static
DWORD MaskOfDataSet(LCMSHANDLE hSheet)
{
        char** Names;
        int i, n;
        DWORD dwMask = 0;

        n = cmsxIT8EnumDataFormat(hSheet, &Names);

        for (i=0; i < n; i++) {

                char *c = Names[i];

                if (IS("RGB_R") || IS("RGB_G") || IS("RGB_B"))
                                dwMask |= PATCH_HAS_RGB;
                else
                if (IS("XYZ_X") || IS("XYZ_Y") || IS("XYZ_Z"))
                                dwMask |= PATCH_HAS_XYZ;
                else
                if (IS("LAB_L") || IS("LAB_A") ||IS("LAB_B"))
                                dwMask |= PATCH_HAS_Lab;
                else
                if (IS("STDEV_DE"))
                    dwMask |= PATCH_HAS_STD_DE;
        }

        return dwMask;
}


/* Addition of a patch programatically */

LPPATCH cmsxPCollAddPatchRGB(LPMEASUREMENT m, const char *Name,
                        double r, double g, double b,
                        LPcmsCIEXYZ XYZ, LPcmsCIELab Lab)
{
        LPPATCH p;

        p = m->Patches + m->nPatches++;

        strcpy(p -> Name, Name);

        p -> Colorant.RGB[0] = r;
        p -> Colorant.RGB[1] = g;
        p -> Colorant.RGB[2] = b;
        p -> dwFlags = PATCH_HAS_RGB;

        if (XYZ) {

                p -> XYZ = *XYZ;
                p -> dwFlags |= PATCH_HAS_XYZ;
        }

        if (Lab) {
                p -> Lab = *Lab;
                p -> dwFlags |= PATCH_HAS_Lab;
        }

        
        return p;
}

/* Some vendors does store colorant data in a non-standard way,  */
/* i.e, from 0.0..1.0 or from 0.0..100.0 This routine tries to */
/* detect such situations */

static 
void NormalizeColorant(LPMEASUREMENT m)
{
	int i, j;
	double MaxColorant=0;
	double Normalize;

	
	for (i=0; i < m -> nPatches; i++) {


           LPPATCH p = m -> Patches + i;
		   	
		   for (j=0; j < MAXCHANNELS; j++) {
						if (p ->Colorant.Hexa[j] > MaxColorant)
								MaxColorant = p ->Colorant.Hexa[j];
		   }
	}

	/* Ok, some heuristics */

	if (MaxColorant < 2) 
		Normalize = 255.0;  /* goes 0..1 */
	else
	if (MaxColorant < 102)
		Normalize = 2.55;  /* goes 0..100 */
	else
	if (MaxColorant > 300)
		Normalize = (255.0 / 65535.0);  /* Goes 0..65535.0 */
	else 
		return;		/* Is ok */


	/* Rearrange patches */
	for (i=0; i < m -> nPatches; i++) {


           LPPATCH p = m -> Patches + i;
		   for (j=0; j < MAXCHANNELS; j++)
				p ->Colorant.Hexa[j] *= Normalize;
	}

}


/* Load a collection from a Sheet */

BOOL cmsxPCollLoadFromSheet(LPMEASUREMENT m, LCMSHANDLE hSheet)
{
    int i;   
    DWORD dwMask;


    if (m -> nPatches == 0) {

            m -> nPatches = (int) cmsxIT8GetPropertyDbl(hSheet, "NUMBER_OF_SETS");				
            m -> Patches  = (PATCH*)calloc(m -> nPatches, sizeof(PATCH));            // C->C++ : cast

            if (m -> Patches == NULL) {
                cmsxIT8Free(hSheet);
                return false;
            }

            for (i=0; i < m -> nPatches; i++) {

                    LPPATCH p = m -> Patches + i;
					p -> dwFlags = 0; 
					cmsxIT8GetPatchName(hSheet, i, p ->Name);				
                                 
            }

    }


    /* Build mask according to data format */

    dwMask = MaskOfDataSet(hSheet);


    /* Read items. Set flags accordly. */
    for (i = 0; i < m->nPatches; i++) {

        LPPATCH Patch = m -> Patches + i;
			      
        /* Fill in data according to mask */

        if (dwMask & PATCH_HAS_Lab) {

        if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_L", &Patch -> Lab.L) &&
            cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_A", &Patch -> Lab.a) &&
            cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "LAB_B", &Patch -> Lab.b))

                                   Patch -> dwFlags |= PATCH_HAS_Lab;
        }

        if (dwMask & PATCH_HAS_XYZ) {

        if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_X", &Patch -> XYZ.X) &&
            cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_Y", &Patch -> XYZ.Y) &&
            cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "XYZ_Z", &Patch -> XYZ.Z))

                                  Patch -> dwFlags |= PATCH_HAS_XYZ;

        }

        if (dwMask & PATCH_HAS_RGB) {

        if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_R", &Patch -> Colorant.RGB[0]) &&
            cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_G", &Patch -> Colorant.RGB[1]) &&
            cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "RGB_B", &Patch -> Colorant.RGB[2]))

                                Patch -> dwFlags |= PATCH_HAS_RGB;
        }

        if (dwMask & PATCH_HAS_STD_DE) {
            
            if (cmsxIT8GetDataSetDbl(hSheet, Patch->Name, "STDEV_DE", &Patch -> dEStd))

                                Patch -> dwFlags |= PATCH_HAS_STD_DE;

        }
	
    }

	NormalizeColorant(m);
    return true;
}


/* Does save parameters to a empty sheet */

BOOL cmsxPCollSaveToSheet(LPMEASUREMENT m, LCMSHANDLE it8)
{			
	int nNumberOfSets   = cmsxPCollCountSet(m, m->Allowed);	
	int nNumberOfFields = 0;
	DWORD dwMask = 0;
	int i;

	/* Find mask of fields */
	for (i=0; i < m ->nPatches; i++) {
		if (m ->Allowed[i]) {

				LPPATCH p = m ->Patches + i;
				dwMask |= p ->dwFlags;
		}
	}

	nNumberOfFields = 1; /* SampleID */

	if (dwMask & PATCH_HAS_RGB)
			nNumberOfFields += 3;

	if (dwMask & PATCH_HAS_XYZ)
			nNumberOfFields += 3;

	if (dwMask & PATCH_HAS_Lab)
			nNumberOfFields += 3;
	

    cmsxIT8SetPropertyDbl(it8, "NUMBER_OF_SETS", nNumberOfSets);
    cmsxIT8SetPropertyDbl(it8, "NUMBER_OF_FIELDS", nNumberOfFields);

	nNumberOfFields = 0;
    cmsxIT8SetDataFormat(it8, nNumberOfFields++, "SAMPLE_ID");

	if (dwMask & PATCH_HAS_RGB) {

		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_R");
		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_G");
		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "RGB_B");
	}
    
	if (dwMask & PATCH_HAS_XYZ) {
		
		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_X");
		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_Y");
		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "XYZ_Z");
		
	}


	if (dwMask & PATCH_HAS_XYZ) {
		
		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_L");
		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_A");
		cmsxIT8SetDataFormat(it8, nNumberOfFields++, "LAB_B");
		
	}

	for (i=0; i < m ->nPatches; i++) {
		if (m ->Allowed[i]) {

			LPPATCH Patch = m ->Patches + i;

			cmsxIT8SetDataSet(it8, Patch->Name, "SAMPLE_ID", Patch->Name);
			
			if (dwMask & PATCH_HAS_RGB) {
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_R", Patch ->Colorant.RGB[0]);
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_G", Patch ->Colorant.RGB[1]);
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "RGB_B", Patch ->Colorant.RGB[2]);
			} 

			if (dwMask & PATCH_HAS_XYZ) {
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_X", Patch ->XYZ.X);
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_Y", Patch ->XYZ.Y);
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "XYZ_Z", Patch ->XYZ.Z);				
			}	

			if (dwMask & PATCH_HAS_Lab) {				
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_L", Patch ->Lab.L);
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_A", Patch ->Lab.a);
				cmsxIT8SetDataSetDbl(it8, Patch->Name, "LAB_B", Patch ->Lab.b);
				
			}	
		}
	}

	return true;
}

static
void FixLabOnly(LPMEASUREMENT m)
{
	int i;

	for (i=0; i < m ->nPatches; i++) {
	
			LPPATCH p = m ->Patches + i;
			if ((p ->dwFlags & PATCH_HAS_Lab) && 
				!(p ->dwFlags & PATCH_HAS_XYZ))
			{
				cmsLab2XYZ(cmsD50_XYZ(), &p->XYZ, &p ->Lab);

				p ->XYZ.X *= 100.;
				p ->XYZ.Y *= 100.;
				p ->XYZ.Z *= 100.;

				p ->dwFlags |= PATCH_HAS_XYZ;
			}
	
	}

}


/* Higher level function. Does merge reference and measurement sheet into  */
/* a MEASUREMENT struct. Data to keep is described in dwNeededSamplesType */
/* mask as follows: */
/*   */
/* PATCH_HAS_Lab         0x00000001 */
/* PATCH_HAS_XYZ         0x00000002 */
/* PATCH_HAS_RGB         0x00000004 */
/* PATCH_HAS_CMY         0x00000008 */
/* PATCH_HAS_CMYK        0x00000010 */
/* PATCH_HAS_HEXACRM     0x00000020 */
/* PATCH_HAS_STD_Lab     0x00010000 */
/* PATCH_HAS_STD_XYZ     0x00020000 */
/* PATCH_HAS_STD_RGB     0x00040000 */
/* PATCH_HAS_STD_CMY     0x00080000 */
/* PATCH_HAS_STD_CMYK    0x00100000 */
/* PATCH_HAS_STD_HEXACRM 0x00100000 */
/* PATCH_HAS_MEAN_DE     0x01000000 */
/* PATCH_HAS_STD_DE      0x02000000 */
/* PATCH_HAS_CHISQ       0x04000000 */
/* */
/* See lprof.h for further info */


BOOL cmsxPCollBuildMeasurement(LPMEASUREMENT m, 
                               const char *ReferenceSheet,
                               const char *MeasurementSheet,
                               DWORD dwNeededSamplesType)
{
    LCMSHANDLE hSheet;
	BOOL rc = true;

    ZeroMemory(m, sizeof(MEASUREMENT));


    if (ReferenceSheet != NULL && *ReferenceSheet) {

                hSheet = cmsxIT8LoadFromFile(ReferenceSheet);       
                if (hSheet == NULL) return false;               
                
				rc = cmsxPCollLoadFromSheet(m,  hSheet);
                cmsxIT8Free(hSheet);
    }

    if (!rc) return false;

    if (MeasurementSheet != NULL && *MeasurementSheet) {

                hSheet = cmsxIT8LoadFromFile(MeasurementSheet);
                if (hSheet == NULL) return false;

                rc = cmsxPCollLoadFromSheet(m,  hSheet);
                cmsxIT8Free(hSheet);
    }

    if (!rc) return false;


	/* Fix up -- If only Lab is present, then compute  */
	/* XYZ based on D50 */

	FixLabOnly(m);

    cmsxPCollValidatePatches(m, dwNeededSamplesType);
    return true;
}



void cmsxPCollFreeMeasurements(LPMEASUREMENT m)
{
    if (m->Patches)
        free(m->Patches);

    m->Patches  = NULL;
    m->nPatches = 0;
    
    if (m -> Allowed)
        free(m -> Allowed);

}

/* Retrieval functions */

LPPATCH cmsxPCollGetPatchByName(LPMEASUREMENT m, const char* name, int* lpPos)
{
    int i;
    for (i=0; i < m->nPatches; i++)
    {
            if (m -> Allowed)
                if (!m -> Allowed[i])
                        continue;

                if (EqualsTo(m->Patches[i].Name, name)) {
                        if (lpPos) *lpPos = i;
                        return m->Patches + i;
                }
    }

    return NULL;
}




/* -------------------------------------------------------------------- Sets */


SETOFPATCHES cmsxPCollBuildSet(LPMEASUREMENT m, BOOL lDefault)
{
    SETOFPATCHES Full = (SETOFPATCHES) malloc(m -> nPatches * sizeof(BOOL));
    int i;

    for (i=0; i < m -> nPatches; i++)
        Full[i] = lDefault;

    return Full;
}

int cmsxPCollCountSet(LPMEASUREMENT m, SETOFPATCHES Set)
{
    int i, Count = 0;

    for (i = 0; i < m -> nPatches; i++) {

        if (Set[i])
            Count++;
        }

    return Count;
}


/* Validate patches */

BOOL cmsxPCollValidatePatches(LPMEASUREMENT m, DWORD dwFlags)
{
        int i, n;

		if (m->Allowed) 
				free(m->Allowed);

        m -> Allowed = cmsxPCollBuildSet(m, true);
        
        /* Check for flags */
        for (i=n=0; i < m -> nPatches; i++) {

                LPPATCH p = m -> Patches + i;               
                m -> Allowed[i] = ((p -> dwFlags & dwFlags) == dwFlags);
                
        }

        return true;
}


/* Several filters */


/* This filter does validate patches placed on 'radius' distance of a */
/* device-color space. Currently only RGB is supported */

static
void PatchesByRGB(LPMEASUREMENT m, SETOFPATCHES Valids, 
				  double R, double G, double B, double radius, SETOFPATCHES Result)
{
    int  i;
    double ra, rmax = sqrt(radius / 255.);
    double dR, dG, dB;
    LPPATCH p;

    for (i=0; i < m->nPatches; i++) {

       
       if (Valids[i]) {

        p = m->Patches + i;

        dR = fabs(R - p -> Colorant.RGB[0]) / 255.;
        dG = fabs(G - p -> Colorant.RGB[1]) / 255.;
        dB = fabs(B - p -> Colorant.RGB[2]) / 255.;

        ra = sqrt(dR*dR + dG*dG + dB*dB);

        if (ra <= rmax) 
            Result[i] = true;       
        else 
            Result[i] = false;

       }
    }

}


/* This filter does validate patches placed at dEmax radius */
/* in the device-independent side. */

static
void PatchesByLab(LPMEASUREMENT m, SETOFPATCHES Valids, 
                  double L, double a, double b, double dEmax, SETOFPATCHES Result)
{
    int  i;
    double dE, dEMaxSQR = sqrt(dEmax);
    double dL, da, db;
    LPPATCH p;


    for (i=0; i < m->nPatches; i++) {

       
       if (Valids[i]) {

        p = m->Patches + i;

        dL = fabs(L - p -> Lab.L);
        da = fabs(a - p -> Lab.a);
        db = fabs(b - p -> Lab.b);

        dE = sqrt(dL*dL + da*da + db*db);
       
        if (dE <= dEMaxSQR) 
            Result[i] = true; 
        else 
            Result[i] = false;
       }
    }
}


/* Restrict Lab in a cube of variable sides. Quick and dirty out-of-gamut  */
/* stripper used in estimations. */

static
void  PatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids, 
                            double Lmin, double Lmax, double da, double db, SETOFPATCHES Result)
{
        int i;
 
        for (i=0; i < m -> nPatches; i++) {

    
                if (Valids[i]) {

                        LPPATCH p = m -> Patches + i;

                        if ((p->Lab.L >= Lmin && p->Lab.L <= Lmax) && 
                            (fabs(p -> Lab.a) < da) &&
                            (fabs(p -> Lab.b) < db)) 

                             Result[i] = true;
                         else   
                             Result[i] = false;
                }
        }

}

/* Restrict to low colorfullness */

static
void PatchesOfLowC(LPMEASUREMENT m, SETOFPATCHES Valids, 
                            double Cmax, SETOFPATCHES Result)
{
        int i;
		cmsCIELCh LCh;
 
        for (i=0; i < m -> nPatches; i++) {

    
                if (Valids[i]) {

                        LPPATCH p = m -> Patches + i;

						cmsLab2LCh(&LCh, &p->Lab);


                        if (LCh.C < Cmax)
                             Result[i] = true;
                         else   
                             Result[i] = false;
                }
        }

}



/* Primary can be -1 for specifying device gray. Does return patches */
/* on device-space Colorants. dEMax is the maximum allowed ratio */

static
void PatchesPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, 
                           int nColorant, double dEMax, SETOFPATCHES Result)
{
        int i, j;
        double n, dE;

        for (i=0; i < m -> nPatches; i++) {

    
                if (Valids[i]) {

                        LPPATCH p = m -> Patches + i;


                        if (nColorant < 0) /* device-grey? */
                        {
							    /* cross. */
							
                                double drg = fabs(p -> Colorant.RGB[0] - p -> Colorant.RGB[1]) / 255.;
                                double drb = fabs(p -> Colorant.RGB[0] - p -> Colorant.RGB[2]) / 255.;
                                double dbg = fabs(p -> Colorant.RGB[1] - p -> Colorant.RGB[2]) / 255.;

                                dE = (drg*drg + drb*drb + dbg*dbg);


                        }
                        else {
                                dE = 0.;
                                for (j=0; j < 3; j++) {

                                        if (j != nColorant) {

                                                n = p -> Colorant.RGB[j] / 255.;
                                                dE += (n * n);


                                        }
                                }
                        }



                        if (sqrt(dE) < dEMax) 
                             Result[i] = true;
                         else   
                             Result[i] = false;
                }
        }

}


/* The high level extractors ----------------------------------------------------- */

int cmsxPCollPatchesNearRGB(LPMEASUREMENT m, SETOFPATCHES Valids,
							double r, double g, double b, 
							int need, SETOFPATCHES Result)
{
    double radius;
    int nCollected;

    /* Collect points inside of a sphere or radius 'radius' by RGB */

    radius = 1;
    do {
        PatchesByRGB(m, Valids, r, g, b, radius, Result);

        nCollected = cmsxPCollCountSet(m, Result);
        if (nCollected <= need) {

            radius += 1.0;
        }

    } while (nCollected <= need && radius < 256.);

    return nCollected; /* Can be less than needed! */
}


int cmsxPCollPatchesNearNeutral(LPMEASUREMENT m, SETOFPATCHES Valids, 
								int need, SETOFPATCHES Result)
{
        int nGrays;
        double Cmax;

        Cmax = 1.;
        do {


				PatchesOfLowC(m, Valids, Cmax, Result);                            
               
                nGrays = cmsxPCollCountSet(m, Result);
                if (nGrays <= need) {

                        Cmax += .2;
                }

        } while (nGrays <= need && Cmax < 10.);

        return nGrays;
}


int cmsxPCollPatchesInLabCube(LPMEASUREMENT m, SETOFPATCHES Valids, 
                              double Lmin, double Lmax, double a, double b, 
							  SETOFPATCHES Result)


{        
        PatchesInLabCube(m, Valids, Lmin, Lmax, a, b, Result);
        return cmsxPCollCountSet(m, Result);                
}




int cmsxPCollPatchesNearPrimary(LPMEASUREMENT m,
                          SETOFPATCHES Valids,
                          int nChannel,
                          int need,
                          SETOFPATCHES Result)
{
    double radius;
    int nCollected;

    /* Collect points inside of a sphere or radius 'radius' by RGB */

    radius = 0.05;
    do {
        PatchesPrimary(m, Valids, nChannel, radius, Result);

        nCollected = cmsxPCollCountSet(m, Result);
        if (nCollected <= need) {

            radius += 0.01;
        }

    } while (nCollected <= need && radius < 256.);

    return nCollected;

}


static
void AddOneGray(LPMEASUREMENT m, int n, SETOFPATCHES Grays)
{
    LPPATCH p;
    char Buffer[cmsxIT8_GRAYCOLS];
    int pos;

    if (n == 0) strcpy(Buffer, "DMIN");
    else
        if (n == cmsxIT8_GRAYCOLS - 1) strcpy(Buffer, "DMAX");
        else
            sprintf(Buffer, "GS%d", n);

    p = cmsxPCollGetPatchByName(m, Buffer, &pos);

    if (p) 
        Grays[pos] = true;
}



void cmsxPCollPatchesGS(LPMEASUREMENT m, SETOFPATCHES Result)
{

    int i;

    for (i=0; i < cmsxIT8_GRAYCOLS; i++) 
        AddOneGray(m, i, Result);
}



/* Refresh RGB of all patches after prelinearization */

void cmsxPCollLinearizePatches(LPMEASUREMENT m, SETOFPATCHES Valids, LPGAMMATABLE Gamma[3])
{
    int i;

    for (i=0; i < m -> nPatches; i++) {

       if (Valids[i]) {

        LPPATCH p = m -> Patches + i;

        cmsxApplyLinearizationTable(p -> Colorant.RGB, Gamma, p -> Colorant.RGB);
        }
    }

}


int cmsxPCollPatchesInGamutLUT(LPMEASUREMENT m, SETOFPATCHES Valids, 
										LPLUT Gamut, SETOFPATCHES Result)
{
	int i;
	int nCollected = 0;

    for (i=0; i < m -> nPatches; i++) {

       if (Valids[i]) {

        LPPATCH p = m -> Patches + i;
		WORD EncodedLab[3];
		WORD dE;

		cmsFloat2LabEncoded(EncodedLab, &p->Lab);
		cmsEvalLUT(Gamut, EncodedLab, &dE);
		Result[i] = (dE < 2) ? true : false;			        
		if (Result[i]) nCollected++;
        }
    }

	return nCollected;
}

LPPATCH cmsxPCollFindWhite(LPMEASUREMENT m, SETOFPATCHES Valids, double* TheDistance)
{
	int i;
	LPPATCH Candidate = NULL;
	double Distance, CandidateDistance = 255;
	double dR, dG, dB;

	Candidate = cmsxPCollGetPatchByName(m, "DMIN", NULL);
	if (Candidate) {

		if (TheDistance) *TheDistance = 0.0;
		return Candidate;
	}

    for (i=0; i < m -> nPatches; i++) {

       if (Valids[i]) {

        LPPATCH p = m -> Patches + i;

		dR = fabs(255.0 - p -> Colorant.RGB[0]) / 255.0;
        dG = fabs(255.0 - p -> Colorant.RGB[1]) / 255.0;
        dB = fabs(255.0 - p -> Colorant.RGB[2]) / 255.0;

        Distance = sqrt(dR*dR + dG*dG + dB*dB);

		if (Distance < CandidateDistance) {
			Candidate = p;
			CandidateDistance = Distance;
		}
	   }		        
    }

	if (TheDistance) 
		*TheDistance = floor(CandidateDistance * 255.0 + .5);

	return Candidate;
}

LPPATCH cmsxPCollFindBlack(LPMEASUREMENT m, SETOFPATCHES Valids, double* TheDistance)
{
	int i;
	LPPATCH Candidate = NULL;
	double Distance, CandidateDistance = 255;
	double dR, dG, dB;


	Candidate = cmsxPCollGetPatchByName(m, "DMAX", NULL);
	if (Candidate) {

		if (TheDistance) *TheDistance = 0.0;
		return Candidate;
	}

    for (i=0; i < m -> nPatches; i++) {

       if (Valids[i]) {

        LPPATCH p = m -> Patches + i;

		dR = (p -> Colorant.RGB[0]) / 255.0;
        dG = (p -> Colorant.RGB[1]) / 255.0;
        dB = (p -> Colorant.RGB[2]) / 255.0;

        Distance = sqrt(dR*dR + dG*dG + dB*dB);

		if (Distance < CandidateDistance) {
			Candidate = p;
			CandidateDistance = Distance;
		}
	   }		        
    }

	if (TheDistance) 
		*TheDistance = floor(CandidateDistance * 255.0 + .5);

	return Candidate;
}


LPPATCH cmsxPCollFindPrimary(LPMEASUREMENT m, SETOFPATCHES Valids, int Channel, double* TheDistance)
{
	int i;
	LPPATCH Candidate = NULL;
	double Distance, CandidateDistance = 255;
	double dR, dG, dB;
	const struct { 
				 double r, g, b; 

				} RGBPrimaries[3] = {		
					{ 255.0, 0, 0}, 
					{ 0, 255.0, 0},
					{ 0, 0, 255  }};


    for (i=0; i < m -> nPatches; i++) {

       if (Valids[i]) {

        LPPATCH p = m -> Patches + i;

		dR = fabs(RGBPrimaries[Channel].r - p -> Colorant.RGB[0]) / 255.0;
        dG = fabs(RGBPrimaries[Channel].g - p -> Colorant.RGB[1]) / 255.0;
        dB = fabs(RGBPrimaries[Channel].b - p -> Colorant.RGB[2]) / 255.0;

        Distance = sqrt(dR*dR + dG*dG + dB*dB);

		if (Distance < CandidateDistance) {
			Candidate = p;
			CandidateDistance = Distance;
		}
	   }		        
    }

	if (TheDistance) 
		*TheDistance = floor(CandidateDistance * 255.0 + .5);

	return Candidate;
}
