/* * 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 Color Management System // Copyright (c) 1998-2022 Marti Maria Saguer // // 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"lcms2_internal.h"
// This is the default routine for ICC-style intents. A user may decide to override it by using a plugin. // Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric static
cmsPipeline* DefaultICCintents(cmsContext ContextID,
cmsUInt32Number nProfiles,
cmsUInt32Number Intents[],
cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags);
// This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile // to do the trick (no devicelinks allowed at that position) static
cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
cmsUInt32Number nProfiles,
cmsUInt32Number Intents[],
cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags);
// This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile // to do the trick (no devicelinks allowed at that position) static
cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
cmsUInt32Number nProfiles,
cmsUInt32Number Intents[],
cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags);
// A pointer to the beginning of the list
_cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };
// Duplicates the zone of memory used by the plug-in in the new context static void DupPluginIntentsList(struct _cmsContext_struct* ctx, conststruct _cmsContext_struct* src)
{
_cmsIntentsPluginChunkType newHead = { NULL };
cmsIntentsList* entry;
cmsIntentsList* Anterior = NULL;
_cmsIntentsPluginChunkType* head = (_cmsIntentsPluginChunkType*) src->chunks[IntentPlugin];
// Walk the list copying all nodes for (entry = head->Intents;
entry != NULL;
entry = entry ->Next) {
// Search the list for a suitable intent. Returns NULL if not found static
cmsIntentsList* SearchIntent(cmsContext ContextID, cmsUInt32Number Intent)
{
_cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
cmsIntentsList* pt;
for (pt = DefaultIntents; pt != NULL; pt = pt -> Next) if (pt ->Intent == Intent) return pt;
return NULL;
}
// Black point compensation. Implemented as a linear scaling in XYZ. Black points // should come relative to the white point. Fills an matrix/offset element m // which is organized as a 4x4 matrix. static void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn, const cmsCIEXYZ* BlackPointOut,
cmsMAT3* m, cmsVEC3* off)
{
cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
// Now we need to compute a matrix plus an offset m and of such of // [m]*bpin + off = bpout // [m]*D50 + off = D50 // // This is a linear scaling in the form ax+b, where // a = (bpout - D50) / (bpin - D50) // b = - D50* (bpout - bpin) / (bpin - D50)
// Approximate a blackbody illuminant based on CHAD information static
cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
{ // Convert D50 across inverse CHAD to get the absolute white point
cmsVEC3 d, s;
cmsCIEXYZ Dest;
cmsCIExyY DestChromaticity;
cmsFloat64Number TempK;
cmsMAT3 m1, m2;
m1 = *Chad; if (!_cmsMAT3inverse(&m1, &m2)) returnFALSE;
if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity)) return -1.0;
return TempK;
}
// Compute a CHAD based on a given temperature static void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
{
cmsCIEXYZ White;
cmsCIExyY ChromaticityOfWhite;
// Join scalings to obtain relative input to absolute and then to relative output. // Result is stored in a 3x3 matrix static
cmsBool ComputeAbsoluteIntent(cmsFloat64Number AdaptationState, const cmsCIEXYZ* WhitePointIn, const cmsMAT3* ChromaticAdaptationMatrixIn, const cmsCIEXYZ* WhitePointOut, const cmsMAT3* ChromaticAdaptationMatrixOut,
cmsMAT3* m)
{
cmsMAT3 Scale, m1, m2, m3, m4;
// TODO: Follow Marc Mahy's recommendation to check if CHAD is same by using M1*M2 == M2*M1. If so, do nothing. // TODO: Add support for ArgyllArts tag
// Adaptation state if (AdaptationState == 1.0) {
// Observer is fully adapted. Keep chromatic adaptation. // That is the standard V4 behaviour
_cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
_cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
_cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
} else {
// Incomplete adaptation. This is an advanced feature.
_cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
_cmsVEC3init(&Scale.v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
_cmsVEC3init(&Scale.v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
if (AdaptationState == 0.0) {
m1 = *ChromaticAdaptationMatrixOut;
_cmsMAT3per(&m2, &m1, &Scale); // m2 holds CHAD from output white to D50 times abs. col. scaling
// Observer is not adapted, undo the chromatic adaptation
_cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);
m3 = *ChromaticAdaptationMatrixIn; if (!_cmsMAT3inverse(&m3, &m4)) returnFALSE;
_cmsMAT3per(m, &m2, &m4);
m1 = *ChromaticAdaptationMatrixIn; if (!_cmsMAT3inverse(&m1, &m2)) returnFALSE;
_cmsMAT3per(&m3, &m2, &Scale); // m3 holds CHAD from input white to D50 times abs. col. scaling
// If black points are equal, then do nothing if (BlackPointIn.X != BlackPointOut.X ||
BlackPointIn.Y != BlackPointOut.Y ||
BlackPointIn.Z != BlackPointOut.Z)
ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
}
}
// Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0, // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so // we have first to convert from encoded to XYZ and then convert back to encoded. // y = Mx + Off // x = x'c // y = M x'c + Off // y = y'c; y' = y / c // y' = (Mx'c + Off) /c = Mx' + (Off / c)
for (k=0; k < 3; k++) {
off ->n[k] /= MAX_ENCODEABLE_XYZ;
}
returnTRUE;
}
// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space static
cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
{
cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
// Handle PCS mismatches. A specialized stage is added to the LUT in such case switch (InPCS) {
case cmsSigXYZData: // Input profile operates in XYZ
switch (OutPCS) {
case cmsSigXYZData: // XYZ -> XYZ if (!IsEmptyLayer(m, off) &&
!cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl))) returnFALSE; break;
case cmsSigLabData: // XYZ -> Lab if (!IsEmptyLayer(m, off) &&
!cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl))) returnFALSE; if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID))) returnFALSE; break;
// On colorspaces other than PCS, check for same space default: if (InPCS != OutPCS) returnFALSE; break;
}
returnTRUE;
}
// Is a given space compatible with another? static
cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
{ // If they are same, they are compatible. if (a == b) returnTRUE;
// Check for MCH4 substitution of CMYK if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) returnTRUE; if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) returnTRUE;
// Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other. if ((a == cmsSigXYZData) && (b == cmsSigLabData)) returnTRUE; if ((a == cmsSigLabData) && (b == cmsSigXYZData)) returnTRUE;
// Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
Result = cmsPipelineAlloc(ContextID, 0, 0); if (Result == NULL) return NULL;
// First profile is used as input unless devicelink or abstract if ((i == 0) && !lIsDeviceLink) {
lIsInput = TRUE;
} else { // Else use profile in the input direction if current space is not PCS
lIsInput = (CurrentColorSpace != cmsSigXYZData) &&
(CurrentColorSpace != cmsSigLabData);
}
// If devicelink is found, then no custom intent is allowed and we can // read the LUT to be applied. Settings don't apply here. if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
// Get the involved LUT from the profile
Lut = _cmsReadDevicelinkLUT(hProfile, Intent); if (Lut == NULL) goto Error;
// What about abstract profiles? if (ClassSig == cmsSigAbstractClass && i > 0) { if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
} else {
_cmsMAT3identity(&m);
_cmsVEC3init(&off, 0, 0, 0);
}
if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
} else {
if (lIsInput) { // Input direction means non-pcs connection, so proceed like devicelinks
Lut = _cmsReadInputLUT(hProfile, Intent); if (Lut == NULL) goto Error;
} else {
// Output direction means PCS connection. Intent may apply here
Lut = _cmsReadOutputLUT(hProfile, Intent); if (Lut == NULL) goto Error;
if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
}
}
// Concatenate to the output LUT if (!cmsPipelineCat(Result, Lut)) goto Error;
cmsPipelineFree(Lut);
Lut = NULL;
// Update current space
CurrentColorSpace = ColorSpaceOut;
}
// Check for non-negatives clip if (dwFlags & cmsFLAGS_NONEGATIVES) {
// Black preserving intents ---------------------------------------------------------------------------------------------
// Translate black-preserving intents to ICC ones static
cmsUInt32Number TranslateNonICCIntents(cmsUInt32Number Intent)
{ switch (Intent) { case INTENT_PRESERVE_K_ONLY_PERCEPTUAL: case INTENT_PRESERVE_K_PLANE_PERCEPTUAL: return INTENT_PERCEPTUAL;
case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC: case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC: return INTENT_RELATIVE_COLORIMETRIC;
case INTENT_PRESERVE_K_ONLY_SATURATION: case INTENT_PRESERVE_K_PLANE_SATURATION: return INTENT_SATURATION;
default: return Intent;
}
}
// Sampler for Black-only preserving CMYK->CMYK transforms
typedefstruct {
cmsPipeline* cmyk2cmyk; // The original transform
cmsToneCurve* KTone; // Black-to-black tone curve
} GrayOnlyParams;
// Preserve black only if that is the only ink used static int BlackPreservingGrayOnlySampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
{
GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
// If going across black only, keep black only if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
// TAC does not apply because it is black ink!
Out[0] = Out[1] = Out[2] = 0;
Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]); returnTRUE;
}
// Keep normal transform for other colors
bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data); returnTRUE;
}
// This is the entry for black-preserving K-only intents, which are non-ICC static
cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
cmsUInt32Number nProfiles,
cmsUInt32Number TheIntents[],
cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags)
{
GrayOnlyParams bp;
cmsPipeline* Result;
cmsUInt32Number ICCIntents[256];
cmsStage* CLUT;
cmsUInt32Number i, nGridPoints;
cmsUInt32Number lastProfilePos;
cmsUInt32Number preservationProfilesCount;
cmsHPROFILE hLastProfile;
// Allocate an empty LUT for holding the result
Result = cmsPipelineAlloc(ContextID, 4, 4); if (Result == NULL) return NULL;
memset(&bp, 0, sizeof(bp));
// Create a LUT holding normal ICC transform
bp.cmyk2cmyk = DefaultICCintents(ContextID,
preservationProfilesCount,
ICCIntents,
hProfiles,
BPC,
AdaptationStates,
dwFlags);
if (bp.cmyk2cmyk == NULL) goto Error;
// Now, compute the tone curve
bp.KTone = _cmsBuildKToneCurve(ContextID,
4096,
preservationProfilesCount,
ICCIntents,
hProfiles,
BPC,
AdaptationStates,
dwFlags);
if (bp.KTone == NULL) goto Error;
// How many gridpoints are we going to use?
nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
// Create the CLUT. 16 bits
CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL); if (CLUT == NULL) goto Error;
// This is the one and only MPE in this LUT if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT)) goto Error;
// Sample it. We cannot afford pre/post linearization this time. if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0)) goto Error;
// Insert possible devicelinks at the end for (i = lastProfilePos + 1; i < nProfiles; i++)
{
cmsPipeline* devlink = _cmsReadDevicelinkLUT(hProfiles[i], ICCIntents[i]); if (devlink == NULL) goto Error;
if (!cmsPipelineCat(Result, devlink)) goto Error;
}
// Get rid of xform and tone curve
cmsPipelineFree(bp.cmyk2cmyk);
cmsFreeToneCurve(bp.KTone);
return Result;
Error:
if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk); if (bp.KTone != NULL) cmsFreeToneCurve(bp.KTone); if (Result != NULL) cmsPipelineFree(Result); return NULL;
}
// K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
typedefstruct {
cmsPipeline* cmyk2cmyk; // The original transform
cmsHTRANSFORM hProofOutput; // Output CMYK to Lab (last profile)
cmsHTRANSFORM cmyk2Lab; // The input chain
cmsToneCurve* KTone; // Black-to-black tone curve
cmsPipeline* LabK2cmyk; // The output profile
cmsFloat64Number MaxError;
// The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision static int BlackPreservingSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
{ int i;
cmsFloat32Number Inf[4], Outf[4];
cmsFloat32Number LabK[4];
cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
cmsCIELab ColorimetricLab, BlackPreservingLab;
PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
// Convert from 16 bits to floating point for (i=0; i < 4; i++)
Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
// Get the K across Tone curve
LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
// If going across black only, keep black only if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
// Try the original transform,
cmsPipelineEvalFloat(Inf, Outf, bp ->cmyk2cmyk);
// Store a copy of the floating point result into 16-bit for (i=0; i < 4; i++)
Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
// Maybe K is already ok (mostly on K=0) if (fabsf(Outf[3] - LabK[3]) < (3.0 / 65535.0)) { returnTRUE;
}
// K differ, measure and keep Lab measurement for further usage // this is done in relative colorimetric intent
cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
// Is not black only and the transform doesn't keep black. // Obtain the Lab of output CMYK. After that we have Lab + K
cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
// Obtain the corresponding CMY using reverse interpolation // (K is fixed in LabK[3]) if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
// Cannot find a suitable value, so use colorimetric xform // which is already stored in Out[] returnTRUE;
}
// Make sure to pass through K (which now is fixed)
Outf[3] = LabK[3];
Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY); if (Ratio < 0)
Ratio = 0;
} else
Ratio = 1.0;
Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0); // C
Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0); // M
Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0); // Y
Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
// Estimate the error (this goes 16 bits to Lab DBL)
cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab); if (Error > bp -> MaxError)
bp->MaxError = Error;
returnTRUE;
}
// This is the entry for black-plane preserving, which are non-ICC static
cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
cmsUInt32Number nProfiles,
cmsUInt32Number TheIntents[],
cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags)
{
PreserveKPlaneParams bp;
cmsPipeline* Result = NULL;
cmsUInt32Number ICCIntents[256];
cmsStage* CLUT;
cmsUInt32Number i, nGridPoints;
cmsUInt32Number lastProfilePos;
cmsUInt32Number preservationProfilesCount;
cmsHPROFILE hLastProfile;
cmsHPROFILE hLab;
// Allocate an empty LUT for holding the result
Result = cmsPipelineAlloc(ContextID, 4, 4); if (Result == NULL) return NULL;
memset(&bp, 0, sizeof(bp));
// We need the input LUT of the last profile, assuming this one is responsible of // black generation. This LUT will be searched in inverse order.
bp.LabK2cmyk = _cmsReadInputLUT(hLastProfile, INTENT_RELATIVE_COLORIMETRIC); if (bp.LabK2cmyk == NULL) goto Cleanup;
// Get total area coverage (in 0..1 domain)
bp.MaxTAC = cmsDetectTAC(hLastProfile) / 100.0; if (bp.MaxTAC <= 0) goto Cleanup;
// Create a LUT holding normal ICC transform
bp.cmyk2cmyk = DefaultICCintents(ContextID,
preservationProfilesCount,
ICCIntents,
hProfiles,
BPC,
AdaptationStates,
dwFlags); if (bp.cmyk2cmyk == NULL) goto Cleanup;
// Now the tone curve
bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, preservationProfilesCount,
ICCIntents,
hProfiles,
BPC,
AdaptationStates,
dwFlags); if (bp.KTone == NULL) goto Cleanup;
// To measure the output, Last profile to Lab
hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
bp.hProofOutput = cmsCreateTransformTHR(ContextID, hLastProfile,
CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
INTENT_RELATIVE_COLORIMETRIC,
cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); if ( bp.hProofOutput == NULL) goto Cleanup;
// Same as anterior, but lab in the 0..1 range
bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hLastProfile,
FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
INTENT_RELATIVE_COLORIMETRIC,
cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); if (bp.cmyk2Lab == NULL) goto Cleanup;
cmsCloseProfile(hLab);
// Insert possible devicelinks at the end for (i = lastProfilePos + 1; i < nProfiles; i++)
{
cmsPipeline* devlink = _cmsReadDevicelinkLUT(hProfiles[i], ICCIntents[i]); if (devlink == NULL) goto Cleanup;
if (!cmsPipelineCat(Result, devlink)) goto Cleanup;
}
Cleanup:
if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk); if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab); if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
if (bp.KTone) cmsFreeToneCurve(bp.KTone); if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
return Result;
}
// Link routines ------------------------------------------------------------------------------------------------------
// Chain several profiles into a single LUT. It just checks the parameters and then calls the handler // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
cmsUInt32Number nProfiles,
cmsUInt32Number TheIntents[],
cmsHPROFILE hProfiles[],
cmsBool BPC[],
cmsFloat64Number AdaptationStates[],
cmsUInt32Number dwFlags)
{
cmsUInt32Number i;
cmsIntentsList* Intent;
// Make sure a reasonable number of profiles is provided if (nProfiles <= 0 || nProfiles > 255) {
cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles); return NULL;
}
for (i=0; i < nProfiles; i++) {
// Check if black point is really needed or allowed. Note that // following Adobe's document: // BPC does not apply to devicelink profiles, nor to abs colorimetric, // and applies always on V4 perceptual and saturation.
if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
BPC[i] = FALSE;
if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
// Force BPC for V4 profiles in perceptual and saturation if (cmsGetEncodedICCversion(hProfiles[i]) >= 0x4000000)
BPC[i] = TRUE;
}
}
// Search for a handler. The first intent in the chain defines the handler. That would // prevent using multiple custom intents in a multiintent chain, but the behaviour of // this case would present some issues if the custom intent tries to do things like // preserve primaries. This solution is not perfect, but works well on most cases.
// Get information about available intents. nMax is the maximum space for the supplied "Codes" // and "Descriptions" the function returns the total number of intents, which may be greater // than nMax, although the matrices are not populated beyond this level.
cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
{
_cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
cmsIntentsList* pt;
cmsUInt32Number nIntents;
for (nIntents=0, pt = ctx->Intents; pt != NULL; pt = pt -> Next)
{ if (nIntents < nMax) { if (Codes != NULL)
Codes[nIntents] = pt ->Intent;
if (Descriptions != NULL)
Descriptions[nIntents] = pt ->Description;
}
nIntents++;
}
for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next)
{ if (nIntents < nMax) { if (Codes != NULL)
Codes[nIntents] = pt ->Intent;
if (Descriptions != NULL)
Descriptions[nIntents] = pt ->Description;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.