1ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//---------------------------------------------------------------------------------
2ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
3ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//  Little Color Management System
4ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//  Copyright (c) 1998-2012 Marti Maria Saguer
5ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
6ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Permission is hereby granted, free of charge, to any person obtaining
7ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// a copy of this software and associated documentation files (the "Software"),
8ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// to deal in the Software without restriction, including without limitation
9ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// the rights to use, copy, modify, merge, publish, distribute, sublicense,
10ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// and/or sell copies of the Software, and to permit persons to whom the Software
11ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// is furnished to do so, subject to the following conditions:
12ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
13ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// The above copyright notice and this permission notice shall be included in
14ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// all copies or substantial portions of the Software.
15ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
16ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
24ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//---------------------------------------------------------------------------------
25ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//
26ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
27ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov#include "lcms2_internal.h"
28ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
29ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
30ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point
31ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// compensation and Adaptation parameters may vary across profiles. BPC and Adaptation refers to the PCS
32ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// after the profile. I.e, BPC[0] refers to connexion between profile(0) and profile(1)
33ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
34ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsUInt32Number nProfiles,
35ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsUInt32Number Intents[],
36ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsHPROFILE     hProfiles[],
37ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsBool         BPC[],
38ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsFloat64Number AdaptationStates[],
39ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsUInt32Number dwFlags);
40ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
41ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//---------------------------------------------------------------------------------
42ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
43ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.
44ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
45ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
46ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline* DefaultICCintents(cmsContext     ContextID,
47ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsUInt32Number nProfiles,
48ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsUInt32Number Intents[],
49ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsHPROFILE     hProfiles[],
50ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsBool         BPC[],
51ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsFloat64Number AdaptationStates[],
52ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsUInt32Number dwFlags);
53ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
54ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//---------------------------------------------------------------------------------
55ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
56ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile
57ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// to do the trick (no devicelinks allowed at that position)
58ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
59ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
60ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number nProfiles,
61ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number Intents[],
62ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsHPROFILE     hProfiles[],
63ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsBool         BPC[],
64ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsFloat64Number AdaptationStates[],
65ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number dwFlags);
66ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
67ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//---------------------------------------------------------------------------------
68ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
69ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile
70ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// to do the trick (no devicelinks allowed at that position)
71ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
72ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline*  BlackPreservingKPlaneIntents(cmsContext     ContextID,
73ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                           cmsUInt32Number nProfiles,
74ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                           cmsUInt32Number Intents[],
75ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                           cmsHPROFILE     hProfiles[],
76ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                           cmsBool         BPC[],
77ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                           cmsFloat64Number AdaptationStates[],
78ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                           cmsUInt32Number dwFlags);
79ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
80ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov//---------------------------------------------------------------------------------
81ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
82ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
83ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This is a structure holding implementations for all supported intents.
84ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovtypedef struct _cms_intents_list {
85ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
86ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number Intent;
87ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    char            Description[256];
88ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsIntentFn     Link;
89ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    struct _cms_intents_list*  Next;
90ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
91ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov} cmsIntentsList;
92ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
93ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
94ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Built-in intents
95ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic cmsIntentsList DefaultIntents[] = {
96ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
97ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_PERCEPTUAL,                            "Perceptual",                                   DefaultICCintents,            &DefaultIntents[1] },
98ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_RELATIVE_COLORIMETRIC,                 "Relative colorimetric",                        DefaultICCintents,            &DefaultIntents[2] },
99ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_SATURATION,                            "Saturation",                                   DefaultICCintents,            &DefaultIntents[3] },
100ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_ABSOLUTE_COLORIMETRIC,                 "Absolute colorimetric",                        DefaultICCintents,            &DefaultIntents[4] },
101ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_PRESERVE_K_ONLY_PERCEPTUAL,            "Perceptual preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[5] },
102ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink",   BlackPreservingKOnlyIntents,  &DefaultIntents[6] },
103ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_PRESERVE_K_ONLY_SATURATION,            "Saturation preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[7] },
104ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_PRESERVE_K_PLANE_PERCEPTUAL,           "Perceptual preserving black plane",            BlackPreservingKPlaneIntents, &DefaultIntents[8] },
105ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] },
106ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    { INTENT_PRESERVE_K_PLANE_SATURATION,           "Saturation preserving black plane",            BlackPreservingKPlaneIntents, NULL }
107ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov};
108ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
109ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
110ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// A pointer to the begining of the list
111ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov_cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };
112ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
113ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Duplicates the zone of memory used by the plug-in in the new context
114ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
115ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovvoid DupPluginIntentsList(struct _cmsContext_struct* ctx,
116ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                               const struct _cmsContext_struct* src)
117ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
118ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   _cmsIntentsPluginChunkType newHead = { NULL };
119ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   cmsIntentsList*  entry;
120ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   cmsIntentsList*  Anterior = NULL;
121ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   _cmsIntentsPluginChunkType* head = (_cmsIntentsPluginChunkType*) src->chunks[IntentPlugin];
122ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
123ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Walk the list copying all nodes
124ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   for (entry = head->Intents;
125ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        entry != NULL;
126ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        entry = entry ->Next) {
127ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
128ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsIntentsList *newEntry = ( cmsIntentsList *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(cmsIntentsList));
129ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
130ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (newEntry == NULL)
131ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return;
132ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
133ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // We want to keep the linked list order, so this is a little bit tricky
134ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            newEntry -> Next = NULL;
135ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (Anterior)
136ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                Anterior -> Next = newEntry;
137ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
138ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            Anterior = newEntry;
139ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
140ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (newHead.Intents == NULL)
141ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                newHead.Intents = newEntry;
142ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
143ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
144ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov  ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsIntentsPluginChunkType));
145ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
146ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
147ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovvoid  _cmsAllocIntentsPluginChunk(struct _cmsContext_struct* ctx,
148ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         const struct _cmsContext_struct* src)
149ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
150ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (src != NULL) {
151ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
152ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Copy all linked list
153ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        DupPluginIntentsList(ctx, src);
154ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
155ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    else {
156ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        static _cmsIntentsPluginChunkType IntentsPluginChunkType = { NULL };
157ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx ->MemPool, &IntentsPluginChunkType, sizeof(_cmsIntentsPluginChunkType));
158ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
159ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
160ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
161ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
162ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Search the list for a suitable intent. Returns NULL if not found
163ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
164ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsIntentsList* SearchIntent(cmsContext ContextID, cmsUInt32Number Intent)
165ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
166ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
167ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsIntentsList* pt;
168ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
169ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (pt = ctx -> Intents; pt != NULL; pt = pt -> Next)
170ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (pt ->Intent == Intent) return pt;
171ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
172ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (pt = DefaultIntents; pt != NULL; pt = pt -> Next)
173ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (pt ->Intent == Intent) return pt;
174ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
175ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return NULL;
176ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
177ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
178ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Black point compensation. Implemented as a linear scaling in XYZ. Black points
179ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// should come relative to the white point. Fills an matrix/offset element m
180ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// which is organized as a 4x4 matrix.
181ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
182ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovvoid ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,
183ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                   const cmsCIEXYZ* BlackPointOut,
184ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                   cmsMAT3* m, cmsVEC3* off)
185ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
186ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov  cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
187ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
188ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   // Now we need to compute a matrix plus an offset m and of such of
189ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   // [m]*bpin + off = bpout
190ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   // [m]*D50  + off = D50
191ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   //
192ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   // This is a linear scaling in the form ax+b, where
193ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   // a = (bpout - D50) / (bpin - D50)
194ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   // b = - D50* (bpout - bpin) / (bpin - D50)
195ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
196ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   tx = BlackPointIn->X - cmsD50_XYZ()->X;
197ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   ty = BlackPointIn->Y - cmsD50_XYZ()->Y;
198ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   tz = BlackPointIn->Z - cmsD50_XYZ()->Z;
199ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
200ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx;
201ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty;
202ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz;
203ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
204ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx;
205ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;
206ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;
207ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
208ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   _cmsVEC3init(&m ->v[0], ax, 0,  0);
209ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   _cmsVEC3init(&m ->v[1], 0, ay,  0);
210ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   _cmsVEC3init(&m ->v[2], 0,  0,  az);
211ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov   _cmsVEC3init(off, bx, by, bz);
212ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
213ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
214ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
215ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
216ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Approximate a blackbody illuminant based on CHAD information
217ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
218ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
219ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
220ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Convert D50 across inverse CHAD to get the absolute white point
221ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsVEC3 d, s;
222ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIEXYZ Dest;
223ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIExyY DestChromaticity;
224ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number TempK;
225ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsMAT3 m1, m2;
226ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
227ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    m1 = *Chad;
228ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
229ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
230ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    s.n[VX] = cmsD50_XYZ() -> X;
231ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    s.n[VY] = cmsD50_XYZ() -> Y;
232ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    s.n[VZ] = cmsD50_XYZ() -> Z;
233ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
234ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsMAT3eval(&d, &m2, &s);
235ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
236ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Dest.X = d.n[VX];
237ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Dest.Y = d.n[VY];
238ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Dest.Z = d.n[VZ];
239ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
240ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsXYZ2xyY(&DestChromaticity, &Dest);
241ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
242ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity))
243ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return -1.0;
244ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
245ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TempK;
246ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
247ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
248ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Compute a CHAD based on a given temperature
249ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
250ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
251ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
252ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIEXYZ White;
253ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIExyY ChromaticityOfWhite;
254ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
255ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);
256ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsxyY2XYZ(&White, &ChromaticityOfWhite);
257ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ());
258ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
259ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
260ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Join scalings to obtain relative input to absolute and then to relative output.
261ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Result is stored in a 3x3 matrix
262ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
263ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool  ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
264ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               const cmsCIEXYZ* WhitePointIn,
265ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               const cmsMAT3* ChromaticAdaptationMatrixIn,
266ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               const cmsCIEXYZ* WhitePointOut,
267ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               const cmsMAT3* ChromaticAdaptationMatrixOut,
268ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsMAT3* m)
269ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
270ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsMAT3 Scale, m1, m2, m3, m4;
271ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
272ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Adaptation state
273ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (AdaptationState == 1.0) {
274ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
275ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Observer is fully adapted. Keep chromatic adaptation.
276ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // That is the standard V4 behaviour
277ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
278ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
279ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
280ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
281ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
282ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    else  {
283ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
284ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Incomplete adaptation. This is an advanced feature.
285ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
286ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsVEC3init(&Scale.v[1], 0,  WhitePointIn->Y / WhitePointOut->Y, 0);
287ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsVEC3init(&Scale.v[2], 0, 0,  WhitePointIn->Z / WhitePointOut->Z);
288ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
289ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
290ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (AdaptationState == 0.0) {
291ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
292ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            m1 = *ChromaticAdaptationMatrixOut;
293ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            _cmsMAT3per(&m2, &m1, &Scale);
294ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // m2 holds CHAD from output white to D50 times abs. col. scaling
295ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
296ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // Observer is not adapted, undo the chromatic adaptation
297ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            _cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);
298ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
299ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            m3 = *ChromaticAdaptationMatrixIn;
300ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
301ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            _cmsMAT3per(m, &m2, &m4);
302ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
303ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        } else {
304ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
305ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsMAT3 MixedCHAD;
306ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsFloat64Number TempSrc, TempDest, Temp;
307ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
308ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            m1 = *ChromaticAdaptationMatrixIn;
309ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
310ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            _cmsMAT3per(&m3, &m2, &Scale);
311ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // m3 holds CHAD from input white to D50 times abs. col. scaling
312ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
313ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            TempSrc  = CHAD2Temp(ChromaticAdaptationMatrixIn);
314ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut);
315ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
316ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
317ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
318ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {
319ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
320ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                _cmsMAT3identity(m);
321ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return TRUE;
322ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            }
323ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
324ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
325ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
326ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
327ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            Temp2CHAD(&MixedCHAD, Temp);
328ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
329ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            _cmsMAT3per(m, &m3, &MixedCHAD);
330ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
331ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
332ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
333ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
334ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
335ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
336ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
337ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Just to see if m matrix should be applied
338ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
339ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
340ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
341ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number diff = 0;
342ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsMAT3 Ident;
343ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    int i;
344ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
345ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (m == NULL && off == NULL) return TRUE;  // NULL is allowed as an empty layer
346ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (m == NULL && off != NULL) return FALSE; // This is an internal error
347ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
348ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsMAT3identity(&Ident);
349ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
350ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < 3*3; i++)
351ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
352ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
353ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < 3; i++)
354ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        diff += fabs(((cmsFloat64Number*)off)[i]);
355ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
356ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
357ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return (diff < 0.002);
358ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
359ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
360ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
361ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Compute the conversion layer
362ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
363ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
364ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                 cmsUInt32Number Intent,
365ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                 cmsBool BPC,
366ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                 cmsFloat64Number AdaptationState,
367ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                 cmsMAT3* m, cmsVEC3* off)
368ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
369ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
370ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    int k;
371ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
372ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // m  and off are set to identity and this is detected latter on
373ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsMAT3identity(m);
374ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsVEC3init(off, 0, 0, 0);
375ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
376ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // If intent is abs. colorimetric,
377ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
378ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
379ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsCIEXYZ WhitePointIn, WhitePointOut;
380ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
381ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
382ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsReadMediaWhitePoint(&WhitePointIn,  hProfiles[i-1]);
383ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
384ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
385ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsReadMediaWhitePoint(&WhitePointOut,  hProfiles[i]);
386ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
387ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
388ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (!ComputeAbsoluteIntent(AdaptationState,
389ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                  &WhitePointIn,  &ChromaticAdaptationMatrixIn,
390ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                  &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
391ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
392ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
393ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    else {
394ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Rest of intents may apply BPC.
395ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
396ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (BPC) {
397ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
398ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsCIEXYZ BlackPointIn, BlackPointOut;
399ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
400ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsDetectBlackPoint(&BlackPointIn,  hProfiles[i-1], Intent, 0);
401ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
402ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
403ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // If black points are equal, then do nothing
404ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (BlackPointIn.X != BlackPointOut.X ||
405ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                BlackPointIn.Y != BlackPointOut.Y ||
406ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                BlackPointIn.Z != BlackPointOut.Z)
407ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                    ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
408ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
409ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
410ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
411ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
412ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
413ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // we have first to convert from encoded to XYZ and then convert back to encoded.
414ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // y = Mx + Off
415ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // x = x'c
416ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // y = M x'c + Off
417ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // y = y'c; y' = y / c
418ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // y' = (Mx'c + Off) /c = Mx' + (Off / c)
419ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
420ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (k=0; k < 3; k++) {
421ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        off ->n[k] /= MAX_ENCODEABLE_XYZ;
422ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
423ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
424ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
425ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
426ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
427ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
428ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
429ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
430ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
431ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
432ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
433ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
434ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
435ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Handle PCS mismatches. A specialized stage is added to the LUT in such case
436ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    switch (InPCS) {
437ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
438ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    case cmsSigXYZData: // Input profile operates in XYZ
439ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
440ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        switch (OutPCS) {
441ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
442ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case cmsSigXYZData:  // XYZ -> XYZ
443ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!IsEmptyLayer(m, off) &&
444ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
445ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return FALSE;
446ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            break;
447ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
448ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case cmsSigLabData:  // XYZ -> Lab
449ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!IsEmptyLayer(m, off) &&
450ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
451ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return FALSE;
452ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
453ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return FALSE;
454ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            break;
455ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
456ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        default:
457ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return FALSE;   // Colorspace mismatch
458ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
459ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        break;
460ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
461ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    case cmsSigLabData: // Input profile operates in Lab
462ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
463ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        switch (OutPCS) {
464ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
465ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case cmsSigXYZData:  // Lab -> XYZ
466ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
467ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)))
468ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return FALSE;
469ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!IsEmptyLayer(m, off) &&
470ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
471ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                return FALSE;
472ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            break;
473ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
474ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case cmsSigLabData:  // Lab -> Lab
475ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
476ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!IsEmptyLayer(m, off)) {
477ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) ||
478ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                    !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) ||
479ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                    !cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
480ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                    return FALSE;
481ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            }
482ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            break;
483ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
484ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        default:
485ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return FALSE;  // Mismatch
486ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
487ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        break;
488ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
489ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // On colorspaces other than PCS, check for same space
490ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    default:
491ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (InPCS != OutPCS) return FALSE;
492ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        break;
493ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
494ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
495ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
496ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
497ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
498ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
499ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Is a given space compatible with another?
500ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
501ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
502ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
503ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // If they are same, they are compatible.
504ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (a == b) return TRUE;
505ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
506ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Check for MCH4 substitution of CMYK
507ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;
508ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;
509ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
510ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
511ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
512ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
513ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
514ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return FALSE;
515ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
516ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
517ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
518ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Default handler for ICC-style intents
519ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
520ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline* DefaultICCintents(cmsContext       ContextID,
521ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsUInt32Number  nProfiles,
522ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsUInt32Number  TheIntents[],
523ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsHPROFILE      hProfiles[],
524ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsBool          BPC[],
525ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsFloat64Number AdaptationStates[],
526ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                               cmsUInt32Number  dwFlags)
527ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
528ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipeline* Lut = NULL;
529ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipeline* Result;
530ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHPROFILE hProfile;
531ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsMAT3 m;
532ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsVEC3 off;
533ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
534ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsProfileClassSignature ClassSig;
535ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number  i, Intent;
536ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
537ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // For safety
538ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (nProfiles == 0) return NULL;
539ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
540ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
541ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Result = cmsPipelineAlloc(ContextID, 0, 0);
542ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Result == NULL) return NULL;
543ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
544ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
545ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
546ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < nProfiles; i++) {
547ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
548ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsBool  lIsDeviceLink, lIsInput;
549ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
550ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        hProfile      = hProfiles[i];
551ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        ClassSig      = cmsGetDeviceClass(hProfile);
552ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
553ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
554ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // First profile is used as input unless devicelink or abstract
555ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if ((i == 0) && !lIsDeviceLink) {
556ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            lIsInput = TRUE;
557ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
558ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        else {
559ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov          // Else use profile in the input direction if current space is not PCS
560ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        lIsInput      = (CurrentColorSpace != cmsSigXYZData) &&
561ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                        (CurrentColorSpace != cmsSigLabData);
562ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
563ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
564ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Intent        = TheIntents[i];
565ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
566ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (lIsInput || lIsDeviceLink) {
567ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
568ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            ColorSpaceIn    = cmsGetColorSpace(hProfile);
569ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            ColorSpaceOut   = cmsGetPCS(hProfile);
570ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
571ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        else {
572ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
573ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            ColorSpaceIn    = cmsGetPCS(hProfile);
574ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            ColorSpaceOut   = cmsGetColorSpace(hProfile);
575ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
576ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
577ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
578ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
579ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
580ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            goto Error;
581ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
582ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
583ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // If devicelink is found, then no custom intent is allowed and we can
584ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // read the LUT to be applied. Settings don't apply here.
585ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
586ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
587ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // Get the involved LUT from the profile
588ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
589ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (Lut == NULL) goto Error;
590ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
591ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // What about abstract profiles?
592ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov             if (ClassSig == cmsSigAbstractClass && i > 0) {
593ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
594ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov             }
595ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov             else {
596ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                _cmsMAT3identity(&m);
597ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                _cmsVEC3init(&off, 0, 0, 0);
598ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov             }
599ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
600ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
601ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
602ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
603ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
604ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        else {
605ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
606ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (lIsInput) {
607ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                // Input direction means non-pcs connection, so proceed like devicelinks
608ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                Lut = _cmsReadInputLUT(hProfile, Intent);
609ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                if (Lut == NULL) goto Error;
610ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            }
611ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            else {
612ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
613ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                // Output direction means PCS connection. Intent may apply here
614ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                Lut = _cmsReadOutputLUT(hProfile, Intent);
615ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                if (Lut == NULL) goto Error;
616ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
617ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
618ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
619ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
620ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
621ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            }
622ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
623ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
624ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Concatenate to the output LUT
625ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (!cmsPipelineCat(Result, Lut))
626ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            goto Error;
627ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
628ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsPipelineFree(Lut);
629ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Lut = NULL;
630ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
631ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Update current space
632ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        CurrentColorSpace = ColorSpaceOut;
633ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
634ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
635ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return Result;
636ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
637ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovError:
638ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
639ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Lut != NULL) cmsPipelineFree(Lut);
640ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Result != NULL) cmsPipelineFree(Result);
641ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return NULL;
642ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
643ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUNUSED_PARAMETER(dwFlags);
644ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
645ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
646ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
647ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Wrapper for DLL calling convention
648ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline*  CMSEXPORT _cmsDefaultICCintents(cmsContext     ContextID,
649ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                              cmsUInt32Number nProfiles,
650ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                              cmsUInt32Number TheIntents[],
651ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                              cmsHPROFILE     hProfiles[],
652ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                              cmsBool         BPC[],
653ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                              cmsFloat64Number AdaptationStates[],
654ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                              cmsUInt32Number dwFlags)
655ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
656ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
657ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
658ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
659ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Black preserving intents ---------------------------------------------------------------------------------------------
660ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
661ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Translate black-preserving intents to ICC ones
662ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
663ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovint TranslateNonICCIntents(int Intent)
664ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
665ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    switch (Intent) {
666ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
667ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
668ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return INTENT_PERCEPTUAL;
669ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
670ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
671ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
672ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return INTENT_RELATIVE_COLORIMETRIC;
673ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
674ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case INTENT_PRESERVE_K_ONLY_SATURATION:
675ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        case INTENT_PRESERVE_K_PLANE_SATURATION:
676ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            return INTENT_SATURATION;
677ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
678ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        default: return Intent;
679ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
680ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
681ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
682ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Sampler for Black-only preserving CMYK->CMYK transforms
683ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
684ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovtypedef struct {
685ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipeline*    cmyk2cmyk;      // The original transform
686ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsToneCurve*   KTone;          // Black-to-black tone curve
687ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
688ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov} GrayOnlyParams;
689ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
690ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
691ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Preserve black only if that is the only ink used
692ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
693ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovint BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
694ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
695ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
696ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
697ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // If going across black only, keep black only
698ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
699ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
700ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // TAC does not apply because it is black ink!
701ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Out[0] = Out[1] = Out[2] = 0;
702ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);
703ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return TRUE;
704ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
705ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
706ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Keep normal transform for other colors
707ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);
708ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
709ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
710ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
711ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This is the entry for black-preserving K-only intents, which are non-ICC
712ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
713ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
714ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number nProfiles,
715ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number TheIntents[],
716ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsHPROFILE     hProfiles[],
717ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsBool         BPC[],
718ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsFloat64Number AdaptationStates[],
719ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number dwFlags)
720ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
721ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    GrayOnlyParams  bp;
722ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipeline*    Result;
723ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number ICCIntents[256];
724ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsStage*         CLUT;
725ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number i, nGridPoints;
726ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
727ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
728ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Sanity check
729ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (nProfiles < 1 || nProfiles > 255) return NULL;
730ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
731ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Translate black-preserving intents to ICC ones
732ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < nProfiles; i++)
733ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
734ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
735ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Check for non-cmyk profiles
736ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
737ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
738ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov           return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
739ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
740ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    memset(&bp, 0, sizeof(bp));
741ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
742ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Allocate an empty LUT for holding the result
743ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Result = cmsPipelineAlloc(ContextID, 4, 4);
744ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Result == NULL) return NULL;
745ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
746ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Create a LUT holding normal ICC transform
747ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.cmyk2cmyk = DefaultICCintents(ContextID,
748ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        nProfiles,
749ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        ICCIntents,
750ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        hProfiles,
751ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BPC,
752ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        AdaptationStates,
753ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        dwFlags);
754ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
755ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.cmyk2cmyk == NULL) goto Error;
756ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
757ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Now, compute the tone curve
758ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.KTone = _cmsBuildKToneCurve(ContextID,
759ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        4096,
760ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        nProfiles,
761ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        ICCIntents,
762ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        hProfiles,
763ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        BPC,
764ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        AdaptationStates,
765ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        dwFlags);
766ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
767ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.KTone == NULL) goto Error;
768ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
769ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
770ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // How many gridpoints are we going to use?
771ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
772ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
773ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Create the CLUT. 16 bits
774ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
775ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (CLUT == NULL) goto Error;
776ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
777ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // This is the one and only MPE in this LUT
778ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
779ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        goto Error;
780ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
781ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Sample it. We cannot afford pre/post linearization this time.
782ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
783ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        goto Error;
784ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
785ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Get rid of xform and tone curve
786ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipelineFree(bp.cmyk2cmyk);
787ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFreeToneCurve(bp.KTone);
788ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
789ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return Result;
790ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
791ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovError:
792ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
793ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);
794ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.KTone != NULL)  cmsFreeToneCurve(bp.KTone);
795ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Result != NULL) cmsPipelineFree(Result);
796ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return NULL;
797ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
798ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
799ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
800ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
801ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
802ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovtypedef struct {
803ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
804ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipeline*     cmyk2cmyk;     // The original transform
805ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHTRANSFORM    hProofOutput;  // Output CMYK to Lab (last profile)
806ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHTRANSFORM    cmyk2Lab;      // The input chain
807ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsToneCurve*    KTone;         // Black-to-black tone curve
808ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipeline*     LabK2cmyk;     // The output profile
809ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number MaxError;
810ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
811ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHTRANSFORM    hRoundTrip;
812ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number MaxTAC;
813ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
814ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
815ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov} PreserveKPlaneParams;
816ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
817ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
818ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
819ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
820ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovint BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
821ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
822ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    int i;
823ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat32Number Inf[4], Outf[4];
824ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat32Number LabK[4];
825ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
826ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCIELab ColorimetricLab, BlackPreservingLab;
827ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
828ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
829ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Convert from 16 bits to floating point
830ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < 4; i++)
831ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
832ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
833ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Get the K across Tone curve
834ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
835ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
836ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // If going across black only, keep black only
837ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
838ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
839ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Out[0] = Out[1] = Out[2] = 0;
840ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
841ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return TRUE;
842ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
843ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
844ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Try the original transform,
845ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);
846ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
847ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Store a copy of the floating point result into 16-bit
848ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < 4; i++)
849ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
850ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
851ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Maybe K is already ok (mostly on K=0)
852ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) {
853ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return TRUE;
854ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
855ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
856ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // K differ, mesure and keep Lab measurement for further usage
857ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // this is done in relative colorimetric intent
858ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
859ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
860ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Is not black only and the transform doesn't keep black.
861ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Obtain the Lab of output CMYK. After that we have Lab + K
862ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
863ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
864ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Obtain the corresponding CMY using reverse interpolation
865ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // (K is fixed in LabK[3])
866ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
867ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
868ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Cannot find a suitable value, so use colorimetric xform
869ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // which is already stored in Out[]
870ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return TRUE;
871ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
872ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
873ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Make sure to pass thru K (which now is fixed)
874ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Outf[3] = LabK[3];
875ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
876ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Apply TAC if needed
877ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    SumCMY   = Outf[0]  + Outf[1] + Outf[2];
878ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    SumCMYK  = SumCMY + Outf[3];
879ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
880ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (SumCMYK > bp ->MaxTAC) {
881ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
882ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
883ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (Ratio < 0)
884ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            Ratio = 0;
885ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
886ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    else
887ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov       Ratio = 1.0;
888ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
889ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0);     // C
890ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0);     // M
891ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0);     // Y
892ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
893ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
894ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Estimate the error (this goes 16 bits to Lab DBL)
895ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
896ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
897ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Error > bp -> MaxError)
898ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        bp->MaxError = Error;
899ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
900ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
901ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
902ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
903ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// This is the entry for black-plane preserving, which are non-ICC
904ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganovstatic
905ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
906ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number nProfiles,
907ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number TheIntents[],
908ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsHPROFILE     hProfiles[],
909ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsBool         BPC[],
910ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsFloat64Number AdaptationStates[],
911ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                          cmsUInt32Number dwFlags)
912ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
913ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    PreserveKPlaneParams bp;
914ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPipeline*    Result = NULL;
915ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number ICCIntents[256];
916ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsStage*         CLUT;
917ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number i, nGridPoints;
918ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsHPROFILE hLab;
919ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
920ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Sanity check
921ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (nProfiles < 1 || nProfiles > 255) return NULL;
922ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
923ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Translate black-preserving intents to ICC ones
924ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < nProfiles; i++)
925ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
926ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
927ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Check for non-cmyk profiles
928ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
929ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData ||
930ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass))
931ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov           return  DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
932ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
933ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Allocate an empty LUT for holding the result
934ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Result = cmsPipelineAlloc(ContextID, 4, 4);
935ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Result == NULL) return NULL;
936ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
937ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
938ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    memset(&bp, 0, sizeof(bp));
939ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
940ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // We need the input LUT of the last profile, assuming this one is responsible of
941ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // black generation. This LUT will be seached in inverse order.
942ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC);
943ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.LabK2cmyk == NULL) goto Cleanup;
944ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
945ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Get total area coverage (in 0..1 domain)
946ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
947ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.MaxTAC <= 0) goto Cleanup;
948ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
949ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
950ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Create a LUT holding normal ICC transform
951ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.cmyk2cmyk = DefaultICCintents(ContextID,
952ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         nProfiles,
953ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         ICCIntents,
954ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         hProfiles,
955ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         BPC,
956ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         AdaptationStates,
957ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         dwFlags);
958ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.cmyk2cmyk == NULL) goto Cleanup;
959ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
960ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Now the tone curve
961ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
962ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                   ICCIntents,
963ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                   hProfiles,
964ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                   BPC,
965ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                   AdaptationStates,
966ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                   dwFlags);
967ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.KTone == NULL) goto Cleanup;
968ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
969ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // To measure the output, Last profile to Lab
970ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
971ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
972ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
973ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         INTENT_RELATIVE_COLORIMETRIC,
974ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
975ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if ( bp.hProofOutput == NULL) goto Cleanup;
976ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
977ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Same as anterior, but lab in the 0..1 range
978ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
979ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
980ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
981ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         INTENT_RELATIVE_COLORIMETRIC,
982ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                                         cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
983ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.cmyk2Lab == NULL) goto Cleanup;
984ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsCloseProfile(hLab);
985ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
986ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Error estimation (for debug only)
987ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    bp.MaxError = 0;
988ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
989ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // How many gridpoints are we going to use?
990ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
991ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
992ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
993ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
994ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (CLUT == NULL) goto Cleanup;
995ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
996ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
997ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        goto Cleanup;
998ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
999ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
1000ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1001ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovCleanup:
1002ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1003ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
1004ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
1005ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
1006ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1007ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.KTone) cmsFreeToneCurve(bp.KTone);
1008ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
1009ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1010ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return Result;
1011ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
1012ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1013ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Link routines ------------------------------------------------------------------------------------------------------
1014ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1015ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
1016ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
1017ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
1018ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
1019ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsUInt32Number nProfiles,
1020ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsUInt32Number TheIntents[],
1021ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsHPROFILE     hProfiles[],
1022ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsBool         BPC[],
1023ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsFloat64Number AdaptationStates[],
1024ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                              cmsUInt32Number dwFlags)
1025ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
1026ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number i;
1027ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsIntentsList* Intent;
1028ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1029ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Make sure a reasonable number of profiles is provided
1030ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (nProfiles <= 0 || nProfiles > 255) {
1031ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov         cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
1032ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return NULL;
1033ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
1034ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1035ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (i=0; i < nProfiles; i++) {
1036ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1037ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // Check if black point is really needed or allowed. Note that
1038ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // following Adobe's document:
1039ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // BPC does not apply to devicelink profiles, nor to abs colorimetric,
1040ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        // and applies always on V4 perceptual and saturation.
1041ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1042ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
1043ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            BPC[i] = FALSE;
1044ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1045ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
1046ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1047ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            // Force BPC for V4 profiles in perceptual and saturation
1048ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (cmsGetProfileVersion(hProfiles[i]) >= 4.0)
1049ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                BPC[i] = TRUE;
1050ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
1051ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
1052ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1053ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Search for a handler. The first intent in the chain defines the handler. That would
1054ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // prevent using multiple custom intents in a multiintent chain, but the behaviour of
1055ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // this case would present some issues if the custom intent tries to do things like
1056ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // preserve primaries. This solution is not perfect, but works well on most cases.
1057ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1058ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    Intent = SearchIntent(ContextID, TheIntents[0]);
1059ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Intent == NULL) {
1060ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
1061ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return NULL;
1062ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
1063ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1064ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Call the handler
1065ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
1066ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
1067ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1068ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// -------------------------------------------------------------------------------------------------
1069ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1070ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// Get information about available intents. nMax is the maximum space for the supplied "Codes"
1071ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// and "Descriptions" the function returns the total number of intents, which may be greater
1072ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// than nMax, although the matrices are not populated beyond this level.
1073ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1074ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
1075ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
1076ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsIntentsList* pt;
1077ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsUInt32Number nIntents;
1078ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1079ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1080ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (nIntents=0, pt = ctx->Intents; pt != NULL; pt = pt -> Next)
1081ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    {
1082ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (nIntents < nMax) {
1083ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (Codes != NULL)
1084ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                Codes[nIntents] = pt ->Intent;
1085ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1086ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (Descriptions != NULL)
1087ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                Descriptions[nIntents] = pt ->Description;
1088ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
1089ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1090ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        nIntents++;
1091ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
1092ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1093ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next)
1094ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    {
1095ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        if (nIntents < nMax) {
1096ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (Codes != NULL)
1097ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                Codes[nIntents] = pt ->Intent;
1098ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1099ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov            if (Descriptions != NULL)
1100ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov                Descriptions[nIntents] = pt ->Description;
1101ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        }
1102ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1103ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        nIntents++;
1104ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
1105ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return nIntents;
1106ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
1107ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1108ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1109ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
1110ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return cmsGetSupportedIntentsTHR(NULL, nMax, Codes, Descriptions);
1111ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
1112ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1113ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov// The plug-in registration. User can add new intents or override default routines
1114ee451cb395940862dad63c85adfe8f2fd55e864cSvet GanovcmsBool  _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)
1115ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov{
1116ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin);
1117ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
1118ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    cmsIntentsList* fl;
1119ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1120ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    // Do we have to reset the custom intents?
1121ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (Data == NULL) {
1122ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1123ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        ctx->Intents = NULL;
1124ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov        return TRUE;
1125ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    }
1126ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1127ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));
1128ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    if (fl == NULL) return FALSE;
1129ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1130ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1131ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    fl ->Intent  = Plugin ->Intent;
1132ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1);
1133ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    fl ->Description[sizeof(fl ->Description)-1] = 0;
1134ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1135ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    fl ->Link    = Plugin ->Link;
1136ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1137ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    fl ->Next = ctx ->Intents;
1138ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    ctx ->Intents = fl;
1139ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1140ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov    return TRUE;
1141ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov}
1142ee451cb395940862dad63c85adfe8f2fd55e864cSvet Ganov
1143