//
//  Little cms
//  Copyright (C) 1998-2007 Marti Maria
//
// 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 "lcms.h"

// Gamma handling. 

LPGAMMATABLE LCMSEXPORT cmsAllocGamma(int nEntries);
void         LCMSEXPORT cmsFreeGamma(LPGAMMATABLE Gamma);
void         LCMSEXPORT cmsFreeGammaTriple(LPGAMMATABLE Gamma[3]);
LPGAMMATABLE LCMSEXPORT cmsBuildGamma(int nEntries, double Gamma);
LPGAMMATABLE LCMSEXPORT cmsDupGamma(LPGAMMATABLE Src);
LPGAMMATABLE LCMSEXPORT cmsReverseGamma(int nResultSamples, LPGAMMATABLE InGamma);
LPGAMMATABLE LCMSEXPORT cmsBuildParametricGamma(int nEntries, int Type, double Params[]);
LPGAMMATABLE LCMSEXPORT cmsJoinGamma(LPGAMMATABLE InGamma, LPGAMMATABLE OutGamma);
LPGAMMATABLE LCMSEXPORT cmsJoinGammaEx(LPGAMMATABLE InGamma, LPGAMMATABLE OutGamma, int nPoints);
LCMSBOOL         LCMSEXPORT cmsSmoothGamma(LPGAMMATABLE Tab, double lambda);

LCMSBOOL         cdecl _cmsSmoothEndpoints(LPWORD Table, int nPoints);


// Sampled curves

LPSAMPLEDCURVE cdecl cmsAllocSampledCurve(int nItems);
void           cdecl cmsFreeSampledCurve(LPSAMPLEDCURVE p);
void           cdecl cmsEndpointsOfSampledCurve(LPSAMPLEDCURVE p, double* Min, double* Max);
void           cdecl cmsClampSampledCurve(LPSAMPLEDCURVE p, double Min, double Max);
LCMSBOOL       cdecl cmsSmoothSampledCurve(LPSAMPLEDCURVE Tab, double SmoothingLambda);
void           cdecl cmsRescaleSampledCurve(LPSAMPLEDCURVE p, double Min, double Max, int nPoints);

LPSAMPLEDCURVE cdecl cmsJoinSampledCurves(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints);

double LCMSEXPORT cmsEstimateGamma(LPGAMMATABLE t);
double LCMSEXPORT cmsEstimateGammaEx(LPWORD GammaTable, int nEntries, double Thereshold);

// ----------------------------------------------------------------------------------------


#define MAX_KNOTS   4096
typedef float vec[MAX_KNOTS+1];


// Ciclic-redundant-check for assuring table is a true representation of parametric curve

// The usual polynomial, which is used for AAL5, FDDI, and probably Ethernet 
#define QUOTIENT 0x04c11db7
    
static
unsigned int Crc32(unsigned int result, LPVOID ptr, int len)
{    
    int          i,j;
    BYTE         octet; 
    LPBYTE       data = (LPBYTE) ptr;
        
    for (i=0; i < len; i++) {

        octet = *data++;

        for (j=0; j < 8; j++) {

            if (result & 0x80000000) {

                result = (result << 1) ^ QUOTIENT ^ (octet >> 7);
            }
            else
            {
                result = (result << 1) ^ (octet >> 7);
            }
            octet <<= 1;
        }
    }
    
    return result;
}

// Get CRC of gamma table 

unsigned int _cmsCrc32OfGammaTable(LPGAMMATABLE Table)
{
    unsigned int crc = ~0U;

    crc = Crc32(crc, &Table -> Seed.Type,  sizeof(int));
    crc = Crc32(crc, Table ->Seed.Params,  sizeof(double)*10);
    crc = Crc32(crc, &Table ->nEntries,    sizeof(int));
    crc = Crc32(crc, Table ->GammaTable,   sizeof(WORD) * Table -> nEntries);

    return ~crc;

}


