1 //---------------------------------------------------------------------------------
3 // Little Color Management System
4 // Copyright (c) 1998-2012 Marti Maria Saguer
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"
29 // Read tags using low-level functions, provides necessary glue code to adapt versions, etc.
32 static const cmsTagSignature Device2PCS16
[] = {cmsSigAToB0Tag
, // Perceptual
33 cmsSigAToB1Tag
, // Relative colorimetric
34 cmsSigAToB2Tag
, // Saturation
35 cmsSigAToB1Tag
}; // Absolute colorimetric
37 static const cmsTagSignature Device2PCSFloat
[] = {cmsSigDToB0Tag
, // Perceptual
38 cmsSigDToB1Tag
, // Relative colorimetric
39 cmsSigDToB2Tag
, // Saturation
40 cmsSigDToB3Tag
}; // Absolute colorimetric
42 static const cmsTagSignature PCS2Device16
[] = {cmsSigBToA0Tag
, // Perceptual
43 cmsSigBToA1Tag
, // Relative colorimetric
44 cmsSigBToA2Tag
, // Saturation
45 cmsSigBToA1Tag
}; // Absolute colorimetric
47 static const cmsTagSignature PCS2DeviceFloat
[] = {cmsSigBToD0Tag
, // Perceptual
48 cmsSigBToD1Tag
, // Relative colorimetric
49 cmsSigBToD2Tag
, // Saturation
50 cmsSigBToD3Tag
}; // Absolute colorimetric
53 // Factors to convert from 1.15 fixed point to 0..1.0 range and vice-versa
54 #define InpAdj (1.0/MAX_ENCODEABLE_XYZ) // (65536.0/(65535.0*2.0))
55 #define OutpAdj (MAX_ENCODEABLE_XYZ) // ((2.0*65535.0)/65536.0)
57 // Several resources for gray conversions.
58 static const cmsFloat64Number GrayInputMatrix
[] = { (InpAdj
*cmsD50X
), (InpAdj
*cmsD50Y
), (InpAdj
*cmsD50Z
) };
59 static const cmsFloat64Number OneToThreeInputMatrix
[] = { 1, 1, 1 };
60 static const cmsFloat64Number PickYMatrix
[] = { 0, (OutpAdj
*cmsD50Y
), 0 };
61 static const cmsFloat64Number PickLstarMatrix
[] = { 1, 0, 0 };
63 // Get a media white point fixing some issues found in certain old profiles
64 cmsBool
_cmsReadMediaWhitePoint(cmsCIEXYZ
* Dest
, cmsHPROFILE hProfile
)
68 _cmsAssert(Dest
!= NULL
);
70 Tag
= (cmsCIEXYZ
*) cmsReadTag(hProfile
, cmsSigMediaWhitePointTag
);
74 *Dest
= *cmsD50_XYZ();
78 // V2 display profiles should give D50
79 if (cmsGetEncodedICCversion(hProfile
) < 0x4000000) {
81 if (cmsGetDeviceClass(hProfile
) == cmsSigDisplayClass
) {
82 *Dest
= *cmsD50_XYZ();
93 // Chromatic adaptation matrix. Fix some issues as well
94 cmsBool
_cmsReadCHAD(cmsMAT3
* Dest
, cmsHPROFILE hProfile
)
98 _cmsAssert(Dest
!= NULL
);
100 Tag
= (cmsMAT3
*) cmsReadTag(hProfile
, cmsSigChromaticAdaptationTag
);
107 // No CHAD available, default it to identity
108 _cmsMAT3identity(Dest
);
110 // V2 display profiles should give D50
111 if (cmsGetEncodedICCversion(hProfile
) < 0x4000000) {
113 if (cmsGetDeviceClass(hProfile
) == cmsSigDisplayClass
) {
115 cmsCIEXYZ
* White
= (cmsCIEXYZ
*) cmsReadTag(hProfile
, cmsSigMediaWhitePointTag
);
119 _cmsMAT3identity(Dest
);
123 return _cmsAdaptationMatrix(Dest
, NULL
, White
, cmsD50_XYZ());
131 // Auxiliar, read colorants as a MAT3 structure. Used by any function that needs a matrix-shaper
133 cmsBool
ReadICCMatrixRGB2XYZ(cmsMAT3
* r
, cmsHPROFILE hProfile
)
135 cmsCIEXYZ
*PtrRed
, *PtrGreen
, *PtrBlue
;
137 _cmsAssert(r
!= NULL
);
139 PtrRed
= (cmsCIEXYZ
*) cmsReadTag(hProfile
, cmsSigRedColorantTag
);
140 PtrGreen
= (cmsCIEXYZ
*) cmsReadTag(hProfile
, cmsSigGreenColorantTag
);
141 PtrBlue
= (cmsCIEXYZ
*) cmsReadTag(hProfile
, cmsSigBlueColorantTag
);
143 if (PtrRed
== NULL
|| PtrGreen
== NULL
|| PtrBlue
== NULL
)
146 _cmsVEC3init(&r
-> v
[0], PtrRed
-> X
, PtrGreen
-> X
, PtrBlue
-> X
);
147 _cmsVEC3init(&r
-> v
[1], PtrRed
-> Y
, PtrGreen
-> Y
, PtrBlue
-> Y
);
148 _cmsVEC3init(&r
-> v
[2], PtrRed
-> Z
, PtrGreen
-> Z
, PtrBlue
-> Z
);
154 // Gray input pipeline
156 cmsPipeline
* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile
)
158 cmsToneCurve
*GrayTRC
;
160 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
162 GrayTRC
= (cmsToneCurve
*) cmsReadTag(hProfile
, cmsSigGrayTRCTag
);
163 if (GrayTRC
== NULL
) return NULL
;
165 Lut
= cmsPipelineAlloc(ContextID
, 1, 3);
169 if (cmsGetPCS(hProfile
) == cmsSigLabData
) {
171 // In this case we implement the profile as an identity matrix plus 3 tone curves
172 cmsUInt16Number Zero
[2] = { 0x8080, 0x8080 };
173 cmsToneCurve
* EmptyTab
;
174 cmsToneCurve
* LabCurves
[3];
176 EmptyTab
= cmsBuildTabulatedToneCurve16(ContextID
, 2, Zero
);
178 if (EmptyTab
== NULL
)
181 LabCurves
[0] = GrayTRC
;
182 LabCurves
[1] = EmptyTab
;
183 LabCurves
[2] = EmptyTab
;
185 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocMatrix(ContextID
, 3, 1, OneToThreeInputMatrix
, NULL
)) ||
186 !cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocToneCurves(ContextID
, 3, LabCurves
))) {
187 cmsFreeToneCurve(EmptyTab
);
191 cmsFreeToneCurve(EmptyTab
);
196 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocToneCurves(ContextID
, 1, &GrayTRC
)) ||
197 !cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocMatrix(ContextID
, 3, 1, GrayInputMatrix
, NULL
)))
204 cmsFreeToneCurve(GrayTRC
);
205 cmsPipelineFree(Lut
);
211 cmsPipeline
* BuildRGBInputMatrixShaper(cmsHPROFILE hProfile
)
215 cmsToneCurve
*Shapes
[3];
216 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
219 if (!ReadICCMatrixRGB2XYZ(&Mat
, hProfile
)) return NULL
;
221 // XYZ PCS in encoded in 1.15 format, and the matrix output comes in 0..0xffff range, so
222 // we need to adjust the output by a factor of (0x10000/0xffff) to put data in
223 // a 1.16 range, and then a >> 1 to obtain 1.15. The total factor is (65536.0)/(65535.0*2)
225 for (i
=0; i
< 3; i
++)
226 for (j
=0; j
< 3; j
++)
227 Mat
.v
[i
].n
[j
] *= InpAdj
;
230 Shapes
[0] = (cmsToneCurve
*) cmsReadTag(hProfile
, cmsSigRedTRCTag
);
231 Shapes
[1] = (cmsToneCurve
*) cmsReadTag(hProfile
, cmsSigGreenTRCTag
);
232 Shapes
[2] = (cmsToneCurve
*) cmsReadTag(hProfile
, cmsSigBlueTRCTag
);
234 if (!Shapes
[0] || !Shapes
[1] || !Shapes
[2])
237 Lut
= cmsPipelineAlloc(ContextID
, 3, 3);
240 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocToneCurves(ContextID
, 3, Shapes
)) ||
241 !cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocMatrix(ContextID
, 3, 3, (cmsFloat64Number
*) &Mat
, NULL
)))
244 // Note that it is certainly possible a single profile would have a LUT based
245 // tag for output working in lab and a matrix-shaper for the fallback cases.
246 // This is not allowed by the spec, but this code is tolerant to those cases
247 if (cmsGetPCS(hProfile
) == cmsSigLabData
) {
249 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageAllocXYZ2Lab(ContextID
)))
258 cmsPipelineFree(Lut
);
264 // Read the DToAX tag, adjusting the encoding of Lab or XYZ if neded
266 cmsPipeline
* _cmsReadFloatInputTag(cmsHPROFILE hProfile
, cmsTagSignature tagFloat
)
268 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
269 cmsPipeline
* Lut
= cmsPipelineDup((cmsPipeline
*) cmsReadTag(hProfile
, tagFloat
));
270 cmsColorSpaceSignature spc
= cmsGetColorSpace(hProfile
);
271 cmsColorSpaceSignature PCS
= cmsGetPCS(hProfile
);
273 if (Lut
== NULL
) return NULL
;
275 // input and output of transform are in lcms 0..1 encoding. If XYZ or Lab spaces are used,
276 // these need to be normalized into the appropriate ranges (Lab = 100,0,0, XYZ=1.0,1.0,1.0)
277 if ( spc
== cmsSigLabData
)
279 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageNormalizeToLabFloat(ContextID
)))
282 else if (spc
== cmsSigXYZData
)
284 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageNormalizeToXyzFloat(ContextID
)))
288 if ( PCS
== cmsSigLabData
)
290 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageNormalizeFromLabFloat(ContextID
)))
293 else if( PCS
== cmsSigXYZData
)
295 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageNormalizeFromXyzFloat(ContextID
)))
302 cmsPipelineFree(Lut
);
307 // Read and create a BRAND NEW MPE LUT from a given profile. All stuff dependent of version, etc
308 // is adjusted here in order to create a LUT that takes care of all those details
309 cmsPipeline
* _cmsReadInputLUT(cmsHPROFILE hProfile
, int Intent
)
311 cmsTagTypeSignature OriginalType
;
312 cmsTagSignature tag16
= Device2PCS16
[Intent
];
313 cmsTagSignature tagFloat
= Device2PCSFloat
[Intent
];
314 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
316 // On named color, take the appropiate tag
317 if (cmsGetDeviceClass(hProfile
) == cmsSigNamedColorClass
) {
320 cmsNAMEDCOLORLIST
* nc
= (cmsNAMEDCOLORLIST
*) cmsReadTag(hProfile
, cmsSigNamedColor2Tag
);
322 if (nc
== NULL
) return NULL
;
324 Lut
= cmsPipelineAlloc(ContextID
, 0, 0);
326 cmsFreeNamedColorList(nc
);
330 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageAllocNamedColor(nc
, TRUE
)) ||
331 !cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageAllocLabV2ToV4(ContextID
))) {
332 cmsPipelineFree(Lut
);
338 if (cmsIsTag(hProfile
, tagFloat
)) { // Float tag takes precedence
340 // Floating point LUT are always V4, but the encoding range is no
341 // longer 0..1.0, so we need to add an stage depending on the color space
342 return _cmsReadFloatInputTag(hProfile
, tagFloat
);
345 // Revert to perceptual if no tag is found
346 if (!cmsIsTag(hProfile
, tag16
)) {
347 tag16
= Device2PCS16
[0];
350 if (cmsIsTag(hProfile
, tag16
)) { // Is there any LUT-Based table?
352 // Check profile version and LUT type. Do the necessary adjustments if needed
354 // First read the tag
355 cmsPipeline
* Lut
= (cmsPipeline
*) cmsReadTag(hProfile
, tag16
);
356 if (Lut
== NULL
) return NULL
;
358 // After reading it, we have now info about the original type
359 OriginalType
= _cmsGetTagTrueType(hProfile
, tag16
);
361 // The profile owns the Lut, so we need to copy it
362 Lut
= cmsPipelineDup(Lut
);
364 // We need to adjust data only for Lab16 on output
365 if (OriginalType
!= cmsSigLut16Type
|| cmsGetPCS(hProfile
) != cmsSigLabData
)
368 // If the input is Lab, add also a conversion at the begin
369 if (cmsGetColorSpace(hProfile
) == cmsSigLabData
&&
370 !cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageAllocLabV4ToV2(ContextID
)))
373 // Add a matrix for conversion V2 to V4 Lab PCS
374 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageAllocLabV2ToV4(ContextID
)))
379 cmsPipelineFree(Lut
);
383 // Lut was not found, try to create a matrix-shaper
385 // Check if this is a grayscale profile.
386 if (cmsGetColorSpace(hProfile
) == cmsSigGrayData
) {
388 // if so, build appropiate conversion tables.
389 // The tables are the PCS iluminant, scaled across GrayTRC
390 return BuildGrayInputMatrixPipeline(hProfile
);
393 // Not gray, create a normal matrix-shaper
394 return BuildRGBInputMatrixShaper(hProfile
);
397 // ---------------------------------------------------------------------------------------------------------------
399 // Gray output pipeline.
400 // XYZ -> Gray or Lab -> Gray. Since we only know the GrayTRC, we need to do some assumptions. Gray component will be
401 // given by Y on XYZ PCS and by L* on Lab PCS, Both across inverse TRC curve.
402 // The complete pipeline on XYZ is Matrix[3:1] -> Tone curve and in Lab Matrix[3:1] -> Tone Curve as well.
405 cmsPipeline
* BuildGrayOutputPipeline(cmsHPROFILE hProfile
)
407 cmsToneCurve
*GrayTRC
, *RevGrayTRC
;
409 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
411 GrayTRC
= (cmsToneCurve
*) cmsReadTag(hProfile
, cmsSigGrayTRCTag
);
412 if (GrayTRC
== NULL
) return NULL
;
414 RevGrayTRC
= cmsReverseToneCurve(GrayTRC
);
415 if (RevGrayTRC
== NULL
) return NULL
;
417 Lut
= cmsPipelineAlloc(ContextID
, 3, 1);
419 cmsFreeToneCurve(RevGrayTRC
);
423 if (cmsGetPCS(hProfile
) == cmsSigLabData
) {
425 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocMatrix(ContextID
, 1, 3, PickLstarMatrix
, NULL
)))
429 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocMatrix(ContextID
, 1, 3, PickYMatrix
, NULL
)))
433 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocToneCurves(ContextID
, 1, &RevGrayTRC
)))
436 cmsFreeToneCurve(RevGrayTRC
);
440 cmsFreeToneCurve(RevGrayTRC
);
441 cmsPipelineFree(Lut
);
447 cmsPipeline
* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile
)
450 cmsToneCurve
*Shapes
[3], *InvShapes
[3];
453 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
455 if (!ReadICCMatrixRGB2XYZ(&Mat
, hProfile
))
458 if (!_cmsMAT3inverse(&Mat
, &Inv
))
461 // XYZ PCS in encoded in 1.15 format, and the matrix input should come in 0..0xffff range, so
462 // we need to adjust the input by a << 1 to obtain a 1.16 fixed and then by a factor of
463 // (0xffff/0x10000) to put data in 0..0xffff range. Total factor is (2.0*65535.0)/65536.0;
465 for (i
=0; i
< 3; i
++)
466 for (j
=0; j
< 3; j
++)
467 Inv
.v
[i
].n
[j
] *= OutpAdj
;
469 Shapes
[0] = (cmsToneCurve
*) cmsReadTag(hProfile
, cmsSigRedTRCTag
);
470 Shapes
[1] = (cmsToneCurve
*) cmsReadTag(hProfile
, cmsSigGreenTRCTag
);
471 Shapes
[2] = (cmsToneCurve
*) cmsReadTag(hProfile
, cmsSigBlueTRCTag
);
473 if (!Shapes
[0] || !Shapes
[1] || !Shapes
[2])
476 InvShapes
[0] = cmsReverseToneCurve(Shapes
[0]);
477 InvShapes
[1] = cmsReverseToneCurve(Shapes
[1]);
478 InvShapes
[2] = cmsReverseToneCurve(Shapes
[2]);
480 if (!InvShapes
[0] || !InvShapes
[1] || !InvShapes
[2]) {
484 Lut
= cmsPipelineAlloc(ContextID
, 3, 3);
487 // Note that it is certainly possible a single profile would have a LUT based
488 // tag for output working in lab and a matrix-shaper for the fallback cases.
489 // This is not allowed by the spec, but this code is tolerant to those cases
490 if (cmsGetPCS(hProfile
) == cmsSigLabData
) {
492 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageAllocLab2XYZ(ContextID
)))
496 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocMatrix(ContextID
, 3, 3, (cmsFloat64Number
*) &Inv
, NULL
)) ||
497 !cmsPipelineInsertStage(Lut
, cmsAT_END
, cmsStageAllocToneCurves(ContextID
, 3, InvShapes
)))
501 cmsFreeToneCurveTriple(InvShapes
);
504 cmsFreeToneCurveTriple(InvShapes
);
505 cmsPipelineFree(Lut
);
510 // Change CLUT interpolation to trilinear
512 void ChangeInterpolationToTrilinear(cmsPipeline
* Lut
)
516 for (Stage
= cmsPipelineGetPtrToFirstStage(Lut
);
518 Stage
= cmsStageNext(Stage
)) {
520 if (cmsStageType(Stage
) == cmsSigCLutElemType
) {
522 _cmsStageCLutData
* CLUT
= (_cmsStageCLutData
*) Stage
->Data
;
524 CLUT
->Params
->dwFlags
|= CMS_LERP_FLAGS_TRILINEAR
;
525 _cmsSetInterpolationRoutine(CLUT
->Params
);
531 // Read the DToAX tag, adjusting the encoding of Lab or XYZ if neded
533 cmsPipeline
* _cmsReadFloatOutputTag(cmsHPROFILE hProfile
, cmsTagSignature tagFloat
)
535 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
536 cmsPipeline
* Lut
= cmsPipelineDup((cmsPipeline
*) cmsReadTag(hProfile
, tagFloat
));
537 cmsColorSpaceSignature PCS
= cmsGetPCS(hProfile
);
538 cmsColorSpaceSignature dataSpace
= cmsGetColorSpace(hProfile
);
540 if (Lut
== NULL
) return NULL
;
542 // If PCS is Lab or XYZ, the floating point tag is accepting data in the space encoding,
543 // and since the formatter has already accomodated to 0..1.0, we should undo this change
544 if ( PCS
== cmsSigLabData
)
546 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageNormalizeToLabFloat(ContextID
)))
550 if (PCS
== cmsSigXYZData
)
552 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageNormalizeToXyzFloat(ContextID
)))
556 // the output can be Lab or XYZ, in which case normalisation is needed on the end of the pipeline
557 if ( dataSpace
== cmsSigLabData
)
559 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageNormalizeFromLabFloat(ContextID
)))
562 else if (dataSpace
== cmsSigXYZData
)
564 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageNormalizeFromXyzFloat(ContextID
)))
571 cmsPipelineFree(Lut
);
575 // Create an output MPE LUT from agiven profile. Version mismatches are handled here
576 cmsPipeline
* _cmsReadOutputLUT(cmsHPROFILE hProfile
, int Intent
)
578 cmsTagTypeSignature OriginalType
;
579 cmsTagSignature tag16
= PCS2Device16
[Intent
];
580 cmsTagSignature tagFloat
= PCS2DeviceFloat
[Intent
];
581 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
583 if (cmsIsTag(hProfile
, tagFloat
)) { // Float tag takes precedence
585 // Floating point LUT are always V4
586 return _cmsReadFloatOutputTag(hProfile
, tagFloat
);
589 // Revert to perceptual if no tag is found
590 if (!cmsIsTag(hProfile
, tag16
)) {
591 tag16
= PCS2Device16
[0];
594 if (cmsIsTag(hProfile
, tag16
)) { // Is there any LUT-Based table?
596 // Check profile version and LUT type. Do the necessary adjustments if needed
598 // First read the tag
599 cmsPipeline
* Lut
= (cmsPipeline
*) cmsReadTag(hProfile
, tag16
);
600 if (Lut
== NULL
) return NULL
;
602 // After reading it, we have info about the original type
603 OriginalType
= _cmsGetTagTrueType(hProfile
, tag16
);
605 // The profile owns the Lut, so we need to copy it
606 Lut
= cmsPipelineDup(Lut
);
607 if (Lut
== NULL
) return NULL
;
609 // Now it is time for a controversial stuff. I found that for 3D LUTS using
610 // Lab used as indexer space, trilinear interpolation should be used
611 if (cmsGetPCS(hProfile
) == cmsSigLabData
)
612 ChangeInterpolationToTrilinear(Lut
);
614 // We need to adjust data only for Lab and Lut16 type
615 if (OriginalType
!= cmsSigLut16Type
|| cmsGetPCS(hProfile
) != cmsSigLabData
)
618 // Add a matrix for conversion V4 to V2 Lab PCS
619 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageAllocLabV4ToV2(ContextID
)))
622 // If the output is Lab, add also a conversion at the end
623 if (cmsGetColorSpace(hProfile
) == cmsSigLabData
)
624 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageAllocLabV2ToV4(ContextID
)))
629 cmsPipelineFree(Lut
);
633 // Lut not found, try to create a matrix-shaper
635 // Check if this is a grayscale profile.
636 if (cmsGetColorSpace(hProfile
) == cmsSigGrayData
) {
638 // if so, build appropiate conversion tables.
639 // The tables are the PCS iluminant, scaled across GrayTRC
640 return BuildGrayOutputPipeline(hProfile
);
643 // Not gray, create a normal matrix-shaper, which only operates in XYZ space
644 return BuildRGBOutputMatrixShaper(hProfile
);
647 // ---------------------------------------------------------------------------------------------------------------
649 // Read the AToD0 tag, adjusting the encoding of Lab or XYZ if neded
651 cmsPipeline
* _cmsReadFloatDevicelinkTag(cmsHPROFILE hProfile
, cmsTagSignature tagFloat
)
653 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
654 cmsPipeline
* Lut
= cmsPipelineDup((cmsPipeline
*) cmsReadTag(hProfile
, tagFloat
));
655 cmsColorSpaceSignature PCS
= cmsGetPCS(hProfile
);
656 cmsColorSpaceSignature spc
= cmsGetColorSpace(hProfile
);
658 if (Lut
== NULL
) return NULL
;
660 if (spc
== cmsSigLabData
)
662 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageNormalizeToLabFloat(ContextID
)))
666 if (spc
== cmsSigXYZData
)
668 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageNormalizeToXyzFloat(ContextID
)))
672 if (PCS
== cmsSigLabData
)
674 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageNormalizeFromLabFloat(ContextID
)))
678 if (PCS
== cmsSigXYZData
)
680 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageNormalizeFromXyzFloat(ContextID
)))
686 cmsPipelineFree(Lut
);
690 // This one includes abstract profiles as well. Matrix-shaper cannot be obtained on that device class. The
691 // tag name here may default to AToB0
692 cmsPipeline
* _cmsReadDevicelinkLUT(cmsHPROFILE hProfile
, int Intent
)
695 cmsTagTypeSignature OriginalType
;
696 cmsTagSignature tag16
= Device2PCS16
[Intent
];
697 cmsTagSignature tagFloat
= Device2PCSFloat
[Intent
];
698 cmsContext ContextID
= cmsGetProfileContextID(hProfile
);
701 // On named color, take the appropiate tag
702 if (cmsGetDeviceClass(hProfile
) == cmsSigNamedColorClass
) {
704 cmsNAMEDCOLORLIST
* nc
= (cmsNAMEDCOLORLIST
*) cmsReadTag(hProfile
, cmsSigNamedColor2Tag
);
706 if (nc
== NULL
) return NULL
;
708 Lut
= cmsPipelineAlloc(ContextID
, 0, 0);
712 if (!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageAllocNamedColor(nc
, FALSE
)))
715 if (cmsGetColorSpace(hProfile
) == cmsSigLabData
)
716 if (!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageAllocLabV2ToV4(ContextID
)))
721 cmsPipelineFree(Lut
);
722 cmsFreeNamedColorList(nc
);
726 if (cmsIsTag(hProfile
, tagFloat
)) { // Float tag takes precedence
728 // Floating point LUT are always V
729 return _cmsReadFloatDevicelinkTag(hProfile
, tagFloat
);
732 tagFloat
= Device2PCSFloat
[0];
733 if (cmsIsTag(hProfile
, tagFloat
)) {
735 return cmsPipelineDup((cmsPipeline
*) cmsReadTag(hProfile
, tagFloat
));
738 if (!cmsIsTag(hProfile
, tag16
)) { // Is there any LUT-Based table?
740 tag16
= Device2PCS16
[0];
741 if (!cmsIsTag(hProfile
, tag16
)) return NULL
;
744 // Check profile version and LUT type. Do the necessary adjustments if needed
747 Lut
= (cmsPipeline
*) cmsReadTag(hProfile
, tag16
);
748 if (Lut
== NULL
) return NULL
;
750 // The profile owns the Lut, so we need to copy it
751 Lut
= cmsPipelineDup(Lut
);
752 if (Lut
== NULL
) return NULL
;
754 // Now it is time for a controversial stuff. I found that for 3D LUTS using
755 // Lab used as indexer space, trilinear interpolation should be used
756 if (cmsGetColorSpace(hProfile
) == cmsSigLabData
)
757 ChangeInterpolationToTrilinear(Lut
);
759 // After reading it, we have info about the original type
760 OriginalType
= _cmsGetTagTrueType(hProfile
, tag16
);
762 // We need to adjust data for Lab16 on output
763 if (OriginalType
!= cmsSigLut16Type
) return Lut
;
765 // Here it is possible to get Lab on both sides
767 if (cmsGetPCS(hProfile
) == cmsSigLabData
) {
768 if(!cmsPipelineInsertStage(Lut
, cmsAT_BEGIN
, _cmsStageAllocLabV4ToV2(ContextID
)))
772 if (cmsGetColorSpace(hProfile
) == cmsSigLabData
) {
773 if(!cmsPipelineInsertStage(Lut
, cmsAT_END
, _cmsStageAllocLabV2ToV4(ContextID
)))
780 cmsPipelineFree(Lut
);
784 // ---------------------------------------------------------------------------------------------------------------
786 // Returns TRUE if the profile is implemented as matrix-shaper
787 cmsBool CMSEXPORT
cmsIsMatrixShaper(cmsHPROFILE hProfile
)
789 switch (cmsGetColorSpace(hProfile
)) {
793 return cmsIsTag(hProfile
, cmsSigGrayTRCTag
);
797 return (cmsIsTag(hProfile
, cmsSigRedColorantTag
) &&
798 cmsIsTag(hProfile
, cmsSigGreenColorantTag
) &&
799 cmsIsTag(hProfile
, cmsSigBlueColorantTag
) &&
800 cmsIsTag(hProfile
, cmsSigRedTRCTag
) &&
801 cmsIsTag(hProfile
, cmsSigGreenTRCTag
) &&
802 cmsIsTag(hProfile
, cmsSigBlueTRCTag
));
810 // Returns TRUE if the intent is implemented as CLUT
811 cmsBool CMSEXPORT
cmsIsCLUT(cmsHPROFILE hProfile
, cmsUInt32Number Intent
, cmsUInt32Number UsedDirection
)
813 const cmsTagSignature
* TagTable
;
815 // For devicelinks, the supported intent is that one stated in the header
816 if (cmsGetDeviceClass(hProfile
) == cmsSigLinkClass
) {
817 return (cmsGetHeaderRenderingIntent(hProfile
) == Intent
);
820 switch (UsedDirection
) {
822 case LCMS_USED_AS_INPUT
: TagTable
= Device2PCS16
; break;
823 case LCMS_USED_AS_OUTPUT
:TagTable
= PCS2Device16
; break;
825 // For proofing, we need rel. colorimetric in output. Let's do some recursion
826 case LCMS_USED_AS_PROOF
:
827 return cmsIsIntentSupported(hProfile
, Intent
, LCMS_USED_AS_INPUT
) &&
828 cmsIsIntentSupported(hProfile
, INTENT_RELATIVE_COLORIMETRIC
, LCMS_USED_AS_OUTPUT
);
831 cmsSignalError(cmsGetProfileContextID(hProfile
), cmsERROR_RANGE
, "Unexpected direction (%d)", UsedDirection
);
835 return cmsIsTag(hProfile
, TagTable
[Intent
]);
840 // Return info about supported intents
841 cmsBool CMSEXPORT
cmsIsIntentSupported(cmsHPROFILE hProfile
,
842 cmsUInt32Number Intent
, cmsUInt32Number UsedDirection
)
845 if (cmsIsCLUT(hProfile
, Intent
, UsedDirection
)) return TRUE
;
847 // Is there any matrix-shaper? If so, the intent is supported. This is a bit odd, since V2 matrix shaper
848 // does not fully support relative colorimetric because they cannot deal with non-zero black points, but
849 // many profiles claims that, and this is certainly not true for V4 profiles. Lets answer "yes" no matter
850 // the accuracy would be less than optimal in rel.col and v2 case.
852 return cmsIsMatrixShaper(hProfile
);
856 // ---------------------------------------------------------------------------------------------------------------
858 // Read both, profile sequence description and profile sequence id if present. Then combine both to
859 // create qa unique structure holding both. Shame on ICC to store things in such complicated way.
860 cmsSEQ
* _cmsReadProfileSequence(cmsHPROFILE hProfile
)
867 // Take profile sequence description first
868 ProfileSeq
= (cmsSEQ
*) cmsReadTag(hProfile
, cmsSigProfileSequenceDescTag
);
870 // Take profile sequence ID
871 ProfileId
= (cmsSEQ
*) cmsReadTag(hProfile
, cmsSigProfileSequenceIdTag
);
873 if (ProfileSeq
== NULL
&& ProfileId
== NULL
) return NULL
;
875 if (ProfileSeq
== NULL
) return cmsDupProfileSequenceDescription(ProfileId
);
876 if (ProfileId
== NULL
) return cmsDupProfileSequenceDescription(ProfileSeq
);
878 // We have to mix both together. For that they must agree
879 if (ProfileSeq
->n
!= ProfileId
->n
) return cmsDupProfileSequenceDescription(ProfileSeq
);
881 NewSeq
= cmsDupProfileSequenceDescription(ProfileSeq
);
883 // Ok, proceed to the mixing
884 if (NewSeq
!= NULL
) {
885 for (i
=0; i
< ProfileSeq
->n
; i
++) {
887 memmove(&NewSeq
->seq
[i
].ProfileID
, &ProfileId
->seq
[i
].ProfileID
, sizeof(cmsProfileID
));
888 NewSeq
->seq
[i
].Description
= cmsMLUdup(ProfileId
->seq
[i
].Description
);
894 // Dump the contents of profile sequence in both tags (if v4 available)
895 cmsBool
_cmsWriteProfileSequence(cmsHPROFILE hProfile
, const cmsSEQ
* seq
)
897 if (!cmsWriteTag(hProfile
, cmsSigProfileSequenceDescTag
, seq
)) return FALSE
;
899 if (cmsGetProfileVersion(hProfile
) >= 4.0) {
901 if (!cmsWriteTag(hProfile
, cmsSigProfileSequenceIdTag
, seq
)) return FALSE
;
908 // Auxiliar, read and duplicate a MLU if found.
910 cmsMLU
* GetMLUFromProfile(cmsHPROFILE h
, cmsTagSignature sig
)
912 cmsMLU
* mlu
= (cmsMLU
*) cmsReadTag(h
, sig
);
913 if (mlu
== NULL
) return NULL
;
915 return cmsMLUdup(mlu
);
918 // Create a sequence description out of an array of profiles
919 cmsSEQ
* _cmsCompileProfileSequence(cmsContext ContextID
, cmsUInt32Number nProfiles
, cmsHPROFILE hProfiles
[])
922 cmsSEQ
* seq
= cmsAllocProfileSequenceDescription(ContextID
, nProfiles
);
924 if (seq
== NULL
) return NULL
;
926 for (i
=0; i
< nProfiles
; i
++) {
928 cmsPSEQDESC
* ps
= &seq
->seq
[i
];
929 cmsHPROFILE h
= hProfiles
[i
];
930 cmsTechnologySignature
* techpt
;
932 cmsGetHeaderAttributes(h
, &ps
->attributes
);
933 cmsGetHeaderProfileID(h
, ps
->ProfileID
.ID8
);
934 ps
->deviceMfg
= cmsGetHeaderManufacturer(h
);
935 ps
->deviceModel
= cmsGetHeaderModel(h
);
937 techpt
= (cmsTechnologySignature
*) cmsReadTag(h
, cmsSigTechnologyTag
);
939 ps
->technology
= (cmsTechnologySignature
) 0;
941 ps
->technology
= *techpt
;
943 ps
->Manufacturer
= GetMLUFromProfile(h
, cmsSigDeviceMfgDescTag
);
944 ps
->Model
= GetMLUFromProfile(h
, cmsSigDeviceModelDescTag
);
945 ps
->Description
= GetMLUFromProfile(h
, cmsSigProfileDescriptionTag
);
952 // -------------------------------------------------------------------------------------------------------------------
956 const cmsMLU
* GetInfo(cmsHPROFILE hProfile
, cmsInfoType Info
)
962 case cmsInfoDescription
:
963 sig
= cmsSigProfileDescriptionTag
;
966 case cmsInfoManufacturer
:
967 sig
= cmsSigDeviceMfgDescTag
;
971 sig
= cmsSigDeviceModelDescTag
;
974 case cmsInfoCopyright
:
975 sig
= cmsSigCopyrightTag
;
978 default: return NULL
;
982 return (cmsMLU
*) cmsReadTag(hProfile
, sig
);
987 cmsUInt32Number CMSEXPORT
cmsGetProfileInfo(cmsHPROFILE hProfile
, cmsInfoType Info
,
988 const char LanguageCode
[3], const char CountryCode
[3],
989 wchar_t* Buffer
, cmsUInt32Number BufferSize
)
991 const cmsMLU
* mlu
= GetInfo(hProfile
, Info
);
992 if (mlu
== NULL
) return 0;
994 return cmsMLUgetWide(mlu
, LanguageCode
, CountryCode
, Buffer
, BufferSize
);
998 cmsUInt32Number CMSEXPORT
cmsGetProfileInfoASCII(cmsHPROFILE hProfile
, cmsInfoType Info
,
999 const char LanguageCode
[3], const char CountryCode
[3],
1000 char* Buffer
, cmsUInt32Number BufferSize
)
1002 const cmsMLU
* mlu
= GetInfo(hProfile
, Info
);
1003 if (mlu
== NULL
) return 0;
1005 return cmsMLUgetASCII(mlu
, LanguageCode
, CountryCode
, Buffer
, BufferSize
);