Path: blob/master/src/java.desktop/share/native/liblcms/cmscnvrt.c
41152 views
/*1* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.2*3* This code is free software; you can redistribute it and/or modify it4* under the terms of the GNU General Public License version 2 only, as5* published by the Free Software Foundation. Oracle designates this6* particular file as subject to the "Classpath" exception as provided7* by Oracle in the LICENSE file that accompanied this code.8*9* This code is distributed in the hope that it will be useful, but WITHOUT10* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or11* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License12* version 2 for more details (a copy is included in the LICENSE file that13* accompanied this code).14*15* You should have received a copy of the GNU General Public License version16* 2 along with this work; if not, write to the Free Software Foundation,17* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.18*19* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA20* or visit www.oracle.com if you need additional information or have any21* questions.22*/2324// This file is available under and governed by the GNU General Public25// License version 2 only, as published by the Free Software Foundation.26// However, the following notice accompanied the original version of this27// file:28//29//---------------------------------------------------------------------------------30//31// Little Color Management System32// Copyright (c) 1998-2020 Marti Maria Saguer33//34// Permission is hereby granted, free of charge, to any person obtaining35// a copy of this software and associated documentation files (the "Software"),36// to deal in the Software without restriction, including without limitation37// the rights to use, copy, modify, merge, publish, distribute, sublicense,38// and/or sell copies of the Software, and to permit persons to whom the Software39// is furnished to do so, subject to the following conditions:40//41// The above copyright notice and this permission notice shall be included in42// all copies or substantial portions of the Software.43//44// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,45// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO46// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND47// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE48// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION49// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION50// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.51//52//---------------------------------------------------------------------------------53//5455#include "lcms2_internal.h"565758// This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.59// Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric60static61cmsPipeline* DefaultICCintents(cmsContext ContextID,62cmsUInt32Number nProfiles,63cmsUInt32Number Intents[],64cmsHPROFILE hProfiles[],65cmsBool BPC[],66cmsFloat64Number AdaptationStates[],67cmsUInt32Number dwFlags);6869//---------------------------------------------------------------------------------7071// This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile72// to do the trick (no devicelinks allowed at that position)73static74cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,75cmsUInt32Number nProfiles,76cmsUInt32Number Intents[],77cmsHPROFILE hProfiles[],78cmsBool BPC[],79cmsFloat64Number AdaptationStates[],80cmsUInt32Number dwFlags);8182//---------------------------------------------------------------------------------8384// This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile85// to do the trick (no devicelinks allowed at that position)86static87cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,88cmsUInt32Number nProfiles,89cmsUInt32Number Intents[],90cmsHPROFILE hProfiles[],91cmsBool BPC[],92cmsFloat64Number AdaptationStates[],93cmsUInt32Number dwFlags);9495//---------------------------------------------------------------------------------969798// This is a structure holding implementations for all supported intents.99typedef struct _cms_intents_list {100101cmsUInt32Number Intent;102char Description[256];103cmsIntentFn Link;104struct _cms_intents_list* Next;105106} cmsIntentsList;107108109// Built-in intents110static cmsIntentsList DefaultIntents[] = {111112{ INTENT_PERCEPTUAL, "Perceptual", DefaultICCintents, &DefaultIntents[1] },113{ INTENT_RELATIVE_COLORIMETRIC, "Relative colorimetric", DefaultICCintents, &DefaultIntents[2] },114{ INTENT_SATURATION, "Saturation", DefaultICCintents, &DefaultIntents[3] },115{ INTENT_ABSOLUTE_COLORIMETRIC, "Absolute colorimetric", DefaultICCintents, &DefaultIntents[4] },116{ INTENT_PRESERVE_K_ONLY_PERCEPTUAL, "Perceptual preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[5] },117{ INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[6] },118{ INTENT_PRESERVE_K_ONLY_SATURATION, "Saturation preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[7] },119{ INTENT_PRESERVE_K_PLANE_PERCEPTUAL, "Perceptual preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[8] },120{ INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] },121{ INTENT_PRESERVE_K_PLANE_SATURATION, "Saturation preserving black plane", BlackPreservingKPlaneIntents, NULL }122};123124125// A pointer to the beginning of the list126_cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };127128// Duplicates the zone of memory used by the plug-in in the new context129static130void DupPluginIntentsList(struct _cmsContext_struct* ctx,131const struct _cmsContext_struct* src)132{133_cmsIntentsPluginChunkType newHead = { NULL };134cmsIntentsList* entry;135cmsIntentsList* Anterior = NULL;136_cmsIntentsPluginChunkType* head = (_cmsIntentsPluginChunkType*) src->chunks[IntentPlugin];137138// Walk the list copying all nodes139for (entry = head->Intents;140entry != NULL;141entry = entry ->Next) {142143cmsIntentsList *newEntry = ( cmsIntentsList *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(cmsIntentsList));144145if (newEntry == NULL)146return;147148// We want to keep the linked list order, so this is a little bit tricky149newEntry -> Next = NULL;150if (Anterior)151Anterior -> Next = newEntry;152153Anterior = newEntry;154155if (newHead.Intents == NULL)156newHead.Intents = newEntry;157}158159ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsIntentsPluginChunkType));160}161162void _cmsAllocIntentsPluginChunk(struct _cmsContext_struct* ctx,163const struct _cmsContext_struct* src)164{165if (src != NULL) {166167// Copy all linked list168DupPluginIntentsList(ctx, src);169}170else {171static _cmsIntentsPluginChunkType IntentsPluginChunkType = { NULL };172ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx ->MemPool, &IntentsPluginChunkType, sizeof(_cmsIntentsPluginChunkType));173}174}175176177// Search the list for a suitable intent. Returns NULL if not found178static179cmsIntentsList* SearchIntent(cmsContext ContextID, cmsUInt32Number Intent)180{181_cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);182cmsIntentsList* pt;183184for (pt = ctx -> Intents; pt != NULL; pt = pt -> Next)185if (pt ->Intent == Intent) return pt;186187for (pt = DefaultIntents; pt != NULL; pt = pt -> Next)188if (pt ->Intent == Intent) return pt;189190return NULL;191}192193// Black point compensation. Implemented as a linear scaling in XYZ. Black points194// should come relative to the white point. Fills an matrix/offset element m195// which is organized as a 4x4 matrix.196static197void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,198const cmsCIEXYZ* BlackPointOut,199cmsMAT3* m, cmsVEC3* off)200{201cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;202203// Now we need to compute a matrix plus an offset m and of such of204// [m]*bpin + off = bpout205// [m]*D50 + off = D50206//207// This is a linear scaling in the form ax+b, where208// a = (bpout - D50) / (bpin - D50)209// b = - D50* (bpout - bpin) / (bpin - D50)210211tx = BlackPointIn->X - cmsD50_XYZ()->X;212ty = BlackPointIn->Y - cmsD50_XYZ()->Y;213tz = BlackPointIn->Z - cmsD50_XYZ()->Z;214215ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx;216ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty;217az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz;218219bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx;220by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;221bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;222223_cmsVEC3init(&m ->v[0], ax, 0, 0);224_cmsVEC3init(&m ->v[1], 0, ay, 0);225_cmsVEC3init(&m ->v[2], 0, 0, az);226_cmsVEC3init(off, bx, by, bz);227228}229230231// Approximate a blackbody illuminant based on CHAD information232static233cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)234{235// Convert D50 across inverse CHAD to get the absolute white point236cmsVEC3 d, s;237cmsCIEXYZ Dest;238cmsCIExyY DestChromaticity;239cmsFloat64Number TempK;240cmsMAT3 m1, m2;241242m1 = *Chad;243if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;244245s.n[VX] = cmsD50_XYZ() -> X;246s.n[VY] = cmsD50_XYZ() -> Y;247s.n[VZ] = cmsD50_XYZ() -> Z;248249_cmsMAT3eval(&d, &m2, &s);250251Dest.X = d.n[VX];252Dest.Y = d.n[VY];253Dest.Z = d.n[VZ];254255cmsXYZ2xyY(&DestChromaticity, &Dest);256257if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity))258return -1.0;259260return TempK;261}262263// Compute a CHAD based on a given temperature264static265void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)266{267cmsCIEXYZ White;268cmsCIExyY ChromaticityOfWhite;269270cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);271cmsxyY2XYZ(&White, &ChromaticityOfWhite);272_cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ());273}274275// Join scalings to obtain relative input to absolute and then to relative output.276// Result is stored in a 3x3 matrix277static278cmsBool ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,279const cmsCIEXYZ* WhitePointIn,280const cmsMAT3* ChromaticAdaptationMatrixIn,281const cmsCIEXYZ* WhitePointOut,282const cmsMAT3* ChromaticAdaptationMatrixOut,283cmsMAT3* m)284{285cmsMAT3 Scale, m1, m2, m3, m4;286287// TODO: Follow Marc Mahy's recommendation to check if CHAD is same by using M1*M2 == M2*M1. If so, do nothing.288// TODO: Add support for ArgyllArts tag289290// Adaptation state291if (AdaptationState == 1.0) {292293// Observer is fully adapted. Keep chromatic adaptation.294// That is the standard V4 behaviour295_cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);296_cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);297_cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);298299}300else {301302// Incomplete adaptation. This is an advanced feature.303_cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);304_cmsVEC3init(&Scale.v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);305_cmsVEC3init(&Scale.v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);306307308if (AdaptationState == 0.0) {309310m1 = *ChromaticAdaptationMatrixOut;311_cmsMAT3per(&m2, &m1, &Scale);312// m2 holds CHAD from output white to D50 times abs. col. scaling313314// Observer is not adapted, undo the chromatic adaptation315_cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);316317m3 = *ChromaticAdaptationMatrixIn;318if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;319_cmsMAT3per(m, &m2, &m4);320321} else {322323cmsMAT3 MixedCHAD;324cmsFloat64Number TempSrc, TempDest, Temp;325326m1 = *ChromaticAdaptationMatrixIn;327if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;328_cmsMAT3per(&m3, &m2, &Scale);329// m3 holds CHAD from input white to D50 times abs. col. scaling330331TempSrc = CHAD2Temp(ChromaticAdaptationMatrixIn);332TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut);333334if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong335336if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {337338_cmsMAT3identity(m);339return TRUE;340}341342Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;343344// Get a CHAD from whatever output temperature to D50. This replaces output CHAD345Temp2CHAD(&MixedCHAD, Temp);346347_cmsMAT3per(m, &m3, &MixedCHAD);348}349350}351return TRUE;352353}354355// Just to see if m matrix should be applied356static357cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)358{359cmsFloat64Number diff = 0;360cmsMAT3 Ident;361int i;362363if (m == NULL && off == NULL) return TRUE; // NULL is allowed as an empty layer364if (m == NULL && off != NULL) return FALSE; // This is an internal error365366_cmsMAT3identity(&Ident);367368for (i=0; i < 3*3; i++)369diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);370371for (i=0; i < 3; i++)372diff += fabs(((cmsFloat64Number*)off)[i]);373374375return (diff < 0.002);376}377378379// Compute the conversion layer380static381cmsBool ComputeConversion(cmsUInt32Number i,382cmsHPROFILE hProfiles[],383cmsUInt32Number Intent,384cmsBool BPC,385cmsFloat64Number AdaptationState,386cmsMAT3* m, cmsVEC3* off)387{388389int k;390391// m and off are set to identity and this is detected latter on392_cmsMAT3identity(m);393_cmsVEC3init(off, 0, 0, 0);394395// If intent is abs. colorimetric,396if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {397398cmsCIEXYZ WhitePointIn, WhitePointOut;399cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;400401_cmsReadMediaWhitePoint(&WhitePointIn, hProfiles[i-1]);402_cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);403404_cmsReadMediaWhitePoint(&WhitePointOut, hProfiles[i]);405_cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);406407if (!ComputeAbsoluteIntent(AdaptationState,408&WhitePointIn, &ChromaticAdaptationMatrixIn,409&WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;410411}412else {413// Rest of intents may apply BPC.414415if (BPC) {416417cmsCIEXYZ BlackPointIn, BlackPointOut;418419cmsDetectBlackPoint(&BlackPointIn, hProfiles[i-1], Intent, 0);420cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);421422// If black points are equal, then do nothing423if (BlackPointIn.X != BlackPointOut.X ||424BlackPointIn.Y != BlackPointOut.Y ||425BlackPointIn.Z != BlackPointOut.Z)426ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);427}428}429430// Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,431// to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so432// we have first to convert from encoded to XYZ and then convert back to encoded.433// y = Mx + Off434// x = x'c435// y = M x'c + Off436// y = y'c; y' = y / c437// y' = (Mx'c + Off) /c = Mx' + (Off / c)438439for (k=0; k < 3; k++) {440off ->n[k] /= MAX_ENCODEABLE_XYZ;441}442443return TRUE;444}445446447// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space448static449cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)450{451cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;452cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;453454// Handle PCS mismatches. A specialized stage is added to the LUT in such case455switch (InPCS) {456457case cmsSigXYZData: // Input profile operates in XYZ458459switch (OutPCS) {460461case cmsSigXYZData: // XYZ -> XYZ462if (!IsEmptyLayer(m, off) &&463!cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))464return FALSE;465break;466467case cmsSigLabData: // XYZ -> Lab468if (!IsEmptyLayer(m, off) &&469!cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))470return FALSE;471if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))472return FALSE;473break;474475default:476return FALSE; // Colorspace mismatch477}478break;479480case cmsSigLabData: // Input profile operates in Lab481482switch (OutPCS) {483484case cmsSigXYZData: // Lab -> XYZ485486if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)))487return FALSE;488if (!IsEmptyLayer(m, off) &&489!cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))490return FALSE;491break;492493case cmsSigLabData: // Lab -> Lab494495if (!IsEmptyLayer(m, off)) {496if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) ||497!cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) ||498!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))499return FALSE;500}501break;502503default:504return FALSE; // Mismatch505}506break;507508// On colorspaces other than PCS, check for same space509default:510if (InPCS != OutPCS) return FALSE;511break;512}513514return TRUE;515}516517518// Is a given space compatible with another?519static520cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)521{522// If they are same, they are compatible.523if (a == b) return TRUE;524525// Check for MCH4 substitution of CMYK526if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;527if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;528529// Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.530if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;531if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;532533return FALSE;534}535536537// Default handler for ICC-style intents538static539cmsPipeline* DefaultICCintents(cmsContext ContextID,540cmsUInt32Number nProfiles,541cmsUInt32Number TheIntents[],542cmsHPROFILE hProfiles[],543cmsBool BPC[],544cmsFloat64Number AdaptationStates[],545cmsUInt32Number dwFlags)546{547cmsPipeline* Lut = NULL;548cmsPipeline* Result;549cmsHPROFILE hProfile;550cmsMAT3 m;551cmsVEC3 off;552cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut = cmsSigLabData, CurrentColorSpace;553cmsProfileClassSignature ClassSig;554cmsUInt32Number i, Intent;555556// For safety557if (nProfiles == 0) return NULL;558559// Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'560Result = cmsPipelineAlloc(ContextID, 0, 0);561if (Result == NULL) return NULL;562563CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);564565for (i=0; i < nProfiles; i++) {566567cmsBool lIsDeviceLink, lIsInput;568569hProfile = hProfiles[i];570ClassSig = cmsGetDeviceClass(hProfile);571lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );572573// First profile is used as input unless devicelink or abstract574if ((i == 0) && !lIsDeviceLink) {575lIsInput = TRUE;576}577else {578// Else use profile in the input direction if current space is not PCS579lIsInput = (CurrentColorSpace != cmsSigXYZData) &&580(CurrentColorSpace != cmsSigLabData);581}582583Intent = TheIntents[i];584585if (lIsInput || lIsDeviceLink) {586587ColorSpaceIn = cmsGetColorSpace(hProfile);588ColorSpaceOut = cmsGetPCS(hProfile);589}590else {591592ColorSpaceIn = cmsGetPCS(hProfile);593ColorSpaceOut = cmsGetColorSpace(hProfile);594}595596if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {597598cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");599goto Error;600}601602// If devicelink is found, then no custom intent is allowed and we can603// read the LUT to be applied. Settings don't apply here.604if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {605606// Get the involved LUT from the profile607Lut = _cmsReadDevicelinkLUT(hProfile, Intent);608if (Lut == NULL) goto Error;609610// What about abstract profiles?611if (ClassSig == cmsSigAbstractClass && i > 0) {612if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;613}614else {615_cmsMAT3identity(&m);616_cmsVEC3init(&off, 0, 0, 0);617}618619620if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;621622}623else {624625if (lIsInput) {626// Input direction means non-pcs connection, so proceed like devicelinks627Lut = _cmsReadInputLUT(hProfile, Intent);628if (Lut == NULL) goto Error;629}630else {631632// Output direction means PCS connection. Intent may apply here633Lut = _cmsReadOutputLUT(hProfile, Intent);634if (Lut == NULL) goto Error;635636637if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;638if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;639640}641}642643// Concatenate to the output LUT644if (!cmsPipelineCat(Result, Lut))645goto Error;646647cmsPipelineFree(Lut);648Lut = NULL;649650// Update current space651CurrentColorSpace = ColorSpaceOut;652}653654// Check for non-negatives clip655if (dwFlags & cmsFLAGS_NONEGATIVES) {656657if (ColorSpaceOut == cmsSigGrayData ||658ColorSpaceOut == cmsSigRgbData ||659ColorSpaceOut == cmsSigCmykData) {660661cmsStage* clip = _cmsStageClipNegatives(Result->ContextID, cmsChannelsOf(ColorSpaceOut));662if (clip == NULL) goto Error;663664if (!cmsPipelineInsertStage(Result, cmsAT_END, clip))665goto Error;666}667668}669670return Result;671672Error:673674if (Lut != NULL) cmsPipelineFree(Lut);675if (Result != NULL) cmsPipelineFree(Result);676return NULL;677678cmsUNUSED_PARAMETER(dwFlags);679}680681682// Wrapper for DLL calling convention683cmsPipeline* CMSEXPORT _cmsDefaultICCintents(cmsContext ContextID,684cmsUInt32Number nProfiles,685cmsUInt32Number TheIntents[],686cmsHPROFILE hProfiles[],687cmsBool BPC[],688cmsFloat64Number AdaptationStates[],689cmsUInt32Number dwFlags)690{691return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);692}693694// Black preserving intents ---------------------------------------------------------------------------------------------695696// Translate black-preserving intents to ICC ones697static698cmsUInt32Number TranslateNonICCIntents(cmsUInt32Number Intent)699{700switch (Intent) {701case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:702case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:703return INTENT_PERCEPTUAL;704705case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:706case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:707return INTENT_RELATIVE_COLORIMETRIC;708709case INTENT_PRESERVE_K_ONLY_SATURATION:710case INTENT_PRESERVE_K_PLANE_SATURATION:711return INTENT_SATURATION;712713default: return Intent;714}715}716717// Sampler for Black-only preserving CMYK->CMYK transforms718719typedef struct {720cmsPipeline* cmyk2cmyk; // The original transform721cmsToneCurve* KTone; // Black-to-black tone curve722723} GrayOnlyParams;724725726// Preserve black only if that is the only ink used727static728int BlackPreservingGrayOnlySampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)729{730GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;731732// If going across black only, keep black only733if (In[0] == 0 && In[1] == 0 && In[2] == 0) {734735// TAC does not apply because it is black ink!736Out[0] = Out[1] = Out[2] = 0;737Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);738return TRUE;739}740741// Keep normal transform for other colors742bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);743return TRUE;744}745746// This is the entry for black-preserving K-only intents, which are non-ICC747static748cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,749cmsUInt32Number nProfiles,750cmsUInt32Number TheIntents[],751cmsHPROFILE hProfiles[],752cmsBool BPC[],753cmsFloat64Number AdaptationStates[],754cmsUInt32Number dwFlags)755{756GrayOnlyParams bp;757cmsPipeline* Result;758cmsUInt32Number ICCIntents[256];759cmsStage* CLUT;760cmsUInt32Number i, nGridPoints;761cmsUInt32Number lastProfilePos;762cmsUInt32Number preservationProfilesCount;763cmsHPROFILE hLastProfile;764765766// Sanity check767if (nProfiles < 1 || nProfiles > 255) return NULL;768769// Translate black-preserving intents to ICC ones770for (i=0; i < nProfiles; i++)771ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);772773774// Trim all CMYK devicelinks at the end775lastProfilePos = nProfiles - 1;776hLastProfile = hProfiles[lastProfilePos];777778while (lastProfilePos > 1)779{780hLastProfile = hProfiles[--lastProfilePos];781if (cmsGetColorSpace(hLastProfile) != cmsSigCmykData ||782cmsGetDeviceClass(hLastProfile) != cmsSigLinkClass)783break;784}785786preservationProfilesCount = lastProfilePos + 1;787788// Check for non-cmyk profiles789if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||790!(cmsGetColorSpace(hLastProfile) == cmsSigCmykData ||791cmsGetDeviceClass(hLastProfile) == cmsSigOutputClass))792return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);793794// Allocate an empty LUT for holding the result795Result = cmsPipelineAlloc(ContextID, 4, 4);796if (Result == NULL) return NULL;797798memset(&bp, 0, sizeof(bp));799800// Create a LUT holding normal ICC transform801bp.cmyk2cmyk = DefaultICCintents(ContextID,802preservationProfilesCount,803ICCIntents,804hProfiles,805BPC,806AdaptationStates,807dwFlags);808809if (bp.cmyk2cmyk == NULL) goto Error;810811// Now, compute the tone curve812bp.KTone = _cmsBuildKToneCurve(ContextID,8134096,814preservationProfilesCount,815ICCIntents,816hProfiles,817BPC,818AdaptationStates,819dwFlags);820821if (bp.KTone == NULL) goto Error;822823824// How many gridpoints are we going to use?825nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);826827// Create the CLUT. 16 bits828CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);829if (CLUT == NULL) goto Error;830831// This is the one and only MPE in this LUT832if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))833goto Error;834835// Sample it. We cannot afford pre/post linearization this time.836if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))837goto Error;838839840// Insert possible devicelinks at the end841for (i = lastProfilePos + 1; i < nProfiles; i++)842{843cmsPipeline* devlink = _cmsReadDevicelinkLUT(hProfiles[i], ICCIntents[i]);844if (devlink == NULL)845goto Error;846847if (!cmsPipelineCat(Result, devlink))848goto Error;849}850851852// Get rid of xform and tone curve853cmsPipelineFree(bp.cmyk2cmyk);854cmsFreeToneCurve(bp.KTone);855856return Result;857858Error:859860if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);861if (bp.KTone != NULL) cmsFreeToneCurve(bp.KTone);862if (Result != NULL) cmsPipelineFree(Result);863return NULL;864865}866867// K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------868869typedef struct {870871cmsPipeline* cmyk2cmyk; // The original transform872cmsHTRANSFORM hProofOutput; // Output CMYK to Lab (last profile)873cmsHTRANSFORM cmyk2Lab; // The input chain874cmsToneCurve* KTone; // Black-to-black tone curve875cmsPipeline* LabK2cmyk; // The output profile876cmsFloat64Number MaxError;877878cmsHTRANSFORM hRoundTrip;879cmsFloat64Number MaxTAC;880881882} PreserveKPlaneParams;883884885// The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision886static887int BlackPreservingSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)888{889int i;890cmsFloat32Number Inf[4], Outf[4];891cmsFloat32Number LabK[4];892cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;893cmsCIELab ColorimetricLab, BlackPreservingLab;894PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;895896// Convert from 16 bits to floating point897for (i=0; i < 4; i++)898Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);899900// Get the K across Tone curve901LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);902903// If going across black only, keep black only904if (In[0] == 0 && In[1] == 0 && In[2] == 0) {905906Out[0] = Out[1] = Out[2] = 0;907Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);908return TRUE;909}910911// Try the original transform,912cmsPipelineEvalFloat(Inf, Outf, bp ->cmyk2cmyk);913914// Store a copy of the floating point result into 16-bit915for (i=0; i < 4; i++)916Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);917918// Maybe K is already ok (mostly on K=0)919if (fabsf(Outf[3] - LabK[3]) < (3.0 / 65535.0)) {920return TRUE;921}922923// K differ, measure and keep Lab measurement for further usage924// this is done in relative colorimetric intent925cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);926927// Is not black only and the transform doesn't keep black.928// Obtain the Lab of output CMYK. After that we have Lab + K929cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);930931// Obtain the corresponding CMY using reverse interpolation932// (K is fixed in LabK[3])933if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {934935// Cannot find a suitable value, so use colorimetric xform936// which is already stored in Out[]937return TRUE;938}939940// Make sure to pass through K (which now is fixed)941Outf[3] = LabK[3];942943// Apply TAC if needed944SumCMY = (cmsFloat64Number) Outf[0] + Outf[1] + Outf[2];945SumCMYK = SumCMY + Outf[3];946947if (SumCMYK > bp ->MaxTAC) {948949Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);950if (Ratio < 0)951Ratio = 0;952}953else954Ratio = 1.0;955956Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0); // C957Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0); // M958Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0); // Y959Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);960961// Estimate the error (this goes 16 bits to Lab DBL)962cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);963Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);964if (Error > bp -> MaxError)965bp->MaxError = Error;966967return TRUE;968}969970971972// This is the entry for black-plane preserving, which are non-ICC973static974cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,975cmsUInt32Number nProfiles,976cmsUInt32Number TheIntents[],977cmsHPROFILE hProfiles[],978cmsBool BPC[],979cmsFloat64Number AdaptationStates[],980cmsUInt32Number dwFlags)981{982PreserveKPlaneParams bp;983984cmsPipeline* Result = NULL;985cmsUInt32Number ICCIntents[256];986cmsStage* CLUT;987cmsUInt32Number i, nGridPoints;988cmsUInt32Number lastProfilePos;989cmsUInt32Number preservationProfilesCount;990cmsHPROFILE hLastProfile;991cmsHPROFILE hLab;992993// Sanity check994if (nProfiles < 1 || nProfiles > 255) return NULL;995996// Translate black-preserving intents to ICC ones997for (i=0; i < nProfiles; i++)998ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);9991000// Trim all CMYK devicelinks at the end1001lastProfilePos = nProfiles - 1;1002hLastProfile = hProfiles[lastProfilePos];10031004while (lastProfilePos > 1)1005{1006hLastProfile = hProfiles[--lastProfilePos];1007if (cmsGetColorSpace(hLastProfile) != cmsSigCmykData ||1008cmsGetDeviceClass(hLastProfile) != cmsSigLinkClass)1009break;1010}10111012preservationProfilesCount = lastProfilePos + 1;10131014// Check for non-cmyk profiles1015if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||1016!(cmsGetColorSpace(hLastProfile) == cmsSigCmykData ||1017cmsGetDeviceClass(hLastProfile) == cmsSigOutputClass))1018return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);10191020// Allocate an empty LUT for holding the result1021Result = cmsPipelineAlloc(ContextID, 4, 4);1022if (Result == NULL) return NULL;10231024memset(&bp, 0, sizeof(bp));10251026// We need the input LUT of the last profile, assuming this one is responsible of1027// black generation. This LUT will be searched in inverse order.1028bp.LabK2cmyk = _cmsReadInputLUT(hLastProfile, INTENT_RELATIVE_COLORIMETRIC);1029if (bp.LabK2cmyk == NULL) goto Cleanup;10301031// Get total area coverage (in 0..1 domain)1032bp.MaxTAC = cmsDetectTAC(hLastProfile) / 100.0;1033if (bp.MaxTAC <= 0) goto Cleanup;103410351036// Create a LUT holding normal ICC transform1037bp.cmyk2cmyk = DefaultICCintents(ContextID,1038preservationProfilesCount,1039ICCIntents,1040hProfiles,1041BPC,1042AdaptationStates,1043dwFlags);1044if (bp.cmyk2cmyk == NULL) goto Cleanup;10451046// Now the tone curve1047bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, preservationProfilesCount,1048ICCIntents,1049hProfiles,1050BPC,1051AdaptationStates,1052dwFlags);1053if (bp.KTone == NULL) goto Cleanup;10541055// To measure the output, Last profile to Lab1056hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);1057bp.hProofOutput = cmsCreateTransformTHR(ContextID, hLastProfile,1058CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,1059INTENT_RELATIVE_COLORIMETRIC,1060cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);1061if ( bp.hProofOutput == NULL) goto Cleanup;10621063// Same as anterior, but lab in the 0..1 range1064bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hLastProfile,1065FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,1066FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),1067INTENT_RELATIVE_COLORIMETRIC,1068cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);1069if (bp.cmyk2Lab == NULL) goto Cleanup;1070cmsCloseProfile(hLab);10711072// Error estimation (for debug only)1073bp.MaxError = 0;10741075// How many gridpoints are we going to use?1076nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);107710781079CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);1080if (CLUT == NULL) goto Cleanup;10811082if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))1083goto Cleanup;10841085cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);10861087// Insert possible devicelinks at the end1088for (i = lastProfilePos + 1; i < nProfiles; i++)1089{1090cmsPipeline* devlink = _cmsReadDevicelinkLUT(hProfiles[i], ICCIntents[i]);1091if (devlink == NULL)1092goto Cleanup;10931094if (!cmsPipelineCat(Result, devlink))1095goto Cleanup;1096}109710981099Cleanup:11001101if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);1102if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);1103if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);11041105if (bp.KTone) cmsFreeToneCurve(bp.KTone);1106if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);11071108return Result;1109}1110111111121113// Link routines ------------------------------------------------------------------------------------------------------11141115// Chain several profiles into a single LUT. It just checks the parameters and then calls the handler1116// for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the1117// rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.1118cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,1119cmsUInt32Number nProfiles,1120cmsUInt32Number TheIntents[],1121cmsHPROFILE hProfiles[],1122cmsBool BPC[],1123cmsFloat64Number AdaptationStates[],1124cmsUInt32Number dwFlags)1125{1126cmsUInt32Number i;1127cmsIntentsList* Intent;11281129// Make sure a reasonable number of profiles is provided1130if (nProfiles <= 0 || nProfiles > 255) {1131cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);1132return NULL;1133}11341135for (i=0; i < nProfiles; i++) {11361137// Check if black point is really needed or allowed. Note that1138// following Adobe's document:1139// BPC does not apply to devicelink profiles, nor to abs colorimetric,1140// and applies always on V4 perceptual and saturation.11411142if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)1143BPC[i] = FALSE;11441145if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {11461147// Force BPC for V4 profiles in perceptual and saturation1148if (cmsGetEncodedICCversion(hProfiles[i]) >= 0x4000000)1149BPC[i] = TRUE;1150}1151}11521153// Search for a handler. The first intent in the chain defines the handler. That would1154// prevent using multiple custom intents in a multiintent chain, but the behaviour of1155// this case would present some issues if the custom intent tries to do things like1156// preserve primaries. This solution is not perfect, but works well on most cases.11571158Intent = SearchIntent(ContextID, TheIntents[0]);1159if (Intent == NULL) {1160cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);1161return NULL;1162}11631164// Call the handler1165return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);1166}11671168// -------------------------------------------------------------------------------------------------11691170// Get information about available intents. nMax is the maximum space for the supplied "Codes"1171// and "Descriptions" the function returns the total number of intents, which may be greater1172// than nMax, although the matrices are not populated beyond this level.1173cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)1174{1175_cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);1176cmsIntentsList* pt;1177cmsUInt32Number nIntents;117811791180for (nIntents=0, pt = ctx->Intents; pt != NULL; pt = pt -> Next)1181{1182if (nIntents < nMax) {1183if (Codes != NULL)1184Codes[nIntents] = pt ->Intent;11851186if (Descriptions != NULL)1187Descriptions[nIntents] = pt ->Description;1188}11891190nIntents++;1191}11921193for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next)1194{1195if (nIntents < nMax) {1196if (Codes != NULL)1197Codes[nIntents] = pt ->Intent;11981199if (Descriptions != NULL)1200Descriptions[nIntents] = pt ->Description;1201}12021203nIntents++;1204}1205return nIntents;1206}12071208cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)1209{1210return cmsGetSupportedIntentsTHR(NULL, nMax, Codes, Descriptions);1211}12121213// The plug-in registration. User can add new intents or override default routines1214cmsBool _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)1215{1216_cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin);1217cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;1218cmsIntentsList* fl;12191220// Do we have to reset the custom intents?1221if (Data == NULL) {12221223ctx->Intents = NULL;1224return TRUE;1225}12261227fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));1228if (fl == NULL) return FALSE;122912301231fl ->Intent = Plugin ->Intent;1232strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1);1233fl ->Description[sizeof(fl ->Description)-1] = 0;12341235fl ->Link = Plugin ->Link;12361237fl ->Next = ctx ->Intents;1238ctx ->Intents = fl;12391240return TRUE;1241}1242124312441245