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