/* $Id: cmsmntr.c,v 1.23 2008/01/03 22:05:23 hvengel Exp $ */
/*  Little cms - profiler construction set */
/*  Copyright (C) 1998-2001 Marti Maria */
/* Copyright (C) 2005-2007 Hal Engel */
/* 
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 THE DEVELOPERS 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 program 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 3 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, see <http://www.gnu.org/licenses/>.
*/
/* Version 1.09a */

#define VCGT_TAG     0x76636774L

#include "lcmsprf.h"

/* dummy for cmsprintf */

static int
printfDummy(const char *format, int parms, char typ, ...) { return 0; }


static
void ClampRGB(LPVEC3 RGB)
{
    int i;

    for (i=0; i < 3; i++)
    {
        if (RGB->n[i] > 1.0)
            RGB->n[i] = 1.0;
        if (RGB->n[i] < 0)
            RGB->n[i] = 0;
    }
}


static
int RegressionSamplerA2B(WORD In[], WORD Out[], LPVOID Cargo)
{
    cmsCIEXYZ xyz;
    cmsCIELab Lab;
    VEC3 RGB, RGBlinear, vxyz;
    LPPROFILERDATA sys = (LPPROFILERDATA) Cargo;

    RGB.n[0] = _cmsxSaturate65535To255(In[0]);
    RGB.n[1] = _cmsxSaturate65535To255(In[1]);
    RGB.n[2] = _cmsxSaturate65535To255(In[2]);

    cmsxApplyLinearizationTable(RGB.n, sys->PreLab, RGBlinear.n);
    cmsxApplyLinearizationTable(RGBlinear.n, sys->Prelinearization, RGBlinear.n);

    RGBlinear.n[0] /= 255.;
    RGBlinear.n[1] /= 255.;
    RGBlinear.n[2] /= 255.;

    MAT3eval(&vxyz, &sys->PrimariesMatrix, &RGBlinear);

    xyz.X = vxyz.n[0];
    xyz.Y = vxyz.n[1];
    xyz.Z = vxyz.n[2];

    cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, FALSE);

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

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


static
int RegressionSamplerB2A(WORD in[], WORD Out[], LPVOID Cargo)
{
    LPPROFILERDATA sys = (LPPROFILERDATA) Cargo;
    double L;
    cmsCIELab Lab;
    cmsCIEXYZ xyz;
    VEC3 vxyz, RGB;
    WORD In[3], Lin[3], Llab[3];
    L16PARAMS Lut16;

    In[0] = in[0];	/* copy, since we overwrite In[] */
    In[1] = in[1];
    In[2] = in[2];

    /* Pass L back to 0..0xff00 domain */

    L = (double) (In[0] * 65280.0) / 65535.0;
    In[0] = (WORD) floor(L + .5);

    /* undo sigmoidal prelinearization for a* and b* */

    cmsCalcL16Params(sys->ReverseSigmoid->nEntries, &Lut16);
    In[1] = cmsLinearInterpLUT16(In[1], sys->ReverseSigmoid->GammaTable, &Lut16);
    In[2] = cmsLinearInterpLUT16(In[2], sys->ReverseSigmoid->GammaTable, &Lut16);

    /* To float values */
    cmsLabEncoded2Float(&Lab, In);
    cmsLab2XYZ(NULL, &xyz, &Lab);

    cmsxChromaticAdaptationAndNormalization(&sys->hdr, &xyz, TRUE);

    vxyz.n[0] = xyz.X;
    vxyz.n[1] = xyz.Y;
    vxyz.n[2] = xyz.Z;

    MAT3eval(&RGB, &sys->PrimariesMatrixRev, &vxyz);

    /* Clamp RGB */
    ClampRGB(&RGB);

    /* Encode output */
    Lin[0] = (WORD) ((double) RGB.n[0] * 65535. + .5);
    Lin[1] = (WORD) ((double) RGB.n[1] * 65535. + .5);
    Lin[2] = (WORD) ((double) RGB.n[2] * 65535. + .5);

    cmsxApplyLinearizationGamma(Lin, sys->ReverseTables, Llab);
    cmsxApplyLinearizationGamma(Llab, sys->PreLabRev, Out);

    return TRUE; /* And done witch success */
}


