/* */
/*  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"
#include <stdio.h>

/* The scanner profiler */


BOOL   cdecl cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys);                                        
BOOL   cdecl cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys);

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



/* Does create regression matrix */

static
void ComputeGlobalRegression(LPSCANNERPROFILERDATA sys)
{
        BOOL lAllOk;
        int nTerms;
        MLRSTATISTICS Stat;

		nTerms = cmsxFindOptimumNumOfTerms(&sys ->hdr, 55, &lAllOk);

  
        if (!lAllOk) {
			if (sys -> hdr.printf) 
					sys -> hdr.printf("*** WARNING: Inconsistence found, profile may be wrong. Check the target!");     
			nTerms = 4;
		}

		/* Create high terms matrix used by interpolation */
        cmsxRegressionCreateMatrix(&sys -> hdr.m, 
                                       sys -> hdr.m.Allowed, 
                                       nTerms,
                                       sys -> hdr.PCSType,
                                       &sys -> HiTerms,
                                       &Stat);
        
        if (sys -> hdr.printf) 
            sys -> hdr.printf("Global regression: %d terms, R2Adj = %g", nTerms, Stat.R2adj);

		/* Create low terms matrix used by extrapolation */
		cmsxRegressionCreateMatrix(&sys -> hdr.m, 
                                       sys -> hdr.m.Allowed, 
                                       (nTerms > 10 ? 10 : nTerms),
                                       sys -> hdr.PCSType,
                                       &sys -> LoTerms,
                                       &Stat);        	
		if (sys -> hdr.printf) 
            sys -> hdr.printf("Extrapolation: R2Adj = %g", Stat.R2adj);

}


/* Fill struct with default values */
        
BOOL cmsxScannerProfilerInit(LPSCANNERPROFILERDATA sys)
{                                
        

        if (sys == NULL) return false;
        ZeroMemory(sys, sizeof(SCANNERPROFILERDATA));

        sys->hdr.DeviceClass = icSigInputClass;
        sys->hdr.ColorSpace  = icSigRgbData;
        sys->hdr.PCSType     = PT_Lab;
		sys->hdr.Medium      = MEDIUM_REFLECTIVE_D50;
		
        /* Default values for generation */

        sys -> hdr.lUseCIECAM97s = false;
        sys -> hdr.CLUTPoints = 16;
       
           
        /* Default viewing conditions for scanner */

        sys -> hdr.device.Yb = 20;
        sys -> hdr.device.La = 20;
        sys -> hdr.device.surround = AVG_SURROUND;
        sys -> hdr.device.D_value  = 1.0;			/* Complete adaptation */


        /* Viewing conditions of PCS */
		cmsxInitPCSViewingConditions(&sys -> hdr);


        sys -> HiTerms = NULL;
		sys -> LoTerms = NULL;

        strcpy(sys -> hdr.Description,  "no description");
        strcpy(sys -> hdr.Manufacturer, "little cms profiler construction set");
        strcpy(sys -> hdr.Copyright,   "No copyright, use freely");
        strcpy(sys -> hdr.Model,       "(unknown)");

		sys ->lLocalConvergenceExtrapolation = false;
		sys ->hdr.ProfileVerbosityLevel = 0;
	

    return true;
}

/* Auxiliar: take RGB and update gauge */
static
void GetRGB(LPPROFILERCOMMONDATA hdr, WORD In[], double* r, double* g, double* b)
{   
    static int Count = 0, n_old = 0;
    double R, G, B;
    int n;
    
        
        R = _cmsxSaturate65535To255(In[0]);       /* Convert from the sheet notation */
        G = _cmsxSaturate65535To255(In[1]);       /* 0..255.0, to our notation */
        B = _cmsxSaturate65535To255(In[2]);       /* of 0..0xffff, 0xffff/255 = 257 */

        if (R == 0 && G == 0 && B == 0)  {
                Count = 0; n_old = -1;
        }

        n = (int) (double) (100. * Count)  / (hdr->CLUTPoints * hdr->CLUTPoints * hdr->CLUTPoints);
        Count++;

        if (n > n_old) {
            if (hdr->Gauger) hdr->Gauger("", 0, 100, (int) n);
        }

        n_old = n;
        *r = R; *g = G; *b = B;

}





