You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
955 lines
24 KiB
955 lines
24 KiB
//
|
|
// 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;
|
|
}
|