LPGAMMATABLE LCMSEXPORT cmsAllocGamma(int nEntries)
{
       LPGAMMATABLE p;
       size_t size;

       if (nEntries > 65530 || nEntries <= 0) {
                cmsSignalError(LCMS_ERRC_ABORTED, "Couldn't create gammatable of more than 65530 entries");
                return NULL;
       }

       size = sizeof(GAMMATABLE) + (sizeof(WORD) * (nEntries-1));

       p = (LPGAMMATABLE) _cmsMalloc(size);
       if (!p) return NULL;

       ZeroMemory(p, size);

       p -> Seed.Type     = 0;
       p -> nEntries = nEntries;
       
       return p;
}

void LCMSEXPORT cmsFreeGamma(LPGAMMATABLE Gamma)
{
       if (Gamma)  _cmsFree(Gamma);
}



void LCMSEXPORT cmsFreeGammaTriple(LPGAMMATABLE Gamma[3])
{
    cmsFreeGamma(Gamma[0]);
    cmsFreeGamma(Gamma[1]);
    cmsFreeGamma(Gamma[2]);
    Gamma[0] = Gamma[1] = Gamma[2] = NULL;
}



// Duplicate a gamma table

LPGAMMATABLE  LCMSEXPORT cmsDupGamma(LPGAMMATABLE In)
{
       LPGAMMATABLE Ptr;
       size_t size;

       Ptr = cmsAllocGamma(In -> nEntries);
       if (Ptr == NULL) return NULL;

       size = sizeof(GAMMATABLE) + (sizeof(WORD) * (In -> nEntries-1));

       CopyMemory(Ptr, In, size);                  
       return Ptr;
}


// Handle gamma using interpolation tables. The resulting curves can become
// very stange, but are pleasent to eye.

LPGAMMATABLE LCMSEXPORT cmsJoinGamma(LPGAMMATABLE InGamma,
                          LPGAMMATABLE OutGamma)
{
       register int i;
       L16PARAMS L16In, L16Out;
       LPWORD InPtr, OutPtr;
       LPGAMMATABLE p;

       p = cmsAllocGamma(256);
       if (!p) return NULL;

       cmsCalcL16Params(InGamma -> nEntries, &L16In);
       InPtr  = InGamma -> GammaTable;

       cmsCalcL16Params(OutGamma -> nEntries, &L16Out);
       OutPtr = OutGamma-> GammaTable;

       for (i=0; i < 256; i++)
       {
              WORD wValIn, wValOut;

              wValIn  = cmsLinearInterpLUT16(RGB_8_TO_16(i), InPtr, &L16In);
              wValOut = cmsReverseLinearInterpLUT16(wValIn, OutPtr, &L16Out);
              
              p -> GammaTable[i] = wValOut;
       }

       return p;
}



// New method, using smoothed parametric curves. This works FAR better.
// We want to get 
//
//      y = f(g^-1(x))      ; f = ingamma, g = outgamma
//
// And this can be parametrized as
//
//      y = f(t)
//      x = g(t)


LPGAMMATABLE LCMSEXPORT cmsJoinGammaEx(LPGAMMATABLE InGamma,
                                       LPGAMMATABLE OutGamma, int nPoints)
{

    LPSAMPLEDCURVE x, y, r;
    LPGAMMATABLE res;
    
    x = cmsConvertGammaToSampledCurve(InGamma,  nPoints);
    y = cmsConvertGammaToSampledCurve(OutGamma, nPoints);
    r = cmsJoinSampledCurves(y, x, nPoints);

    // Does clean "hair"
    cmsSmoothSampledCurve(r, 0.001);

    cmsClampSampledCurve(r, 0.0, 65535.0); 
    
    cmsFreeSampledCurve(x);
    cmsFreeSampledCurve(y);

    res = cmsConvertSampledCurveToGamma(r, 65535.0);
    cmsFreeSampledCurve(r);

    return res;
}



// Reverse a gamma table

