mirror of
https://github.com/koenkooi/foo2zjs.git
synced 2026-01-22 11:44:49 +08:00
2015 lines
60 KiB
C
2015 lines
60 KiB
C
|
|
//
|
||
|
|
// 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"
|
||
|
|
|
||
|
|
|
||
|
|
// Transformations stuff
|
||
|
|
// -----------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
// Interface
|
||
|
|
|
||
|
|
cmsHTRANSFORM LCMSEXPORT cmsCreateTransform(cmsHPROFILE Input,
|
||
|
|
DWORD InputFormat,
|
||
|
|
cmsHPROFILE Output,
|
||
|
|
DWORD OutputFormat,
|
||
|
|
int Intent,
|
||
|
|
DWORD dwFlags);
|
||
|
|
|
||
|
|
cmsHTRANSFORM LCMSEXPORT cmsCreateProofingTransform(cmsHPROFILE Input,
|
||
|
|
DWORD InputFormat,
|
||
|
|
cmsHPROFILE Output,
|
||
|
|
DWORD OutputFormat,
|
||
|
|
cmsHPROFILE Proofing,
|
||
|
|
int Intent,
|
||
|
|
int ProofingIntent,
|
||
|
|
DWORD dwFlags);
|
||
|
|
|
||
|
|
|
||
|
|
void LCMSEXPORT cmsDeleteTransform(cmsHTRANSFORM hTransform);
|
||
|
|
|
||
|
|
void LCMSEXPORT cmsDoTransform(cmsHTRANSFORM Transform,
|
||
|
|
LPVOID InputBuffer,
|
||
|
|
LPVOID OutputBuffer, unsigned int Size);
|
||
|
|
|
||
|
|
void LCMSEXPORT cmsGetAlarmCodes(int *r, int *g, int *b);
|
||
|
|
void LCMSEXPORT cmsSetAlarmCodes(int r, int g, int b);
|
||
|
|
LCMSBOOL LCMSEXPORT cmsIsIntentSupported(cmsHPROFILE hProfile,
|
||
|
|
int Intent, int UsedDirection);
|
||
|
|
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
// Alarm RGB codes
|
||
|
|
|
||
|
|
static WORD AlarmR = 0x8fff, AlarmG = 0x8fff, AlarmB = 0x8fff;
|
||
|
|
|
||
|
|
// Tag tables, soted by intents
|
||
|
|
|
||
|
|
static icTagSignature Device2PCS[] = {icSigAToB0Tag, // Perceptual
|
||
|
|
icSigAToB1Tag, // Relative colorimetric
|
||
|
|
icSigAToB2Tag, // Saturation
|
||
|
|
icSigAToB1Tag }; // Absolute colorimetric
|
||
|
|
// (Relative/WhitePoint)
|
||
|
|
|
||
|
|
static icTagSignature PCS2Device[] = {icSigBToA0Tag, // Perceptual
|
||
|
|
icSigBToA1Tag, // Relative colorimetric
|
||
|
|
icSigBToA2Tag, // Saturation
|
||
|
|
icSigBToA1Tag }; // Absolute colorimetric
|
||
|
|
// (Relative/WhitePoint)
|
||
|
|
|
||
|
|
|
||
|
|
static icTagSignature Preview[] = {icSigPreview0Tag,
|
||
|
|
icSigPreview1Tag,
|
||
|
|
icSigPreview2Tag,
|
||
|
|
icSigPreview1Tag };
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
static volatile double GlobalAdaptationState = 0;
|
||
|
|
|
||
|
|
// --------------------------------Stages--------------------------------------
|
||
|
|
|
||
|
|
// Following routines does implement several kind of steps inside
|
||
|
|
// transform. On building the transform, code chooses adequate.
|
||
|
|
|
||
|
|
|
||
|
|
// From Shaper-Matrix to PCS
|
||
|
|
|
||
|
|
static
|
||
|
|
void ShaperMatrixToPCS(struct _cmstransform_struct *p,
|
||
|
|
WORD In[3], WORD Out[3])
|
||
|
|
{
|
||
|
|
cmsEvalMatShaper(p -> InMatShaper, In, Out);
|
||
|
|
}
|
||
|
|
|
||
|
|
// From LUT to PCS
|
||
|
|
|
||
|
|
static
|
||
|
|
void LUTtoPCS(struct _cmstransform_struct *p,
|
||
|
|
WORD In[], WORD Out[3])
|
||
|
|
{
|
||
|
|
cmsEvalLUT(p -> Device2PCS, In, Out);
|
||
|
|
}
|
||
|
|
|
||
|
|
// From indexed named color to PCS
|
||
|
|
|
||
|
|
static
|
||
|
|
void NC2toPCS(struct _cmstransform_struct *p,
|
||
|
|
WORD In[], WORD Out[3])
|
||
|
|
{
|
||
|
|
int index = In[0];
|
||
|
|
|
||
|
|
if (index >= p ->NamedColorList-> nColors)
|
||
|
|
cmsSignalError(LCMS_ERRC_WARNING, "Color %d out of range", index);
|
||
|
|
else
|
||
|
|
CopyMemory(Out, p ->NamedColorList->List[index].PCS, 3 * sizeof(WORD));
|
||
|
|
}
|
||
|
|
|
||
|
|
// From PCS to Shaper-Matrix
|
||
|
|
|
||
|
|
static
|
||
|
|
void PCStoShaperMatrix(struct _cmstransform_struct *p,
|
||
|
|
WORD In[3], WORD Out[3])
|
||
|
|
{
|
||
|
|
cmsEvalMatShaper(p -> OutMatShaper, In, Out);
|
||
|
|
}
|
||
|
|
|
||
|
|
// From PCS to LUT
|
||
|
|
|
||
|
|
static
|
||
|
|
void PCStoLUT(struct _cmstransform_struct *p,
|
||
|
|
WORD In[3], WORD Out[])
|
||
|
|
{
|
||
|
|
cmsEvalLUT(p -> PCS2Device, In, Out);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// ----------------------- TRANSFORMATIONS --------------------------
|
||
|
|
|
||
|
|
|
||
|
|
// Inlining some assignations
|
||
|
|
|
||
|
|
#define COPY_3CHANS(to, from) { to[0]=from[0]; to[1]=from[1]; to[2]=from[2]; }
|
||
|
|
|
||
|
|
|
||
|
|
// Null transformation, only hold channels
|
||
|
|
|
||
|
|
static
|
||
|
|
void NullXFORM(_LPcmsTRANSFORM p,
|
||
|
|
LPVOID in,
|
||
|
|
LPVOID out, unsigned int Size)
|
||
|
|
{
|
||
|
|
register LPBYTE accum;
|
||
|
|
register LPBYTE output;
|
||
|
|
WORD wIn[MAXCHANNELS];
|
||
|
|
register unsigned int i, n;
|
||
|
|
|
||
|
|
|
||
|
|
accum = (LPBYTE) in;
|
||
|
|
output = (LPBYTE) out;
|
||
|
|
n = Size; // Buffer len
|
||
|
|
|
||
|
|
for (i=0; i < n; i++)
|
||
|
|
{
|
||
|
|
accum = p -> FromInput(p, wIn, accum);
|
||
|
|
output = p -> ToOutput(p, wIn, output);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// This is the "normal" proofing transform
|
||
|
|
|
||
|
|
static
|
||
|
|
void NormalXFORM(_LPcmsTRANSFORM p,
|
||
|
|
LPVOID in,
|
||
|
|
LPVOID out, unsigned int Size)
|
||
|
|
{
|
||
|
|
register LPBYTE accum;
|
||
|
|
register LPBYTE output;
|
||
|
|
WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
|
||
|
|
WORD wStageABC[3], wPCS[3], wStageLMN[MAXCHANNELS];
|
||
|
|
WORD wGamut[1];
|
||
|
|
register unsigned int i, n;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
accum = (LPBYTE) in;
|
||
|
|
output = (LPBYTE) out;
|
||
|
|
n = Size; // Buffer len
|
||
|
|
|
||
|
|
for (i=0; i < n; i++)
|
||
|
|
{
|
||
|
|
|
||
|
|
accum = p -> FromInput(p, wIn, accum);
|
||
|
|
|
||
|
|
p -> FromDevice(p, wIn, wStageABC);
|
||
|
|
|
||
|
|
if (p -> Stage1) {
|
||
|
|
|
||
|
|
p -> Stage1(wStageABC, wPCS, &p->m1, &p->of1);
|
||
|
|
|
||
|
|
if (wPCS[0] == 0xFFFF &&
|
||
|
|
wPCS[1] == 0xFFFF &&
|
||
|
|
wPCS[2] == 0xFFFF) {
|
||
|
|
|
||
|
|
// White cutoff
|
||
|
|
|
||
|
|
output = p -> ToOutput((_LPcmsTRANSFORM) p,
|
||
|
|
_cmsWhiteBySpace(cmsGetColorSpace(p -> OutputProfile)),
|
||
|
|
output);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
COPY_3CHANS(wPCS, wStageABC);
|
||
|
|
|
||
|
|
|
||
|
|
if (p->Gamut) {
|
||
|
|
|
||
|
|
// Gamut check, enabled across CLUT
|
||
|
|
|
||
|
|
cmsEvalLUT(p -> Gamut, wPCS, wGamut);
|
||
|
|
|
||
|
|
if (wGamut[0] >= 1) {
|
||
|
|
|
||
|
|
wOut[0] = AlarmR; // Gamut alarm
|
||
|
|
wOut[1] = AlarmG;
|
||
|
|
wOut[2] = AlarmB;
|
||
|
|
wOut[3] = 0;
|
||
|
|
|
||
|
|
output = p -> ToOutput((_LPcmsTRANSFORM)p, wOut, output);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (p -> Preview)
|
||
|
|
{
|
||
|
|
WORD wPreview[3]; // PCS
|
||
|
|
|
||
|
|
cmsEvalLUT(p -> Preview, wPCS, wPreview);
|
||
|
|
COPY_3CHANS(wPCS, wPreview);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (p -> Stage2) {
|
||
|
|
|
||
|
|
p -> Stage2(wPCS, wStageLMN, &p->m2, &p->of2);
|
||
|
|
|
||
|
|
if (wPCS[0] == 0xFFFF &&
|
||
|
|
wPCS[1] == 0xFFFF &&
|
||
|
|
wPCS[2] == 0xFFFF) {
|
||
|
|
|
||
|
|
// White cutoff
|
||
|
|
|
||
|
|
output = p -> ToOutput((_LPcmsTRANSFORM)p,
|
||
|
|
_cmsWhiteBySpace(cmsGetColorSpace(p -> OutputProfile)),
|
||
|
|
output);
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
COPY_3CHANS(wStageLMN, wPCS);
|
||
|
|
|
||
|
|
// Here wOut may come as MAXCHANNELS channels
|
||
|
|
|
||
|
|
p -> ToDevice(p, wStageLMN, wOut);
|
||
|
|
|
||
|
|
output = p -> ToOutput((_LPcmsTRANSFORM)p, wOut, output);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Using precalculated LUT
|
||
|
|
|
||
|
|
static
|
||
|
|
void PrecalculatedXFORM(_LPcmsTRANSFORM p,
|
||
|
|
LPVOID in,
|
||
|
|
LPVOID out, unsigned int Size)
|
||
|
|
{
|
||
|
|
register LPBYTE accum;
|
||
|
|
register LPBYTE output;
|
||
|
|
WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
|
||
|
|
unsigned int i, n;
|
||
|
|
|
||
|
|
|
||
|
|
accum = (LPBYTE) in;
|
||
|
|
output = (LPBYTE) out;
|
||
|
|
n = Size; // Buffer len
|
||
|
|
|
||
|
|
|
||
|
|
for (i=0; i < n; i++) {
|
||
|
|
|
||
|
|
accum = p -> FromInput(p, wIn, accum);
|
||
|
|
|
||
|
|
// Try to speedup things on plain devicelinks
|
||
|
|
|
||
|
|
if (p ->DeviceLink ->wFlags == LUT_HAS3DGRID) {
|
||
|
|
|
||
|
|
p ->DeviceLink ->CLut16params.Interp3D(wIn, wOut,
|
||
|
|
p ->DeviceLink -> T,
|
||
|
|
&p ->DeviceLink -> CLut16params);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
cmsEvalLUT(p -> DeviceLink, wIn, wOut);
|
||
|
|
|
||
|
|
|
||
|
|
output = p -> ToOutput(p, wOut, output);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Auxiliar: Handle precalculated gamut check
|
||
|
|
|
||
|
|
static
|
||
|
|
void TransformOnePixelWithGamutCheck(_LPcmsTRANSFORM p, WORD wIn[], WORD wOut[])
|
||
|
|
{
|
||
|
|
WORD wOutOfGamut;
|
||
|
|
|
||
|
|
cmsEvalLUT(p ->GamutCheck, wIn, &wOutOfGamut);
|
||
|
|
|
||
|
|
if (wOutOfGamut >= 1) {
|
||
|
|
|
||
|
|
ZeroMemory(wOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
|
||
|
|
wOut[0] = AlarmR;
|
||
|
|
wOut[1] = AlarmG;
|
||
|
|
wOut[2] = AlarmB;
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
cmsEvalLUT(p -> DeviceLink, wIn, wOut);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static
|
||
|
|
void PrecalculatedXFORMGamutCheck(_LPcmsTRANSFORM p,
|
||
|
|
LPVOID in,
|
||
|
|
LPVOID out, unsigned int Size)
|
||
|
|
{
|
||
|
|
register LPBYTE accum;
|
||
|
|
register LPBYTE output;
|
||
|
|
WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
|
||
|
|
register unsigned int i, n;
|
||
|
|
|
||
|
|
|
||
|
|
accum = (LPBYTE) in;
|
||
|
|
output = (LPBYTE) out;
|
||
|
|
n = Size; // Buffer len
|
||
|
|
|
||
|
|
for (i=0; i < n; i++) {
|
||
|
|
|
||
|
|
accum = p -> FromInput(p, wIn, accum);
|
||
|
|
|
||
|
|
TransformOnePixelWithGamutCheck(p, wIn, wOut);
|
||
|
|
|
||
|
|
output = p -> ToOutput(p, wOut, output);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// Using precalculated LUT + Cache
|
||
|
|
|
||
|
|
static
|
||
|
|
void CachedXFORM(_LPcmsTRANSFORM p,
|
||
|
|
LPVOID in,
|
||
|
|
LPVOID out, unsigned int Size)
|
||
|
|
{
|
||
|
|
register LPBYTE accum;
|
||
|
|
register LPBYTE output;
|
||
|
|
WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
|
||
|
|
register unsigned int i, n;
|
||
|
|
WORD CacheIn[MAXCHANNELS], CacheOut[MAXCHANNELS];
|
||
|
|
|
||
|
|
|
||
|
|
accum = (LPBYTE) in;
|
||
|
|
output = (LPBYTE) out;
|
||
|
|
n = Size; // Buffer len
|
||
|
|
|
||
|
|
// Empty buffers for quick memcmp
|
||
|
|
|
||
|
|
ZeroMemory(wIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
ZeroMemory(wOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
|
||
|
|
|
||
|
|
LCMS_READ_LOCK(&p ->rwlock);
|
||
|
|
CopyMemory(CacheIn, p ->CacheIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
CopyMemory(CacheOut, p ->CacheOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
LCMS_UNLOCK(&p ->rwlock);
|
||
|
|
|
||
|
|
for (i=0; i < n; i++) {
|
||
|
|
|
||
|
|
accum = p -> FromInput(p, wIn, accum);
|
||
|
|
|
||
|
|
|
||
|
|
if (memcmp(wIn, CacheIn, sizeof(WORD) * MAXCHANNELS) == 0) {
|
||
|
|
|
||
|
|
CopyMemory(wOut, CacheOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
// Try to speedup things on plain devicelinks
|
||
|
|
|
||
|
|
if (p ->DeviceLink ->wFlags == LUT_HAS3DGRID) {
|
||
|
|
|
||
|
|
p ->DeviceLink ->CLut16params.Interp3D(wIn, wOut,
|
||
|
|
p ->DeviceLink -> T,
|
||
|
|
&p ->DeviceLink -> CLut16params);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
cmsEvalLUT(p -> DeviceLink, wIn, wOut);
|
||
|
|
|
||
|
|
|
||
|
|
CopyMemory(CacheIn, wIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
CopyMemory(CacheOut, wOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
}
|
||
|
|
|
||
|
|
output = p -> ToOutput(p, wOut, output);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
LCMS_WRITE_LOCK(&p ->rwlock);
|
||
|
|
CopyMemory(p->CacheIn, CacheIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
CopyMemory(p->CacheOut, CacheOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
LCMS_UNLOCK(&p ->rwlock);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// Using precalculated LUT + Cache
|
||
|
|
|
||
|
|
static
|
||
|
|
void CachedXFORMGamutCheck(_LPcmsTRANSFORM p,
|
||
|
|
LPVOID in,
|
||
|
|
LPVOID out, unsigned int Size)
|
||
|
|
{
|
||
|
|
register LPBYTE accum;
|
||
|
|
register LPBYTE output;
|
||
|
|
WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
|
||
|
|
register unsigned int i, n;
|
||
|
|
WORD CacheIn[MAXCHANNELS], CacheOut[MAXCHANNELS];
|
||
|
|
|
||
|
|
|
||
|
|
accum = (LPBYTE) in;
|
||
|
|
output = (LPBYTE) out;
|
||
|
|
n = Size; // Buffer len
|
||
|
|
|
||
|
|
// Empty buffers for quick memcmp
|
||
|
|
|
||
|
|
ZeroMemory(wIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
ZeroMemory(wOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
|
||
|
|
LCMS_READ_LOCK(&p ->rwlock);
|
||
|
|
CopyMemory(CacheIn, p ->CacheIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
CopyMemory(CacheOut, p ->CacheOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
LCMS_UNLOCK(&p ->rwlock);
|
||
|
|
|
||
|
|
|
||
|
|
for (i=0; i < n; i++) {
|
||
|
|
|
||
|
|
accum = p -> FromInput(p, wIn, accum);
|
||
|
|
|
||
|
|
if (memcmp(wIn, CacheIn, sizeof(WORD) * MAXCHANNELS) == 0) {
|
||
|
|
|
||
|
|
CopyMemory(wOut, CacheOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
TransformOnePixelWithGamutCheck(p, wIn, wOut);
|
||
|
|
|
||
|
|
CopyMemory(CacheIn, wIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
CopyMemory(CacheOut, wOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
}
|
||
|
|
|
||
|
|
output = p -> ToOutput(p, wOut, output);
|
||
|
|
}
|
||
|
|
|
||
|
|
LCMS_WRITE_LOCK(&p ->rwlock);
|
||
|
|
CopyMemory(p->CacheIn, CacheIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
CopyMemory(p->CacheOut, CacheOut, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
LCMS_UNLOCK(&p ->rwlock);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Using smelted Matrix/Shaper
|
||
|
|
|
||
|
|
static
|
||
|
|
void MatrixShaperXFORM(_LPcmsTRANSFORM p,
|
||
|
|
LPVOID in,
|
||
|
|
LPVOID out, unsigned int Size)
|
||
|
|
{
|
||
|
|
register LPBYTE accum;
|
||
|
|
register LPBYTE output;
|
||
|
|
WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
|
||
|
|
register unsigned int i, n;
|
||
|
|
|
||
|
|
|
||
|
|
accum = (LPBYTE) in;
|
||
|
|
output = (LPBYTE) out;
|
||
|
|
n = Size; // Buffer len
|
||
|
|
|
||
|
|
for (i=0; i < n; i++)
|
||
|
|
{
|
||
|
|
accum = p -> FromInput(p, wIn, accum);
|
||
|
|
cmsEvalMatShaper(p -> SmeltMatShaper, wIn, wOut);
|
||
|
|
output = p -> ToOutput(p, wOut, output);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Using Named color input table
|
||
|
|
|
||
|
|
static
|
||
|
|
void NC2deviceXform(_LPcmsTRANSFORM p,
|
||
|
|
LPVOID in,
|
||
|
|
LPVOID out, unsigned int Size)
|
||
|
|
{
|
||
|
|
|
||
|
|
register LPBYTE accum;
|
||
|
|
register LPBYTE output;
|
||
|
|
WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
|
||
|
|
register unsigned int i;
|
||
|
|
|
||
|
|
|
||
|
|
accum = (LPBYTE) in;
|
||
|
|
output = (LPBYTE) out;
|
||
|
|
|
||
|
|
for (i=0; i < Size; i++) {
|
||
|
|
|
||
|
|
accum = p -> FromInput(p, wIn, accum);
|
||
|
|
CopyMemory(wOut, p ->NamedColorList->List[wIn[0]].DeviceColorant, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
output = p -> ToOutput(p, wOut, output);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// --------------------------------------------------------------------------
|
||
|
|
// Build a LUT based on shape-matrix method.
|
||
|
|
|
||
|
|
|
||
|
|
// Some non-conformant gray profiles are using kTCR as L*,
|
||
|
|
// this function converts the curve to XYZ PCS.
|
||
|
|
|
||
|
|
static
|
||
|
|
void FromLstarToXYZ(LPGAMMATABLE g, LPGAMMATABLE gxyz[3])
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
int nPoints = 4096;
|
||
|
|
cmsCIELab Lab;
|
||
|
|
cmsCIEXYZ XYZ;
|
||
|
|
L16PARAMS L16;
|
||
|
|
|
||
|
|
// Setup interpolation across origin
|
||
|
|
cmsCalcL16Params(g ->nEntries, &L16);
|
||
|
|
|
||
|
|
// Allocate curves
|
||
|
|
gxyz[0] = cmsAllocGamma(nPoints);
|
||
|
|
gxyz[1] = cmsAllocGamma(nPoints);
|
||
|
|
gxyz[2] = cmsAllocGamma(nPoints);
|
||
|
|
|
||
|
|
// Transport from Lab to XYZ
|
||
|
|
|
||
|
|
for (i=0; i < nPoints; i++) {
|
||
|
|
|
||
|
|
WORD val = _cmsQuantizeVal(i, nPoints);
|
||
|
|
WORD w = cmsLinearInterpLUT16(val, g->GammaTable, &L16);
|
||
|
|
|
||
|
|
Lab.L = ((double) 100.0 * w ) / 65535.0;
|
||
|
|
Lab.a = Lab.b = 0;
|
||
|
|
|
||
|
|
cmsLab2XYZ(NULL, &XYZ, &Lab);
|
||
|
|
|
||
|
|
// Should be same curve
|
||
|
|
gxyz[0] ->GammaTable[i] = (WORD) floor((65535.0 * XYZ.X) / D50X + 0.5);
|
||
|
|
gxyz[1] ->GammaTable[i] = (WORD) floor((65535.0 * XYZ.Y) / D50Y + 0.5);
|
||
|
|
gxyz[2] ->GammaTable[i] = (WORD) floor((65535.0 * XYZ.Z) / D50Z + 0.5);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Monochrome version
|
||
|
|
|
||
|
|
static
|
||
|
|
LPMATSHAPER cmsBuildGrayInputMatrixShaper(cmsHPROFILE hProfile)
|
||
|
|
{
|
||
|
|
cmsCIEXYZ Illuminant;
|
||
|
|
LPGAMMATABLE GrayTRC, Shapes[3];
|
||
|
|
LPMATSHAPER MatShaper;
|
||
|
|
MAT3 Scale;
|
||
|
|
|
||
|
|
GrayTRC = cmsReadICCGamma(hProfile, icSigGrayTRCTag); // Y
|
||
|
|
if (GrayTRC == NULL) return NULL;
|
||
|
|
|
||
|
|
cmsTakeIluminant(&Illuminant, hProfile);
|
||
|
|
|
||
|
|
if (cmsGetPCS(hProfile) == icSigLabData) {
|
||
|
|
|
||
|
|
// Fixup for Lab monochrome
|
||
|
|
FromLstarToXYZ(GrayTRC, Shapes);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
Shapes[0] = cmsDupGamma(GrayTRC);
|
||
|
|
Shapes[1] = cmsDupGamma(GrayTRC);
|
||
|
|
Shapes[2] = cmsDupGamma(GrayTRC);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!Shapes[0] || !Shapes[1] || !Shapes[2])
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
cmsFreeGamma(GrayTRC);
|
||
|
|
|
||
|
|
// R=G=B as precondition
|
||
|
|
|
||
|
|
VEC3init(&Scale.v[0], Illuminant.X/3, Illuminant.X/3, Illuminant.X/3);
|
||
|
|
VEC3init(&Scale.v[1], Illuminant.Y/3, Illuminant.Y/3, Illuminant.Y/3);
|
||
|
|
VEC3init(&Scale.v[2], Illuminant.Z/3, Illuminant.Z/3, Illuminant.Z/3);
|
||
|
|
|
||
|
|
|
||
|
|
MatShaper = cmsAllocMatShaper(&Scale, Shapes, MATSHAPER_INPUT);
|
||
|
|
cmsFreeGammaTriple(Shapes);
|
||
|
|
return MatShaper;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Monochrome as output
|
||
|
|
|
||
|
|
static
|
||
|
|
LPMATSHAPER cmsBuildGrayOutputMatrixShaper(cmsHPROFILE hProfile)
|
||
|
|
{
|
||
|
|
cmsCIEXYZ Illuminant;
|
||
|
|
LPGAMMATABLE GrayTRC, Shapes[3];
|
||
|
|
LPMATSHAPER MatShaper;
|
||
|
|
MAT3 Scale;
|
||
|
|
|
||
|
|
cmsTakeIluminant(&Illuminant, hProfile);
|
||
|
|
|
||
|
|
// That is a special case for non-compliant profiles.
|
||
|
|
|
||
|
|
if (cmsGetPCS(hProfile) == icSigLabData) {
|
||
|
|
|
||
|
|
LPGAMMATABLE Shapes1[3];
|
||
|
|
|
||
|
|
GrayTRC = cmsReadICCGamma(hProfile, icSigGrayTRCTag);
|
||
|
|
FromLstarToXYZ(GrayTRC, Shapes1);
|
||
|
|
|
||
|
|
// Reversing must be done after curve translation
|
||
|
|
|
||
|
|
Shapes[0] = cmsReverseGamma(Shapes1[0]->nEntries, Shapes1[0]);
|
||
|
|
Shapes[1] = cmsReverseGamma(Shapes1[1]->nEntries, Shapes1[1]);
|
||
|
|
Shapes[2] = cmsReverseGamma(Shapes1[2]->nEntries, Shapes1[2]);
|
||
|
|
|
||
|
|
cmsFreeGammaTriple(Shapes1);
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
// Normal case
|
||
|
|
|
||
|
|
GrayTRC = cmsReadICCGammaReversed(hProfile, icSigGrayTRCTag); // Y
|
||
|
|
|
||
|
|
Shapes[0] = cmsDupGamma(GrayTRC);
|
||
|
|
Shapes[1] = cmsDupGamma(GrayTRC);
|
||
|
|
Shapes[2] = cmsDupGamma(GrayTRC);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!Shapes[0] || !Shapes[1] || !Shapes[2])
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
cmsFreeGamma(GrayTRC);
|
||
|
|
|
||
|
|
VEC3init(&Scale.v[0], 0, 1.0/Illuminant.Y, 0);
|
||
|
|
VEC3init(&Scale.v[1], 0, 1.0/Illuminant.Y, 0);
|
||
|
|
VEC3init(&Scale.v[2], 0, 1.0/Illuminant.Y, 0);
|
||
|
|
|
||
|
|
|
||
|
|
MatShaper = cmsAllocMatShaper(&Scale, Shapes, MATSHAPER_OUTPUT);
|
||
|
|
cmsFreeGammaTriple(Shapes);
|
||
|
|
return MatShaper;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// Input matrix, only in XYZ
|
||
|
|
|
||
|
|
LPMATSHAPER cmsBuildInputMatrixShaper(cmsHPROFILE InputProfile)
|
||
|
|
{
|
||
|
|
MAT3 DoubleMat;
|
||
|
|
LPGAMMATABLE Shapes[3];
|
||
|
|
LPMATSHAPER InMatSh;
|
||
|
|
|
||
|
|
// Check if this is a grayscale profile. If so, build
|
||
|
|
// appropiate conversion tables. The tables are the PCS
|
||
|
|
// iluminant, scaled across GrayTRC
|
||
|
|
|
||
|
|
if (cmsGetColorSpace(InputProfile) == icSigGrayData)
|
||
|
|
{
|
||
|
|
return cmsBuildGrayInputMatrixShaper(InputProfile);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!cmsReadICCMatrixRGB2XYZ(&DoubleMat, InputProfile))
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
Shapes[0] = cmsReadICCGamma(InputProfile, icSigRedTRCTag);
|
||
|
|
Shapes[1] = cmsReadICCGamma(InputProfile, icSigGreenTRCTag);
|
||
|
|
Shapes[2] = cmsReadICCGamma(InputProfile, icSigBlueTRCTag);
|
||
|
|
|
||
|
|
if (!Shapes[0] || !Shapes[1] || !Shapes[2])
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
InMatSh = cmsAllocMatShaper(&DoubleMat, Shapes, MATSHAPER_INPUT);
|
||
|
|
|
||
|
|
cmsFreeGammaTriple(Shapes);
|
||
|
|
|
||
|
|
return InMatSh;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Output style matrix-shaper
|
||
|
|
|
||
|
|
|
||
|
|
LPMATSHAPER cmsBuildOutputMatrixShaper(cmsHPROFILE OutputProfile)
|
||
|
|
{
|
||
|
|
MAT3 DoubleMat, DoubleInv;
|
||
|
|
LPGAMMATABLE InverseShapes[3];
|
||
|
|
LPMATSHAPER OutMatSh;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
if (cmsGetColorSpace(OutputProfile) == icSigGrayData)
|
||
|
|
{
|
||
|
|
return cmsBuildGrayOutputMatrixShaper(OutputProfile);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if (!cmsReadICCMatrixRGB2XYZ(&DoubleMat, OutputProfile))
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
if (MAT3inverse(&DoubleMat, &DoubleInv) < 0)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
|
||
|
|
InverseShapes[0] = cmsReadICCGammaReversed(OutputProfile, icSigRedTRCTag);
|
||
|
|
InverseShapes[1] = cmsReadICCGammaReversed(OutputProfile, icSigGreenTRCTag);
|
||
|
|
InverseShapes[2] = cmsReadICCGammaReversed(OutputProfile, icSigBlueTRCTag);
|
||
|
|
|
||
|
|
if (InverseShapes[0] == NULL ||
|
||
|
|
InverseShapes[1] == NULL ||
|
||
|
|
InverseShapes[2] == NULL) return NULL;
|
||
|
|
|
||
|
|
OutMatSh = cmsAllocMatShaper(&DoubleInv, InverseShapes, MATSHAPER_OUTPUT);
|
||
|
|
|
||
|
|
cmsFreeGammaTriple(InverseShapes);
|
||
|
|
|
||
|
|
return OutMatSh;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// This function builds a transform matrix chaining parameters
|
||
|
|
|
||
|
|
static
|
||
|
|
LCMSBOOL cmsBuildSmeltMatShaper(_LPcmsTRANSFORM p)
|
||
|
|
{
|
||
|
|
MAT3 From, To, ToInv, Transfer;
|
||
|
|
LPGAMMATABLE In[3], InverseOut[3];
|
||
|
|
|
||
|
|
|
||
|
|
if (!cmsReadICCMatrixRGB2XYZ(&From, p -> InputProfile))
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
|
||
|
|
if (!cmsReadICCMatrixRGB2XYZ(&To, p -> OutputProfile))
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
// invert dest
|
||
|
|
|
||
|
|
if (MAT3inverse(&To, &ToInv) < 0)
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
// Multiply
|
||
|
|
MAT3per(&Transfer, &ToInv, &From);
|
||
|
|
|
||
|
|
|
||
|
|
// Read gamma curves
|
||
|
|
|
||
|
|
In[0] = cmsReadICCGamma(p -> InputProfile, icSigRedTRCTag);
|
||
|
|
In[1] = cmsReadICCGamma(p -> InputProfile, icSigGreenTRCTag);
|
||
|
|
In[2] = cmsReadICCGamma(p -> InputProfile, icSigBlueTRCTag);
|
||
|
|
|
||
|
|
if (!In[0] || !In[1] || !In[2])
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
|
||
|
|
InverseOut[0] = cmsReadICCGammaReversed(p -> OutputProfile, icSigRedTRCTag);
|
||
|
|
InverseOut[1] = cmsReadICCGammaReversed(p -> OutputProfile, icSigGreenTRCTag);
|
||
|
|
InverseOut[2] = cmsReadICCGammaReversed(p -> OutputProfile, icSigBlueTRCTag);
|
||
|
|
|
||
|
|
if (!InverseOut[0] || !InverseOut[1] || !InverseOut[2]) {
|
||
|
|
cmsFreeGammaTriple(In);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
p -> SmeltMatShaper = cmsAllocMatShaper2(&Transfer, In, InverseOut, MATSHAPER_ALLSMELTED);
|
||
|
|
|
||
|
|
cmsFreeGammaTriple(In);
|
||
|
|
cmsFreeGammaTriple(InverseOut);
|
||
|
|
|
||
|
|
return (p -> SmeltMatShaper != NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// Conversion between PCS ------------------------------------------
|
||
|
|
|
||
|
|
// Identifies intent archieved by LUT
|
||
|
|
|
||
|
|
static
|
||
|
|
int GetPhase(cmsHPROFILE hProfile)
|
||
|
|
{
|
||
|
|
switch (cmsGetPCS(hProfile)) {
|
||
|
|
|
||
|
|
case icSigXYZData: return XYZRel;
|
||
|
|
|
||
|
|
case icSigLabData: return LabRel;
|
||
|
|
|
||
|
|
default: cmsSignalError(LCMS_ERRC_ABORTED, "Invalid PCS");
|
||
|
|
}
|
||
|
|
|
||
|
|
return XYZRel;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
static
|
||
|
|
void TakeConversionRoutines(_LPcmsTRANSFORM p, int DoBPC)
|
||
|
|
{
|
||
|
|
cmsCIEXYZ BlackPointIn, WhitePointIn, IlluminantIn;
|
||
|
|
cmsCIEXYZ BlackPointOut, WhitePointOut, IlluminantOut;
|
||
|
|
cmsCIEXYZ BlackPointProof, WhitePointProof, IlluminantProof;
|
||
|
|
MAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
|
||
|
|
MAT3 ChromaticAdaptationMatrixProof;
|
||
|
|
|
||
|
|
|
||
|
|
cmsTakeIluminant(&IlluminantIn, p -> InputProfile);
|
||
|
|
cmsTakeMediaWhitePoint(&WhitePointIn, p -> InputProfile);
|
||
|
|
cmsTakeMediaBlackPoint(&BlackPointIn, p -> InputProfile);
|
||
|
|
cmsReadChromaticAdaptationMatrix(&ChromaticAdaptationMatrixIn, p -> InputProfile);
|
||
|
|
|
||
|
|
cmsTakeIluminant(&IlluminantOut, p -> OutputProfile);
|
||
|
|
cmsTakeMediaWhitePoint(&WhitePointOut, p -> OutputProfile);
|
||
|
|
cmsTakeMediaBlackPoint(&BlackPointOut, p -> OutputProfile);
|
||
|
|
cmsReadChromaticAdaptationMatrix(&ChromaticAdaptationMatrixOut, p -> OutputProfile);
|
||
|
|
|
||
|
|
|
||
|
|
if (p -> Preview == NULL && p ->Gamut == NULL) // Non-proofing
|
||
|
|
{
|
||
|
|
if (p ->Intent == INTENT_PERCEPTUAL ||
|
||
|
|
p ->Intent == INTENT_SATURATION) {
|
||
|
|
|
||
|
|
|
||
|
|
// For v4 profiles, Perceptual PCS has a reference black point
|
||
|
|
// which v2 profiles should scale to.
|
||
|
|
|
||
|
|
if ((cmsGetProfileICCversion(p ->InputProfile) >= 0x4000000) ||
|
||
|
|
(cmsGetProfileICCversion(p ->OutputProfile) >= 0x4000000)) {
|
||
|
|
|
||
|
|
DoBPC = TRUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Black point compensation does not apply to absolute intent
|
||
|
|
|
||
|
|
if (p ->Intent == INTENT_ABSOLUTE_COLORIMETRIC)
|
||
|
|
DoBPC = FALSE;
|
||
|
|
|
||
|
|
// Black point compensation does not apply to devicelink profiles
|
||
|
|
|
||
|
|
if (cmsGetDeviceClass(p ->InputProfile) == icSigLinkClass)
|
||
|
|
DoBPC = FALSE;
|
||
|
|
|
||
|
|
if (cmsGetDeviceClass(p ->OutputProfile) == icSigLinkClass)
|
||
|
|
DoBPC = FALSE;
|
||
|
|
|
||
|
|
if (DoBPC) {
|
||
|
|
|
||
|
|
// Detect Black points
|
||
|
|
|
||
|
|
cmsDetectBlackPoint(&BlackPointIn, p->InputProfile, p->Intent, 0);
|
||
|
|
cmsDetectBlackPoint(&BlackPointOut, p->OutputProfile, p->Intent, 0);
|
||
|
|
|
||
|
|
// If equal black points, then do nothing. This often applies to BP=0
|
||
|
|
|
||
|
|
if (BlackPointIn.X == BlackPointOut.X &&
|
||
|
|
BlackPointIn.Y == BlackPointOut.Y &&
|
||
|
|
BlackPointIn.Z == BlackPointOut.Z)
|
||
|
|
DoBPC = FALSE;
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
cmsChooseCnvrt(p -> Intent == INTENT_ABSOLUTE_COLORIMETRIC,
|
||
|
|
|
||
|
|
p -> Phase1,
|
||
|
|
&BlackPointIn,
|
||
|
|
&WhitePointIn,
|
||
|
|
&IlluminantIn,
|
||
|
|
&ChromaticAdaptationMatrixIn,
|
||
|
|
|
||
|
|
p -> Phase3,
|
||
|
|
&BlackPointOut,
|
||
|
|
&WhitePointOut,
|
||
|
|
&IlluminantOut,
|
||
|
|
&ChromaticAdaptationMatrixOut,
|
||
|
|
|
||
|
|
DoBPC,
|
||
|
|
p ->AdaptationState,
|
||
|
|
&p->Stage1,
|
||
|
|
&p->m1, &p->of1);
|
||
|
|
|
||
|
|
}
|
||
|
|
else // Proofing
|
||
|
|
{
|
||
|
|
|
||
|
|
|
||
|
|
cmsTakeIluminant(&IlluminantProof, p -> PreviewProfile);
|
||
|
|
cmsTakeMediaWhitePoint(&WhitePointProof, p -> PreviewProfile);
|
||
|
|
cmsTakeMediaBlackPoint(&BlackPointProof, p -> PreviewProfile);
|
||
|
|
cmsReadChromaticAdaptationMatrix(&ChromaticAdaptationMatrixProof, p -> PreviewProfile);
|
||
|
|
|
||
|
|
if (DoBPC) {
|
||
|
|
|
||
|
|
cmsDetectBlackPoint(&BlackPointProof, p->PreviewProfile, p->Intent, 0);
|
||
|
|
cmsDetectBlackPoint(&BlackPointIn, p->InputProfile, p->Intent, 0);
|
||
|
|
cmsDetectBlackPoint(&BlackPointOut, p->OutputProfile, p->Intent, 0);
|
||
|
|
|
||
|
|
// Reality check
|
||
|
|
|
||
|
|
if (BlackPointIn.X == BlackPointProof.X &&
|
||
|
|
BlackPointIn.Y == BlackPointProof.Y &&
|
||
|
|
BlackPointIn.Z == BlackPointProof.Z)
|
||
|
|
DoBPC = FALSE;
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
cmsChooseCnvrt(p -> Intent == INTENT_ABSOLUTE_COLORIMETRIC,
|
||
|
|
|
||
|
|
p -> Phase1,
|
||
|
|
&BlackPointIn,
|
||
|
|
&WhitePointIn,
|
||
|
|
&IlluminantIn,
|
||
|
|
&ChromaticAdaptationMatrixIn,
|
||
|
|
|
||
|
|
p -> Phase2,
|
||
|
|
&BlackPointProof,
|
||
|
|
&WhitePointProof,
|
||
|
|
&IlluminantProof,
|
||
|
|
&ChromaticAdaptationMatrixProof,
|
||
|
|
DoBPC,
|
||
|
|
p ->AdaptationState,
|
||
|
|
&p->Stage1,
|
||
|
|
&p->m1, &p->of1);
|
||
|
|
|
||
|
|
cmsChooseCnvrt(p -> ProofIntent == INTENT_ABSOLUTE_COLORIMETRIC,
|
||
|
|
|
||
|
|
p -> Phase2,
|
||
|
|
&BlackPointProof,
|
||
|
|
&WhitePointProof,
|
||
|
|
&IlluminantProof,
|
||
|
|
&ChromaticAdaptationMatrixProof,
|
||
|
|
|
||
|
|
p -> Phase3,
|
||
|
|
&BlackPointOut,
|
||
|
|
&WhitePointOut,
|
||
|
|
&IlluminantOut,
|
||
|
|
&ChromaticAdaptationMatrixOut,
|
||
|
|
0,
|
||
|
|
0.0,
|
||
|
|
&p->Stage2,
|
||
|
|
&p->m2, &p->of2);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Check colorspace
|
||
|
|
|
||
|
|
static
|
||
|
|
LCMSBOOL IsProperColorSpace(cmsHPROFILE hProfile, DWORD dwFormat, LCMSBOOL lUsePCS)
|
||
|
|
{
|
||
|
|
int Space = T_COLORSPACE(dwFormat);
|
||
|
|
|
||
|
|
if (Space == PT_ANY) return TRUE;
|
||
|
|
|
||
|
|
if (lUsePCS)
|
||
|
|
return (Space == _cmsLCMScolorSpace(cmsGetPCS(hProfile)));
|
||
|
|
else
|
||
|
|
return (Space == _cmsLCMScolorSpace(cmsGetColorSpace(hProfile)));
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Auxiliary: allocate transform struct and set to defaults
|
||
|
|
|
||
|
|
static
|
||
|
|
_LPcmsTRANSFORM AllocEmptyTransform(void)
|
||
|
|
{
|
||
|
|
// Allocate needed memory
|
||
|
|
|
||
|
|
_LPcmsTRANSFORM p = (_LPcmsTRANSFORM) _cmsMalloc(sizeof(_cmsTRANSFORM));
|
||
|
|
if (!p) {
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "cmsCreateTransform: _cmsMalloc() failed");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
ZeroMemory(p, sizeof(_cmsTRANSFORM));
|
||
|
|
|
||
|
|
// Initialize default methods
|
||
|
|
|
||
|
|
p -> xform = NULL;
|
||
|
|
p -> Intent = INTENT_PERCEPTUAL;
|
||
|
|
p -> ProofIntent = INTENT_ABSOLUTE_COLORIMETRIC;
|
||
|
|
p -> DoGamutCheck = FALSE;
|
||
|
|
p -> InputProfile = NULL;
|
||
|
|
p -> OutputProfile = NULL;
|
||
|
|
p -> PreviewProfile = NULL;
|
||
|
|
p -> Preview = NULL;
|
||
|
|
p -> Gamut = NULL;
|
||
|
|
p -> DeviceLink = NULL;
|
||
|
|
p -> InMatShaper = NULL;
|
||
|
|
p -> OutMatShaper = NULL;
|
||
|
|
p -> SmeltMatShaper = NULL;
|
||
|
|
p -> NamedColorList = NULL;
|
||
|
|
p -> EntryColorSpace = (icColorSpaceSignature) 0;
|
||
|
|
p -> ExitColorSpace = (icColorSpaceSignature) 0;
|
||
|
|
p -> AdaptationState = GlobalAdaptationState;
|
||
|
|
|
||
|
|
LCMS_CREATE_LOCK(&p->rwlock);
|
||
|
|
|
||
|
|
return p;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Identify whatever a transform is to be cached
|
||
|
|
|
||
|
|
static
|
||
|
|
void SetPrecalculatedTransform(_LPcmsTRANSFORM p)
|
||
|
|
{
|
||
|
|
if ((p->dwOriginalFlags & cmsFLAGS_GAMUTCHECK) && p ->GamutCheck != NULL) {
|
||
|
|
|
||
|
|
p -> xform = PrecalculatedXFORMGamutCheck;
|
||
|
|
|
||
|
|
if (!(p->dwOriginalFlags & cmsFLAGS_NOTCACHE)) {
|
||
|
|
|
||
|
|
ZeroMemory(p ->CacheIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
TransformOnePixelWithGamutCheck(p, p->CacheIn, p ->CacheOut);
|
||
|
|
p ->xform = CachedXFORMGamutCheck;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
p -> xform = PrecalculatedXFORM;
|
||
|
|
|
||
|
|
if (!(p->dwOriginalFlags & cmsFLAGS_NOTCACHE)) {
|
||
|
|
|
||
|
|
ZeroMemory(p ->CacheIn, sizeof(WORD) * MAXCHANNELS);
|
||
|
|
cmsEvalLUT(p ->DeviceLink, p->CacheIn, p ->CacheOut);
|
||
|
|
p ->xform = CachedXFORM;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Transform is identified as device-link
|
||
|
|
static
|
||
|
|
cmsHPROFILE CreateDeviceLinkTransform(_LPcmsTRANSFORM p)
|
||
|
|
{
|
||
|
|
|
||
|
|
if (!IsProperColorSpace(p->InputProfile, p->InputFormat, FALSE)) {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Device link is operating on wrong colorspace on input");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!IsProperColorSpace(p->InputProfile, p->OutputFormat, TRUE)) {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Device link is operating on wrong colorspace on output");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Device link does only have AToB0Tag (ICC-Spec 1998/09)
|
||
|
|
|
||
|
|
p->DeviceLink = cmsReadICCLut(p->InputProfile, icSigAToB0Tag);
|
||
|
|
|
||
|
|
if (!p->DeviceLink) {
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Noncompliant device-link profile");
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (p ->PreviewProfile != NULL) {
|
||
|
|
cmsSignalError(LCMS_ERRC_WARNING, "Proofing not supported on device link transforms");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (p ->OutputProfile != NULL) {
|
||
|
|
cmsSignalError(LCMS_ERRC_WARNING, "Output profile should be NULL, since this is a device-link transform");
|
||
|
|
}
|
||
|
|
|
||
|
|
p -> Phase1 = -1;
|
||
|
|
p -> Phase2 = -1;
|
||
|
|
p -> Phase3 = -1;
|
||
|
|
|
||
|
|
SetPrecalculatedTransform(p);
|
||
|
|
|
||
|
|
p -> EntryColorSpace = cmsGetColorSpace(p -> InputProfile);
|
||
|
|
p -> ExitColorSpace = cmsGetPCS(p -> InputProfile);
|
||
|
|
|
||
|
|
if (p ->EntryColorSpace == icSigRgbData ||
|
||
|
|
p ->EntryColorSpace == icSigCmyData) {
|
||
|
|
|
||
|
|
p->DeviceLink -> CLut16params.Interp3D = cmsTetrahedralInterp16;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Precalculated device-link profile is ready
|
||
|
|
return (cmsHTRANSFORM) p;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Transform that includes proofing
|
||
|
|
static
|
||
|
|
void CreateProof(_LPcmsTRANSFORM p, icTagSignature *ToTagPtr)
|
||
|
|
|
||
|
|
{
|
||
|
|
icTagSignature ProofTag;
|
||
|
|
|
||
|
|
if (p -> dwOriginalFlags & cmsFLAGS_SOFTPROOFING) {
|
||
|
|
|
||
|
|
// Apr-15, 2002 - Too much profiles does have bogus content
|
||
|
|
// on preview tag, so I do compute it by my own.
|
||
|
|
|
||
|
|
p -> Preview = _cmsComputeSoftProofLUT(p ->PreviewProfile, p ->Intent);
|
||
|
|
p -> Phase2 = LabRel;
|
||
|
|
|
||
|
|
// That's a proofing transfor, so use second intent for output.
|
||
|
|
|
||
|
|
*ToTagPtr = PCS2Device[p->ProofIntent];
|
||
|
|
|
||
|
|
if (p -> Preview == NULL) {
|
||
|
|
|
||
|
|
ProofTag = Preview[p -> Intent];
|
||
|
|
|
||
|
|
if (!cmsIsTag(p ->PreviewProfile, ProofTag)) {
|
||
|
|
|
||
|
|
ProofTag = Preview[0];
|
||
|
|
if (!cmsIsTag(p ->PreviewProfile, ProofTag))
|
||
|
|
ProofTag = (icTagSignature)0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ProofTag) {
|
||
|
|
|
||
|
|
p -> Preview = cmsReadICCLut(p ->PreviewProfile, ProofTag);
|
||
|
|
p -> Phase2 = GetPhase(p ->PreviewProfile);
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
p -> Preview = NULL;
|
||
|
|
p ->PreviewProfile = NULL;
|
||
|
|
cmsSignalError(LCMS_ERRC_WARNING, "Sorry, the proof profile has not previewing capabilities");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Aug-31, 2001 - Too much profiles does have bogus content
|
||
|
|
// on gamut tag, so I do compute it by my own.
|
||
|
|
|
||
|
|
if ((p -> dwOriginalFlags & cmsFLAGS_GAMUTCHECK) && (p -> dwOriginalFlags & cmsFLAGS_NOTPRECALC)) {
|
||
|
|
|
||
|
|
|
||
|
|
p -> Gamut = _cmsComputeGamutLUT(p->PreviewProfile, p ->Intent);
|
||
|
|
p -> Phase2 = LabRel;
|
||
|
|
|
||
|
|
if (p -> Gamut == NULL) {
|
||
|
|
|
||
|
|
// Profile goes only in one direction... try to see
|
||
|
|
// if profile has the tag, and use it, no matter it
|
||
|
|
// could be bogus. This is the last chance!
|
||
|
|
|
||
|
|
if (cmsIsTag(p ->PreviewProfile, icSigGamutTag)) {
|
||
|
|
|
||
|
|
p -> Gamut = cmsReadICCLut(p ->PreviewProfile, icSigGamutTag);
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
// Nope, cannot be done.
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_WARNING, "Sorry, the proof profile has not gamut checking capabilities");
|
||
|
|
p -> Gamut = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
// Choose the adequate transform routine
|
||
|
|
|
||
|
|
static
|
||
|
|
_LPcmsTRANSFORM PickTransformRoutine(_LPcmsTRANSFORM p,
|
||
|
|
icTagSignature *FromTagPtr,
|
||
|
|
icTagSignature *ToTagPtr)
|
||
|
|
{
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// Is a named color profile?
|
||
|
|
if (cmsGetDeviceClass(p->InputProfile) == icSigNamedColorClass) {
|
||
|
|
|
||
|
|
// Yes, and used as input
|
||
|
|
p ->FromDevice = NC2toPCS;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
// Can we optimize matrix-shaper only transform?
|
||
|
|
|
||
|
|
if ((*FromTagPtr == 0) &&
|
||
|
|
(*ToTagPtr == 0) &&
|
||
|
|
(!p->PreviewProfile) &&
|
||
|
|
(p -> Intent != INTENT_ABSOLUTE_COLORIMETRIC) &&
|
||
|
|
(p -> EntryColorSpace == icSigRgbData) &&
|
||
|
|
(p -> ExitColorSpace == icSigRgbData) &&
|
||
|
|
!(p -> dwOriginalFlags & cmsFLAGS_BLACKPOINTCOMPENSATION)) {
|
||
|
|
|
||
|
|
// Yes... try to smelt matrix-shapers
|
||
|
|
p -> xform = MatrixShaperXFORM;
|
||
|
|
p -> dwOriginalFlags |= cmsFLAGS_NOTPRECALC;
|
||
|
|
|
||
|
|
if (!cmsBuildSmeltMatShaper(p))
|
||
|
|
{
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "unable to smelt shaper-matrix, required tags missing");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
p -> Phase1 = p -> Phase3 = XYZRel;
|
||
|
|
return p;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
// No, is a transform involving LUT
|
||
|
|
|
||
|
|
if (*FromTagPtr != 0) {
|
||
|
|
|
||
|
|
p -> FromDevice = LUTtoPCS;
|
||
|
|
p -> Device2PCS = cmsReadICCLut(p -> InputProfile, *FromTagPtr);
|
||
|
|
if (!p -> Device2PCS) {
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "profile is unsuitable for input");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
p -> FromDevice = ShaperMatrixToPCS;
|
||
|
|
p -> InMatShaper = cmsBuildInputMatrixShaper(p -> InputProfile);
|
||
|
|
|
||
|
|
if (!p ->InMatShaper) {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "profile is unsuitable for input");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
p -> Phase1 = XYZRel;
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (*ToTagPtr != 0) {
|
||
|
|
|
||
|
|
p -> ToDevice = PCStoLUT;
|
||
|
|
p -> PCS2Device = cmsReadICCLut(p -> OutputProfile, *ToTagPtr);
|
||
|
|
if (!p -> PCS2Device) {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "profile is unsuitable for output");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
p -> ToDevice = PCStoShaperMatrix;
|
||
|
|
p -> OutMatShaper = cmsBuildOutputMatrixShaper(p->OutputProfile);
|
||
|
|
|
||
|
|
if (!p -> OutMatShaper) {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "profile is unsuitable for output");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
p -> Phase3 = XYZRel;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
return p;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// Create a transform.
|
||
|
|
|
||
|
|
cmsHTRANSFORM LCMSEXPORT cmsCreateProofingTransform(cmsHPROFILE InputProfile,
|
||
|
|
DWORD InputFormat,
|
||
|
|
cmsHPROFILE OutputProfile,
|
||
|
|
DWORD OutputFormat,
|
||
|
|
cmsHPROFILE ProofingProfile,
|
||
|
|
int nIntent,
|
||
|
|
int ProofingIntent,
|
||
|
|
DWORD dwFlags)
|
||
|
|
|
||
|
|
{
|
||
|
|
_LPcmsTRANSFORM p;
|
||
|
|
icTagSignature FromTag;
|
||
|
|
icTagSignature ToTag;
|
||
|
|
|
||
|
|
if (nIntent < 0 || nIntent > 3 ||
|
||
|
|
ProofingIntent < 0 || ProofingIntent > 3) {
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "cmsCreateTransform: intent mismatch");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
p = AllocEmptyTransform();
|
||
|
|
if (p == NULL) return NULL;
|
||
|
|
|
||
|
|
p -> xform = NormalXFORM;
|
||
|
|
p -> Intent = nIntent;
|
||
|
|
p -> ProofIntent = ProofingIntent;
|
||
|
|
p -> DoGamutCheck = FALSE;
|
||
|
|
p -> InputProfile = InputProfile;
|
||
|
|
p -> OutputProfile = OutputProfile;
|
||
|
|
p -> PreviewProfile = ProofingProfile;
|
||
|
|
p -> InputFormat = InputFormat;
|
||
|
|
p -> OutputFormat = OutputFormat;
|
||
|
|
p -> dwOriginalFlags = dwFlags;
|
||
|
|
|
||
|
|
p -> lInputV4Lab = p ->lOutputV4Lab = FALSE;
|
||
|
|
|
||
|
|
|
||
|
|
p -> FromInput = _cmsIdentifyInputFormat(p, InputFormat);
|
||
|
|
p -> ToOutput = _cmsIdentifyOutputFormat(p, OutputFormat);
|
||
|
|
|
||
|
|
// Null transform can be done without profiles
|
||
|
|
if ((p->dwOriginalFlags & cmsFLAGS_NULLTRANSFORM) ||
|
||
|
|
((InputProfile == NULL) &&
|
||
|
|
(OutputProfile == NULL))) {
|
||
|
|
|
||
|
|
p -> xform = NullXFORM;
|
||
|
|
return (cmsHTRANSFORM) p;
|
||
|
|
}
|
||
|
|
|
||
|
|
// From here we need at least one input profile
|
||
|
|
if (InputProfile == NULL) {
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Input profile cannot be NULL!");
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Device link are means to store precalculated transform grids.
|
||
|
|
if (cmsGetDeviceClass(InputProfile) == icSigLinkClass) {
|
||
|
|
|
||
|
|
return CreateDeviceLinkTransform(p);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!IsProperColorSpace(InputProfile, InputFormat, FALSE)) {
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Input profile is operating on wrong colorspace");
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
p ->EntryColorSpace = cmsGetColorSpace(InputProfile);
|
||
|
|
|
||
|
|
// Oct-21-2002: Added named color transforms
|
||
|
|
if (cmsGetDeviceClass(InputProfile) == icSigNamedColorClass) {
|
||
|
|
|
||
|
|
if (p ->NamedColorList == NULL)
|
||
|
|
p ->NamedColorList = cmsAllocNamedColorList(0);
|
||
|
|
|
||
|
|
cmsReadICCnamedColorList(p, InputProfile, icSigNamedColor2Tag);
|
||
|
|
|
||
|
|
// Special case. If output profile == NULL, then the transform gives
|
||
|
|
// device values from named colors.
|
||
|
|
|
||
|
|
if (OutputProfile == NULL) {
|
||
|
|
|
||
|
|
p ->ExitColorSpace = p -> EntryColorSpace;
|
||
|
|
p ->xform = NC2deviceXform;
|
||
|
|
return (cmsHTRANSFORM) p;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Named color doesn't precalc anything
|
||
|
|
p -> dwOriginalFlags |= cmsFLAGS_NOTPRECALC;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// From here we need also output profile.
|
||
|
|
if (OutputProfile == NULL) {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Output profile cannot be NULL!");
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if (!IsProperColorSpace(OutputProfile, OutputFormat, FALSE)) {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Output profile is operating on wrong colorspace");
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
p -> ExitColorSpace = cmsGetColorSpace(OutputProfile);
|
||
|
|
|
||
|
|
// Named color only on input
|
||
|
|
if (cmsGetDeviceClass(OutputProfile) == icSigNamedColorClass) {
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Named color profiles are not supported as output");
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
p -> Phase1 = GetPhase(InputProfile);
|
||
|
|
p -> Phase2 = -1;
|
||
|
|
p -> Phase3 = GetPhase(OutputProfile);
|
||
|
|
|
||
|
|
// Try to locate a LUT
|
||
|
|
|
||
|
|
FromTag = Device2PCS[nIntent];
|
||
|
|
ToTag = PCS2Device[nIntent];
|
||
|
|
|
||
|
|
if (!cmsIsTag(InputProfile, FromTag)) {
|
||
|
|
|
||
|
|
FromTag = Device2PCS[0];
|
||
|
|
|
||
|
|
if (!cmsIsTag(InputProfile, FromTag)) {
|
||
|
|
FromTag = (icTagSignature)0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If proofing is needed, add required tags/parameters
|
||
|
|
if (ProofingProfile)
|
||
|
|
CreateProof(p, &ToTag);
|
||
|
|
|
||
|
|
|
||
|
|
if (!cmsIsTag(OutputProfile, ToTag)) {
|
||
|
|
|
||
|
|
ToTag = PCS2Device[0];
|
||
|
|
|
||
|
|
// 12-Dec-2003, Abstract profiles can be placed as output and still using AToB0
|
||
|
|
if (cmsGetDeviceClass(OutputProfile) == icSigAbstractClass) {
|
||
|
|
|
||
|
|
if (!cmsIsTag(OutputProfile, ToTag)) {
|
||
|
|
ToTag = (icTagSignature) icSigAToB0Tag;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!cmsIsTag(OutputProfile, ToTag))
|
||
|
|
ToTag = (icTagSignature)0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if (p-> dwOriginalFlags & cmsFLAGS_MATRIXINPUT)
|
||
|
|
FromTag = (icTagSignature)0;
|
||
|
|
|
||
|
|
if (p -> dwOriginalFlags & cmsFLAGS_MATRIXOUTPUT)
|
||
|
|
ToTag = (icTagSignature)0;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
if (PickTransformRoutine(p, &FromTag, &ToTag) == NULL) {
|
||
|
|
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
TakeConversionRoutines(p, dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION);
|
||
|
|
|
||
|
|
if (!(p -> dwOriginalFlags & cmsFLAGS_NOTPRECALC)) {
|
||
|
|
|
||
|
|
LPLUT DeviceLink;
|
||
|
|
LPLUT GamutCheck = NULL;
|
||
|
|
|
||
|
|
|
||
|
|
if (p ->EntryColorSpace == icSigCmykData &&
|
||
|
|
p ->ExitColorSpace == icSigCmykData &&
|
||
|
|
(dwFlags & cmsFLAGS_PRESERVEBLACK)) {
|
||
|
|
|
||
|
|
DeviceLink = _cmsPrecalculateBlackPreservingDeviceLink((cmsHTRANSFORM) p, dwFlags);
|
||
|
|
|
||
|
|
// Cannot be done at all?
|
||
|
|
if (DeviceLink == NULL)
|
||
|
|
DeviceLink = _cmsPrecalculateDeviceLink((cmsHTRANSFORM) p, dwFlags);
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
DeviceLink = _cmsPrecalculateDeviceLink((cmsHTRANSFORM) p, dwFlags);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Allow to specify cmsFLAGS_GAMUTCHECK, even if no proofing profile is given
|
||
|
|
if ((p ->PreviewProfile != NULL) && (p -> dwOriginalFlags & cmsFLAGS_GAMUTCHECK)) {
|
||
|
|
|
||
|
|
GamutCheck = _cmsPrecalculateGamutCheck((cmsHTRANSFORM) p);
|
||
|
|
}
|
||
|
|
|
||
|
|
// If input colorspace is Rgb, Cmy, then use tetrahedral interpolation
|
||
|
|
// for speed reasons (it only works well on spaces on Luma is diagonal, and
|
||
|
|
// not if luma is in separate channel)
|
||
|
|
if (p ->EntryColorSpace == icSigRgbData ||
|
||
|
|
p ->EntryColorSpace == icSigCmyData) {
|
||
|
|
|
||
|
|
|
||
|
|
cmsCalcCLUT16ParamsEx(DeviceLink->CLut16params.nSamples,
|
||
|
|
DeviceLink->CLut16params.nInputs,
|
||
|
|
DeviceLink->CLut16params.nOutputs,
|
||
|
|
TRUE, &DeviceLink->CLut16params);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
// If this is a 8-bit transform, optimize LUT further.
|
||
|
|
|
||
|
|
if ((T_BYTES(InputFormat) == 1) && (T_CHANNELS(InputFormat) == 3)) {
|
||
|
|
|
||
|
|
DeviceLink = _cmsBlessLUT8(DeviceLink);
|
||
|
|
if (DeviceLink == NULL) return NULL;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
p ->GamutCheck = GamutCheck;
|
||
|
|
|
||
|
|
if (DeviceLink) {
|
||
|
|
|
||
|
|
p ->DeviceLink = DeviceLink;
|
||
|
|
|
||
|
|
if ((nIntent != INTENT_ABSOLUTE_COLORIMETRIC) &&
|
||
|
|
!(p -> dwOriginalFlags & cmsFLAGS_NOWHITEONWHITEFIXUP))
|
||
|
|
|
||
|
|
_cmsFixWhiteMisalignment(p);
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED,
|
||
|
|
"Cannot precalculate %d->%d channels transform!",
|
||
|
|
T_CHANNELS(InputFormat), T_CHANNELS(OutputFormat));
|
||
|
|
|
||
|
|
cmsDeleteTransform(p);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
SetPrecalculatedTransform(p);
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
// Re-Identify formats
|
||
|
|
p -> FromInput = _cmsIdentifyInputFormat(p, InputFormat);
|
||
|
|
p -> ToOutput = _cmsIdentifyOutputFormat(p, OutputFormat);
|
||
|
|
|
||
|
|
|
||
|
|
return p;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Wrapper por simpler non-proofing transforms.
|
||
|
|
|
||
|
|
cmsHTRANSFORM LCMSEXPORT cmsCreateTransform(cmsHPROFILE Input,
|
||
|
|
DWORD InputFormat,
|
||
|
|
cmsHPROFILE Output,
|
||
|
|
DWORD OutputFormat,
|
||
|
|
int Intent,
|
||
|
|
DWORD dwFlags)
|
||
|
|
|
||
|
|
{
|
||
|
|
return cmsCreateProofingTransform(Input, InputFormat,
|
||
|
|
Output, OutputFormat,
|
||
|
|
NULL,
|
||
|
|
Intent, INTENT_ABSOLUTE_COLORIMETRIC,
|
||
|
|
dwFlags);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Profiles are *NOT* closed
|
||
|
|
|
||
|
|
void LCMSEXPORT cmsDeleteTransform(cmsHTRANSFORM hTransform)
|
||
|
|
{
|
||
|
|
_LPcmsTRANSFORM p = (_LPcmsTRANSFORM) (LPSTR) hTransform;
|
||
|
|
|
||
|
|
if (p -> Device2PCS)
|
||
|
|
cmsFreeLUT(p -> Device2PCS);
|
||
|
|
if (p -> PCS2Device)
|
||
|
|
cmsFreeLUT(p -> PCS2Device);
|
||
|
|
if (p -> Gamut)
|
||
|
|
cmsFreeLUT(p -> Gamut);
|
||
|
|
if (p -> Preview)
|
||
|
|
cmsFreeLUT(p -> Preview);
|
||
|
|
if (p -> DeviceLink)
|
||
|
|
cmsFreeLUT(p -> DeviceLink);
|
||
|
|
if (p -> InMatShaper)
|
||
|
|
cmsFreeMatShaper(p -> InMatShaper);
|
||
|
|
if (p -> OutMatShaper)
|
||
|
|
cmsFreeMatShaper(p -> OutMatShaper);
|
||
|
|
if (p -> SmeltMatShaper)
|
||
|
|
cmsFreeMatShaper(p -> SmeltMatShaper);
|
||
|
|
if (p ->NamedColorList)
|
||
|
|
cmsFreeNamedColorList(p ->NamedColorList);
|
||
|
|
if (p -> GamutCheck)
|
||
|
|
cmsFreeLUT(p -> GamutCheck);
|
||
|
|
|
||
|
|
LCMS_FREE_LOCK(&p->rwlock);
|
||
|
|
|
||
|
|
_cmsFree((void *) p);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Apply transform code
|
||
|
|
void LCMSEXPORT cmsDoTransform(cmsHTRANSFORM Transform,
|
||
|
|
LPVOID InputBuffer,
|
||
|
|
LPVOID OutputBuffer, unsigned int Size)
|
||
|
|
|
||
|
|
{
|
||
|
|
|
||
|
|
_LPcmsTRANSFORM p = (_LPcmsTRANSFORM) (LPSTR) Transform;
|
||
|
|
|
||
|
|
p -> StrideIn = p -> StrideOut = Size;
|
||
|
|
|
||
|
|
p -> xform(p, InputBuffer, OutputBuffer, Size);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void LCMSEXPORT cmsSetAlarmCodes(int r, int g, int b)
|
||
|
|
{
|
||
|
|
AlarmR = RGB_8_TO_16(r);
|
||
|
|
AlarmG = RGB_8_TO_16(g);
|
||
|
|
AlarmB = RGB_8_TO_16(b);
|
||
|
|
}
|
||
|
|
|
||
|
|
void LCMSEXPORT cmsGetAlarmCodes(int *r, int *g, int *b)
|
||
|
|
{
|
||
|
|
*r = RGB_16_TO_8(AlarmR);
|
||
|
|
*g = RGB_16_TO_8(AlarmG);
|
||
|
|
*b = RGB_16_TO_8(AlarmB);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Returns TRUE if the profile is implemented as matrix-shaper
|
||
|
|
|
||
|
|
LCMSBOOL LCMSEXPORT _cmsIsMatrixShaper(cmsHPROFILE hProfile)
|
||
|
|
{
|
||
|
|
switch (cmsGetColorSpace(hProfile)) {
|
||
|
|
|
||
|
|
case icSigGrayData:
|
||
|
|
|
||
|
|
return cmsIsTag(hProfile, icSigGrayTRCTag);
|
||
|
|
|
||
|
|
case icSigRgbData:
|
||
|
|
|
||
|
|
return (cmsIsTag(hProfile, icSigRedColorantTag) &&
|
||
|
|
cmsIsTag(hProfile, icSigGreenColorantTag) &&
|
||
|
|
cmsIsTag(hProfile, icSigBlueColorantTag) &&
|
||
|
|
cmsIsTag(hProfile, icSigRedTRCTag) &&
|
||
|
|
cmsIsTag(hProfile, icSigGreenTRCTag) &&
|
||
|
|
cmsIsTag(hProfile, icSigBlueTRCTag));
|
||
|
|
|
||
|
|
default:
|
||
|
|
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
LCMSBOOL LCMSEXPORT cmsIsIntentSupported(cmsHPROFILE hProfile,
|
||
|
|
int Intent, int UsedDirection)
|
||
|
|
{
|
||
|
|
|
||
|
|
icTagSignature* TagTable;
|
||
|
|
|
||
|
|
// Device link profiles only implements the intent in header
|
||
|
|
|
||
|
|
if (cmsGetDeviceClass(hProfile) != icSigLinkClass) {
|
||
|
|
|
||
|
|
switch (UsedDirection) {
|
||
|
|
|
||
|
|
case LCMS_USED_AS_INPUT: TagTable = Device2PCS; break;
|
||
|
|
case LCMS_USED_AS_OUTPUT:TagTable = PCS2Device; break;
|
||
|
|
case LCMS_USED_AS_PROOF: TagTable = Preview; break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Unexpected direction (%d)", UsedDirection);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (cmsIsTag(hProfile, TagTable[Intent])) return TRUE;
|
||
|
|
return _cmsIsMatrixShaper(hProfile);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (cmsTakeRenderingIntent(hProfile) == Intent);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Multiple profile transform.
|
||
|
|
static
|
||
|
|
int MultiprofileSampler(register WORD In[], register WORD Out[], register LPVOID Cargo)
|
||
|
|
{
|
||
|
|
cmsHTRANSFORM* Transforms = (cmsHTRANSFORM*) Cargo;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
cmsDoTransform(Transforms[0], In, Out, 1);
|
||
|
|
|
||
|
|
for (i=1; Transforms[i]; i++)
|
||
|
|
cmsDoTransform(Transforms[i], Out, Out, 1);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static
|
||
|
|
int IsAllowedInSingleXform(icProfileClassSignature aClass)
|
||
|
|
{
|
||
|
|
return (aClass == icSigInputClass) ||
|
||
|
|
(aClass == icSigDisplayClass) ||
|
||
|
|
(aClass == icSigOutputClass) ||
|
||
|
|
(aClass == icSigColorSpaceClass);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// A multiprofile transform does chain several profiles into a single
|
||
|
|
// devicelink. It couls also be used to merge named color profiles into
|
||
|
|
// a single database.
|
||
|
|
|
||
|
|
|
||
|
|
cmsHTRANSFORM LCMSEXPORT cmsCreateMultiprofileTransform(cmsHPROFILE hProfiles[],
|
||
|
|
int nProfiles,
|
||
|
|
DWORD dwInput,
|
||
|
|
DWORD dwOutput,
|
||
|
|
int Intent,
|
||
|
|
DWORD dwFlags)
|
||
|
|
{
|
||
|
|
cmsHTRANSFORM Transforms[257];
|
||
|
|
DWORD dwPrecalcFlags = (dwFlags|cmsFLAGS_NOTPRECALC|cmsFLAGS_NOTCACHE);
|
||
|
|
DWORD FormatInput, FormatOutput;
|
||
|
|
cmsHPROFILE hLab, hXYZ, hProfile;
|
||
|
|
icColorSpaceSignature ColorSpace, CurrentColorSpace;
|
||
|
|
icColorSpaceSignature ColorSpaceIn, ColorSpaceOut;
|
||
|
|
LPLUT Grid;
|
||
|
|
int nGridPoints, ChannelsInput, ChannelsOutput = 3, i;
|
||
|
|
_LPcmsTRANSFORM p;
|
||
|
|
int nNamedColor;
|
||
|
|
|
||
|
|
if (nProfiles > 255) {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "What are you trying to do with more that 255 profiles?!?, of course aborted");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
// There is a simple case with just two profiles, try to catch it in order of getting
|
||
|
|
// black preservation to work on this function, at least with two profiles.
|
||
|
|
|
||
|
|
|
||
|
|
if (nProfiles == 2) {
|
||
|
|
|
||
|
|
icProfileClassSignature Class1 = cmsGetDeviceClass(hProfiles[0]);
|
||
|
|
icProfileClassSignature Class2 = cmsGetDeviceClass(hProfiles[1]);
|
||
|
|
|
||
|
|
// Only input, output and display are allowed
|
||
|
|
|
||
|
|
if (IsAllowedInSingleXform(Class1) &&
|
||
|
|
IsAllowedInSingleXform(Class2))
|
||
|
|
return cmsCreateTransform(hProfiles[0], dwInput, hProfiles[1], dwOutput, Intent, dwFlags);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Creates a phantom transform for latter filling
|
||
|
|
p = (_LPcmsTRANSFORM) cmsCreateTransform(NULL, dwInput,
|
||
|
|
NULL, dwOutput, Intent, cmsFLAGS_NULLTRANSFORM);
|
||
|
|
|
||
|
|
// If user wants null one, give it
|
||
|
|
if (dwFlags & cmsFLAGS_NULLTRANSFORM) return (cmsHPROFILE) p;
|
||
|
|
|
||
|
|
// Is a bunch of named color profiles?
|
||
|
|
nNamedColor = 0;
|
||
|
|
for (i=0; i < nProfiles; i++) {
|
||
|
|
if (cmsGetDeviceClass(hProfiles[i]) == icSigNamedColorClass)
|
||
|
|
nNamedColor++;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if (nNamedColor == nProfiles) {
|
||
|
|
|
||
|
|
// Yes, only named color. Create a named color-device
|
||
|
|
// and append to named color table
|
||
|
|
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
|
||
|
|
p = (_LPcmsTRANSFORM) cmsCreateTransform(hProfiles[0], dwInput, NULL, dwOutput, Intent, dwFlags);
|
||
|
|
for (i=1; i < nProfiles; i++) {
|
||
|
|
cmsReadICCnamedColorList(p, hProfiles[i], icSigNamedColor2Tag);
|
||
|
|
}
|
||
|
|
|
||
|
|
return p; // Ok, done so far
|
||
|
|
}
|
||
|
|
else
|
||
|
|
if (nNamedColor > 0) {
|
||
|
|
|
||
|
|
cmsDeleteTransform((cmsHTRANSFORM) p);
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "Could not mix named color profiles with other types in multiprofile transform");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// We will need a 3DCLUT for device link
|
||
|
|
Grid = cmsAllocLUT();
|
||
|
|
if (!Grid) return NULL;
|
||
|
|
|
||
|
|
// This one is our PCS (Always Lab)
|
||
|
|
hLab = cmsCreateLabProfile(NULL);
|
||
|
|
hXYZ = cmsCreateXYZProfile();
|
||
|
|
|
||
|
|
if (!hLab || !hXYZ) goto ErrorCleanup;
|
||
|
|
|
||
|
|
// Take some info....
|
||
|
|
|
||
|
|
p ->EntryColorSpace = CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
|
||
|
|
|
||
|
|
|
||
|
|
for (i=0; i < nProfiles; i++) {
|
||
|
|
|
||
|
|
int lIsDeviceLink, lIsInput;
|
||
|
|
|
||
|
|
// Check colorspace
|
||
|
|
|
||
|
|
hProfile = hProfiles[i];
|
||
|
|
lIsDeviceLink = (cmsGetDeviceClass(hProfile) == icSigLinkClass);
|
||
|
|
lIsInput = (CurrentColorSpace != icSigXYZData) &&
|
||
|
|
(CurrentColorSpace != icSigLabData);
|
||
|
|
|
||
|
|
if (lIsInput) {
|
||
|
|
|
||
|
|
ColorSpaceIn = cmsGetColorSpace(hProfile);
|
||
|
|
ColorSpaceOut = cmsGetPCS(hProfile);
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
ColorSpaceIn = cmsGetPCS(hProfile);
|
||
|
|
ColorSpaceOut = cmsGetColorSpace(hProfile);
|
||
|
|
}
|
||
|
|
|
||
|
|
ChannelsInput = _cmsChannelsOf(ColorSpaceIn);
|
||
|
|
ChannelsOutput = _cmsChannelsOf(ColorSpaceOut);
|
||
|
|
|
||
|
|
FormatInput = BYTES_SH(2)|CHANNELS_SH(ChannelsInput);
|
||
|
|
FormatOutput = BYTES_SH(2)|CHANNELS_SH(ChannelsOutput);
|
||
|
|
|
||
|
|
ColorSpace = ColorSpaceIn;
|
||
|
|
|
||
|
|
|
||
|
|
if (ColorSpace == CurrentColorSpace) {
|
||
|
|
|
||
|
|
if (lIsDeviceLink) {
|
||
|
|
|
||
|
|
Transforms[i] = cmsCreateTransform(hProfile, FormatInput,
|
||
|
|
NULL, FormatOutput,
|
||
|
|
Intent, dwPrecalcFlags);
|
||
|
|
}
|
||
|
|
|
||
|
|
else {
|
||
|
|
|
||
|
|
if (lIsInput) {
|
||
|
|
|
||
|
|
Transforms[i] = cmsCreateTransform(hProfile, FormatInput,
|
||
|
|
(ColorSpaceOut == icSigLabData ? hLab : hXYZ), FormatOutput,
|
||
|
|
Intent, dwPrecalcFlags);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
Transforms[i] = cmsCreateTransform((ColorSpaceIn == icSigLabData ? hLab : hXYZ), FormatInput,
|
||
|
|
hProfile, FormatOutput,
|
||
|
|
Intent, dwPrecalcFlags);
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
else // Can come from pcs?
|
||
|
|
if (CurrentColorSpace == icSigXYZData) {
|
||
|
|
|
||
|
|
Transforms[i] = cmsCreateTransform(hXYZ, FormatInput,
|
||
|
|
hProfile, FormatOutput,
|
||
|
|
Intent, dwPrecalcFlags);
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
if (CurrentColorSpace == icSigLabData) {
|
||
|
|
|
||
|
|
Transforms[i] = cmsCreateTransform(hLab, FormatInput,
|
||
|
|
hProfile, FormatOutput,
|
||
|
|
Intent, dwPrecalcFlags);
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
cmsSignalError(LCMS_ERRC_ABORTED, "cmsCreateMultiprofileTransform: ColorSpace mismatch");
|
||
|
|
goto ErrorCleanup;
|
||
|
|
}
|
||
|
|
|
||
|
|
CurrentColorSpace = ColorSpaceOut;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
p ->ExitColorSpace = CurrentColorSpace;
|
||
|
|
Transforms[i] = NULL; // End marker
|
||
|
|
|
||
|
|
p ->InputProfile = hProfiles[0];
|
||
|
|
p ->OutputProfile = hProfiles[nProfiles - 1];
|
||
|
|
|
||
|
|
nGridPoints = _cmsReasonableGridpointsByColorspace(p ->EntryColorSpace, dwFlags);
|
||
|
|
|
||
|
|
ChannelsInput = _cmsChannelsOf(cmsGetColorSpace(p ->InputProfile));
|
||
|
|
|
||
|
|
Grid = cmsAlloc3DGrid(Grid, nGridPoints, ChannelsInput, ChannelsOutput);
|
||
|
|
|
||
|
|
if (!(dwFlags & cmsFLAGS_NOPRELINEARIZATION))
|
||
|
|
_cmsComputePrelinearizationTablesFromXFORM(Transforms, nProfiles, Grid);
|
||
|
|
|
||
|
|
// Compute device link on 16-bit basis
|
||
|
|
if (!cmsSample3DGrid(Grid, MultiprofileSampler, (LPVOID) Transforms, Grid -> wFlags)) {
|
||
|
|
|
||
|
|
cmsFreeLUT(Grid);
|
||
|
|
goto ErrorCleanup;
|
||
|
|
}
|
||
|
|
|
||
|
|
// All ok, store the newly created LUT
|
||
|
|
p -> DeviceLink = Grid;
|
||
|
|
|
||
|
|
SetPrecalculatedTransform(p);
|
||
|
|
|
||
|
|
for (i=nProfiles-1; i >= 0; --i)
|
||
|
|
cmsDeleteTransform(Transforms[i]);
|
||
|
|
|
||
|
|
|
||
|
|
if (hLab) cmsCloseProfile(hLab);
|
||
|
|
if (hXYZ) cmsCloseProfile(hXYZ);
|
||
|
|
|
||
|
|
|
||
|
|
if (p ->EntryColorSpace == icSigRgbData ||
|
||
|
|
p ->EntryColorSpace == icSigCmyData) {
|
||
|
|
|
||
|
|
p->DeviceLink -> CLut16params.Interp3D = cmsTetrahedralInterp16;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if ((Intent != INTENT_ABSOLUTE_COLORIMETRIC) &&
|
||
|
|
!(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP))
|
||
|
|
_cmsFixWhiteMisalignment(p);
|
||
|
|
|
||
|
|
return (cmsHTRANSFORM) p;
|
||
|
|
|
||
|
|
|
||
|
|
ErrorCleanup:
|
||
|
|
|
||
|
|
if (hLab) cmsCloseProfile(hLab);
|
||
|
|
if (hXYZ) cmsCloseProfile(hXYZ);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
double LCMSEXPORT cmsSetAdaptationState(double d)
|
||
|
|
{
|
||
|
|
double OldVal = GlobalAdaptationState;
|
||
|
|
|
||
|
|
if (d >= 0)
|
||
|
|
GlobalAdaptationState = d;
|
||
|
|
|
||
|
|
return OldVal;
|
||
|
|
|
||
|
|
}
|