BOOL cmsxMonitorProfilerInit(LPPROFILERDATA sys)
{

    if (sys == NULL) return FALSE;
    ZeroMemory(sys, sizeof(PROFILERDATA));

    sys->hdr.DeviceClass = icSigDisplayClass;
    sys->hdr.ColorSpace  = icSigRgbData;
    sys->hdr.PCSType     = PT_Lab;
    sys->hdr.Medium      = MEDIUM_TRANSMISSIVE;

    /* Default values for generation */

    sys -> hdr.lUseCIECAM02 = FALSE;
    sys -> hdr.CLUTPoints = 16;

    /* Default viewing conditions  */

    sys -> hdr.device.Yb = 20;			/* sRGB VCs */
    sys -> hdr.device.La = 4;
    sys -> hdr.device.surround = DIM_SURROUND;
    sys -> hdr.device.D_value  = 1.0;		/* Complete adaptation */

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

    strcpy(sys -> hdr.Description,  "no description - Many applications need this to be set to a meaningful value.  Please enter a desciption that will identify this profile.");
    strcpy(sys -> hdr.Manufacturer, "little cms profiler construction set");
    strcpy(sys -> hdr.Copyright,   "No copyright, use freely");
    strcpy(sys -> hdr.Model,       "(unknown)");
    strcpy(sys->hdr.Comments, "Your comment");

    sys -> hdr.ProfileVerbosityLevel = 0;

    return TRUE;
}


static
void CreatePrimaryMatrices(LPPROFILERDATA sys)
{
    cmsCIExyY White;
    MAT3 tmp;

    cmsXYZ2xyY(&White, &sys->hdr.WhitePoint);
    cmsBuildRGB2XYZtransferMatrix(&sys -> PrimariesMatrix, &White, &sys->hdr.Primaries);

    CopyMemory(&tmp, &sys -> PrimariesMatrix, sizeof(MAT3));
    MAT3inverse(&tmp, &sys->PrimariesMatrixRev);
}