LPGAMMATABLE LCMSEXPORT cmsReverseGamma(int nResultSamples, LPGAMMATABLE InGamma)
{
       register int i;
       L16PARAMS L16In;
       LPWORD InPtr;
       LPGAMMATABLE p;

       // Try to reverse it analytically whatever possible
       if (InGamma -> Seed.Type > 0 && InGamma -> Seed.Type <= 5 &&             
            _cmsCrc32OfGammaTable(InGamma) == InGamma -> Seed.Crc32) {

                return cmsBuildParametricGamma(nResultSamples, -(InGamma -> Seed.Type), InGamma ->Seed.Params);
       }


       // Nope, reverse the table 
       p = cmsAllocGamma(nResultSamples);
       if (!p) return NULL;

       cmsCalcL16Params(InGamma -> nEntries, &L16In);
       InPtr  = InGamma -> GammaTable;

       for (i=0; i < nResultSamples; i++)
       {
              WORD wValIn, wValOut;

              wValIn = _cmsQuantizeVal(i, nResultSamples);           
              wValOut = cmsReverseLinearInterpLUT16(wValIn, InPtr, &L16In);
              p -> GammaTable[i] = wValOut;
       }

           
       return p;
}



// Parametric curves
//
// Parameters goes as: Gamma, a, b, c, d, e, f
// Type is the ICC type +1
// if type is negative, then the curve is analyticaly inverted

LPGAMMATABLE LCMSEXPORT cmsBuildParametricGamma(int nEntries, int Type, double Params[])
{
        LPGAMMATABLE Table;
        double R, Val, dval, e;
        int i;
        int ParamsByType[] = { 0, 1, 3, 4, 5, 7 };

        Table = cmsAllocGamma(nEntries);
        if (NULL == Table) return NULL;

        Table -> Seed.Type = Type;       

        CopyMemory(Table ->Seed.Params, Params, ParamsByType[abs(Type)] * sizeof(double));


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

                R   = (double) i / (nEntries-1);

                switch (Type) {

                // X = Y ^ Gamma
                case 1:
                      Val = pow(R, Params[0]);
                      break;

                // Type 1 Reversed: X = Y ^1/gamma
                case -1:
                      Val = pow(R, 1/Params[0]);
                      break;

                // CIE 122-1966
                // Y = (aX + b)^Gamma  | X >= -b/a
                // Y = 0               | else
                case 2:
                    if (R >= -Params[2] / Params[1]) {
                              
                              e = Params[1]*R + Params[2];

                              if (e > 0)
                                Val = pow(e, Params[0]);
                              else
                                Val = 0;
                    }
                    else
                              Val = 0;
                      break;

                // Type 2 Reversed
                // X = (Y ^1/g  - b) / a
                case -2: 
                    
                    Val = (pow(R, 1.0/Params[0]) - Params[2]) / Params[1];
                    if (Val < 0)
                            Val = 0;                            
                    break;


                // IEC 61966-3
                // Y = (aX + b)^Gamma | X <= -b/a
                // Y = c              | else
                case 3:
                    if (R >= -Params[2] / Params[1]) {
                            
                      e = Params[1]*R + Params[2];                    
                      Val = pow(e, Params[0]) + Params[3];
                    }
                    else
                      Val = Params[3];
                    break;


                // Type 3 reversed
                // X=((Y-c)^1/g - b)/a      | (Y>=c)
                // X=-b/a                   | (Y<c) 
                    
                case -3:
                    if (R >= Params[3])  {
                        e = R - Params[3];
                        Val = (pow(e, 1/Params[0]) - Params[2]) / Params[1];
                        if (Val < 0) Val = 0;
                    }
                    else {
                        Val = -Params[2] / Params[1];
                    }
                    break;


                // IEC 61966-2.1 (sRGB)
                // Y = (aX + b)^Gamma | X >= d
                // Y = cX             | X < d
                case 4:
                    if (R >= Params[4]) {
                              
                              e = Params[1]*R + Params[2];
                              if (e > 0)
                                Val = pow(e, Params[0]);
                              else
                                Val = 0;
                    }
                      else
                              Val = R * Params[3];
                      break;

                // Type 4 reversed
                // X=((Y^1/g-b)/a)    | Y >= (ad+b)^g
                // X=Y/c              | Y< (ad+b)^g

                case -4:
                    if (R >= pow(Params[1] * Params[4] + Params[2], Params[0])) {

                        Val = (pow(R, 1.0/Params[0]) - Params[2]) / Params[1];
                    }
                    else {
                        Val = R / Params[3];
                    }
                    break;
                


                // Y = (aX + b)^Gamma + e | X <= d
                // Y = cX + f             | else
                case 5:
                    if (R >= Params[4]) {
                             
                        e = Params[1]*R + Params[2];
                        Val = pow(e, Params[0]) + Params[5];
                    }        
                    else
                        Val = R*Params[3] + Params[6];
                    break;


                // Reversed type 5
                // X=((Y-e)1/g-b)/a   | Y >=(ad+b)^g+e)
                // X=(Y-f)/c          | else
                case -5:

                if (R >= pow(Params[1] * Params[4], Params[0]) + Params[5]) {

                    Val = pow(R - Params[5], 1/Params[0]) - Params[2] / Params[1];
                }
                else {
                    Val = (R - Params[6]) / Params[3];
                }
                break;

                default:
                        cmsSignalError(LCMS_ERRC_ABORTED, "Unsupported parametric curve type=%d", abs(Type)-1);
                        cmsFreeGamma(Table);
                        return NULL;
                }


        // Saturate

        dval = Val * 65535.0 + .5;
        if (dval > 65535.) dval = 65535.0;
        if (dval < 0) dval = 0;

        Table->GammaTable[i] = (WORD) floor(dval);
        }

        Table -> Seed.Crc32 = _cmsCrc32OfGammaTable(Table);

        return Table;
}