/* The sampler for Lab */
static
int RegressionSamplerLab(WORD In[], WORD Out[], LPVOID Cargo)
{
    cmsCIEXYZ xyz;  
    cmsCIELab Lab;
    double r, g, b;    	
    LPSCANNERPROFILERDATA sys = (LPSCANNERPROFILERDATA) Cargo;
	char code;

        
        GetRGB(&sys->hdr, In, &r, &g, &b);


		code = cmsxHullCheckpoint(sys->hdr.hRGBHull, 
							      (int) floor(r + .5),
								  (int) floor(g + .5),
								  (int) floor(b + .5));


		if (code == 'i') { /* Inside gamut */

			if (!cmsxRegressionRGB2Lab(r, g, b, sys -> HiTerms, &Lab)) return false;
		}
		else  
		if (!sys -> lLocalConvergenceExtrapolation && code == 'o') {  /* outside gamut */

			if (!cmsxRegressionRGB2Lab(r, g, b, sys -> LoTerms, &Lab)) return false;
		}				
		else {	/* At gamut hull boundaries */

			if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m,
												PT_Lab,                                        
												10,
												true,
												30,
												r, g, b,
												&Lab)) return false;
		}
		
                       
        /* Regression CAN deliver wrong values. Clamp these. */
        cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128);

        /* Normalize */
        cmsLab2XYZ(cmsD50_XYZ(), &xyz, &Lab);
        cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, false);       
        cmsXYZ2Lab(cmsD50_XYZ(), &Lab, &xyz);

        /* Clamping again, adaptation could move slightly values */
        cmsClampLab(&Lab, 127.9961, -128, 127.9961, -128);

        /* To PCS encoding */
        cmsFloat2LabEncoded(Out, &Lab);

				
    return true; /* And done with success */
}





/* The sampler for XYZ */
static
int RegressionSamplerXYZ(WORD In[], WORD Out[], LPVOID Cargo)
{
    cmsCIEXYZ xyz;
    double r, g, b;
    LPSCANNERPROFILERDATA sys = (LPSCANNERPROFILERDATA) Cargo;
	char code;

      GetRGB(&sys -> hdr, In, &r, &g, &b);

	    code = cmsxHullCheckpoint(sys ->hdr.hRGBHull,
								  (int) floor(r + .5),
								  (int) floor(g + .5),
								  (int) floor(b + .5));

		if (code == 'i') { /* Inside gamut */

			if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> HiTerms, &xyz)) return false;
		}
		else  
		if (!sys -> lLocalConvergenceExtrapolation && code == 'o') {  /* outside gamut */

			if (!cmsxRegressionRGB2XYZ(r, g, b, sys -> LoTerms, &xyz)) return false;
		}
				
		else {	/* At gamut hull boundaries */

			if (!cmsxRegressionInterpolatorRGB(&sys -> hdr.m,
												PT_XYZ,                                        
												10,
												true,
												30,
												r, g, b,
												&xyz)) return false;
		}
		                                        
        
      xyz.X /= 100.;
      xyz.Y /= 100.;
      xyz.Z /= 100.;

      cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, false);       
     
      /* To PCS encoding. It also claps bad values */
      cmsFloat2XYZEncoded(Out, &xyz);
        
    return true; /* And done witch success */
}