static BOOL
CreateLUTS(LPPROFILERDATA sys, LPLUT* A2B0, LPLUT* B2A0, LPLUT* A2B1, LPLUT* B2A1, LPLUT* A2B2, LPLUT* B2A2)
{
    int i, B2APreLinPoints = 1029;	/* 257*N+1 */

    /* perceptual LUTS */
    LPLUT AToB0 = cmsAllocLUT();
    LPLUT BToA0 = cmsAllocLUT();

    /* colorimeteric LUTS */
    LPLUT AToB1 = cmsAllocLUT();
    LPLUT BToA1 = cmsAllocLUT();

    /* saturation LUTS */
    LPLUT AToB2 = cmsAllocLUT();
    LPLUT BToA2 = cmsAllocLUT();

    LPGAMMATABLE LabG, B2APreLin[3];
    cmsCIExyY xyY;

    cmsAlloc3DGrid(AToB0, sys->hdr.CLUTPoints, 3, 3);
    cmsAlloc3DGrid(BToA0, sys->hdr.CLUTPoints, 3, 3);
    cmsAlloc3DGrid(AToB1, sys->hdr.CLUTPoints, 3, 3);
    cmsAlloc3DGrid(BToA1, sys->hdr.CLUTPoints, 3, 3);
    cmsAlloc3DGrid(AToB2, sys->hdr.CLUTPoints, 3, 3);
    cmsAlloc3DGrid(BToA2, sys->hdr.CLUTPoints, 3, 3);

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

    sys->ReverseTables[0] = cmsReverseGamma(4096, sys ->Prelinearization[0]);
    sys->ReverseTables[1] = cmsReverseGamma(4096, sys ->Prelinearization[1]);
    sys->ReverseTables[2] = cmsReverseGamma(4096, sys ->Prelinearization[2]);

    /* Prelinearization */

    LabG = cmsBuildGamma(4096, 3.0);

    sys -> PreLab[0] = cmsJoinGammaEx(LabG, sys->Prelinearization[0], 4096);
    sys -> PreLab[1] = cmsJoinGammaEx(LabG, sys->Prelinearization[1], 4096);
    sys -> PreLab[2] = cmsJoinGammaEx(LabG, sys->Prelinearization[2], 4096);

    sys -> PreLabRev[0] = cmsJoinGammaEx(sys->Prelinearization[0], LabG, 4096);
    sys -> PreLabRev[1] = cmsJoinGammaEx(sys->Prelinearization[1], LabG, 4096);
    sys -> PreLabRev[2] = cmsJoinGammaEx(sys->Prelinearization[2], LabG, 4096);

    cmsFreeGamma(LabG);

    B2APreLin[0] = cmsAllocGamma(B2APreLinPoints);
    B2APreLin[1] = cmsBuildGamma(B2APreLinPoints, 1.0);
    B2APreLin[2] = cmsBuildGamma(B2APreLinPoints, 1.0);

    /* This is the 0xff00 trick to map white at 3D lattice point */

    for (i = 0; i < B2APreLinPoints; i++) {
        double x = i * 65535.0 / (B2APreLinPoints-1.0) * (65535.0 / 65280.0);
        if (x > 65535.0) x = 65535.0;
        B2APreLin[0]->GammaTable[i] = floor(x + 0.5);
    }

    /*
     * Compute sigmoidal B2A prelinearization tables for a* and b*.
     * Use a cubic polynomial with a slope of 2 at a=0, and slope
     * 0.5 at a=128.
     */

    for (i = 0; i < B2APreLinPoints; i++) {
        double x = i / (B2APreLinPoints-1.0) * 65535.0 / 256.0 - 128.0;
	double xp = fabs(x);
	double y = 3.0517578125e-05 * xp*xp*xp - 0.01171875 * xp*xp + 2 * xp;
	if (x < 0) y = -y;
	y = floor((y + 128) * 256 + 0.5);
        B2APreLin[1]->GammaTable[i] = B2APreLin[2]->GammaTable[i] = y;
    }

    /* compute inverse sigmoidal table */
    sys->ReverseSigmoid = cmsReverseGamma(B2APreLinPoints, B2APreLin[1]);

    cmsAllocLinearTable(AToB0, sys->PreLabRev, 1);
    cmsAllocLinearTable(BToA0, B2APreLin,      1);
    cmsAllocLinearTable(BToA0, sys->PreLab,    2);
    cmsAllocLinearTable(AToB1, sys->PreLabRev, 1);
    cmsAllocLinearTable(BToA1, B2APreLin,      1);
    cmsAllocLinearTable(BToA1, sys->PreLab,    2);
    cmsAllocLinearTable(AToB2, sys->PreLabRev, 1);
    cmsAllocLinearTable(BToA2, B2APreLin,      1);
    cmsAllocLinearTable(BToA2, sys->PreLab,    2);

    /* Set CIECAMxx 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.;

    /* Normalize White point for CIECAMxx model */
    cmsXYZ2xyY(&xyY, &sys->hdr.device.whitePoint);
    xyY.Y = 100.;
    cmsxyY2XYZ(&sys->hdr.device.whitePoint, &xyY);

    sys->hdr.hDevice = cmsCIECAM02Init(&sys->hdr.device);
    sys->hdr.hPCS    = cmsCIECAM02Init(&sys->hdr.PCS);

    sys->hdr.lUseCIECAM02 = TRUE;
    cmsSample3DGrid(AToB0, RegressionSamplerA2B, sys, 0);
    cmsSample3DGrid(BToA0, RegressionSamplerB2A, sys, 0);

    cmsSample3DGrid(AToB2, RegressionSamplerA2B, sys, 0);
    cmsSample3DGrid(BToA2, RegressionSamplerB2A, sys, 0);

    sys->hdr.lUseCIECAM02 = FALSE;
    cmsSample3DGrid(AToB1, RegressionSamplerA2B, sys, 0);
    cmsSample3DGrid(BToA1, RegressionSamplerB2A, sys, 0);

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

    cmsAddTag(sys->hdr.hProfile, icSigAToB0Tag, AToB0);
    cmsAddTag(sys->hdr.hProfile, icSigBToA0Tag, BToA0);
    cmsAddTag(sys->hdr.hProfile, icSigAToB1Tag, AToB1);
    cmsAddTag(sys->hdr.hProfile, icSigBToA1Tag, BToA1);
    cmsAddTag(sys->hdr.hProfile, icSigAToB2Tag, AToB2);
    cmsAddTag(sys->hdr.hProfile, icSigBToA2Tag, BToA2);

    *A2B0 = AToB0;
    *B2A0 = BToA0;
    *A2B1 = AToB1;
    *B2A1 = BToA1;
    *A2B2 = AToB2;
    *B2A2 = BToA2;

    cmsFreeGammaTriple(sys->ReverseTables);
    cmsFreeGammaTriple(sys->PreLab);
    cmsFreeGammaTriple(sys->PreLabRev);
    cmsFreeGammaTriple(B2APreLin);
    cmsFreeGamma(sys->ReverseSigmoid);

    return TRUE;
}