// Build a gamma table based on gamma constant

LPGAMMATABLE LCMSEXPORT cmsBuildGamma(int nEntries, double Gamma)
{
    return cmsBuildParametricGamma(nEntries, 1, &Gamma);
}



// From: Eilers, P.H.C. (1994) Smoothing and interpolation with finite
// differences. in: Graphic Gems IV, Heckbert, P.S. (ed.), Academic press.
//
// Smoothing and interpolation with second differences.
//
//   Input:  weights (w), data (y): vector from 1 to m.
//   Input:  smoothing parameter (lambda), length (m).
//   Output: smoothed vector (z): vector from 1 to m.


static
void smooth2(vec w, vec y, vec z, float lambda, int m)
{
  int i, i1, i2;
  vec c, d, e;
  d[1] = w[1] + lambda;
  c[1] = -2 * lambda / d[1];
  e[1] = lambda /d[1];
  z[1] = w[1] * y[1];
  d[2] = w[2] + 5 * lambda - d[1] * c[1] *  c[1];
  c[2] = (-4 * lambda - d[1] * c[1] * e[1]) / d[2];
  e[2] = lambda / d[2];
  z[2] = w[2] * y[2] - c[1] * z[1];
  for (i = 3; i < m - 1; i++) {
    i1 = i - 1; i2 = i - 2;
    d[i]= w[i] + 6 * lambda - c[i1] * c[i1] * d[i1] - e[i2] * e[i2] * d[i2];
    c[i] = (-4 * lambda -d[i1] * c[i1] * e[i1])/ d[i];
    e[i] = lambda / d[i];
    z[i] = w[i] * y[i] - c[i1] * z[i1] - e[i2] * z[i2];
  }
  i1 = m - 2; i2 = m - 3;
  d[m - 1] = w[m - 1] + 5 * lambda -c[i1] * c[i1] * d[i1] - e[i2] * e[i2] * d[i2];
  c[m - 1] = (-2 * lambda - d[i1] * c[i1] * e[i1]) / d[m - 1];
  z[m - 1] = w[m - 1] * y[m - 1] - c[i1] * z[i1] - e[i2] * z[i2];
  i1 = m - 1; i2 = m - 2;
  d[m] = w[m] + lambda - c[i1] * c[i1] * d[i1] - e[i2] * e[i2] * d[i2];
  z[m] = (w[m] * y[m] - c[i1] * z[i1] - e[i2] * z[i2]) / d[m];
  z[m - 1] = z[m - 1] / d[m - 1] - c[m - 1] * z[m];
  for (i = m - 2; 1<= i; i--)
     z[i] = z[i] / d[i] - c[i] * z[i + 1] - e[i] * z[i + 2];
}



