1/*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3 *
4 * This code is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 only, as
6 * published by the Free Software Foundation. Oracle designates this
7 * particular file as subject to the "Classpath" exception as provided
8 * by Oracle in the LICENSE file that accompanied this code.
9 *
10 * This code is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * version 2 for more details (a copy is included in the LICENSE file that
14 * accompanied this code).
15 *
16 * You should have received a copy of the GNU General Public License version
17 * 2 along with this work; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21 * or visit www.oracle.com if you need additional information or have any
22 * questions.
23 */
24
25// This file is available under and governed by the GNU General Public
26// License version 2 only, as published by the Free Software Foundation.
27// However, the following notice accompanied the original version of this
28// file:
29//
30//---------------------------------------------------------------------------------
31//
32// Little Color Management System
33// Copyright (c) 1998-2017 Marti Maria Saguer
34//
35// Permission is hereby granted, free of charge, to any person obtaining
36// a copy of this software and associated documentation files (the "Software"),
37// to deal in the Software without restriction, including without limitation
38// the rights to use, copy, modify, merge, publish, distribute, sublicense,
39// and/or sell copies of the Software, and to permit persons to whom the Software
40// is furnished to do so, subject to the following conditions:
41//
42// The above copyright notice and this permission notice shall be included in
43// all copies or substantial portions of the Software.
44//
45// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
46// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
47// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
48// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
49// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
50// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
51// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
52//
53//---------------------------------------------------------------------------------
54//
55
56#include "lcms2_internal.h"
57
58// Read tags using low-level functions, provides necessary glue code to adapt versions, etc.
59
60// LUT tags
61static const cmsTagSignature Device2PCS16[] = {cmsSigAToB0Tag, // Perceptual
62 cmsSigAToB1Tag, // Relative colorimetric
63 cmsSigAToB2Tag, // Saturation
64 cmsSigAToB1Tag }; // Absolute colorimetric
65
66static const cmsTagSignature Device2PCSFloat[] = {cmsSigDToB0Tag, // Perceptual
67 cmsSigDToB1Tag, // Relative colorimetric
68 cmsSigDToB2Tag, // Saturation
69 cmsSigDToB3Tag }; // Absolute colorimetric
70
71static const cmsTagSignature PCS2Device16[] = {cmsSigBToA0Tag, // Perceptual
72 cmsSigBToA1Tag, // Relative colorimetric
73 cmsSigBToA2Tag, // Saturation
74 cmsSigBToA1Tag }; // Absolute colorimetric
75
76static const cmsTagSignature PCS2DeviceFloat[] = {cmsSigBToD0Tag, // Perceptual
77 cmsSigBToD1Tag, // Relative colorimetric
78 cmsSigBToD2Tag, // Saturation
79 cmsSigBToD3Tag }; // Absolute colorimetric
80
81
82// Factors to convert from 1.15 fixed point to 0..1.0 range and vice-versa
83#define InpAdj (1.0/MAX_ENCODEABLE_XYZ) // (65536.0/(65535.0*2.0))
84#define OutpAdj (MAX_ENCODEABLE_XYZ) // ((2.0*65535.0)/65536.0)
85
86// Several resources for gray conversions.
87static const cmsFloat64Number GrayInputMatrix[] = { (InpAdj*cmsD50X), (InpAdj*cmsD50Y), (InpAdj*cmsD50Z) };
88static const cmsFloat64Number OneToThreeInputMatrix[] = { 1, 1, 1 };
89static const cmsFloat64Number PickYMatrix[] = { 0, (OutpAdj*cmsD50Y), 0 };
90static const cmsFloat64Number PickLstarMatrix[] = { 1, 0, 0 };
91
92// Get a media white point fixing some issues found in certain old profiles
93cmsBool _cmsReadMediaWhitePoint(cmsCIEXYZ* Dest, cmsHPROFILE hProfile)
94{
95 cmsCIEXYZ* Tag;
96
97 _cmsAssert(Dest != NULL);
98
99 Tag = (cmsCIEXYZ*) cmsReadTag(hProfile, cmsSigMediaWhitePointTag);
100
101 // If no wp, take D50
102 if (Tag == NULL) {
103 *Dest = *cmsD50_XYZ();
104 return TRUE;
105 }
106
107 // V2 display profiles should give D50
108 if (cmsGetEncodedICCversion(hProfile) < 0x4000000) {
109
110 if (cmsGetDeviceClass(hProfile) == cmsSigDisplayClass) {
111 *Dest = *cmsD50_XYZ();
112 return TRUE;
113 }
114 }
115
116 // All seems ok
117 *Dest = *Tag;
118 return TRUE;
119}
120
121
122// Chromatic adaptation matrix. Fix some issues as well
123cmsBool _cmsReadCHAD(cmsMAT3* Dest, cmsHPROFILE hProfile)
124{
125 cmsMAT3* Tag;
126
127 _cmsAssert(Dest != NULL);
128
129 Tag = (cmsMAT3*) cmsReadTag(hProfile, cmsSigChromaticAdaptationTag);
130
131 if (Tag != NULL) {
132 *Dest = *Tag;
133 return TRUE;
134 }
135
136 // No CHAD available, default it to identity
137 _cmsMAT3identity(Dest);
138
139 // V2 display profiles should give D50
140 if (cmsGetEncodedICCversion(hProfile) < 0x4000000) {
141
142 if (cmsGetDeviceClass(hProfile) == cmsSigDisplayClass) {
143
144 cmsCIEXYZ* White = (cmsCIEXYZ*) cmsReadTag(hProfile, cmsSigMediaWhitePointTag);
145
146 if (White == NULL) {
147
148 _cmsMAT3identity(Dest);
149 return TRUE;
150 }
151
152 return _cmsAdaptationMatrix(Dest, NULL, White, cmsD50_XYZ());
153 }
154 }
155
156 return TRUE;
157}
158
159
160// Auxiliary, read colorants as a MAT3 structure. Used by any function that needs a matrix-shaper
161static
162cmsBool ReadICCMatrixRGB2XYZ(cmsMAT3* r, cmsHPROFILE hProfile)
163{
164 cmsCIEXYZ *PtrRed, *PtrGreen, *PtrBlue;
165
166 _cmsAssert(r != NULL);
167
168 PtrRed = (cmsCIEXYZ *) cmsReadTag(hProfile, cmsSigRedColorantTag);
169 PtrGreen = (cmsCIEXYZ *) cmsReadTag(hProfile, cmsSigGreenColorantTag);
170 PtrBlue = (cmsCIEXYZ *) cmsReadTag(hProfile, cmsSigBlueColorantTag);
171
172 if (PtrRed == NULL || PtrGreen == NULL || PtrBlue == NULL)
173 return FALSE;
174
175 _cmsVEC3init(&r -> v[0], PtrRed -> X, PtrGreen -> X, PtrBlue -> X);
176 _cmsVEC3init(&r -> v[1], PtrRed -> Y, PtrGreen -> Y, PtrBlue -> Y);
177 _cmsVEC3init(&r -> v[2], PtrRed -> Z, PtrGreen -> Z, PtrBlue -> Z);
178
179 return TRUE;
180}
181
182
183// Gray input pipeline
184static
185cmsPipeline* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile)
186{
187 cmsToneCurve *GrayTRC;
188 cmsPipeline* Lut;
189 cmsContext ContextID = cmsGetProfileContextID(hProfile);
190
191 GrayTRC = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGrayTRCTag);
192 if (GrayTRC == NULL) return NULL;
193
194 Lut = cmsPipelineAlloc(ContextID, 1, 3);
195 if (Lut == NULL)
196 goto Error;
197
198 if (cmsGetPCS(hProfile) == cmsSigLabData) {
199
200 // In this case we implement the profile as an identity matrix plus 3 tone curves
201 cmsUInt16Number Zero[2] = { 0x8080, 0x8080 };
202 cmsToneCurve* EmptyTab;
203 cmsToneCurve* LabCurves[3];
204
205 EmptyTab = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero);
206
207 if (EmptyTab == NULL)
208 goto Error;
209
210 LabCurves[0] = GrayTRC;
211 LabCurves[1] = EmptyTab;
212 LabCurves[2] = EmptyTab;
213
214 if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 1, OneToThreeInputMatrix, NULL)) ||
215 !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, LabCurves))) {
216 cmsFreeToneCurve(EmptyTab);
217 goto Error;
218 }
219
220 cmsFreeToneCurve(EmptyTab);
221
222 }
223 else {
224
225 if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 1, &GrayTRC)) ||
226 !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 1, GrayInputMatrix, NULL)))
227 goto Error;
228 }
229
230 return Lut;
231
232Error:
233 cmsFreeToneCurve(GrayTRC);
234 cmsPipelineFree(Lut);
235 return NULL;
236}
237
238// RGB Matrix shaper
239static
240cmsPipeline* BuildRGBInputMatrixShaper(cmsHPROFILE hProfile)
241{
242 cmsPipeline* Lut;
243 cmsMAT3 Mat;
244 cmsToneCurve *Shapes[3];
245 cmsContext ContextID = cmsGetProfileContextID(hProfile);
246 int i, j;
247
248 if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL;
249
250 // XYZ PCS in encoded in 1.15 format, and the matrix output comes in 0..0xffff range, so
251 // we need to adjust the output by a factor of (0x10000/0xffff) to put data in
252 // a 1.16 range, and then a >> 1 to obtain 1.15. The total factor is (65536.0)/(65535.0*2)
253
254 for (i=0; i < 3; i++)
255 for (j=0; j < 3; j++)
256 Mat.v[i].n[j] *= InpAdj;
257
258
259 Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag);
260 Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag);
261 Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag);
262
263 if (!Shapes[0] || !Shapes[1] || !Shapes[2])
264 return NULL;
265
266 Lut = cmsPipelineAlloc(ContextID, 3, 3);
267 if (Lut != NULL) {
268
269 if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, Shapes)) ||
270 !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Mat, NULL)))
271 goto Error;
272
273 // Note that it is certainly possible a single profile would have a LUT based
274 // tag for output working in lab and a matrix-shaper for the fallback cases.
275 // This is not allowed by the spec, but this code is tolerant to those cases
276 if (cmsGetPCS(hProfile) == cmsSigLabData) {
277
278 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocXYZ2Lab(ContextID)))
279 goto Error;
280 }
281
282 }
283
284 return Lut;
285
286Error:
287 cmsPipelineFree(Lut);
288 return NULL;
289}
290
291
292
293// Read the DToAX tag, adjusting the encoding of Lab or XYZ if neded
294static
295cmsPipeline* _cmsReadFloatInputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat)
296{
297 cmsContext ContextID = cmsGetProfileContextID(hProfile);
298 cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat));
299 cmsColorSpaceSignature spc = cmsGetColorSpace(hProfile);
300 cmsColorSpaceSignature PCS = cmsGetPCS(hProfile);
301
302 if (Lut == NULL) return NULL;
303
304 // input and output of transform are in lcms 0..1 encoding. If XYZ or Lab spaces are used,
305 // these need to be normalized into the appropriate ranges (Lab = 100,0,0, XYZ=1.0,1.0,1.0)
306 if ( spc == cmsSigLabData)
307 {
308 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID)))
309 goto Error;
310 }
311 else if (spc == cmsSigXYZData)
312 {
313 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID)))
314 goto Error;
315 }
316
317 if ( PCS == cmsSigLabData)
318 {
319 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID)))
320 goto Error;
321 }
322 else if( PCS == cmsSigXYZData)
323 {
324 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID)))
325 goto Error;
326 }
327
328 return Lut;
329
330Error:
331 cmsPipelineFree(Lut);
332 return NULL;
333}
334
335
336// Read and create a BRAND NEW MPE LUT from a given profile. All stuff dependent of version, etc
337// is adjusted here in order to create a LUT that takes care of all those details.
338// We add intent = 0xffffffff as a way to read matrix shaper always, no matter of other LUT
339cmsPipeline* CMSEXPORT _cmsReadInputLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent)
340{
341 cmsTagTypeSignature OriginalType;
342 cmsTagSignature tag16;
343 cmsTagSignature tagFloat;
344 cmsContext ContextID = cmsGetProfileContextID(hProfile);
345
346 // On named color, take the appropriate tag
347 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
348
349 cmsPipeline* Lut;
350 cmsNAMEDCOLORLIST* nc = (cmsNAMEDCOLORLIST*) cmsReadTag(hProfile, cmsSigNamedColor2Tag);
351
352 if (nc == NULL) return NULL;
353
354 Lut = cmsPipelineAlloc(ContextID, 0, 0);
355 if (Lut == NULL) {
356 cmsFreeNamedColorList(nc);
357 return NULL;
358 }
359
360 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, TRUE)) ||
361 !cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) {
362 cmsPipelineFree(Lut);
363 return NULL;
364 }
365 return Lut;
366 }
367
368 // This is an attempt to reuse this function to retrieve the matrix-shaper as pipeline no
369 // matter other LUT are present and have precedence. Intent = 0xffffffff can be used for that.
370 if (Intent <= INTENT_ABSOLUTE_COLORIMETRIC) {
371
372 tag16 = Device2PCS16[Intent];
373 tagFloat = Device2PCSFloat[Intent];
374
375 if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence
376
377 // Floating point LUT are always V4, but the encoding range is no
378 // longer 0..1.0, so we need to add an stage depending on the color space
379 return _cmsReadFloatInputTag(hProfile, tagFloat);
380 }
381
382 // Revert to perceptual if no tag is found
383 if (!cmsIsTag(hProfile, tag16)) {
384 tag16 = Device2PCS16[0];
385 }
386
387 if (cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table?
388
389 // Check profile version and LUT type. Do the necessary adjustments if needed
390
391 // First read the tag
392 cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16);
393 if (Lut == NULL) return NULL;
394
395 // After reading it, we have now info about the original type
396 OriginalType = _cmsGetTagTrueType(hProfile, tag16);
397
398 // The profile owns the Lut, so we need to copy it
399 Lut = cmsPipelineDup(Lut);
400
401 // We need to adjust data only for Lab16 on output
402 if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData)
403 return Lut;
404
405 // If the input is Lab, add also a conversion at the begin
406 if (cmsGetColorSpace(hProfile) == cmsSigLabData &&
407 !cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID)))
408 goto Error;
409
410 // Add a matrix for conversion V2 to V4 Lab PCS
411 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)))
412 goto Error;
413
414 return Lut;
415Error:
416 cmsPipelineFree(Lut);
417 return NULL;
418 }
419 }
420
421 // Lut was not found, try to create a matrix-shaper
422
423 // Check if this is a grayscale profile.
424 if (cmsGetColorSpace(hProfile) == cmsSigGrayData) {
425
426 // if so, build appropriate conversion tables.
427 // The tables are the PCS iluminant, scaled across GrayTRC
428 return BuildGrayInputMatrixPipeline(hProfile);
429 }
430
431 // Not gray, create a normal matrix-shaper
432 return BuildRGBInputMatrixShaper(hProfile);
433}
434
435// ---------------------------------------------------------------------------------------------------------------
436
437// Gray output pipeline.
438// XYZ -> Gray or Lab -> Gray. Since we only know the GrayTRC, we need to do some assumptions. Gray component will be
439// given by Y on XYZ PCS and by L* on Lab PCS, Both across inverse TRC curve.
440// The complete pipeline on XYZ is Matrix[3:1] -> Tone curve and in Lab Matrix[3:1] -> Tone Curve as well.
441
442static
443cmsPipeline* BuildGrayOutputPipeline(cmsHPROFILE hProfile)
444{
445 cmsToneCurve *GrayTRC, *RevGrayTRC;
446 cmsPipeline* Lut;
447 cmsContext ContextID = cmsGetProfileContextID(hProfile);
448
449 GrayTRC = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGrayTRCTag);
450 if (GrayTRC == NULL) return NULL;
451
452 RevGrayTRC = cmsReverseToneCurve(GrayTRC);
453 if (RevGrayTRC == NULL) return NULL;
454
455 Lut = cmsPipelineAlloc(ContextID, 3, 1);
456 if (Lut == NULL) {
457 cmsFreeToneCurve(RevGrayTRC);
458 return NULL;
459 }
460
461 if (cmsGetPCS(hProfile) == cmsSigLabData) {
462
463 if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickLstarMatrix, NULL)))
464 goto Error;
465 }
466 else {
467 if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickYMatrix, NULL)))
468 goto Error;
469 }
470
471 if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 1, &RevGrayTRC)))
472 goto Error;
473
474 cmsFreeToneCurve(RevGrayTRC);
475 return Lut;
476
477Error:
478 cmsFreeToneCurve(RevGrayTRC);
479 cmsPipelineFree(Lut);
480 return NULL;
481}
482
483
484static
485cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile)
486{
487 cmsPipeline* Lut;
488 cmsToneCurve *Shapes[3], *InvShapes[3];
489 cmsMAT3 Mat, Inv;
490 int i, j;
491 cmsContext ContextID = cmsGetProfileContextID(hProfile);
492
493 if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile))
494 return NULL;
495
496 if (!_cmsMAT3inverse(&Mat, &Inv))
497 return NULL;
498
499 // XYZ PCS in encoded in 1.15 format, and the matrix input should come in 0..0xffff range, so
500 // we need to adjust the input by a << 1 to obtain a 1.16 fixed and then by a factor of
501 // (0xffff/0x10000) to put data in 0..0xffff range. Total factor is (2.0*65535.0)/65536.0;
502
503 for (i=0; i < 3; i++)
504 for (j=0; j < 3; j++)
505 Inv.v[i].n[j] *= OutpAdj;
506
507 Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag);
508 Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag);
509 Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag);
510
511 if (!Shapes[0] || !Shapes[1] || !Shapes[2])
512 return NULL;
513
514 InvShapes[0] = cmsReverseToneCurve(Shapes[0]);
515 InvShapes[1] = cmsReverseToneCurve(Shapes[1]);
516 InvShapes[2] = cmsReverseToneCurve(Shapes[2]);
517
518 if (!InvShapes[0] || !InvShapes[1] || !InvShapes[2]) {
519 return NULL;
520 }
521
522 Lut = cmsPipelineAlloc(ContextID, 3, 3);
523 if (Lut != NULL) {
524
525 // Note that it is certainly possible a single profile would have a LUT based
526 // tag for output working in lab and a matrix-shaper for the fallback cases.
527 // This is not allowed by the spec, but this code is tolerant to those cases
528 if (cmsGetPCS(hProfile) == cmsSigLabData) {
529
530 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLab2XYZ(ContextID)))
531 goto Error;
532 }
533
534 if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Inv, NULL)) ||
535 !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, InvShapes)))
536 goto Error;
537 }
538
539 cmsFreeToneCurveTriple(InvShapes);
540 return Lut;
541Error:
542 cmsFreeToneCurveTriple(InvShapes);
543 cmsPipelineFree(Lut);
544 return NULL;
545}
546
547
548// Change CLUT interpolation to trilinear
549static
550void ChangeInterpolationToTrilinear(cmsPipeline* Lut)
551{
552 cmsStage* Stage;
553
554 for (Stage = cmsPipelineGetPtrToFirstStage(Lut);
555 Stage != NULL;
556 Stage = cmsStageNext(Stage)) {
557
558 if (cmsStageType(Stage) == cmsSigCLutElemType) {
559
560 _cmsStageCLutData* CLUT = (_cmsStageCLutData*) Stage ->Data;
561
562 CLUT ->Params->dwFlags |= CMS_LERP_FLAGS_TRILINEAR;
563 _cmsSetInterpolationRoutine(Lut->ContextID, CLUT ->Params);
564 }
565 }
566}
567
568
569// Read the DToAX tag, adjusting the encoding of Lab or XYZ if neded
570static
571cmsPipeline* _cmsReadFloatOutputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat)
572{
573 cmsContext ContextID = cmsGetProfileContextID(hProfile);
574 cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat));
575 cmsColorSpaceSignature PCS = cmsGetPCS(hProfile);
576 cmsColorSpaceSignature dataSpace = cmsGetColorSpace(hProfile);
577
578 if (Lut == NULL) return NULL;
579
580 // If PCS is Lab or XYZ, the floating point tag is accepting data in the space encoding,
581 // and since the formatter has already accommodated to 0..1.0, we should undo this change
582 if ( PCS == cmsSigLabData)
583 {
584 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID)))
585 goto Error;
586 }
587 else
588 if (PCS == cmsSigXYZData)
589 {
590 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID)))
591 goto Error;
592 }
593
594 // the output can be Lab or XYZ, in which case normalisation is needed on the end of the pipeline
595 if ( dataSpace == cmsSigLabData)
596 {
597 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID)))
598 goto Error;
599 }
600 else if (dataSpace == cmsSigXYZData)
601 {
602 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID)))
603 goto Error;
604 }
605
606 return Lut;
607
608Error:
609 cmsPipelineFree(Lut);
610 return NULL;
611}
612
613// Create an output MPE LUT from agiven profile. Version mismatches are handled here
614cmsPipeline* CMSEXPORT _cmsReadOutputLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent)
615{
616 cmsTagTypeSignature OriginalType;
617 cmsTagSignature tag16;
618 cmsTagSignature tagFloat;
619 cmsContext ContextID = cmsGetProfileContextID(hProfile);
620
621
622 if (Intent <= INTENT_ABSOLUTE_COLORIMETRIC) {
623
624 tag16 = PCS2Device16[Intent];
625 tagFloat = PCS2DeviceFloat[Intent];
626
627 if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence
628
629 // Floating point LUT are always V4
630 return _cmsReadFloatOutputTag(hProfile, tagFloat);
631 }
632
633 // Revert to perceptual if no tag is found
634 if (!cmsIsTag(hProfile, tag16)) {
635 tag16 = PCS2Device16[0];
636 }
637
638 if (cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table?
639
640 // Check profile version and LUT type. Do the necessary adjustments if needed
641
642 // First read the tag
643 cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16);
644 if (Lut == NULL) return NULL;
645
646 // After reading it, we have info about the original type
647 OriginalType = _cmsGetTagTrueType(hProfile, tag16);
648
649 // The profile owns the Lut, so we need to copy it
650 Lut = cmsPipelineDup(Lut);
651 if (Lut == NULL) return NULL;
652
653 // Now it is time for a controversial stuff. I found that for 3D LUTS using
654 // Lab used as indexer space, trilinear interpolation should be used
655 if (cmsGetPCS(hProfile) == cmsSigLabData)
656 ChangeInterpolationToTrilinear(Lut);
657
658 // We need to adjust data only for Lab and Lut16 type
659 if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData)
660 return Lut;
661
662 // Add a matrix for conversion V4 to V2 Lab PCS
663 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID)))
664 goto Error;
665
666 // If the output is Lab, add also a conversion at the end
667 if (cmsGetColorSpace(hProfile) == cmsSigLabData)
668 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)))
669 goto Error;
670
671 return Lut;
672Error:
673 cmsPipelineFree(Lut);
674 return NULL;
675 }
676 }
677
678 // Lut not found, try to create a matrix-shaper
679
680 // Check if this is a grayscale profile.
681 if (cmsGetColorSpace(hProfile) == cmsSigGrayData) {
682
683 // if so, build appropriate conversion tables.
684 // The tables are the PCS iluminant, scaled across GrayTRC
685 return BuildGrayOutputPipeline(hProfile);
686 }
687
688 // Not gray, create a normal matrix-shaper, which only operates in XYZ space
689 return BuildRGBOutputMatrixShaper(hProfile);
690}
691
692// ---------------------------------------------------------------------------------------------------------------
693
694// Read the AToD0 tag, adjusting the encoding of Lab or XYZ if neded
695static
696cmsPipeline* _cmsReadFloatDevicelinkTag(cmsHPROFILE hProfile, cmsTagSignature tagFloat)
697{
698 cmsContext ContextID = cmsGetProfileContextID(hProfile);
699 cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*)cmsReadTag(hProfile, tagFloat));
700 cmsColorSpaceSignature PCS = cmsGetPCS(hProfile);
701 cmsColorSpaceSignature spc = cmsGetColorSpace(hProfile);
702
703 if (Lut == NULL) return NULL;
704
705 if (spc == cmsSigLabData)
706 {
707 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID)))
708 goto Error;
709 }
710 else
711 if (spc == cmsSigXYZData)
712 {
713 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID)))
714 goto Error;
715 }
716
717 if (PCS == cmsSigLabData)
718 {
719 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID)))
720 goto Error;
721 }
722 else
723 if (PCS == cmsSigXYZData)
724 {
725 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID)))
726 goto Error;
727 }
728
729 return Lut;
730Error:
731 cmsPipelineFree(Lut);
732 return NULL;
733}
734
735// This one includes abstract profiles as well. Matrix-shaper cannot be obtained on that device class. The
736// tag name here may default to AToB0
737cmsPipeline* CMSEXPORT _cmsReadDevicelinkLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent)
738{
739 cmsPipeline* Lut;
740 cmsTagTypeSignature OriginalType;
741 cmsTagSignature tag16;
742 cmsTagSignature tagFloat;
743 cmsContext ContextID = cmsGetProfileContextID(hProfile);
744
745
746 if (Intent > INTENT_ABSOLUTE_COLORIMETRIC)
747 return NULL;
748
749 tag16 = Device2PCS16[Intent];
750 tagFloat = Device2PCSFloat[Intent];
751
752 // On named color, take the appropriate tag
753 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
754
755 cmsNAMEDCOLORLIST* nc = (cmsNAMEDCOLORLIST*)cmsReadTag(hProfile, cmsSigNamedColor2Tag);
756
757 if (nc == NULL) return NULL;
758
759 Lut = cmsPipelineAlloc(ContextID, 0, 0);
760 if (Lut == NULL)
761 goto Error;
762
763 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, FALSE)))
764 goto Error;
765
766 if (cmsGetColorSpace(hProfile) == cmsSigLabData)
767 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)))
768 goto Error;
769
770 return Lut;
771 Error:
772 cmsPipelineFree(Lut);
773 cmsFreeNamedColorList(nc);
774 return NULL;
775 }
776
777
778 if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence
779
780 // Floating point LUT are always V
781 return _cmsReadFloatDevicelinkTag(hProfile, tagFloat);
782 }
783
784 tagFloat = Device2PCSFloat[0];
785 if (cmsIsTag(hProfile, tagFloat)) {
786
787 return cmsPipelineDup((cmsPipeline*)cmsReadTag(hProfile, tagFloat));
788 }
789
790 if (!cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table?
791
792 tag16 = Device2PCS16[0];
793 if (!cmsIsTag(hProfile, tag16)) return NULL;
794 }
795
796 // Check profile version and LUT type. Do the necessary adjustments if needed
797
798 // Read the tag
799 Lut = (cmsPipeline*)cmsReadTag(hProfile, tag16);
800 if (Lut == NULL) return NULL;
801
802 // The profile owns the Lut, so we need to copy it
803 Lut = cmsPipelineDup(Lut);
804 if (Lut == NULL) return NULL;
805
806 // Now it is time for a controversial stuff. I found that for 3D LUTS using
807 // Lab used as indexer space, trilinear interpolation should be used
808 if (cmsGetPCS(hProfile) == cmsSigLabData)
809 ChangeInterpolationToTrilinear(Lut);
810
811 // After reading it, we have info about the original type
812 OriginalType = _cmsGetTagTrueType(hProfile, tag16);
813
814 // We need to adjust data for Lab16 on output
815 if (OriginalType != cmsSigLut16Type) return Lut;
816
817 // Here it is possible to get Lab on both sides
818
819 if (cmsGetColorSpace(hProfile) == cmsSigLabData) {
820 if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID)))
821 goto Error2;
822 }
823
824 if (cmsGetPCS(hProfile) == cmsSigLabData) {
825 if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID)))
826 goto Error2;
827 }
828
829 return Lut;
830
831Error2:
832 cmsPipelineFree(Lut);
833 return NULL;
834}
835
836// ---------------------------------------------------------------------------------------------------------------
837
838// Returns TRUE if the profile is implemented as matrix-shaper
839cmsBool CMSEXPORT cmsIsMatrixShaper(cmsHPROFILE hProfile)
840{
841 switch (cmsGetColorSpace(hProfile)) {
842
843 case cmsSigGrayData:
844
845 return cmsIsTag(hProfile, cmsSigGrayTRCTag);
846
847 case cmsSigRgbData:
848
849 return (cmsIsTag(hProfile, cmsSigRedColorantTag) &&
850 cmsIsTag(hProfile, cmsSigGreenColorantTag) &&
851 cmsIsTag(hProfile, cmsSigBlueColorantTag) &&
852 cmsIsTag(hProfile, cmsSigRedTRCTag) &&
853 cmsIsTag(hProfile, cmsSigGreenTRCTag) &&
854 cmsIsTag(hProfile, cmsSigBlueTRCTag));
855
856 default:
857
858 return FALSE;
859 }
860}
861
862// Returns TRUE if the intent is implemented as CLUT
863cmsBool CMSEXPORT cmsIsCLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number UsedDirection)
864{
865 const cmsTagSignature* TagTable;
866
867 // For devicelinks, the supported intent is that one stated in the header
868 if (cmsGetDeviceClass(hProfile) == cmsSigLinkClass) {
869 return (cmsGetHeaderRenderingIntent(hProfile) == Intent);
870 }
871
872 switch (UsedDirection) {
873
874 case LCMS_USED_AS_INPUT: TagTable = Device2PCS16; break;
875 case LCMS_USED_AS_OUTPUT:TagTable = PCS2Device16; break;
876
877 // For proofing, we need rel. colorimetric in output. Let's do some recursion
878 case LCMS_USED_AS_PROOF:
879 return cmsIsIntentSupported(hProfile, Intent, LCMS_USED_AS_INPUT) &&
880 cmsIsIntentSupported(hProfile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_OUTPUT);
881
882 default:
883 cmsSignalError(cmsGetProfileContextID(hProfile), cmsERROR_RANGE, "Unexpected direction (%d)", UsedDirection);
884 return FALSE;
885 }
886
887 return cmsIsTag(hProfile, TagTable[Intent]);
888
889}
890
891
892// Return info about supported intents
893cmsBool CMSEXPORT cmsIsIntentSupported(cmsHPROFILE hProfile,
894 cmsUInt32Number Intent, cmsUInt32Number UsedDirection)
895{
896
897 if (cmsIsCLUT(hProfile, Intent, UsedDirection)) return TRUE;
898
899 // Is there any matrix-shaper? If so, the intent is supported. This is a bit odd, since V2 matrix shaper
900 // does not fully support relative colorimetric because they cannot deal with non-zero black points, but
901 // many profiles claims that, and this is certainly not true for V4 profiles. Lets answer "yes" no matter
902 // the accuracy would be less than optimal in rel.col and v2 case.
903
904 return cmsIsMatrixShaper(hProfile);
905}
906
907
908// ---------------------------------------------------------------------------------------------------------------
909
910// Read both, profile sequence description and profile sequence id if present. Then combine both to
911// create qa unique structure holding both. Shame on ICC to store things in such complicated way.
912cmsSEQ* _cmsReadProfileSequence(cmsHPROFILE hProfile)
913{
914 cmsSEQ* ProfileSeq;
915 cmsSEQ* ProfileId;
916 cmsSEQ* NewSeq;
917 cmsUInt32Number i;
918
919 // Take profile sequence description first
920 ProfileSeq = (cmsSEQ*) cmsReadTag(hProfile, cmsSigProfileSequenceDescTag);
921
922 // Take profile sequence ID
923 ProfileId = (cmsSEQ*) cmsReadTag(hProfile, cmsSigProfileSequenceIdTag);
924
925 if (ProfileSeq == NULL && ProfileId == NULL) return NULL;
926
927 if (ProfileSeq == NULL) return cmsDupProfileSequenceDescription(ProfileId);
928 if (ProfileId == NULL) return cmsDupProfileSequenceDescription(ProfileSeq);
929
930 // We have to mix both together. For that they must agree
931 if (ProfileSeq ->n != ProfileId ->n) return cmsDupProfileSequenceDescription(ProfileSeq);
932
933 NewSeq = cmsDupProfileSequenceDescription(ProfileSeq);
934
935 // Ok, proceed to the mixing
936 if (NewSeq != NULL) {
937 for (i=0; i < ProfileSeq ->n; i++) {
938
939 memmove(&NewSeq ->seq[i].ProfileID, &ProfileId ->seq[i].ProfileID, sizeof(cmsProfileID));
940 NewSeq ->seq[i].Description = cmsMLUdup(ProfileId ->seq[i].Description);
941 }
942 }
943 return NewSeq;
944}
945
946// Dump the contents of profile sequence in both tags (if v4 available)
947cmsBool _cmsWriteProfileSequence(cmsHPROFILE hProfile, const cmsSEQ* seq)
948{
949 if (!cmsWriteTag(hProfile, cmsSigProfileSequenceDescTag, seq)) return FALSE;
950
951 if (cmsGetEncodedICCversion(hProfile) >= 0x4000000) {
952
953 if (!cmsWriteTag(hProfile, cmsSigProfileSequenceIdTag, seq)) return FALSE;
954 }
955
956 return TRUE;
957}
958
959
960// Auxiliary, read and duplicate a MLU if found.
961static
962cmsMLU* GetMLUFromProfile(cmsHPROFILE h, cmsTagSignature sig)
963{
964 cmsMLU* mlu = (cmsMLU*) cmsReadTag(h, sig);
965 if (mlu == NULL) return NULL;
966
967 return cmsMLUdup(mlu);
968}
969
970// Create a sequence description out of an array of profiles
971cmsSEQ* _cmsCompileProfileSequence(cmsContext ContextID, cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[])
972{
973 cmsUInt32Number i;
974 cmsSEQ* seq = cmsAllocProfileSequenceDescription(ContextID, nProfiles);
975
976 if (seq == NULL) return NULL;
977
978 for (i=0; i < nProfiles; i++) {
979
980 cmsPSEQDESC* ps = &seq ->seq[i];
981 cmsHPROFILE h = hProfiles[i];
982 cmsTechnologySignature* techpt;
983
984 cmsGetHeaderAttributes(h, &ps ->attributes);
985 cmsGetHeaderProfileID(h, ps ->ProfileID.ID8);
986 ps ->deviceMfg = cmsGetHeaderManufacturer(h);
987 ps ->deviceModel = cmsGetHeaderModel(h);
988
989 techpt = (cmsTechnologySignature*) cmsReadTag(h, cmsSigTechnologyTag);
990 if (techpt == NULL)
991 ps ->technology = (cmsTechnologySignature) 0;
992 else
993 ps ->technology = *techpt;
994
995 ps ->Manufacturer = GetMLUFromProfile(h, cmsSigDeviceMfgDescTag);
996 ps ->Model = GetMLUFromProfile(h, cmsSigDeviceModelDescTag);
997 ps ->Description = GetMLUFromProfile(h, cmsSigProfileDescriptionTag);
998
999 }
1000
1001 return seq;
1002}
1003
1004// -------------------------------------------------------------------------------------------------------------------
1005
1006
1007static
1008const cmsMLU* GetInfo(cmsHPROFILE hProfile, cmsInfoType Info)
1009{
1010 cmsTagSignature sig;
1011
1012 switch (Info) {
1013
1014 case cmsInfoDescription:
1015 sig = cmsSigProfileDescriptionTag;
1016 break;
1017
1018 case cmsInfoManufacturer:
1019 sig = cmsSigDeviceMfgDescTag;
1020 break;
1021
1022 case cmsInfoModel:
1023 sig = cmsSigDeviceModelDescTag;
1024 break;
1025
1026 case cmsInfoCopyright:
1027 sig = cmsSigCopyrightTag;
1028 break;
1029
1030 default: return NULL;
1031 }
1032
1033
1034 return (cmsMLU*) cmsReadTag(hProfile, sig);
1035}
1036
1037
1038
1039cmsUInt32Number CMSEXPORT cmsGetProfileInfo(cmsHPROFILE hProfile, cmsInfoType Info,
1040 const char LanguageCode[3], const char CountryCode[3],
1041 wchar_t* Buffer, cmsUInt32Number BufferSize)
1042{
1043 const cmsMLU* mlu = GetInfo(hProfile, Info);
1044 if (mlu == NULL) return 0;
1045
1046 return cmsMLUgetWide(mlu, LanguageCode, CountryCode, Buffer, BufferSize);
1047}
1048
1049
1050cmsUInt32Number CMSEXPORT cmsGetProfileInfoASCII(cmsHPROFILE hProfile, cmsInfoType Info,
1051 const char LanguageCode[3], const char CountryCode[3],
1052 char* Buffer, cmsUInt32Number BufferSize)
1053{
1054 const cmsMLU* mlu = GetInfo(hProfile, Info);
1055 if (mlu == NULL) return 0;
1056
1057 return cmsMLUgetASCII(mlu, LanguageCode, CountryCode, Buffer, BufferSize);
1058}
1059