BOOL addVCGT_tag(cmsHPROFILE hProfile, char calFile[])
{
    LCMSHANDLE calIT8;
    LPVCGTTABLES data;
    int j, i, size;
    double rampsize;

    if (calFile[0])
    {
        calIT8 = cmsIT8LoadFromFile(calFile);
        if (calIT8 == NULL) return FALSE;
        rampsize = cmsIT8GetPropertyDbl(calIT8, "NUMBER_OF_SETS");
        size = sizeof(VCGTTABLES) + ((int) rampsize * 3) * sizeof (unsigned short);
        data = malloc(size);
        for (i=0; i < (int) rampsize * 3; i++)
        {
            if (i < rampsize)
                data -> ramps[i] = (WORD) cmsIT8GetDataRowColDbl(calIT8, i, 1);
            else if (i >= rampsize && i < rampsize * 2)
            {
                data -> ramps[i] = (WORD) cmsIT8GetDataRowColDbl(calIT8, i - rampsize, 2);
            }
            else
            {
                data -> ramps[i] = (WORD) cmsIT8GetDataRowColDbl(calIT8, i - (rampsize * 2), 3);
            }
        }
        // data -> spacer = 3;
        data -> nEntries = (WORD) rampsize;
        data -> nChannels = 3;
        data -> entrySize = 2;
        data -> gType = 0;

        cmsAddTag(hProfile, icSigVideoCardGammaTag, data);
    }
    return TRUE;
}