// Smooths a curve sampled at regular intervals

LCMSBOOL LCMSEXPORT cmsSmoothGamma(LPGAMMATABLE Tab, double lambda)

{
    vec w, y, z;
    int i, nItems, Zeros, Poles;


    if (cmsIsLinear(Tab->GammaTable, Tab->nEntries)) return FALSE; // Nothing to do

    nItems = Tab -> nEntries;

    if (nItems > MAX_KNOTS) {
                cmsSignalError(LCMS_ERRC_ABORTED, "cmsSmoothGamma: too many points.");
                return FALSE;
                }

    ZeroMemory(w, nItems * sizeof(float));
    ZeroMemory(y, nItems * sizeof(float));
    ZeroMemory(z, nItems * sizeof(float));

    for (i=0; i < nItems; i++)
    {
        y[i+1] = (float) Tab -> GammaTable[i];
        w[i+1] = 1.0;
    }

    smooth2(w, y, z, (float) lambda, nItems);

    // Do some reality - checking...
    Zeros = Poles = 0;
    for (i=nItems; i > 1; --i) {

            if (z[i] == 0.) Zeros++;
            if (z[i] >= 65535.) Poles++;
            if (z[i] < z[i-1]) return FALSE; // Non-Monotonic
    }

    if (Zeros > (nItems / 3)) return FALSE;  // Degenerated, mostly zeros
    if (Poles > (nItems / 3)) return FALSE;  // Degenerated, mostly poles

    // Seems ok

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

        // Clamp to WORD

        float v = z[i+1];

        if (v < 0) v = 0;
        if (v > 65535.) v = 65535.;

        Tab -> GammaTable[i] = (WORD) floor(v + .5);
        }

    return TRUE;
}


// Check if curve is exponential, return gamma if so.

double LCMSEXPORT cmsEstimateGammaEx(LPWORD GammaTable, int nEntries, double Thereshold)
{
    double gamma, sum, sum2;
    double n, x, y, Std;
    int i;

    sum = sum2 = n = 0;
    
    // Does exclude endpoints   
    for (i=1; i < nEntries - 1; i++) {

            x = (double) i / (nEntries - 1);
            y = (double) GammaTable[i] / 65535.;
            
            // Avoid 7% on lower part to prevent 
            // artifacts due to linear ramps

            if (y > 0. && y < 1. && x > 0.07) {

            gamma = log(y) / log(x);
            sum  += gamma;
            sum2 += gamma * gamma;
            n++;
            }
            
    }

    // Take a look on SD to see if gamma isn't exponential at all
    Std = sqrt((n * sum2 - sum * sum) / (n*(n-1)));
    

    if (Std > Thereshold)
        return -1.0;

    return (sum / n);   // The mean
}


double LCMSEXPORT cmsEstimateGamma(LPGAMMATABLE t)
{
        return cmsEstimateGammaEx(t->GammaTable, t->nEntries, 0.7);
}


// -----------------------------------------------------------------Sampled curves

// Allocate a empty curve

LPSAMPLEDCURVE cmsAllocSampledCurve(int nItems)
{
    LPSAMPLEDCURVE pOut;

    pOut = (LPSAMPLEDCURVE) _cmsMalloc(sizeof(SAMPLEDCURVE));
    if (pOut == NULL)
            return NULL;

    if((pOut->Values = (double *) _cmsMalloc(nItems * sizeof(double))) == NULL)
    {
         _cmsFree(pOut);
        return NULL;
    }

    pOut->nItems = nItems;
    ZeroMemory(pOut->Values, nItems * sizeof(double));

    return pOut;
}