/* The main scanner profiler */
BOOL cmsxScannerProfilerDo(LPSCANNERPROFILERDATA sys)
{

        LPLUT AToB0;
        DWORD dwNeedSamples;

                   
        if (!*sys -> hdr.OutputProfileFile)
                return false;

    
        if (!cmsxChoosePCS(&sys->hdr))
                return false;               

        dwNeedSamples = PATCH_HAS_RGB;
        if (sys ->hdr.PCSType == PT_Lab)
                dwNeedSamples |= PATCH_HAS_Lab;
        else
                dwNeedSamples |= PATCH_HAS_XYZ;

            
        if (sys->hdr.printf) {
            
            sys->hdr.printf("Loading sheets...");

            if (sys->hdr.ReferenceSheet[0])
                sys->hdr.printf("Reference sheet: %s", sys->hdr.ReferenceSheet);
            if (sys->hdr.MeasurementSheet[0])
                sys->hdr.printf("Measurement sheet: %s", sys->hdr.MeasurementSheet);
        }


        if (!cmsxPCollBuildMeasurement(&sys->hdr.m, 
                                    sys->hdr.ReferenceSheet, 
                                    sys->hdr.MeasurementSheet,
                                    dwNeedSamples)) return false;

			
		
        sys->hdr.hProfile = cmsCreateRGBProfile(NULL, NULL, NULL);
     

        cmsSetDeviceClass(sys->hdr.hProfile, sys->hdr.DeviceClass);
        cmsSetColorSpace(sys->hdr.hProfile,  sys->hdr.ColorSpace);
        cmsSetPCS(sys->hdr. hProfile,  _cmsICCcolorSpace(sys->hdr.PCSType));
    
        /* Save char target tag */
		if (sys->hdr.ProfileVerbosityLevel >= 2) {
			
			cmsxEmbedCharTarget(&sys ->hdr);
		}

        AToB0 = cmsAllocLUT();

        cmsAlloc3DGrid(AToB0, sys->hdr.CLUTPoints, 3, 3);

        cmsxComputeLinearizationTables(&sys-> hdr.m, 
                                        sys -> hdr.PCSType, 
                                        sys -> Prelinearization, 
										1024,
										MEDIUM_REFLECTIVE_D50);
                                        	
        /* Refresh RGB of all patches. This converts all regression into */
        /* near linear RGB->Lab or XYZ */
            
        cmsxPCollLinearizePatches(&sys->hdr.m, sys -> hdr.m.Allowed, sys -> Prelinearization);   

		cmsxComputeGamutHull(&sys->hdr);
        ComputeGlobalRegression(sys);

        cmsAllocLinearTable(AToB0, sys -> Prelinearization, 1);                              

        /* Set CIECAM97s parameters */

        sys -> hdr.device.whitePoint.X = sys -> hdr.WhitePoint.X * 100.;
        sys -> hdr.device.whitePoint.Y = sys -> hdr.WhitePoint.Y * 100.;
        sys -> hdr.device.whitePoint.Z = sys -> hdr.WhitePoint.Z * 100.;
	   
		  	   
        sys->hdr.hDevice = cmsCIECAM97sInit(&sys->hdr.device);
        sys->hdr.hPCS    = cmsCIECAM97sInit(&sys->hdr.PCS);

        
        if (sys -> hdr.PCSType == PT_Lab)
                cmsSample3DGrid(AToB0, RegressionSamplerLab, sys, 0);
        else
                cmsSample3DGrid(AToB0, RegressionSamplerXYZ, sys, 0);

        cmsCIECAM97sDone(sys->hdr.hDevice);
        cmsCIECAM97sDone(sys->hdr.hPCS);

        cmsAddTag(sys->hdr.hProfile, icSigAToB0Tag, AToB0);


	    cmsxEmbedTextualInfo(&sys -> hdr);
       
		cmsAddTag(sys->hdr.hProfile, icSigMediaWhitePointTag, &sys->hdr.WhitePoint);
        cmsAddTag(sys->hdr.hProfile, icSigMediaBlackPointTag, &sys->hdr.BlackPoint);


		/* Save primaries & gamma curves */
		if (sys->hdr.ProfileVerbosityLevel >= 1) {

			cmsxEmbedMatrixShaper(&sys ->hdr);
		}

        _cmsSaveProfile(sys->hdr.hProfile, sys->hdr.OutputProfileFile);

        cmsCloseProfile(sys->hdr.hProfile);
        sys->hdr.hProfile = NULL;

        cmsxPCollFreeMeasurements(&sys->hdr.m);    
               
        cmsFreeLUT(AToB0);
       
        if (sys -> HiTerms)
                MATNfree(sys -> HiTerms);
        sys -> HiTerms = NULL;


		if (sys -> LoTerms)
                MATNfree(sys -> LoTerms);
        sys -> LoTerms = NULL;

		

        if (sys ->Prelinearization[0])
            cmsFreeGammaTriple(sys -> Prelinearization);   
		
		if (sys ->hdr.Gamma)
			cmsFreeGammaTriple(sys->hdr.Gamma);
		
    return true;
}
