|
|
/* */
|
|
|
/* 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.09a */
|
|
|
|
|
|
|
|
|
#include "lcmsprf.h"
|
|
|
|
|
|
|
|
|
LPGAMMATABLE cdecl cmsxEstimateGamma(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints);
|
|
|
void cdecl cmsxCompleteLabOfPatches(LPMEASUREMENT m, SETOFPATCHES Valids, int Medium);
|
|
|
|
|
|
void cdecl cmsxComputeLinearizationTables(LPMEASUREMENT m,
|
|
|
int ColorSpace,
|
|
|
LPGAMMATABLE Lin[3],
|
|
|
int nResultingPoints,
|
|
|
int Medium);
|
|
|
|
|
|
|
|
|
void cdecl cmsxApplyLinearizationTable(double In[3],
|
|
|
LPGAMMATABLE Gamma[3],
|
|
|
double Out[3]);
|
|
|
|
|
|
void cdecl cmsxApplyLinearizationGamma(WORD In[3], LPGAMMATABLE Gamma[3], WORD Out[3]);
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------- Implementation */
|
|
|
|
|
|
|
|
|
#define EPSILON 0.00005
|
|
|
#define LEVENBERG_MARTQUARDT_ITERATE_MAX 150
|
|
|
|
|
|
/* In order to track linearization tables, we use following procedure */
|
|
|
/* */
|
|
|
/* We first assume R', G' and B' does exhibit a non-linear behaviour */
|
|
|
/* that can be separated for each channel as Yr(R'), Yg(G'), Yb(B') */
|
|
|
/* This is the shaper step */
|
|
|
/* */
|
|
|
/* R = Lr(R') */
|
|
|
/* G = Lg(G') */
|
|
|
/* B = Lb(B') (0.0) */
|
|
|
/* */
|
|
|
/* After this step, RGB is converted to XYZ by a matrix multiplication */
|
|
|
/* */
|
|
|
/* |X| |R| */
|
|
|
/* |Y| = [M]<5D>|G| */
|
|
|
/* |Z| |B| (1.0) */
|
|
|
/* */
|
|
|
/* In order to extract Lr,Lg,Lb tables, we are interested only on Y part */
|
|
|
/* */
|
|
|
/* Y = (m1 * R + m2 * G + m3 * B) (1.1) */
|
|
|
/* */
|
|
|
/* The total intensity for maximum RGB = (1, 1, 1) should be 1, */
|
|
|
/* */
|
|
|
/* 1 = m1 * 1 + m2 * 1 + m3 * 1, so */
|
|
|
/* */
|
|
|
/* m1 + m2 + m3 = 1.0 (1.2) */
|
|
|
/* */
|
|
|
/* We now impose that for neutral (gray) patches, RGB components must be equal */
|
|
|
/* */
|
|
|
/* R = G = B = Gray */
|
|
|
/* */
|
|
|
/* So, substituting in (1.1): */
|
|
|
/* */
|
|
|
/* Y = (m1 + m2 + m3) Gray */
|
|
|
/* */
|
|
|
/* and for (1.2), (m1+m2+m3) = 1, so */
|
|
|
/* */
|
|
|
/* Y = Gray = Lr(R') = Lg(G') = Lb(B') */
|
|
|
/* */
|
|
|
/* That is, after prelinearization, RGB of gray patches should give */
|
|
|
/* same values for R, G and B. And this value is Y. */
|
|
|
/* */
|
|
|
/* */
|
|
|
|
|
|
|
|
|
static
|
|
|
LPSAMPLEDCURVE NormalizeTo(LPSAMPLEDCURVE X, double N, BOOL lAddEndPoint)
|
|
|
{
|
|
|
int i, nItems;
|
|
|
LPSAMPLEDCURVE XNorm;
|
|
|
|
|
|
nItems = X ->nItems;
|
|
|
if (lAddEndPoint) nItems++;
|
|
|
|
|
|
XNorm = cmsAllocSampledCurve(nItems);
|
|
|
|
|
|
for (i=0; i < X ->nItems; i++) {
|
|
|
|
|
|
XNorm ->Values[i] = X ->Values[i] / N;
|
|
|
}
|
|
|
|
|
|
if (lAddEndPoint)
|
|
|
XNorm -> Values[X ->nItems] = 1.0;
|
|
|
|
|
|
return XNorm;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* */
|
|
|
/* ------------------------------------------------------------------------------ */
|
|
|
/* */
|
|
|
/* Our Monitor model. We assume gamma has a general expression of */
|
|
|
/* */
|
|
|
/* Fn(x) = (Gain * x + offset) ^ gamma | for x >= 0 */
|
|
|
/* Fn(x) = 0 | for x < 0 */
|
|
|
/* */
|
|
|
/* First partial derivatives are */
|
|
|
/* */
|
|
|
/* dFn/dGamma = Fn * ln(Base) */
|
|
|
/* dFn/dGain = gamma * x * ((Gain * x + Offset) ^ (gamma -1)) */
|
|
|
/* dFn/dOffset = gamma * ((Gain * x + Offset) ^ (gamma -1)) */
|
|
|
/* */
|
|
|
|
|
|
static
|
|
|
void GammaGainOffsetFn(double x, double *a, double *y, double *dyda, int na)
|
|
|
{
|
|
|
double Gamma,Gain,Offset;
|
|
|
double Base;
|
|
|
|
|
|
Gamma = a[0];
|
|
|
Gain = a[1];
|
|
|
Offset = a[2];
|
|
|
|
|
|
Base = Gain * x + Offset;
|
|
|
|
|
|
if (Base < 0) {
|
|
|
|
|
|
Base = 0.0;
|
|
|
*y = 0.0;
|
|
|
dyda[0] = 0.0;
|
|
|
dyda[1] = 0.0;
|
|
|
dyda[2] = 0.0;
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
/* The function itself */
|
|
|
*y = pow(Base, Gamma);
|
|
|
|
|
|
/* dyda[0] is partial derivative across Gamma */
|
|
|
dyda[0] = *y * log(Base);
|
|
|
|
|
|
/* dyda[1] is partial derivative across gain */
|
|
|
dyda[1] = (x * Gamma) * pow(Base, Gamma-1.0);
|
|
|
|
|
|
/* dyda[2] is partial derivative across offset */
|
|
|
dyda[2] = Gamma * pow(Base, Gamma-1.0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Fit curve to our gamma-gain-offset model. */
|
|
|
|
|
|
static
|
|
|
BOOL OneTry(LPSAMPLEDCURVE XNorm, LPSAMPLEDCURVE YNorm, double a[])
|
|
|
{
|
|
|
LCMSHANDLE h;
|
|
|
double ChiSq, OldChiSq;
|
|
|
int i;
|
|
|
BOOL Status = true;
|
|
|
|
|
|
/* initial guesses */
|
|
|
|
|
|
a[0] = 3.0; /* gamma */
|
|
|
a[1] = 4.0; /* gain */
|
|
|
a[2] = 6.0; /* offset */
|
|
|
a[3] = 0.0; /* Thereshold */
|
|
|
a[4] = 0.0; /* Black */
|
|
|
|
|
|
|
|
|
/* Significance = 0.02 gives good results */
|
|
|
|
|
|
h = cmsxLevenbergMarquardtInit(XNorm, YNorm, 0.02, a, 3, GammaGainOffsetFn);
|
|
|
if (h == NULL) return false;
|
|
|
|
|
|
|
|
|
OldChiSq = cmsxLevenbergMarquardtChiSq(h);
|
|
|
|
|
|
for(i = 0; i < LEVENBERG_MARTQUARDT_ITERATE_MAX; i++) {
|
|
|
|
|
|
if (!cmsxLevenbergMarquardtIterate(h)) {
|
|
|
Status = false;
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
ChiSq = cmsxLevenbergMarquardtChiSq(h);
|
|
|
|
|
|
if(OldChiSq != ChiSq && (OldChiSq - ChiSq) < EPSILON)
|
|
|
break;
|
|
|
|
|
|
OldChiSq = ChiSq;
|
|
|
}
|
|
|
|
|
|
cmsxLevenbergMarquardtFree(h);
|
|
|
|
|
|
return Status;
|
|
|
}
|
|
|
|
|
|
/* Tries to fit gamma as per IEC 61966-2.1 using Levenberg-Marquardt method */
|
|
|
/* */
|
|
|
/* Y = (aX + b)^Gamma | X >= d */
|
|
|
/* Y = cX | X < d */
|
|
|
|
|
|
LPGAMMATABLE cmsxEstimateGamma(LPSAMPLEDCURVE X, LPSAMPLEDCURVE Y, int nResultingPoints)
|
|
|
{
|
|
|
double a[5];
|
|
|
LPSAMPLEDCURVE XNorm, YNorm;
|
|
|
double e, Max;
|
|
|
|
|
|
|
|
|
/* Coarse approximation, to find maximum. */
|
|
|
/* We have only a portion of curve. It is likely */
|
|
|
/* maximum will not fall on exactly 100. */
|
|
|
|
|
|
if (!OneTry(X, Y, a))
|
|
|
return false;
|
|
|
|
|
|
/* Got parameters. Compute maximum. */
|
|
|
e = a[1]* 255.0 + a[2];
|
|
|
if (e < 0) return false;
|
|
|
Max = pow(e, a[0]);
|
|
|
|
|
|
|
|
|
/* Normalize values to maximum */
|
|
|
XNorm = NormalizeTo(X, 255.0, false);
|
|
|
YNorm = NormalizeTo(Y, Max, false);
|
|
|
|
|
|
/* Do the final fitting */
|
|
|
if (!OneTry(XNorm, YNorm, a))
|
|
|
return false;
|
|
|
|
|
|
/* Type 3 = IEC 61966-2.1 (sRGB) */
|
|
|
/* Y = (aX + b)^Gamma | X >= d */
|
|
|
/* Y = cX | X < d */
|
|
|
return cmsBuildParametricGamma(nResultingPoints, 3, a);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* A dumb bubble sort */
|
|
|
|
|
|
static
|
|
|
void Bubble(LPSAMPLEDCURVE C, LPSAMPLEDCURVE L)
|
|
|
{
|
|
|
#define SWAP(a, b) { tmp = (a); (a) = (b); (b) = tmp; }
|
|
|
|
|
|
BOOL lSwapped;
|
|
|
int i, nItems;
|
|
|
double tmp;
|
|
|
|
|
|
nItems = C -> nItems;
|
|
|
do {
|
|
|
lSwapped = false;
|
|
|
|
|
|
for (i= 0; i < nItems - 1; i++) {
|
|
|
|
|
|
if (C->Values[i] > C->Values[i+1]) {
|
|
|
|
|
|
SWAP(C->Values[i], C->Values[i+1]);
|
|
|
SWAP(L->Values[i], L->Values[i+1]);
|
|
|
lSwapped = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
} while (lSwapped);
|
|
|
|
|
|
#undef SWAP
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Check for monotonicity. Force it if is not the case. */
|
|
|
|
|
|
static
|
|
|
void CheckForMonotonicSampledCurve(LPSAMPLEDCURVE t)
|
|
|
{
|
|
|
int n = t ->nItems;
|
|
|
int i;
|
|
|
double last;
|
|
|
|
|
|
last = t ->Values[n-1];
|
|
|
for (i = n-2; i >= 0; --i) {
|
|
|
|
|
|
if (t ->Values[i] > last)
|
|
|
|
|
|
t ->Values[i] = last;
|
|
|
else
|
|
|
last = t ->Values[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/* The main gamma inferer. Tries first by gamma-gain-offset, */
|
|
|
/* if not proper reverts to curve guessing. */
|
|
|
|
|
|
static
|
|
|
LPGAMMATABLE BuildGammaTable(LPSAMPLEDCURVE C, LPSAMPLEDCURVE L, int nResultingPoints)
|
|
|
{
|
|
|
LPSAMPLEDCURVE Cw, Lw, Cn, Ln;
|
|
|
LPSAMPLEDCURVE out;
|
|
|
LPGAMMATABLE Result;
|
|
|
double Lmax, Lend, Cmax;
|
|
|
|
|
|
/* Try to see if it can be fitted */
|
|
|
Result = cmsxEstimateGamma(C, L, nResultingPoints);
|
|
|
if (Result)
|
|
|
return Result;
|
|
|
|
|
|
|
|
|
/* No... build curve from scratch. Since we have not */
|
|
|
/* endpoints, a coarse linear extrapolation should be */
|
|
|
/* applied in order to get the expected maximum. */
|
|
|
|
|
|
Cw = cmsDupSampledCurve(C);
|
|
|
Lw = cmsDupSampledCurve(L);
|
|
|
|
|
|
Bubble(Cw, Lw);
|
|
|
|
|
|
/* Get endpoint */
|
|
|
Lmax = Lw->Values[Lw ->nItems - 1];
|
|
|
Cmax = Cw->Values[Cw ->nItems - 1];
|
|
|
|
|
|
/* Linearly extrapolate */
|
|
|
Lend = (255 * Lmax) / Cmax;
|
|
|
|
|
|
Ln = NormalizeTo(Lw, Lend, true);
|
|
|
Cn = NormalizeTo(Cw, 255.0, true);
|
|
|
|
|
|
cmsFreeSampledCurve(Cw);
|
|
|
cmsFreeSampledCurve(Lw);
|
|
|
|
|
|
/* Add endpoint */
|
|
|
out = cmsJoinSampledCurves(Cn, Ln, nResultingPoints);
|
|
|
|
|
|
cmsFreeSampledCurve(Cn);
|
|
|
cmsFreeSampledCurve(Ln);
|
|
|
|
|
|
CheckForMonotonicSampledCurve(out);
|
|
|
|
|
|
cmsSmoothSampledCurve(out, nResultingPoints*4.);
|
|
|
cmsClampSampledCurve(out, 0, 1.0);
|
|
|
|
|
|
Result = cmsConvertSampledCurveToGamma(out, 1.0);
|
|
|
|
|
|
cmsFreeSampledCurve(out);
|
|
|
return Result;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void cmsxCompleteLabOfPatches(LPMEASUREMENT m, SETOFPATCHES Valids, int Medium)
|
|
|
{
|
|
|
LPPATCH White;
|
|
|
cmsCIEXYZ WhiteXYZ;
|
|
|
int i;
|
|
|
|
|
|
if (Medium == MEDIUM_REFLECTIVE_D50)
|
|
|
{
|
|
|
WhiteXYZ.X = D50X * 100.;
|
|
|
WhiteXYZ.Y = D50Y * 100.;
|
|
|
WhiteXYZ.Z = D50Z * 100.;
|
|
|
}
|
|
|
else {
|
|
|
|
|
|
White = cmsxPCollFindWhite(m, Valids, NULL);
|
|
|
if (!White) return;
|
|
|
|
|
|
WhiteXYZ = White ->XYZ;
|
|
|
}
|
|
|
|
|
|
/* For all patches with XYZ and without Lab, add Lab values. */
|
|
|
/* Transmissive profiles does need to locate its own white */
|
|
|
/* point for device gray. Reflective does use D50 */
|
|
|
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
|
|
if (Valids[i]) {
|
|
|
|
|
|
LPPATCH p = m -> Patches + i;
|
|
|
|
|
|
if ((p ->dwFlags & PATCH_HAS_XYZ) &&
|
|
|
(!(p ->dwFlags & PATCH_HAS_Lab) || (Medium == MEDIUM_TRANSMISSIVE))) {
|
|
|
|
|
|
cmsXYZ2Lab(&WhiteXYZ, &p->Lab, &p->XYZ);
|
|
|
p -> dwFlags |= PATCH_HAS_Lab;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Compute linearization tables, trying to fit in a pure */
|
|
|
/* exponential gamma. If gamma cannot be accurately infered, */
|
|
|
/* then does build a smooth, monotonic curve that does the job. */
|
|
|
|
|
|
void cmsxComputeLinearizationTables(LPMEASUREMENT m,
|
|
|
int ColorSpace,
|
|
|
LPGAMMATABLE Lin[3],
|
|
|
int nResultingPoints,
|
|
|
int Medium)
|
|
|
|
|
|
{
|
|
|
LPSAMPLEDCURVE R, G, B, L;
|
|
|
LPGAMMATABLE gr, gg, gb;
|
|
|
SETOFPATCHES Neutrals;
|
|
|
int nGrays;
|
|
|
int i;
|
|
|
|
|
|
/* We need Lab for grays. */
|
|
|
cmsxCompleteLabOfPatches(m, m->Allowed, Medium);
|
|
|
|
|
|
/* Add neutrals, normalize to max */
|
|
|
Neutrals = cmsxPCollBuildSet(m, false);
|
|
|
cmsxPCollPatchesNearNeutral(m, m ->Allowed, 15, Neutrals);
|
|
|
|
|
|
nGrays = cmsxPCollCountSet(m, Neutrals);
|
|
|
|
|
|
R = cmsAllocSampledCurve(nGrays);
|
|
|
G = cmsAllocSampledCurve(nGrays);
|
|
|
B = cmsAllocSampledCurve(nGrays);
|
|
|
L = cmsAllocSampledCurve(nGrays);
|
|
|
|
|
|
nGrays = 0;
|
|
|
|
|
|
/* Collect patches */
|
|
|
for (i=0; i < m -> nPatches; i++) {
|
|
|
|
|
|
if (Neutrals[i]) {
|
|
|
|
|
|
LPPATCH gr = m -> Patches + i;
|
|
|
|
|
|
|
|
|
R -> Values[nGrays] = gr -> Colorant.RGB[0];
|
|
|
G -> Values[nGrays] = gr -> Colorant.RGB[1];
|
|
|
B -> Values[nGrays] = gr -> Colorant.RGB[2];
|
|
|
L -> Values[nGrays] = gr -> XYZ.Y;
|
|
|
|
|
|
nGrays++;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
gr = BuildGammaTable(R, L, nResultingPoints);
|
|
|
gg = BuildGammaTable(G, L, nResultingPoints);
|
|
|
gb = BuildGammaTable(B, L, nResultingPoints);
|
|
|
|
|
|
cmsFreeSampledCurve(R);
|
|
|
cmsFreeSampledCurve(G);
|
|
|
cmsFreeSampledCurve(B);
|
|
|
cmsFreeSampledCurve(L);
|
|
|
|
|
|
if (ColorSpace == PT_Lab) {
|
|
|
|
|
|
LPGAMMATABLE Gamma3 = cmsBuildGamma(nResultingPoints, 3.0);
|
|
|
|
|
|
Lin[0] = cmsJoinGammaEx(gr, Gamma3, nResultingPoints);
|
|
|
Lin[1] = cmsJoinGammaEx(gg, Gamma3, nResultingPoints);
|
|
|
Lin[2] = cmsJoinGammaEx(gb, Gamma3, nResultingPoints);
|
|
|
|
|
|
cmsFreeGamma(gr); cmsFreeGamma(gg); cmsFreeGamma(gb);
|
|
|
cmsFreeGamma(Gamma3);
|
|
|
}
|
|
|
else {
|
|
|
|
|
|
|
|
|
LPGAMMATABLE Gamma1 = cmsBuildGamma(nResultingPoints, 1.0);
|
|
|
|
|
|
Lin[0] = cmsJoinGammaEx(gr, Gamma1, nResultingPoints);
|
|
|
Lin[1] = cmsJoinGammaEx(gg, Gamma1, nResultingPoints);
|
|
|
Lin[2] = cmsJoinGammaEx(gb, Gamma1, nResultingPoints);
|
|
|
|
|
|
cmsFreeGamma(gr); cmsFreeGamma(gg); cmsFreeGamma(gb);
|
|
|
cmsFreeGamma(Gamma1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Apply linearization. WORD encoded version */
|
|
|
|
|
|
void cmsxApplyLinearizationGamma(WORD In[3], LPGAMMATABLE Gamma[3], WORD Out[3])
|
|
|
{
|
|
|
L16PARAMS Lut16;
|
|
|
|
|
|
cmsCalcL16Params(Gamma[0] -> nEntries, &Lut16);
|
|
|
|
|
|
Out[0] = cmsLinearInterpLUT16(In[0], Gamma[0] -> GammaTable, &Lut16);
|
|
|
Out[1] = cmsLinearInterpLUT16(In[1], Gamma[1] -> GammaTable, &Lut16);
|
|
|
Out[2] = cmsLinearInterpLUT16(In[2], Gamma[2] -> GammaTable, &Lut16);
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Apply linearization. double version */
|
|
|
|
|
|
void cmsxApplyLinearizationTable(double In[3], LPGAMMATABLE Gamma[3], double Out[3])
|
|
|
{
|
|
|
WORD rw, gw, bw;
|
|
|
double rd, gd, bd;
|
|
|
L16PARAMS Lut16;
|
|
|
|
|
|
|
|
|
cmsCalcL16Params(Gamma[0] -> nEntries, &Lut16);
|
|
|
|
|
|
rw = (WORD) floor(_cmsxSaturate255To65535(In[0]) + .5);
|
|
|
gw = (WORD) floor(_cmsxSaturate255To65535(In[1]) + .5);
|
|
|
bw = (WORD) floor(_cmsxSaturate255To65535(In[2]) + .5);
|
|
|
|
|
|
rd = cmsLinearInterpLUT16(rw , Gamma[0] -> GammaTable, &Lut16);
|
|
|
gd = cmsLinearInterpLUT16(gw, Gamma[1] -> GammaTable, &Lut16);
|
|
|
bd = cmsLinearInterpLUT16(bw, Gamma[2] -> GammaTable, &Lut16);
|
|
|
|
|
|
Out[0] = _cmsxSaturate65535To255(rd); /* back to 0..255 */
|
|
|
Out[1] = _cmsxSaturate65535To255(gd);
|
|
|
Out[2] = _cmsxSaturate65535To255(bd);
|
|
|
}
|
|
|
|