BOOL cmsxMonitorProfilerDo(LPPROFILERDATA sys)
{
    cmsCIExyY White;
    LPLUT AToB0, BToA0, AToB1, BToA1, AToB2, BToA2;

    AToB0 = BToA0 = AToB1 = BToA1 = AToB2 = BToA2 = NULL;

    /* to avoid seg fault, if not suppled */
    if (! sys->hdr.cmsprintf) sys->hdr.cmsprintf = printfDummy;

    if (! sys->hdr.MonOutputProfileFile[0]) {
        sys->hdr.cmsprintf("No display profile name was selected", 0, 'c');
        return FALSE;
    }

    if (sys->hdr.MonMeasurementSheet[0])
    {
        /* it is a measurement based profile */
        /* if not then it is a rough profile */
        /* and we continue on after this block */

	/* Use CIELAB PCS for CLUT profiles, otherwise XYZ */
        sys->hdr.PCSType = (sys->hdr.CLUTPoints > 2) ? PT_Lab : PT_XYZ;

	/*
	 * An even number of CLUT points is not a good idea for B2A tables,
	 * since there won't be lattice points at/near the gray axis
	 */
        if (sys->hdr.CLUTPoints > 2 && sys->hdr.CLUTPoints % 2 != 1) {
	    sys->hdr.cmsprintf("Number of CLUT points is even, increasing by one", 0, 'c');
	    sys->hdr.CLUTPoints++;
	}

        sys->hdr.cmsprintf("Loading sheets...", 0, 'c');
        if (sys->hdr.ReferenceSheet[0])
            sys->hdr.cmsprintf("Reference sheet: %1", 1, 's',  sys->hdr.ReferenceSheet);
        sys->hdr.cmsprintf("Measurement sheet: %1", 1, 's', sys->hdr.MonMeasurementSheet);

        if (!cmsxComputeMatrixShaper(sys->hdr.ReferenceSheet,
                                     sys->hdr.MonMeasurementSheet,
                                     MEDIUM_TRANSMISSIVE,
                                     sys->Prelinearization,
                                     &sys->hdr.WhitePoint,
                                     &sys->hdr.BlackPoint,
                                     &sys->hdr.Primaries))
        {
            sys->hdr.cmsprintf("Could not compute Matrix Shaper", 0, 'c');
            return FALSE;
        }

        if (sys->hdr.cmsprintf)
        {
            char Buffer[1024];

            _cmsIdentifyWhitePoint(Buffer, &sys ->hdr.WhitePoint);
            sys->hdr.cmsprintf("%1", 1, 's', Buffer);

            sys->hdr.cmsprintf("Primaries: R:%1, %2  G:%3, %4  B:%5, %6",
                               6, 'f',
                               sys->hdr.Primaries.Red.x,
                               sys->hdr.Primaries.Red.y,
                               sys->hdr.Primaries.Green.x,
                               sys->hdr.Primaries.Green.y,
                               sys->hdr.Primaries.Blue.x,
                               sys->hdr.Primaries.Blue.y);
        }

        sys->hdr.WhitePoint.X *= 0.01;
        sys->hdr.WhitePoint.Y *= 0.01;
        sys->hdr.WhitePoint.Z *= 0.01;
        sys->hdr.BlackPoint.X *= 0.01;
        sys->hdr.BlackPoint.Y *= 0.01;
        sys->hdr.BlackPoint.Z *= 0.01;
    }

    CreatePrimaryMatrices(sys);
    cmsXYZ2xyY(&White, &sys->hdr.WhitePoint);

    sys->hdr.hProfile = cmsCreateRGBProfile(&White,
                                            &sys->hdr.Primaries,
                                            sys->Prelinearization);

    cmsSetProfileICCversion(sys->hdr.hProfile, 0x2400000);
    cmsSetDeviceClass(sys->hdr.hProfile, sys->hdr.DeviceClass);
    cmsSetPCS(sys->hdr.hProfile, _cmsICCcolorSpace(sys->hdr.PCSType));

    /* might be able to call cmsxComputeGamutHull here */
    /* to create VRML file to enahnce profile checker functionality */

    if (sys->hdr.CLUTPoints > 2) {
	CreateLUTS(sys, &AToB0, &BToA0, &AToB1, &BToA1, &AToB2, &BToA2);
    }

    cmsxEmbedTextualInfo(&sys->hdr);

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

    if (sys->hdr.MonCalibrationSheet[0])
        if (!addVCGT_tag(sys->hdr.hProfile, sys->hdr.MonCalibrationSheet))
             printf("\nCould not add VCGT tag\n");


    if (sys->hdr.ProfileVerbosityLevel >= 2)
    {
        cmsxEmbedCharTarget(&sys ->hdr);
    }

    /* Save primaries & gamma curves */
    if (sys->hdr.ProfileVerbosityLevel >= 1 || sys->hdr.CLUTPoints <= 2)
    {
       sys -> hdr.Gamma[0] = sys -> Prelinearization[0];
       sys -> hdr.Gamma[1] = sys -> Prelinearization[1];
       sys -> hdr.Gamma[2] = sys -> Prelinearization[2];
       cmsxEmbedMatrixShaper(&sys ->hdr);
    }

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

    if (AToB0) cmsFreeLUT(AToB0);
    if (BToA0) cmsFreeLUT(BToA0);
    if (AToB1) cmsFreeLUT(AToB1);
    if (BToA1) cmsFreeLUT(BToA1);
    if (AToB2) cmsFreeLUT(AToB2);
    if (BToA2) cmsFreeLUT(BToA2);

    if (sys ->Prelinearization[0])
        cmsFreeGammaTriple(sys -> Prelinearization);

    return TRUE;
}
