grub2: bring back build of aros-side grub2 tools
[AROS.git] / workbench / libs / lcms2 / src / cmscnvrt.c
blob908f7874af83bd8eb39c12a8b1a6b66ce466e0b1
1 //---------------------------------------------------------------------------------
2 //
3 // Little Color Management System
4 // Copyright (c) 1998-2012 Marti Maria Saguer
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the Software
11 // is furnished to do so, subject to the following conditions:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 //---------------------------------------------------------------------------------
27 #include "lcms2_internal.h"
30 // Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point
31 // compensation and Adaptation parameters may vary across profiles. BPC and Adaptation refers to the PCS
32 // after the profile. I.e, BPC[0] refers to connexion between profile(0) and profile(1)
33 cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
34 cmsUInt32Number nProfiles,
35 cmsUInt32Number Intents[],
36 cmsHPROFILE hProfiles[],
37 cmsBool BPC[],
38 cmsFloat64Number AdaptationStates[],
39 cmsUInt32Number dwFlags);
41 //---------------------------------------------------------------------------------
43 // This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.
44 // Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
45 static
46 cmsPipeline* DefaultICCintents(cmsContext ContextID,
47 cmsUInt32Number nProfiles,
48 cmsUInt32Number Intents[],
49 cmsHPROFILE hProfiles[],
50 cmsBool BPC[],
51 cmsFloat64Number AdaptationStates[],
52 cmsUInt32Number dwFlags);
54 //---------------------------------------------------------------------------------
56 // This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile
57 // to do the trick (no devicelinks allowed at that position)
58 static
59 cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
60 cmsUInt32Number nProfiles,
61 cmsUInt32Number Intents[],
62 cmsHPROFILE hProfiles[],
63 cmsBool BPC[],
64 cmsFloat64Number AdaptationStates[],
65 cmsUInt32Number dwFlags);
67 //---------------------------------------------------------------------------------
69 // This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile
70 // to do the trick (no devicelinks allowed at that position)
71 static
72 cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
73 cmsUInt32Number nProfiles,
74 cmsUInt32Number Intents[],
75 cmsHPROFILE hProfiles[],
76 cmsBool BPC[],
77 cmsFloat64Number AdaptationStates[],
78 cmsUInt32Number dwFlags);
80 //---------------------------------------------------------------------------------
83 // This is a structure holding implementations for all supported intents.
84 typedef struct _cms_intents_list {
86 cmsUInt32Number Intent;
87 char Description[256];
88 cmsIntentFn Link;
89 struct _cms_intents_list* Next;
91 } cmsIntentsList;
94 // Built-in intents
95 static cmsIntentsList DefaultIntents[] = {
97 { INTENT_PERCEPTUAL, "Perceptual", DefaultICCintents, &DefaultIntents[1] },
98 { INTENT_RELATIVE_COLORIMETRIC, "Relative colorimetric", DefaultICCintents, &DefaultIntents[2] },
99 { INTENT_SATURATION, "Saturation", DefaultICCintents, &DefaultIntents[3] },
100 { INTENT_ABSOLUTE_COLORIMETRIC, "Absolute colorimetric", DefaultICCintents, &DefaultIntents[4] },
101 { INTENT_PRESERVE_K_ONLY_PERCEPTUAL, "Perceptual preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[5] },
102 { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[6] },
103 { INTENT_PRESERVE_K_ONLY_SATURATION, "Saturation preserving black ink", BlackPreservingKOnlyIntents, &DefaultIntents[7] },
104 { INTENT_PRESERVE_K_PLANE_PERCEPTUAL, "Perceptual preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[8] },
105 { INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] },
106 { INTENT_PRESERVE_K_PLANE_SATURATION, "Saturation preserving black plane", BlackPreservingKPlaneIntents, NULL }
110 // A pointer to the begining of the list
111 static cmsIntentsList *Intents = DefaultIntents;
113 // Search the list for a suitable intent. Returns NULL if not found
114 static
115 cmsIntentsList* SearchIntent(cmsUInt32Number Intent)
117 cmsIntentsList* pt;
119 for (pt = Intents; pt != NULL; pt = pt -> Next)
120 if (pt ->Intent == Intent) return pt;
122 return NULL;
125 // Black point compensation. Implemented as a linear scaling in XYZ. Black points
126 // should come relative to the white point. Fills an matrix/offset element m
127 // which is organized as a 4x4 matrix.
128 static
129 void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,
130 const cmsCIEXYZ* BlackPointOut,
131 cmsMAT3* m, cmsVEC3* off)
133 cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
135 // Now we need to compute a matrix plus an offset m and of such of
136 // [m]*bpin + off = bpout
137 // [m]*D50 + off = D50
139 // This is a linear scaling in the form ax+b, where
140 // a = (bpout - D50) / (bpin - D50)
141 // b = - D50* (bpout - bpin) / (bpin - D50)
143 tx = BlackPointIn->X - cmsD50_XYZ()->X;
144 ty = BlackPointIn->Y - cmsD50_XYZ()->Y;
145 tz = BlackPointIn->Z - cmsD50_XYZ()->Z;
147 ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx;
148 ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty;
149 az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz;
151 bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx;
152 by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;
153 bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;
155 _cmsVEC3init(&m ->v[0], ax, 0, 0);
156 _cmsVEC3init(&m ->v[1], 0, ay, 0);
157 _cmsVEC3init(&m ->v[2], 0, 0, az);
158 _cmsVEC3init(off, bx, by, bz);
163 // Approximate a blackbody illuminant based on CHAD information
164 static
165 cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
167 // Convert D50 across inverse CHAD to get the absolute white point
168 cmsVEC3 d, s;
169 cmsCIEXYZ Dest;
170 cmsCIExyY DestChromaticity;
171 cmsFloat64Number TempK;
172 cmsMAT3 m1, m2;
174 m1 = *Chad;
175 if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
177 s.n[VX] = cmsD50_XYZ() -> X;
178 s.n[VY] = cmsD50_XYZ() -> Y;
179 s.n[VZ] = cmsD50_XYZ() -> Z;
181 _cmsMAT3eval(&d, &m2, &s);
183 Dest.X = d.n[VX];
184 Dest.Y = d.n[VY];
185 Dest.Z = d.n[VZ];
187 cmsXYZ2xyY(&DestChromaticity, &Dest);
189 if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity))
190 return -1.0;
192 return TempK;
195 // Compute a CHAD based on a given temperature
196 static
197 void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
199 cmsCIEXYZ White;
200 cmsCIExyY ChromaticityOfWhite;
202 cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);
203 cmsxyY2XYZ(&White, &ChromaticityOfWhite);
204 _cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ());
207 // Join scalings to obtain relative input to absolute and then to relative output.
208 // Result is stored in a 3x3 matrix
209 static
210 cmsBool ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
211 const cmsCIEXYZ* WhitePointIn,
212 const cmsMAT3* ChromaticAdaptationMatrixIn,
213 const cmsCIEXYZ* WhitePointOut,
214 const cmsMAT3* ChromaticAdaptationMatrixOut,
215 cmsMAT3* m)
217 cmsMAT3 Scale, m1, m2, m3, m4;
219 // Adaptation state
220 if (AdaptationState == 1.0) {
222 // Observer is fully adapted. Keep chromatic adaptation.
223 // That is the standard V4 behaviour
224 _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
225 _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
226 _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
229 else {
231 // Incomplete adaptation. This is an advanced feature.
232 _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
233 _cmsVEC3init(&Scale.v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
234 _cmsVEC3init(&Scale.v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
237 if (AdaptationState == 0.0) {
239 m1 = *ChromaticAdaptationMatrixOut;
240 _cmsMAT3per(&m2, &m1, &Scale);
241 // m2 holds CHAD from output white to D50 times abs. col. scaling
243 // Observer is not adapted, undo the chromatic adaptation
244 _cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);
246 m3 = *ChromaticAdaptationMatrixIn;
247 if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
248 _cmsMAT3per(m, &m2, &m4);
250 } else {
252 cmsMAT3 MixedCHAD;
253 cmsFloat64Number TempSrc, TempDest, Temp;
255 m1 = *ChromaticAdaptationMatrixIn;
256 if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
257 _cmsMAT3per(&m3, &m2, &Scale);
258 // m3 holds CHAD from input white to D50 times abs. col. scaling
260 TempSrc = CHAD2Temp(ChromaticAdaptationMatrixIn);
261 TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut);
263 if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
265 if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {
267 _cmsMAT3identity(m);
268 return TRUE;
271 Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
273 // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
274 Temp2CHAD(&MixedCHAD, Temp);
276 _cmsMAT3per(m, &m3, &MixedCHAD);
280 return TRUE;
284 // Just to see if m matrix should be applied
285 static
286 cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
288 cmsFloat64Number diff = 0;
289 cmsMAT3 Ident;
290 int i;
292 if (m == NULL && off == NULL) return TRUE; // NULL is allowed as an empty layer
293 if (m == NULL && off != NULL) return FALSE; // This is an internal error
295 _cmsMAT3identity(&Ident);
297 for (i=0; i < 3*3; i++)
298 diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
300 for (i=0; i < 3; i++)
301 diff += fabs(((cmsFloat64Number*)off)[i]);
304 return (diff < 0.002);
308 // Compute the conversion layer
309 static
310 cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
311 cmsUInt32Number Intent,
312 cmsBool BPC,
313 cmsFloat64Number AdaptationState,
314 cmsMAT3* m, cmsVEC3* off)
317 int k;
319 // m and off are set to identity and this is detected latter on
320 _cmsMAT3identity(m);
321 _cmsVEC3init(off, 0, 0, 0);
323 // If intent is abs. colorimetric,
324 if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
326 cmsCIEXYZ WhitePointIn, WhitePointOut;
327 cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
329 _cmsReadMediaWhitePoint(&WhitePointIn, hProfiles[i-1]);
330 _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
332 _cmsReadMediaWhitePoint(&WhitePointOut, hProfiles[i]);
333 _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
335 if (!ComputeAbsoluteIntent(AdaptationState,
336 &WhitePointIn, &ChromaticAdaptationMatrixIn,
337 &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
340 else {
341 // Rest of intents may apply BPC.
343 if (BPC) {
345 cmsCIEXYZ BlackPointIn, BlackPointOut;
347 cmsDetectBlackPoint(&BlackPointIn, hProfiles[i-1], Intent, 0);
348 cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
350 // If black points are equal, then do nothing
351 if (BlackPointIn.X != BlackPointOut.X ||
352 BlackPointIn.Y != BlackPointOut.Y ||
353 BlackPointIn.Z != BlackPointOut.Z)
354 ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
358 // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
359 // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
360 // we have first to convert from encoded to XYZ and then convert back to encoded.
361 // y = Mx + Off
362 // x = x'c
363 // y = M x'c + Off
364 // y = y'c; y' = y / c
365 // y' = (Mx'c + Off) /c = Mx' + (Off / c)
367 for (k=0; k < 3; k++) {
368 off ->n[k] /= MAX_ENCODEABLE_XYZ;
371 return TRUE;
375 // Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
376 static
377 cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
379 cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
380 cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
382 // Handle PCS mismatches. A specialized stage is added to the LUT in such case
383 switch (InPCS) {
385 case cmsSigXYZData: // Input profile operates in XYZ
387 switch (OutPCS) {
389 case cmsSigXYZData: // XYZ -> XYZ
390 if (!IsEmptyLayer(m, off) &&
391 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
392 return FALSE;
393 break;
395 case cmsSigLabData: // XYZ -> Lab
396 if (!IsEmptyLayer(m, off) &&
397 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
398 return FALSE;
399 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
400 return FALSE;
401 break;
403 default:
404 return FALSE; // Colorspace mismatch
406 break;
408 case cmsSigLabData: // Input profile operates in Lab
410 switch (OutPCS) {
412 case cmsSigXYZData: // Lab -> XYZ
414 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)))
415 return FALSE;
416 if (!IsEmptyLayer(m, off) &&
417 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
418 return FALSE;
419 break;
421 case cmsSigLabData: // Lab -> Lab
423 if (!IsEmptyLayer(m, off)) {
424 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) ||
425 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) ||
426 !cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
427 return FALSE;
429 break;
431 default:
432 return FALSE; // Mismatch
434 break;
436 // On colorspaces other than PCS, check for same space
437 default:
438 if (InPCS != OutPCS) return FALSE;
439 break;
442 return TRUE;
446 // Is a given space compatible with another?
447 static
448 cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
450 // If they are same, they are compatible.
451 if (a == b) return TRUE;
453 // Check for MCH4 substitution of CMYK
454 if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;
455 if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;
457 // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
458 if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
459 if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
461 return FALSE;
465 // Default handler for ICC-style intents
466 static
467 cmsPipeline* DefaultICCintents(cmsContext ContextID,
468 cmsUInt32Number nProfiles,
469 cmsUInt32Number TheIntents[],
470 cmsHPROFILE hProfiles[],
471 cmsBool BPC[],
472 cmsFloat64Number AdaptationStates[],
473 cmsUInt32Number dwFlags)
475 cmsPipeline* Lut = NULL;
476 cmsPipeline* Result;
477 cmsHPROFILE hProfile;
478 cmsMAT3 m;
479 cmsVEC3 off;
480 cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
481 cmsProfileClassSignature ClassSig;
482 cmsUInt32Number i, Intent;
484 // For safety
485 if (nProfiles == 0) return NULL;
487 // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
488 Result = cmsPipelineAlloc(ContextID, 0, 0);
489 if (Result == NULL) return NULL;
491 CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
493 for (i=0; i < nProfiles; i++) {
495 cmsBool lIsDeviceLink, lIsInput;
497 hProfile = hProfiles[i];
498 ClassSig = cmsGetDeviceClass(hProfile);
499 lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
501 // First profile is used as input unless devicelink or abstract
502 if ((i == 0) && !lIsDeviceLink) {
503 lIsInput = TRUE;
505 else {
506 // Else use profile in the input direction if current space is not PCS
507 lIsInput = (CurrentColorSpace != cmsSigXYZData) &&
508 (CurrentColorSpace != cmsSigLabData);
511 Intent = TheIntents[i];
513 if (lIsInput || lIsDeviceLink) {
515 ColorSpaceIn = cmsGetColorSpace(hProfile);
516 ColorSpaceOut = cmsGetPCS(hProfile);
518 else {
520 ColorSpaceIn = cmsGetPCS(hProfile);
521 ColorSpaceOut = cmsGetColorSpace(hProfile);
524 if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
526 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
527 goto Error;
530 // If devicelink is found, then no custom intent is allowed and we can
531 // read the LUT to be applied. Settings don't apply here.
532 if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
534 // Get the involved LUT from the profile
535 Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
536 if (Lut == NULL) goto Error;
538 // What about abstract profiles?
539 if (ClassSig == cmsSigAbstractClass && i > 0) {
540 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
542 else {
543 _cmsMAT3identity(&m);
544 _cmsVEC3init(&off, 0, 0, 0);
548 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
551 else {
553 if (lIsInput) {
554 // Input direction means non-pcs connection, so proceed like devicelinks
555 Lut = _cmsReadInputLUT(hProfile, Intent);
556 if (Lut == NULL) goto Error;
558 else {
560 // Output direction means PCS connection. Intent may apply here
561 Lut = _cmsReadOutputLUT(hProfile, Intent);
562 if (Lut == NULL) goto Error;
565 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
566 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
571 // Concatenate to the output LUT
572 if (!cmsPipelineCat(Result, Lut))
573 goto Error;
574 cmsPipelineFree(Lut);
576 // Update current space
577 CurrentColorSpace = ColorSpaceOut;
580 return Result;
582 Error:
584 cmsPipelineFree(Lut);
585 if (Result != NULL) cmsPipelineFree(Result);
586 return NULL;
588 cmsUNUSED_PARAMETER(dwFlags);
592 // Wrapper for DLL calling convention
593 cmsPipeline* CMSEXPORT _cmsDefaultICCintents(cmsContext ContextID,
594 cmsUInt32Number nProfiles,
595 cmsUInt32Number TheIntents[],
596 cmsHPROFILE hProfiles[],
597 cmsBool BPC[],
598 cmsFloat64Number AdaptationStates[],
599 cmsUInt32Number dwFlags)
601 return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
604 // Black preserving intents ---------------------------------------------------------------------------------------------
606 // Translate black-preserving intents to ICC ones
607 static
608 int TranslateNonICCIntents(int Intent)
610 switch (Intent) {
611 case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
612 case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
613 return INTENT_PERCEPTUAL;
615 case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
616 case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
617 return INTENT_RELATIVE_COLORIMETRIC;
619 case INTENT_PRESERVE_K_ONLY_SATURATION:
620 case INTENT_PRESERVE_K_PLANE_SATURATION:
621 return INTENT_SATURATION;
623 default: return Intent;
627 // Sampler for Black-only preserving CMYK->CMYK transforms
629 typedef struct {
630 cmsPipeline* cmyk2cmyk; // The original transform
631 cmsToneCurve* KTone; // Black-to-black tone curve
633 } GrayOnlyParams;
636 // Preserve black only if that is the only ink used
637 static
638 int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
640 GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
642 // If going across black only, keep black only
643 if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
645 // TAC does not apply because it is black ink!
646 Out[0] = Out[1] = Out[2] = 0;
647 Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);
648 return TRUE;
651 // Keep normal transform for other colors
652 bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);
653 return TRUE;
656 // This is the entry for black-preserving K-only intents, which are non-ICC
657 static
658 cmsPipeline* BlackPreservingKOnlyIntents(cmsContext ContextID,
659 cmsUInt32Number nProfiles,
660 cmsUInt32Number TheIntents[],
661 cmsHPROFILE hProfiles[],
662 cmsBool BPC[],
663 cmsFloat64Number AdaptationStates[],
664 cmsUInt32Number dwFlags)
666 GrayOnlyParams bp;
667 cmsPipeline* Result;
668 cmsUInt32Number ICCIntents[256];
669 cmsStage* CLUT;
670 cmsUInt32Number i, nGridPoints;
673 // Sanity check
674 if (nProfiles < 1 || nProfiles > 255) return NULL;
676 // Translate black-preserving intents to ICC ones
677 for (i=0; i < nProfiles; i++)
678 ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
680 // Check for non-cmyk profiles
681 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
682 cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
683 return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
685 memset(&bp, 0, sizeof(bp));
687 // Allocate an empty LUT for holding the result
688 Result = cmsPipelineAlloc(ContextID, 4, 4);
689 if (Result == NULL) return NULL;
691 // Create a LUT holding normal ICC transform
692 bp.cmyk2cmyk = DefaultICCintents(ContextID,
693 nProfiles,
694 ICCIntents,
695 hProfiles,
696 BPC,
697 AdaptationStates,
698 dwFlags);
700 if (bp.cmyk2cmyk == NULL) goto Error;
702 // Now, compute the tone curve
703 bp.KTone = _cmsBuildKToneCurve(ContextID,
704 4096,
705 nProfiles,
706 ICCIntents,
707 hProfiles,
708 BPC,
709 AdaptationStates,
710 dwFlags);
712 if (bp.KTone == NULL) goto Error;
715 // How many gridpoints are we going to use?
716 nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
718 // Create the CLUT. 16 bits
719 CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
720 if (CLUT == NULL) goto Error;
722 // This is the one and only MPE in this LUT
723 if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
724 goto Error;
726 // Sample it. We cannot afford pre/post linearization this time.
727 if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
728 goto Error;
730 // Get rid of xform and tone curve
731 cmsPipelineFree(bp.cmyk2cmyk);
732 cmsFreeToneCurve(bp.KTone);
734 return Result;
736 Error:
738 if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);
739 if (bp.KTone != NULL) cmsFreeToneCurve(bp.KTone);
740 if (Result != NULL) cmsPipelineFree(Result);
741 return NULL;
745 // K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
747 typedef struct {
749 cmsPipeline* cmyk2cmyk; // The original transform
750 cmsHTRANSFORM hProofOutput; // Output CMYK to Lab (last profile)
751 cmsHTRANSFORM cmyk2Lab; // The input chain
752 cmsToneCurve* KTone; // Black-to-black tone curve
753 cmsPipeline* LabK2cmyk; // The output profile
754 cmsFloat64Number MaxError;
756 cmsHTRANSFORM hRoundTrip;
757 cmsFloat64Number MaxTAC;
760 } PreserveKPlaneParams;
763 // The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
764 static
765 int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
767 int i;
768 cmsFloat32Number Inf[4], Outf[4];
769 cmsFloat32Number LabK[4];
770 cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
771 cmsCIELab ColorimetricLab, BlackPreservingLab;
772 PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
774 // Convert from 16 bits to floating point
775 for (i=0; i < 4; i++)
776 Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
778 // Get the K across Tone curve
779 LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
781 // If going across black only, keep black only
782 if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
784 Out[0] = Out[1] = Out[2] = 0;
785 Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
786 return TRUE;
789 // Try the original transform,
790 cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);
792 // Store a copy of the floating point result into 16-bit
793 for (i=0; i < 4; i++)
794 Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
796 // Maybe K is already ok (mostly on K=0)
797 if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) {
798 return TRUE;
801 // K differ, mesure and keep Lab measurement for further usage
802 // this is done in relative colorimetric intent
803 cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
805 // Is not black only and the transform doesn't keep black.
806 // Obtain the Lab of output CMYK. After that we have Lab + K
807 cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
809 // Obtain the corresponding CMY using reverse interpolation
810 // (K is fixed in LabK[3])
811 if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
813 // Cannot find a suitable value, so use colorimetric xform
814 // which is already stored in Out[]
815 return TRUE;
818 // Make sure to pass thru K (which now is fixed)
819 Outf[3] = LabK[3];
821 // Apply TAC if needed
822 SumCMY = Outf[0] + Outf[1] + Outf[2];
823 SumCMYK = SumCMY + Outf[3];
825 if (SumCMYK > bp ->MaxTAC) {
827 Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
828 if (Ratio < 0)
829 Ratio = 0;
831 else
832 Ratio = 1.0;
834 Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0); // C
835 Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0); // M
836 Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0); // Y
837 Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
839 // Estimate the error (this goes 16 bits to Lab DBL)
840 cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
841 Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
842 if (Error > bp -> MaxError)
843 bp->MaxError = Error;
845 return TRUE;
848 // This is the entry for black-plane preserving, which are non-ICC
849 static
850 cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID,
851 cmsUInt32Number nProfiles,
852 cmsUInt32Number TheIntents[],
853 cmsHPROFILE hProfiles[],
854 cmsBool BPC[],
855 cmsFloat64Number AdaptationStates[],
856 cmsUInt32Number dwFlags)
858 PreserveKPlaneParams bp;
859 cmsPipeline* Result = NULL;
860 cmsUInt32Number ICCIntents[256];
861 cmsStage* CLUT;
862 cmsUInt32Number i, nGridPoints;
863 cmsHPROFILE hLab;
865 // Sanity check
866 if (nProfiles < 1 || nProfiles > 255) return NULL;
868 // Translate black-preserving intents to ICC ones
869 for (i=0; i < nProfiles; i++)
870 ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
872 // Check for non-cmyk profiles
873 if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
874 !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData ||
875 cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass))
876 return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
878 // Allocate an empty LUT for holding the result
879 Result = cmsPipelineAlloc(ContextID, 4, 4);
880 if (Result == NULL) return NULL;
883 memset(&bp, 0, sizeof(bp));
885 // We need the input LUT of the last profile, assuming this one is responsible of
886 // black generation. This LUT will be seached in inverse order.
887 bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC);
888 if (bp.LabK2cmyk == NULL) goto Cleanup;
890 // Get total area coverage (in 0..1 domain)
891 bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
892 if (bp.MaxTAC <= 0) goto Cleanup;
895 // Create a LUT holding normal ICC transform
896 bp.cmyk2cmyk = DefaultICCintents(ContextID,
897 nProfiles,
898 ICCIntents,
899 hProfiles,
900 BPC,
901 AdaptationStates,
902 dwFlags);
903 if (bp.cmyk2cmyk == NULL) goto Cleanup;
905 // Now the tone curve
906 bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
907 ICCIntents,
908 hProfiles,
909 BPC,
910 AdaptationStates,
911 dwFlags);
912 if (bp.KTone == NULL) goto Cleanup;
914 // To measure the output, Last profile to Lab
915 hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
916 bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
917 CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
918 INTENT_RELATIVE_COLORIMETRIC,
919 cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
920 if ( bp.hProofOutput == NULL) goto Cleanup;
922 // Same as anterior, but lab in the 0..1 range
923 bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
924 FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
925 FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
926 INTENT_RELATIVE_COLORIMETRIC,
927 cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
928 if (bp.cmyk2Lab == NULL) goto Cleanup;
929 cmsCloseProfile(hLab);
931 // Error estimation (for debug only)
932 bp.MaxError = 0;
934 // How many gridpoints are we going to use?
935 nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
938 CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
939 if (CLUT == NULL) goto Cleanup;
941 if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
942 goto Cleanup;
944 cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
946 Cleanup:
948 if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
949 if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
950 if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
952 if (bp.KTone) cmsFreeToneCurve(bp.KTone);
953 if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
955 return Result;
958 // Link routines ------------------------------------------------------------------------------------------------------
960 // Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
961 // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
962 // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
963 cmsPipeline* _cmsLinkProfiles(cmsContext ContextID,
964 cmsUInt32Number nProfiles,
965 cmsUInt32Number TheIntents[],
966 cmsHPROFILE hProfiles[],
967 cmsBool BPC[],
968 cmsFloat64Number AdaptationStates[],
969 cmsUInt32Number dwFlags)
971 cmsUInt32Number i;
972 cmsIntentsList* Intent;
974 // Make sure a reasonable number of profiles is provided
975 if (nProfiles <= 0 || nProfiles > 255) {
976 cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
977 return NULL;
980 for (i=0; i < nProfiles; i++) {
982 // Check if black point is really needed or allowed. Note that
983 // following Adobe's document:
984 // BPC does not apply to devicelink profiles, nor to abs colorimetric,
985 // and applies always on V4 perceptual and saturation.
987 if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
988 BPC[i] = FALSE;
990 if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
992 // Force BPC for V4 profiles in perceptual and saturation
993 if (cmsGetProfileVersion(hProfiles[i]) >= 4.0)
994 BPC[i] = TRUE;
998 // Search for a handler. The first intent in the chain defines the handler. That would
999 // prevent using multiple custom intents in a multiintent chain, but the behaviour of
1000 // this case would present some issues if the custom intent tries to do things like
1001 // preserve primaries. This solution is not perfect, but works well on most cases.
1003 Intent = SearchIntent(TheIntents[0]);
1004 if (Intent == NULL) {
1005 cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
1006 return NULL;
1009 // Call the handler
1010 return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
1013 // -------------------------------------------------------------------------------------------------
1015 // Get information about available intents. nMax is the maximum space for the supplied "Codes"
1016 // and "Descriptions" the function returns the total number of intents, which may be greater
1017 // than nMax, although the matrices are not populated beyond this level.
1018 cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1020 cmsIntentsList* pt;
1021 cmsUInt32Number nIntents;
1023 for (nIntents=0, pt = Intents; pt != NULL; pt = pt -> Next)
1025 if (nIntents < nMax) {
1026 if (Codes != NULL)
1027 Codes[nIntents] = pt ->Intent;
1029 if (Descriptions != NULL)
1030 Descriptions[nIntents] = pt ->Description;
1033 nIntents++;
1036 return nIntents;
1039 // The plug-in registration. User can add new intents or override default routines
1040 cmsBool _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)
1042 cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
1043 cmsIntentsList* fl;
1045 // Do we have to reset the intents?
1046 if (Data == NULL) {
1048 Intents = DefaultIntents;
1049 return TRUE;
1052 fl = SearchIntent(Plugin ->Intent);
1054 if (fl == NULL) {
1055 fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));
1056 if (fl == NULL) return FALSE;
1059 fl ->Intent = Plugin ->Intent;
1060 strncpy(fl ->Description, Plugin ->Description, 255);
1061 fl ->Description[255] = 0;
1063 fl ->Link = Plugin ->Link;
1065 fl ->Next = Intents;
1066 Intents = fl;
1068 return TRUE;