void cmsFreeSampledCurve(LPSAMPLEDCURVE p)
{
     _cmsFree((LPVOID) p -> Values);
     _cmsFree((LPVOID) p);
}



// Does duplicate a sampled curve

LPSAMPLEDCURVE cmsDupSampledCurve(LPSAMPLEDCURVE p)
{
    LPSAMPLEDCURVE out;

    out = cmsAllocSampledCurve(p -> nItems);
    if (!out) return NULL;

    CopyMemory(out ->Values, p ->Values, p->nItems * sizeof(double));

    return out;
}


// Take min, max of curve

void cmsEndpointsOfSampledCurve(LPSAMPLEDCURVE p, double* Min, double* Max)
{
        int i;

        *Min = 65536.;
        *Max = 0.;

        for (i=0; i < p -> nItems; i++) {

                double v = p -> Values[i];

                if (v < *Min)
                        *Min = v;

                if (v > *Max)
                        *Max = v;
        }

        if (*Min < 0) *Min = 0;
        if (*Max > 65535.0) *Max = 65535.0;
}

// Clamps to Min, Max

void cmsClampSampledCurve(LPSAMPLEDCURVE p, double Min, double Max)
{

        int i;

        for (i=0; i < p -> nItems; i++) {

                double v = p -> Values[i];

                if (v < Min)
                        v = Min;

                if (v > Max)
                        v = Max;

                p -> Values[i] = v;

        }

}



// Smooths a curve sampled at regular intervals

LCMSBOOL cmsSmoothSampledCurve(LPSAMPLEDCURVE Tab, double lambda)
{
    vec w, y, z;
    int i, nItems;

    nItems = Tab -> nItems;

    if (nItems > MAX_KNOTS) {
                cmsSignalError(LCMS_ERRC_ABORTED, "cmsSmoothSampledCurve: too many points.");
                return FALSE;
                }

    ZeroMemory(w, nItems * sizeof(float));
    ZeroMemory(y, nItems * sizeof(float));
    ZeroMemory(z, nItems * sizeof(float));

    for (i=0; i < nItems; i++)
    {
        float value = (float) Tab -> Values[i];

        y[i+1] = value;
        w[i+1] = (float) ((value < 0.0) ?  0 : 1);
    }


    smooth2(w, y, z, (float) lambda, nItems);

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

        Tab -> Values[i] = z[i+1];;
     }

    return TRUE;

}


// Scale a value v, within domain Min .. Max 
// to a domain 0..(nPoints-1)

static
double ScaleVal(double v, double Min, double Max, int nPoints)
{

        double a, b;

        if (v <= Min) return 0;
        if (v >= Max) return (nPoints-1);
    
        a = (double) (nPoints - 1) / (Max - Min);
        b = a * Min;

        return (a * v) - b;

}


// Does rescale a sampled curve to fit in a 0..(nPoints-1) domain

void cmsRescaleSampledCurve(LPSAMPLEDCURVE p, double Min, double Max, int nPoints)
{

        int i;

        for (i=0; i < p -> nItems; i++) {

                double v = p -> Values[i];

                p -> Values[i] = ScaleVal(v, Min, Max, nPoints);
        }

}


// Joins two sampled curves for X and Y. Curves should be sorted.

LPSAMPLEDCURVE cmsJoinSampledCurves(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints)
{
    int i, j;   
    LPSAMPLEDCURVE out;
    double MinX, MinY, MaxX, MaxY;
    double x, y, x1, y1, x2, y2, a, b;

    out = cmsAllocSampledCurve(nResultingPoints);
    if (out == NULL)
        return NULL;

    if (X -> nItems != Y -> nItems) {

        cmsSignalError(LCMS_ERRC_ABORTED, "cmsJoinSampledCurves: invalid curve.");
        cmsFreeSampledCurve(out);
        return NULL;
    }

    // Get endpoints of sampled curves
    cmsEndpointsOfSampledCurve(X, &MinX, &MaxX);
    cmsEndpointsOfSampledCurve(Y, &MinY, &MaxY);

    
    // Set our points
    out ->Values[0] = MinY; 
    for (i=1; i < nResultingPoints; i++) {

        // Scale t to x domain
        x = (i * (MaxX - MinX) / (nResultingPoints-1)) + MinX;

        // Find interval in which j is within (always up, 
        // since fn should be monotonic at all)

        j = 1;
        while ((j < X ->nItems - 1) && X ->Values[j] < x)
            j++;
            
        // Now x is within X[j-1], X[j]
        x1 = X ->Values[j-1]; x2 = X ->Values[j];
        y1 = Y ->Values[j-1]; y2 = Y ->Values[j];

        // Interpolate  the value
        a = (y1 - y2) / (x1 - x2);
        b = y1 - a * x1;
        y = a* x + b;
        
        out ->Values[i] = y;
    }
    

    cmsClampSampledCurve(out, MinY, MaxY);
    return out;
}



