blob: 707637444ad0e90de8b277535d2d36437aec2501 [file] [log] [blame]
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// This file is available under and governed by the GNU General Public
// License version 2 only, as published by the Free Software Foundation.
// However, the following notice accompanied the original version of this
// file:
//
//
// 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"
// Pipeline of LUT. Enclosed by {} are new revision 4.0 of ICC spec.
//
// [Mat] -> [L1] -> { [Mat3] -> [Ofs3] -> [L3] ->} [CLUT] { -> [L4] -> [Mat4] -> [Ofs4] } -> [L2]
//
// Some of these stages would be missing. This implements the totality of
// combinations of old and new LUT types as follows:
//
// Lut8 & Lut16
// ============
// [Mat] -> [L1] -> [CLUT] -> [L2]
//
// Mat2, Ofs2, L3, L3, Mat3, Ofs3 are missing
//
// LutAToB
// ========
//
// [L1] -> [CLUT] -> [L4] -> [Mat4] -> [Ofs4] -> [L2]
//
// Mat, Mat3, Ofs3, L3 are missing
// L1 = A curves
// L4 = M curves
// L2 = B curves
//
// LutBToA
// =======
//
// [L1] -> [Mat3] -> [Ofs3] -> [L3] -> [CLUT] -> [L2]
//
// Mat, L4, Mat4, Ofs4 are missing
// L1 = B Curves
// L3 = M Curves
// L2 = A curves
//
//
// V2&3 emulation
// ===============
//
// For output, Mat is multiplied by
//
//
// | 0xff00 / 0xffff 0 0 |
// | 0 0xff00 / 0xffff 0 |
// | 0 0 0xff00 / 0xffff |
//
//
// For input, an additional matrix is needed at the very last end of the chain
//
//
// | 0xffff / 0xff00 0 0 |
// | 0 0xffff / 0xff00 0 |
// | 0 0 0xffff / 0xff00 |
//
//
// Which reduces to (val * 257) >> 8
// A couple of macros to convert between revisions
#define FROM_V2_TO_V4(x) (((((x)<<8)+(x))+0x80)>>8) // BY 65535 DIV 65280 ROUND
#define FROM_V4_TO_V2(x) ((((x)<<8)+0x80)/257) // BY 65280 DIV 65535 ROUND
// Lut Creation & Destruction
LPLUT LCMSEXPORT cmsAllocLUT(void)
{
LPLUT NewLUT;
NewLUT = (LPLUT) _cmsMalloc(sizeof(LUT));
if (NewLUT)
ZeroMemory(NewLUT, sizeof(LUT));
return NewLUT;
}
void LCMSEXPORT cmsFreeLUT(LPLUT Lut)
{
unsigned int i;
if (!Lut) return;
if (Lut -> T) free(Lut -> T);
for (i=0; i < Lut -> OutputChan; i++)
{
if (Lut -> L2[i]) free(Lut -> L2[i]);
}
for (i=0; i < Lut -> InputChan; i++)
{
if (Lut -> L1[i]) free(Lut -> L1[i]);
}
if (Lut ->wFlags & LUT_HASTL3) {
for (i=0; i < Lut -> InputChan; i++) {
if (Lut -> L3[i]) free(Lut -> L3[i]);
}
}
if (Lut ->wFlags & LUT_HASTL4) {
for (i=0; i < Lut -> OutputChan; i++) {
if (Lut -> L4[i]) free(Lut -> L4[i]);
}
}
if (Lut ->CLut16params.p8)
free(Lut ->CLut16params.p8);
free(Lut);
}
static
LPVOID DupBlockTab(LPVOID Org, size_t size)
{
LPVOID mem = _cmsMalloc(size);
if (mem != NULL)
CopyMemory(mem, Org, size);
return mem;
}
LPLUT LCMSEXPORT cmsDupLUT(LPLUT Orig)
{
LPLUT NewLUT = cmsAllocLUT();
unsigned int i;
CopyMemory(NewLUT, Orig, sizeof(LUT));
for (i=0; i < Orig ->InputChan; i++)
NewLUT -> L1[i] = (LPWORD) DupBlockTab((LPVOID) Orig ->L1[i],
sizeof(WORD) * Orig ->In16params.nSamples);
for (i=0; i < Orig ->OutputChan; i++)
NewLUT -> L2[i] = (LPWORD) DupBlockTab((LPVOID) Orig ->L2[i],
sizeof(WORD) * Orig ->Out16params.nSamples);
NewLUT -> T = (LPWORD) DupBlockTab((LPVOID) Orig ->T, Orig -> Tsize);
return NewLUT;
}
static
unsigned int UIpow(unsigned int a, unsigned int b)
{
unsigned int rv = 1;
for (; b > 0; b--)
rv *= a;
return rv;
}
LCMSBOOL _cmsValidateLUT(LPLUT NewLUT)
{
unsigned int calc = 1;
unsigned int oldCalc;
unsigned int power = NewLUT -> InputChan;
if (NewLUT -> cLutPoints > 100) return FALSE;
if (NewLUT -> InputChan > MAXCHANNELS) return FALSE;
if (NewLUT -> OutputChan > MAXCHANNELS) return FALSE;
if (NewLUT -> cLutPoints == 0) return TRUE;
for (; power > 0; power--) {
oldCalc = calc;
calc *= NewLUT -> cLutPoints;
if (calc / NewLUT -> cLutPoints != oldCalc) {
return FALSE;
}
}
oldCalc = calc;
calc *= NewLUT -> OutputChan;
if (NewLUT -> OutputChan && calc / NewLUT -> OutputChan != oldCalc) {
return FALSE;
}
return TRUE;
}
LPLUT LCMSEXPORT cmsAlloc3DGrid(LPLUT NewLUT, int clutPoints, int inputChan, int outputChan)
{
DWORD nTabSize;
NewLUT -> wFlags |= LUT_HAS3DGRID;
NewLUT -> cLutPoints = clutPoints;
NewLUT -> InputChan = inputChan;
NewLUT -> OutputChan = outputChan;
if (!_cmsValidateLUT(NewLUT)) {
return NULL;
}
nTabSize = NewLUT -> OutputChan * UIpow(NewLUT->cLutPoints,
NewLUT->InputChan);
NewLUT -> T = (LPWORD) _cmsCalloc(sizeof(WORD), nTabSize);
nTabSize *= sizeof(WORD);
if (NewLUT -> T == NULL) return NULL;
ZeroMemory(NewLUT -> T, nTabSize);
NewLUT ->Tsize = nTabSize;
cmsCalcCLUT16Params(NewLUT -> cLutPoints, NewLUT -> InputChan,
NewLUT -> OutputChan,
&NewLUT -> CLut16params);
return NewLUT;
}
LPLUT LCMSEXPORT cmsAllocLinearTable(LPLUT NewLUT, LPGAMMATABLE Tables[], int nTable)
{
unsigned int i;
LPWORD PtrW;
switch (nTable) {
case 1: NewLUT -> wFlags |= LUT_HASTL1;
cmsCalcL16Params(Tables[0] -> nEntries, &NewLUT -> In16params);
NewLUT -> InputEntries = Tables[0] -> nEntries;
for (i=0; i < NewLUT -> InputChan; i++) {
PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * NewLUT -> InputEntries);
if (PtrW == NULL) return NULL;
NewLUT -> L1[i] = PtrW;
CopyMemory(PtrW, Tables[i]->GammaTable, sizeof(WORD) * NewLUT -> InputEntries);
CopyMemory(&NewLUT -> LCurvesSeed[0][i], &Tables[i] -> Seed, sizeof(LCMSGAMMAPARAMS));
}
break;
case 2: NewLUT -> wFlags |= LUT_HASTL2;
cmsCalcL16Params(Tables[0] -> nEntries, &NewLUT -> Out16params);
NewLUT -> OutputEntries = Tables[0] -> nEntries;
for (i=0; i < NewLUT -> OutputChan; i++) {
PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * NewLUT -> OutputEntries);
if (PtrW == NULL) return NULL;
NewLUT -> L2[i] = PtrW;
CopyMemory(PtrW, Tables[i]->GammaTable, sizeof(WORD) * NewLUT -> OutputEntries);
CopyMemory(&NewLUT -> LCurvesSeed[1][i], &Tables[i] -> Seed, sizeof(LCMSGAMMAPARAMS));
}
break;
// 3 & 4 according ICC 4.0 spec
case 3:
NewLUT -> wFlags |= LUT_HASTL3;
cmsCalcL16Params(Tables[0] -> nEntries, &NewLUT -> L3params);
NewLUT -> L3Entries = Tables[0] -> nEntries;
for (i=0; i < NewLUT -> InputChan; i++) {
PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * NewLUT -> L3Entries);
if (PtrW == NULL) return NULL;
NewLUT -> L3[i] = PtrW;
CopyMemory(PtrW, Tables[i]->GammaTable, sizeof(WORD) * NewLUT -> L3Entries);
CopyMemory(&NewLUT -> LCurvesSeed[2][i], &Tables[i] -> Seed, sizeof(LCMSGAMMAPARAMS));
}
break;
case 4:
NewLUT -> wFlags |= LUT_HASTL4;
cmsCalcL16Params(Tables[0] -> nEntries, &NewLUT -> L4params);
NewLUT -> L4Entries = Tables[0] -> nEntries;
for (i=0; i < NewLUT -> OutputChan; i++) {
PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * NewLUT -> L4Entries);
if (PtrW == NULL) return NULL;
NewLUT -> L4[i] = PtrW;
CopyMemory(PtrW, Tables[i]->GammaTable, sizeof(WORD) * NewLUT -> L4Entries);
CopyMemory(&NewLUT -> LCurvesSeed[3][i], &Tables[i] -> Seed, sizeof(LCMSGAMMAPARAMS));
}
break;
default:;
}
return NewLUT;
}
// Set the LUT matrix
LPLUT LCMSEXPORT cmsSetMatrixLUT(LPLUT Lut, LPMAT3 M)
{
MAT3toFix(&Lut ->Matrix, M);
if (!MAT3isIdentity(&Lut->Matrix, 0.0001))
Lut ->wFlags |= LUT_HASMATRIX;
return Lut;
}
// Set matrix & offset, v4 compatible
LPLUT LCMSEXPORT cmsSetMatrixLUT4(LPLUT Lut, LPMAT3 M, LPVEC3 off, DWORD dwFlags)
{
WMAT3 WMat;
WVEC3 Woff;
VEC3 Zero = {{0, 0, 0}};
MAT3toFix(&WMat, M);
if (off == NULL)
off = &Zero;
VEC3toFix(&Woff, off);
// Nop if identity
if (MAT3isIdentity(&WMat, 0.0001) &&
(Woff.n[VX] == 0 && Woff.n[VY] == 0 && Woff.n[VZ] == 0))
return Lut;
switch (dwFlags) {
case LUT_HASMATRIX:
Lut ->Matrix = WMat;
Lut ->wFlags |= LUT_HASMATRIX;
break;
case LUT_HASMATRIX3:
Lut ->Mat3 = WMat;
Lut ->Ofs3 = Woff;
Lut ->wFlags |= LUT_HASMATRIX3;
break;
case LUT_HASMATRIX4:
Lut ->Mat4 = WMat;
Lut ->Ofs4 = Woff;
Lut ->wFlags |= LUT_HASMATRIX4;
break;
default:;
}
return Lut;
}
// The full evaluator
void LCMSEXPORT cmsEvalLUT(LPLUT Lut, WORD In[], WORD Out[])
{
register unsigned int i;
WORD StageABC[MAXCHANNELS], StageLMN[MAXCHANNELS];
// Try to speedup things on plain devicelinks
if (Lut ->wFlags == LUT_HAS3DGRID) {
Lut ->CLut16params.Interp3D(In, Out, Lut -> T, &Lut -> CLut16params);
return;
}
// Nope, evaluate whole LUT
for (i=0; i < Lut -> InputChan; i++)
StageABC[i] = In[i];
if (Lut ->wFlags & LUT_V4_OUTPUT_EMULATE_V2) {
// Clamp Lab to avoid overflow
if (StageABC[0] > 0xFF00)
StageABC[0] = 0xFF00;
StageABC[0] = (WORD) FROM_V2_TO_V4(StageABC[0]);
StageABC[1] = (WORD) FROM_V2_TO_V4(StageABC[1]);
StageABC[2] = (WORD) FROM_V2_TO_V4(StageABC[2]);
}
if (Lut ->wFlags & LUT_V2_OUTPUT_EMULATE_V4) {
StageABC[0] = (WORD) FROM_V4_TO_V2(StageABC[0]);
StageABC[1] = (WORD) FROM_V4_TO_V2(StageABC[1]);
StageABC[2] = (WORD) FROM_V4_TO_V2(StageABC[2]);
}
// Matrix handling.
if (Lut -> wFlags & LUT_HASMATRIX) {
WVEC3 InVect, OutVect;
// In LUT8 here comes the special gray axis fixup
if (Lut ->FixGrayAxes) {
StageABC[1] = _cmsClampWord(StageABC[1] - 128);
StageABC[2] = _cmsClampWord(StageABC[2] - 128);
}
// Matrix
InVect.n[VX] = ToFixedDomain(StageABC[0]);
InVect.n[VY] = ToFixedDomain(StageABC[1]);
InVect.n[VZ] = ToFixedDomain(StageABC[2]);
MAT3evalW(&OutVect, &Lut -> Matrix, &InVect);
// PCS in 1Fixed15 format, adjusting
StageABC[0] = _cmsClampWord(FromFixedDomain(OutVect.n[VX]));
StageABC[1] = _cmsClampWord(FromFixedDomain(OutVect.n[VY]));
StageABC[2] = _cmsClampWord(FromFixedDomain(OutVect.n[VZ]));
}
// First linearization
if (Lut -> wFlags & LUT_HASTL1)
{
for (i=0; i < Lut -> InputChan; i++)
StageABC[i] = cmsLinearInterpLUT16(StageABC[i],
Lut -> L1[i],
&Lut -> In16params);
}
// Mat3, Ofs3, L3 processing
if (Lut ->wFlags & LUT_HASMATRIX3) {
WVEC3 InVect, OutVect;
InVect.n[VX] = ToFixedDomain(StageABC[0]);
InVect.n[VY] = ToFixedDomain(StageABC[1]);
InVect.n[VZ] = ToFixedDomain(StageABC[2]);
MAT3evalW(&OutVect, &Lut -> Mat3, &InVect);
OutVect.n[VX] += Lut ->Ofs3.n[VX];
OutVect.n[VY] += Lut ->Ofs3.n[VY];
OutVect.n[VZ] += Lut ->Ofs3.n[VZ];
StageABC[0] = _cmsClampWord(FromFixedDomain(OutVect.n[VX]));
StageABC[1] = _cmsClampWord(FromFixedDomain(OutVect.n[VY]));
StageABC[2] = _cmsClampWord(FromFixedDomain(OutVect.n[VZ]));
}
if (Lut ->wFlags & LUT_HASTL3) {
for (i=0; i < Lut -> InputChan; i++)
StageABC[i] = cmsLinearInterpLUT16(StageABC[i],
Lut -> L3[i],
&Lut -> L3params);
}
if (Lut -> wFlags & LUT_HAS3DGRID) {
Lut ->CLut16params.Interp3D(StageABC, StageLMN, Lut -> T, &Lut -> CLut16params);
}
else
{
for (i=0; i < Lut -> InputChan; i++)
StageLMN[i] = StageABC[i];
}
// Mat4, Ofs4, L4 processing
if (Lut ->wFlags & LUT_HASTL4) {
for (i=0; i < Lut -> OutputChan; i++)
StageLMN[i] = cmsLinearInterpLUT16(StageLMN[i],
Lut -> L4[i],
&Lut -> L4params);
}
if (Lut ->wFlags & LUT_HASMATRIX4) {
WVEC3 InVect, OutVect;
InVect.n[VX] = ToFixedDomain(StageLMN[0]);
InVect.n[VY] = ToFixedDomain(StageLMN[1]);
InVect.n[VZ] = ToFixedDomain(StageLMN[2]);
MAT3evalW(&OutVect, &Lut -> Mat4, &InVect);
OutVect.n[VX] += Lut ->Ofs4.n[VX];
OutVect.n[VY] += Lut ->Ofs4.n[VY];
OutVect.n[VZ] += Lut ->Ofs4.n[VZ];
StageLMN[0] = _cmsClampWord(FromFixedDomain(OutVect.n[VX]));
StageLMN[1] = _cmsClampWord(FromFixedDomain(OutVect.n[VY]));
StageLMN[2] = _cmsClampWord(FromFixedDomain(OutVect.n[VZ]));
}
// Last linearitzation
if (Lut -> wFlags & LUT_HASTL2)
{
for (i=0; i < Lut -> OutputChan; i++)
Out[i] = cmsLinearInterpLUT16(StageLMN[i],
Lut -> L2[i],
&Lut -> Out16params);
}
else
{
for (i=0; i < Lut -> OutputChan; i++)
Out[i] = StageLMN[i];
}
if (Lut ->wFlags & LUT_V4_INPUT_EMULATE_V2) {
Out[0] = (WORD) FROM_V4_TO_V2(Out[0]);
Out[1] = (WORD) FROM_V4_TO_V2(Out[1]);
Out[2] = (WORD) FROM_V4_TO_V2(Out[2]);
}
if (Lut ->wFlags & LUT_V2_INPUT_EMULATE_V4) {
Out[0] = (WORD) FROM_V2_TO_V4(Out[0]);
Out[1] = (WORD) FROM_V2_TO_V4(Out[1]);
Out[2] = (WORD) FROM_V2_TO_V4(Out[2]);
}
}
// Precomputes tables for 8-bit on input devicelink.
//
LPLUT _cmsBlessLUT8(LPLUT Lut)
{
int i, j;
WORD StageABC[3];
Fixed32 v1, v2, v3;
LPL8PARAMS p8;
LPL16PARAMS p = &Lut ->CLut16params;
p8 = (LPL8PARAMS) _cmsMalloc(sizeof(L8PARAMS));
if (p8 == NULL) return NULL;
// values comes * 257, so we can safely take first byte (x << 8 + x)
// if there are prelinearization, is already smelted in tables
for (i=0; i < 256; i++) {
StageABC[0] = StageABC[1] = StageABC[2] = RGB_8_TO_16(i);
if (Lut ->wFlags & LUT_HASTL1) {
for (j=0; j < 3; j++)
StageABC[j] = cmsLinearInterpLUT16(StageABC[j],
Lut -> L1[j],
&Lut -> In16params);
Lut ->wFlags &= ~LUT_HASTL1;
}
v1 = ToFixedDomain(StageABC[0] * p -> Domain);
v2 = ToFixedDomain(StageABC[1] * p -> Domain);
v3 = ToFixedDomain(StageABC[2] * p -> Domain);
p8 ->X0[i] = p->opta3 * FIXED_TO_INT(v1);
p8 ->Y0[i] = p->opta2 * FIXED_TO_INT(v2);
p8 ->Z0[i] = p->opta1 * FIXED_TO_INT(v3);
p8 ->rx[i] = (WORD) FIXED_REST_TO_INT(v1);
p8 ->ry[i] = (WORD) FIXED_REST_TO_INT(v2);
p8 ->rz[i] = (WORD) FIXED_REST_TO_INT(v3);
}
Lut -> CLut16params.p8 = p8;
Lut -> CLut16params.Interp3D = cmsTetrahedralInterp8;
return Lut;
}
// ----------------------------------------------------------- Reverse interpolation
// Here's how it goes. The derivative Df(x) of the function f is the linear
// transformation that best approximates f near the point x. It can be represented
// by a matrix A whose entries are the partial derivatives of the components of f
// with respect to all the coordinates. This is know as the Jacobian
//
// The best linear approximation to f is given by the matrix equation:
//
// y-y0 = A (x-x0)
//
// So, if x0 is a good "guess" for the zero of f, then solving for the zero of this
// linear approximation will give a "better guess" for the zero of f. Thus let y=0,
// and since y0=f(x0) one can solve the above equation for x. This leads to the
// Newton's method formula:
//
// xn+1 = xn - A-1 f(xn)
//
// where xn+1 denotes the (n+1)-st guess, obtained from the n-th guess xn in the
// fashion described above. Iterating this will give better and better approximations
// if you have a "good enough" initial guess.
#define JACOBIAN_EPSILON 0.001
#define INVERSION_MAX_ITERATIONS 30
// Increment with reflexion on boundary
static
void IncDelta(double *Val)
{
if (*Val < (1.0 - JACOBIAN_EPSILON))
*Val += JACOBIAN_EPSILON;
else
*Val -= JACOBIAN_EPSILON;
}
static
void ToEncoded(WORD Encoded[3], LPVEC3 Float)
{
Encoded[0] = (WORD) floor(Float->n[0] * 65535.0 + 0.5);
Encoded[1] = (WORD) floor(Float->n[1] * 65535.0 + 0.5);
Encoded[2] = (WORD) floor(Float->n[2] * 65535.0 + 0.5);
}
static
void FromEncoded(LPVEC3 Float, WORD Encoded[3])
{
Float->n[0] = Encoded[0] / 65535.0;
Float->n[1] = Encoded[1] / 65535.0;
Float->n[2] = Encoded[2] / 65535.0;
}
// Evaluates the CLUT part of a LUT (4 -> 3 only)
static
void EvalLUTdoubleKLab(LPLUT Lut, const VEC3* In, WORD FixedK, LPcmsCIELab Out)
{
WORD wIn[4], wOut[3];
wIn[0] = (WORD) floor(In ->n[0] * 65535.0 + 0.5);
wIn[1] = (WORD) floor(In ->n[1] * 65535.0 + 0.5);
wIn[2] = (WORD) floor(In ->n[2] * 65535.0 + 0.5);
wIn[3] = FixedK;
cmsEvalLUT(Lut, wIn, wOut);
cmsLabEncoded2Float(Out, wOut);
}
// Builds a Jacobian CMY->Lab
static
void ComputeJacobianLab(LPLUT Lut, LPMAT3 Jacobian, const VEC3* Colorant, WORD K)
{
VEC3 ColorantD;
cmsCIELab Lab, LabD;
int j;
EvalLUTdoubleKLab(Lut, Colorant, K, &Lab);
for (j = 0; j < 3; j++) {
ColorantD.n[0] = Colorant ->n[0];
ColorantD.n[1] = Colorant ->n[1];
ColorantD.n[2] = Colorant ->n[2];
IncDelta(&ColorantD.n[j]);
EvalLUTdoubleKLab(Lut, &ColorantD, K, &LabD);
Jacobian->v[0].n[j] = ((LabD.L - Lab.L) / JACOBIAN_EPSILON);
Jacobian->v[1].n[j] = ((LabD.a - Lab.a) / JACOBIAN_EPSILON);
Jacobian->v[2].n[j] = ((LabD.b - Lab.b) / JACOBIAN_EPSILON);
}
}
// Evaluate a LUT in reverse direction. It only searches on 3->3 LUT, but It
// can be used on CMYK -> Lab LUT to obtain black preservation.
// Target holds LabK in this case
// x1 <- x - [J(x)]^-1 * f(x)
LCMSAPI double LCMSEXPORT cmsEvalLUTreverse(LPLUT Lut, WORD Target[], WORD Result[], LPWORD Hint)
{
int i;
double error, LastError = 1E20;
cmsCIELab fx, Goal;
VEC3 tmp, tmp2, x;
MAT3 Jacobian;
WORD FixedK;
WORD LastResult[4];
// This is our Lab goal
cmsLabEncoded2Float(&Goal, Target);
// Special case for CMYK->Lab
if (Lut ->InputChan == 4)
FixedK = Target[3];
else
FixedK = 0;
// Take the hint as starting point if specified
if (Hint == NULL) {
// Begin at any point, we choose 1/3 of neutral CMY gray
x.n[0] = x.n[1] = x.n[2] = 0.3;
}
else {
FromEncoded(&x, Hint);
}
// Iterate
for (i = 0; i < INVERSION_MAX_ITERATIONS; i++) {
// Get beginning fx
EvalLUTdoubleKLab(Lut, &x, FixedK, &fx);
// Compute error
error = cmsDeltaE(&fx, &Goal);
// If not convergent, return last safe value
if (error >= LastError)
break;
// Keep latest values
LastError = error;
ToEncoded(LastResult, &x);
LastResult[3] = FixedK;
// Obtain slope
ComputeJacobianLab(Lut, &Jacobian, &x, FixedK);
// Solve system
tmp2.n[0] = fx.L - Goal.L;
tmp2.n[1] = fx.a - Goal.a;
tmp2.n[2] = fx.b - Goal.b;
if (!MAT3solve(&tmp, &Jacobian, &tmp2))
break;
// Move our guess
x.n[0] -= tmp.n[0];
x.n[1] -= tmp.n[1];
x.n[2] -= tmp.n[2];
// Some clipping....
VEC3saturate(&x);
}
Result[0] = LastResult[0];
Result[1] = LastResult[1];
Result[2] = LastResult[2];
Result[3] = LastResult[3];
return LastError;
}