// Convert between curve types

LPGAMMATABLE cmsConvertSampledCurveToGamma(LPSAMPLEDCURVE Sampled, double Max)
{
    LPGAMMATABLE Gamma;
    int i, nPoints;
    

    nPoints = Sampled ->nItems;

    Gamma = cmsAllocGamma(nPoints);
    for (i=0; i < nPoints; i++) {
        
        Gamma->GammaTable[i] = (WORD) floor(ScaleVal(Sampled ->Values[i], 0, Max, 65536) + .5);
    }

    return Gamma;

}

// Inverse of anterior

LPSAMPLEDCURVE cmsConvertGammaToSampledCurve(LPGAMMATABLE Gamma, int nPoints)
{
    LPSAMPLEDCURVE Sampled;
    L16PARAMS L16;
    int i;
    WORD wQuant, wValIn;

    if (nPoints > 4096) {

        cmsSignalError(LCMS_ERRC_ABORTED, "cmsConvertGammaToSampledCurve: too many points (max=4096)");
        return NULL;
    }

    cmsCalcL16Params(Gamma -> nEntries, &L16);
       
    Sampled = cmsAllocSampledCurve(nPoints);
    for (i=0; i < nPoints; i++) {
            wQuant  = _cmsQuantizeVal(i, nPoints);
            wValIn  = cmsLinearInterpLUT16(wQuant, Gamma ->GammaTable, &L16);
            Sampled ->Values[i] = (float) wValIn;
    }

    return Sampled;
}




// Smooth endpoints (used in Black/White compensation)

LCMSBOOL _cmsSmoothEndpoints(LPWORD Table, int nEntries)
{
    vec w, y, z;
    int i, Zeros, Poles;



    if (cmsIsLinear(Table, nEntries)) return FALSE; // Nothing to do

    
    if (nEntries > MAX_KNOTS) {
                cmsSignalError(LCMS_ERRC_ABORTED, "_cmsSmoothEndpoints: too many points.");
                return FALSE;
                }

    ZeroMemory(w, nEntries * sizeof(float));
    ZeroMemory(y, nEntries * sizeof(float));
    ZeroMemory(z, nEntries * sizeof(float));

    for (i=0; i < nEntries; i++)
    {
        y[i+1] = (float) Table[i];
        w[i+1] = 1.0;
    }

    w[1]        = 65535.0;
    w[nEntries] = 65535.0;

    smooth2(w, y, z, (float) nEntries, nEntries);

    // Do some reality - checking...
    Zeros = Poles = 0;
    for (i=nEntries; i > 1; --i) {

            if (z[i] == 0.) Zeros++;
            if (z[i] >= 65535.) Poles++;
            if (z[i] < z[i-1]) return FALSE; // Non-Monotonic
    }

    if (Zeros > (nEntries / 3)) return FALSE;  // Degenerated, mostly zeros
    if (Poles > (nEntries / 3)) return FALSE;    // Degenerated, mostly poles

    // Seems ok

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

        // Clamp to WORD

        float v = z[i+1];

        if (v < 0) v = 0;
        if (v > 65535.) v = 65535.;

        Table[i] = (WORD) floor(v + .5);
        }

    return TRUE;
}
