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.5k | #define InpAdj (1.0/MAX_ENCODEABLE_XYZ) // (65536.0/(65535.0*2.0)) |
55 | 36.2k | #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 | 869 | { |
66 | 869 | cmsCIEXYZ* Tag; |
67 | | |
68 | 869 | _cmsAssert(Dest != NULL); |
69 | | |
70 | 869 | Tag = (cmsCIEXYZ*) cmsReadTag(hProfile, cmsSigMediaWhitePointTag); |
71 | | |
72 | | // If no wp, take D50 |
73 | 869 | if (Tag == NULL) { |
74 | 509 | *Dest = *cmsD50_XYZ(); |
75 | 509 | return TRUE; |
76 | 509 | } |
77 | | |
78 | | // V2 display profiles should give D50 |
79 | 360 | if (cmsGetEncodedICCversion(hProfile) < 0x4000000) { |
80 | | |
81 | 64 | if (cmsGetDeviceClass(hProfile) == cmsSigDisplayClass) { |
82 | 6 | *Dest = *cmsD50_XYZ(); |
83 | 6 | return TRUE; |
84 | 6 | } |
85 | 64 | } |
86 | | |
87 | | // All seems ok |
88 | 354 | *Dest = *Tag; |
89 | 354 | return TRUE; |
90 | 360 | } |
91 | | |
92 | | |
93 | | // Chromatic adaptation matrix. Fix some issues as well |
94 | | cmsBool _cmsReadCHAD(cmsMAT3* Dest, cmsHPROFILE hProfile) |
95 | 836 | { |
96 | 836 | cmsMAT3* Tag; |
97 | | |
98 | 836 | _cmsAssert(Dest != NULL); |
99 | | |
100 | 836 | Tag = (cmsMAT3*) cmsReadTag(hProfile, cmsSigChromaticAdaptationTag); |
101 | | |
102 | 836 | if (Tag != NULL) { |
103 | 313 | *Dest = *Tag; |
104 | 313 | return TRUE; |
105 | 313 | } |
106 | | |
107 | | // No CHAD available, default it to identity |
108 | 523 | _cmsMAT3identity(Dest); |
109 | | |
110 | | // V2 display profiles should give D50 |
111 | 523 | if (cmsGetEncodedICCversion(hProfile) < 0x4000000) { |
112 | | |
113 | 402 | if (cmsGetDeviceClass(hProfile) == cmsSigDisplayClass) { |
114 | | |
115 | 165 | cmsCIEXYZ* White = (cmsCIEXYZ*) cmsReadTag(hProfile, cmsSigMediaWhitePointTag); |
116 | | |
117 | 165 | if (White == NULL) { |
118 | | |
119 | 159 | _cmsMAT3identity(Dest); |
120 | 159 | return TRUE; |
121 | 159 | } |
122 | | |
123 | 6 | return _cmsAdaptationMatrix(Dest, NULL, White, cmsD50_XYZ()); |
124 | 165 | } |
125 | 402 | } |
126 | | |
127 | 358 | return TRUE; |
128 | 523 | } |
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 | 9.82k | { |
135 | 9.82k | cmsCIEXYZ *PtrRed, *PtrGreen, *PtrBlue; |
136 | | |
137 | 9.82k | _cmsAssert(r != NULL); |
138 | | |
139 | 9.82k | PtrRed = (cmsCIEXYZ *) cmsReadTag(hProfile, cmsSigRedColorantTag); |
140 | 9.82k | PtrGreen = (cmsCIEXYZ *) cmsReadTag(hProfile, cmsSigGreenColorantTag); |
141 | 9.82k | PtrBlue = (cmsCIEXYZ *) cmsReadTag(hProfile, cmsSigBlueColorantTag); |
142 | | |
143 | 9.82k | if (PtrRed == NULL || PtrGreen == NULL || PtrBlue == NULL) |
144 | 2.84k | return FALSE; |
145 | | |
146 | 6.98k | _cmsVEC3init(&r -> v[0], PtrRed -> X, PtrGreen -> X, PtrBlue -> X); |
147 | 6.98k | _cmsVEC3init(&r -> v[1], PtrRed -> Y, PtrGreen -> Y, PtrBlue -> Y); |
148 | 6.98k | _cmsVEC3init(&r -> v[2], PtrRed -> Z, PtrGreen -> Z, PtrBlue -> Z); |
149 | | |
150 | 6.98k | return TRUE; |
151 | 9.82k | } |
152 | | |
153 | | |
154 | | // Gray input pipeline |
155 | | static |
156 | | cmsPipeline* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile) |
157 | 2.62k | { |
158 | 2.62k | cmsToneCurve *GrayTRC; |
159 | 2.62k | cmsPipeline* Lut; |
160 | 2.62k | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
161 | | |
162 | 2.62k | GrayTRC = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGrayTRCTag); |
163 | 2.62k | if (GrayTRC == NULL) return NULL; |
164 | | |
165 | 2.51k | Lut = cmsPipelineAlloc(ContextID, 1, 3); |
166 | 2.51k | if (Lut == NULL) |
167 | 0 | goto Error; |
168 | | |
169 | 2.51k | if (cmsGetPCS(hProfile) == cmsSigLabData) { |
170 | | |
171 | | // In this case we implement the profile as an identity matrix plus 3 tone curves |
172 | 1.51k | cmsUInt16Number Zero[2] = { 0x8080, 0x8080 }; |
173 | 1.51k | cmsToneCurve* EmptyTab; |
174 | 1.51k | cmsToneCurve* LabCurves[3]; |
175 | | |
176 | 1.51k | EmptyTab = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero); |
177 | | |
178 | 1.51k | if (EmptyTab == NULL) |
179 | 0 | goto Error; |
180 | | |
181 | 1.51k | LabCurves[0] = GrayTRC; |
182 | 1.51k | LabCurves[1] = EmptyTab; |
183 | 1.51k | LabCurves[2] = EmptyTab; |
184 | | |
185 | 1.51k | if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 1, OneToThreeInputMatrix, NULL)) || |
186 | 1.51k | !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, LabCurves))) { |
187 | 0 | cmsFreeToneCurve(EmptyTab); |
188 | 0 | goto Error; |
189 | 0 | } |
190 | | |
191 | 1.51k | cmsFreeToneCurve(EmptyTab); |
192 | | |
193 | 1.51k | } |
194 | 1.00k | else { |
195 | | |
196 | 1.00k | if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 1, &GrayTRC)) || |
197 | 1.00k | !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 1, GrayInputMatrix, NULL))) |
198 | 0 | goto Error; |
199 | 1.00k | } |
200 | | |
201 | 2.51k | return Lut; |
202 | | |
203 | 0 | Error: |
204 | 0 | cmsPipelineFree(Lut); |
205 | 0 | return NULL; |
206 | 2.51k | } |
207 | | |
208 | | // RGB Matrix shaper |
209 | | static |
210 | | cmsPipeline* BuildRGBInputMatrixShaper(cmsHPROFILE hProfile) |
211 | 5.36k | { |
212 | 5.36k | cmsPipeline* Lut; |
213 | 5.36k | cmsMAT3 Mat; |
214 | 5.36k | cmsToneCurve *Shapes[3]; |
215 | 5.36k | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
216 | 5.36k | int i, j; |
217 | | |
218 | 5.36k | 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.4k | for (i=0; i < 3; i++) |
225 | 40.2k | for (j=0; j < 3; j++) |
226 | 30.2k | Mat.v[i].n[j] *= InpAdj; |
227 | | |
228 | | |
229 | 3.35k | Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag); |
230 | 3.35k | Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag); |
231 | 3.35k | Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag); |
232 | | |
233 | 3.35k | if (!Shapes[0] || !Shapes[1] || !Shapes[2]) |
234 | 85 | return NULL; |
235 | | |
236 | 3.27k | Lut = cmsPipelineAlloc(ContextID, 3, 3); |
237 | 3.27k | if (Lut != NULL) { |
238 | | |
239 | 3.27k | if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, Shapes)) || |
240 | 3.27k | !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.27k | if (cmsGetPCS(hProfile) == cmsSigLabData) { |
247 | | |
248 | 108 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocXYZ2Lab(ContextID))) |
249 | 0 | goto Error; |
250 | 108 | } |
251 | | |
252 | 3.27k | } |
253 | | |
254 | 3.27k | return Lut; |
255 | | |
256 | 0 | Error: |
257 | 0 | cmsPipelineFree(Lut); |
258 | 0 | return NULL; |
259 | 3.27k | } |
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.53k | { |
267 | 4.53k | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
268 | 4.53k | cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); |
269 | 4.53k | cmsColorSpaceSignature spc = cmsGetColorSpace(hProfile); |
270 | 4.53k | cmsColorSpaceSignature PCS = cmsGetPCS(hProfile); |
271 | | |
272 | 4.53k | 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 | 811 | if ( spc == cmsSigLabData) |
277 | 68 | { |
278 | 68 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID))) |
279 | 4 | goto Error; |
280 | 68 | } |
281 | 743 | else if (spc == cmsSigXYZData) |
282 | 157 | { |
283 | 157 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID))) |
284 | 5 | goto Error; |
285 | 157 | } |
286 | | |
287 | 802 | if ( PCS == cmsSigLabData) |
288 | 272 | { |
289 | 272 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID))) |
290 | 21 | goto Error; |
291 | 272 | } |
292 | 530 | else if( PCS == cmsSigXYZData) |
293 | 152 | { |
294 | 152 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID))) |
295 | 10 | goto Error; |
296 | 152 | } |
297 | | |
298 | 771 | return Lut; |
299 | | |
300 | 40 | Error: |
301 | 40 | cmsPipelineFree(Lut); |
302 | 40 | return NULL; |
303 | 802 | } |
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 | 27.4k | { |
311 | 27.4k | cmsTagTypeSignature OriginalType; |
312 | 27.4k | cmsTagSignature tag16; |
313 | 27.4k | cmsTagSignature tagFloat; |
314 | 27.4k | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
315 | | |
316 | | // On named color, take the appropriate tag |
317 | 27.4k | if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { |
318 | | |
319 | 730 | cmsPipeline* Lut; |
320 | 730 | cmsNAMEDCOLORLIST* nc = (cmsNAMEDCOLORLIST*) cmsReadTag(hProfile, cmsSigNamedColor2Tag); |
321 | | |
322 | 730 | if (nc == NULL) return NULL; |
323 | | |
324 | 259 | Lut = cmsPipelineAlloc(ContextID, 0, 0); |
325 | 259 | if (Lut == NULL) |
326 | 0 | return NULL; |
327 | | |
328 | 259 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, TRUE)) || |
329 | 259 | !cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) { |
330 | 0 | cmsPipelineFree(Lut); |
331 | 0 | return NULL; |
332 | 0 | } |
333 | 259 | return Lut; |
334 | 259 | } |
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 | 26.7k | if (Intent <= INTENT_ABSOLUTE_COLORIMETRIC) { |
339 | | |
340 | 26.6k | tag16 = Device2PCS16[Intent]; |
341 | 26.6k | tagFloat = Device2PCSFloat[Intent]; |
342 | | |
343 | 26.6k | 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.53k | return _cmsReadFloatInputTag(hProfile, tagFloat); |
348 | 4.53k | } |
349 | | |
350 | | // Revert to perceptual if no tag is found |
351 | 22.0k | if (!cmsIsTag(hProfile, tag16)) { |
352 | 10.5k | tag16 = Device2PCS16[0]; |
353 | 10.5k | } |
354 | | |
355 | 22.0k | 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 | 14.1k | cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16); |
361 | 14.1k | if (Lut == NULL) return NULL; |
362 | | |
363 | | // After reading it, we have now info about the original type |
364 | 9.70k | OriginalType = _cmsGetTagTrueType(hProfile, tag16); |
365 | | |
366 | | // The profile owns the Lut, so we need to copy it |
367 | 9.70k | Lut = cmsPipelineDup(Lut); |
368 | | |
369 | | // We need to adjust data only for Lab16 on output |
370 | 9.70k | if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData) |
371 | 5.73k | return Lut; |
372 | | |
373 | | // If the input is Lab, add also a conversion at the begin |
374 | 3.96k | if (cmsGetColorSpace(hProfile) == cmsSigLabData && |
375 | 3.96k | !cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID))) |
376 | 10 | goto Error; |
377 | | |
378 | | // Add a matrix for conversion V2 to V4 Lab PCS |
379 | 3.95k | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) |
380 | 81 | goto Error; |
381 | | |
382 | 3.87k | return Lut; |
383 | 91 | Error: |
384 | 91 | cmsPipelineFree(Lut); |
385 | 91 | return NULL; |
386 | 3.95k | } |
387 | 22.0k | } |
388 | | |
389 | | // Lut was not found, try to create a matrix-shaper |
390 | | |
391 | | // Check if this is a grayscale profile. |
392 | 7.99k | if (cmsGetColorSpace(hProfile) == cmsSigGrayData) { |
393 | | |
394 | | // if so, build appropriate conversion tables. |
395 | | // The tables are the PCS iluminant, scaled across GrayTRC |
396 | 2.62k | return BuildGrayInputMatrixPipeline(hProfile); |
397 | 2.62k | } |
398 | | |
399 | | // Not gray, create a normal matrix-shaper |
400 | 5.36k | return BuildRGBInputMatrixShaper(hProfile); |
401 | 7.99k | } |
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 | 969 | { |
413 | 969 | cmsToneCurve *GrayTRC, *RevGrayTRC; |
414 | 969 | cmsPipeline* Lut; |
415 | 969 | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
416 | | |
417 | 969 | GrayTRC = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGrayTRCTag); |
418 | 969 | if (GrayTRC == NULL) return NULL; |
419 | | |
420 | 825 | RevGrayTRC = cmsReverseToneCurve(GrayTRC); |
421 | 825 | if (RevGrayTRC == NULL) return NULL; |
422 | | |
423 | 825 | Lut = cmsPipelineAlloc(ContextID, 3, 1); |
424 | 825 | if (Lut == NULL) { |
425 | 0 | cmsFreeToneCurve(RevGrayTRC); |
426 | 0 | return NULL; |
427 | 0 | } |
428 | | |
429 | 825 | if (cmsGetPCS(hProfile) == cmsSigLabData) { |
430 | | |
431 | 422 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickLstarMatrix, NULL))) |
432 | 0 | goto Error; |
433 | 422 | } |
434 | 403 | else { |
435 | 403 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 1, 3, PickYMatrix, NULL))) |
436 | 0 | goto Error; |
437 | 403 | } |
438 | | |
439 | 825 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 1, &RevGrayTRC))) |
440 | 0 | goto Error; |
441 | | |
442 | 825 | cmsFreeToneCurve(RevGrayTRC); |
443 | 825 | return Lut; |
444 | | |
445 | 0 | Error: |
446 | 0 | cmsFreeToneCurve(RevGrayTRC); |
447 | 0 | cmsPipelineFree(Lut); |
448 | 0 | return NULL; |
449 | 825 | } |
450 | | |
451 | | |
452 | | static |
453 | | cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile) |
454 | 4.46k | { |
455 | 4.46k | cmsPipeline* Lut; |
456 | 4.46k | cmsToneCurve *Shapes[3], *InvShapes[3]; |
457 | 4.46k | cmsMAT3 Mat, Inv; |
458 | 4.46k | int i, j; |
459 | 4.46k | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
460 | | |
461 | 4.46k | if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) |
462 | 836 | return NULL; |
463 | | |
464 | 3.62k | if (!_cmsMAT3inverse(&Mat, &Inv)) |
465 | 2 | 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.5k | for (i=0; i < 3; i++) |
472 | 43.5k | for (j=0; j < 3; j++) |
473 | 32.6k | Inv.v[i].n[j] *= OutpAdj; |
474 | | |
475 | 3.62k | Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag); |
476 | 3.62k | Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag); |
477 | 3.62k | Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag); |
478 | | |
479 | 3.62k | if (!Shapes[0] || !Shapes[1] || !Shapes[2]) |
480 | 12 | return NULL; |
481 | | |
482 | 3.61k | InvShapes[0] = cmsReverseToneCurve(Shapes[0]); |
483 | 3.61k | InvShapes[1] = cmsReverseToneCurve(Shapes[1]); |
484 | 3.61k | InvShapes[2] = cmsReverseToneCurve(Shapes[2]); |
485 | | |
486 | 3.61k | if (!InvShapes[0] || !InvShapes[1] || !InvShapes[2]) { |
487 | 0 | return NULL; |
488 | 0 | } |
489 | | |
490 | 3.61k | Lut = cmsPipelineAlloc(ContextID, 3, 3); |
491 | 3.61k | 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.61k | if (cmsGetPCS(hProfile) == cmsSigLabData) { |
497 | | |
498 | 35 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLab2XYZ(ContextID))) |
499 | 0 | goto Error; |
500 | 35 | } |
501 | | |
502 | 3.61k | if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Inv, NULL)) || |
503 | 3.61k | !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, InvShapes))) |
504 | 0 | goto Error; |
505 | 3.61k | } |
506 | | |
507 | 3.61k | cmsFreeToneCurveTriple(InvShapes); |
508 | 3.61k | return Lut; |
509 | 0 | Error: |
510 | 0 | cmsFreeToneCurveTriple(InvShapes); |
511 | 0 | cmsPipelineFree(Lut); |
512 | 0 | return NULL; |
513 | 3.61k | } |
514 | | |
515 | | |
516 | | // Change CLUT interpolation to trilinear |
517 | | static |
518 | | void ChangeInterpolationToTrilinear(cmsPipeline* Lut) |
519 | 11.7k | { |
520 | 11.7k | cmsStage* Stage; |
521 | | |
522 | 11.7k | for (Stage = cmsPipelineGetPtrToFirstStage(Lut); |
523 | 25.0k | Stage != NULL; |
524 | 13.2k | Stage = cmsStageNext(Stage)) { |
525 | | |
526 | 13.2k | if (cmsStageType(Stage) == cmsSigCLutElemType) { |
527 | | |
528 | 7.03k | _cmsStageCLutData* CLUT = (_cmsStageCLutData*) Stage ->Data; |
529 | | |
530 | 7.03k | CLUT ->Params->dwFlags |= CMS_LERP_FLAGS_TRILINEAR; |
531 | 7.03k | _cmsSetInterpolationRoutine(Lut->ContextID, CLUT ->Params); |
532 | 7.03k | } |
533 | 13.2k | } |
534 | 11.7k | } |
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 | 445 | { |
541 | 445 | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
542 | 445 | cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*) cmsReadTag(hProfile, tagFloat)); |
543 | 445 | cmsColorSpaceSignature PCS = cmsGetPCS(hProfile); |
544 | 445 | cmsColorSpaceSignature dataSpace = cmsGetColorSpace(hProfile); |
545 | | |
546 | 445 | 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 | 102 | if ( PCS == cmsSigLabData) |
551 | 88 | { |
552 | 88 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID))) |
553 | 1 | goto Error; |
554 | 88 | } |
555 | 14 | else |
556 | 14 | if (PCS == cmsSigXYZData) |
557 | 14 | { |
558 | 14 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID))) |
559 | 1 | goto Error; |
560 | 14 | } |
561 | | |
562 | | // the output can be Lab or XYZ, in which case normalisation is needed on the end of the pipeline |
563 | 100 | if ( dataSpace == cmsSigLabData) |
564 | 2 | { |
565 | 2 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID))) |
566 | 0 | goto Error; |
567 | 2 | } |
568 | 98 | else if (dataSpace == cmsSigXYZData) |
569 | 1 | { |
570 | 1 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID))) |
571 | 0 | goto Error; |
572 | 1 | } |
573 | | |
574 | 100 | return Lut; |
575 | | |
576 | 2 | Error: |
577 | 2 | cmsPipelineFree(Lut); |
578 | 2 | return NULL; |
579 | 100 | } |
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 | 7.22k | { |
584 | 7.22k | cmsTagTypeSignature OriginalType; |
585 | 7.22k | cmsTagSignature tag16; |
586 | 7.22k | cmsTagSignature tagFloat; |
587 | 7.22k | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
588 | | |
589 | | |
590 | 7.22k | if (Intent <= INTENT_ABSOLUTE_COLORIMETRIC) { |
591 | | |
592 | 7.22k | tag16 = PCS2Device16[Intent]; |
593 | 7.22k | tagFloat = PCS2DeviceFloat[Intent]; |
594 | | |
595 | 7.22k | if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence |
596 | | |
597 | | // Floating point LUT are always V4 |
598 | 445 | return _cmsReadFloatOutputTag(hProfile, tagFloat); |
599 | 445 | } |
600 | | |
601 | | // Revert to perceptual if no tag is found |
602 | 6.77k | if (!cmsIsTag(hProfile, tag16)) { |
603 | 5.61k | tag16 = PCS2Device16[0]; |
604 | 5.61k | } |
605 | | |
606 | 6.77k | 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 | 1.34k | cmsPipeline* Lut = (cmsPipeline*) cmsReadTag(hProfile, tag16); |
612 | 1.34k | if (Lut == NULL) return NULL; |
613 | | |
614 | | // After reading it, we have info about the original type |
615 | 700 | OriginalType = _cmsGetTagTrueType(hProfile, tag16); |
616 | | |
617 | | // The profile owns the Lut, so we need to copy it |
618 | 700 | Lut = cmsPipelineDup(Lut); |
619 | 700 | 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 | 700 | if (cmsGetPCS(hProfile) == cmsSigLabData) |
624 | 427 | ChangeInterpolationToTrilinear(Lut); |
625 | | |
626 | | // We need to adjust data only for Lab and Lut16 type |
627 | 700 | if (OriginalType != cmsSigLut16Type || cmsGetPCS(hProfile) != cmsSigLabData) |
628 | 419 | return Lut; |
629 | | |
630 | | // Add a matrix for conversion V4 to V2 Lab PCS |
631 | 281 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID))) |
632 | 15 | goto Error; |
633 | | |
634 | | // If the output is Lab, add also a conversion at the end |
635 | 266 | if (cmsGetColorSpace(hProfile) == cmsSigLabData) |
636 | 6 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) |
637 | 1 | goto Error; |
638 | | |
639 | 265 | return Lut; |
640 | 16 | Error: |
641 | 16 | cmsPipelineFree(Lut); |
642 | 16 | return NULL; |
643 | 266 | } |
644 | 6.77k | } |
645 | | |
646 | | // Lut not found, try to create a matrix-shaper |
647 | | |
648 | | // Check if this is a grayscale profile. |
649 | 5.43k | if (cmsGetColorSpace(hProfile) == cmsSigGrayData) { |
650 | | |
651 | | // if so, build appropriate conversion tables. |
652 | | // The tables are the PCS iluminant, scaled across GrayTRC |
653 | 969 | return BuildGrayOutputPipeline(hProfile); |
654 | 969 | } |
655 | | |
656 | | // Not gray, create a normal matrix-shaper, which only operates in XYZ space |
657 | 4.46k | return BuildRGBOutputMatrixShaper(hProfile); |
658 | 5.43k | } |
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.35k | { |
666 | 1.35k | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
667 | 1.35k | cmsPipeline* Lut = cmsPipelineDup((cmsPipeline*)cmsReadTag(hProfile, tagFloat)); |
668 | 1.35k | cmsColorSpaceSignature PCS = cmsGetPCS(hProfile); |
669 | 1.35k | cmsColorSpaceSignature spc = cmsGetColorSpace(hProfile); |
670 | | |
671 | 1.35k | if (Lut == NULL) return NULL; |
672 | | |
673 | 568 | if (spc == cmsSigLabData) |
674 | 62 | { |
675 | 62 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToLabFloat(ContextID))) |
676 | 6 | goto Error; |
677 | 62 | } |
678 | 506 | else |
679 | 506 | if (spc == cmsSigXYZData) |
680 | 110 | { |
681 | 110 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageNormalizeToXyzFloat(ContextID))) |
682 | 4 | goto Error; |
683 | 110 | } |
684 | | |
685 | 558 | if (PCS == cmsSigLabData) |
686 | 153 | { |
687 | 153 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromLabFloat(ContextID))) |
688 | 10 | goto Error; |
689 | 153 | } |
690 | 405 | else |
691 | 405 | if (PCS == cmsSigXYZData) |
692 | 44 | { |
693 | 44 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageNormalizeFromXyzFloat(ContextID))) |
694 | 3 | goto Error; |
695 | 44 | } |
696 | | |
697 | 545 | return Lut; |
698 | 23 | Error: |
699 | 23 | cmsPipelineFree(Lut); |
700 | 23 | return NULL; |
701 | 558 | } |
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 | 18.9k | { |
707 | 18.9k | cmsPipeline* Lut; |
708 | 18.9k | cmsTagTypeSignature OriginalType; |
709 | 18.9k | cmsTagSignature tag16; |
710 | 18.9k | cmsTagSignature tagFloat; |
711 | 18.9k | cmsContext ContextID = cmsGetProfileContextID(hProfile); |
712 | | |
713 | | |
714 | 18.9k | if (Intent > INTENT_ABSOLUTE_COLORIMETRIC) |
715 | 0 | return NULL; |
716 | | |
717 | 18.9k | tag16 = Device2PCS16[Intent]; |
718 | 18.9k | tagFloat = Device2PCSFloat[Intent]; |
719 | | |
720 | | // On named color, take the appropriate tag |
721 | 18.9k | if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { |
722 | | |
723 | 302 | cmsNAMEDCOLORLIST* nc = (cmsNAMEDCOLORLIST*)cmsReadTag(hProfile, cmsSigNamedColor2Tag); |
724 | | |
725 | 302 | if (nc == NULL) return NULL; |
726 | | |
727 | 145 | Lut = cmsPipelineAlloc(ContextID, 0, 0); |
728 | 145 | if (Lut == NULL) |
729 | 0 | goto Error; |
730 | | |
731 | 145 | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocNamedColor(nc, FALSE))) |
732 | 0 | goto Error; |
733 | | |
734 | 145 | if (cmsGetColorSpace(hProfile) == cmsSigLabData) |
735 | 29 | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) |
736 | 6 | goto Error; |
737 | | |
738 | 139 | return Lut; |
739 | 6 | Error: |
740 | 6 | cmsPipelineFree(Lut); |
741 | 6 | return NULL; |
742 | 145 | } |
743 | | |
744 | | |
745 | 18.6k | if (cmsIsTag(hProfile, tagFloat)) { // Float tag takes precedence |
746 | | |
747 | | // Floating point LUT are always V |
748 | 1.35k | return _cmsReadFloatDevicelinkTag(hProfile, tagFloat); |
749 | 1.35k | } |
750 | | |
751 | 17.2k | tagFloat = Device2PCSFloat[0]; |
752 | 17.2k | if (cmsIsTag(hProfile, tagFloat)) { |
753 | | |
754 | 34 | return cmsPipelineDup((cmsPipeline*)cmsReadTag(hProfile, tagFloat)); |
755 | 34 | } |
756 | | |
757 | 17.2k | if (!cmsIsTag(hProfile, tag16)) { // Is there any LUT-Based table? |
758 | | |
759 | 7.85k | tag16 = Device2PCS16[0]; |
760 | 7.85k | if (!cmsIsTag(hProfile, tag16)) return NULL; |
761 | 7.85k | } |
762 | | |
763 | | // Check profile version and LUT type. Do the necessary adjustments if needed |
764 | | |
765 | | // Read the tag |
766 | 16.5k | Lut = (cmsPipeline*)cmsReadTag(hProfile, tag16); |
767 | 16.5k | if (Lut == NULL) return NULL; |
768 | | |
769 | | // The profile owns the Lut, so we need to copy it |
770 | 14.7k | Lut = cmsPipelineDup(Lut); |
771 | 14.7k | 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 | 14.7k | if (cmsGetPCS(hProfile) == cmsSigLabData) |
776 | 11.3k | ChangeInterpolationToTrilinear(Lut); |
777 | | |
778 | | // After reading it, we have info about the original type |
779 | 14.7k | OriginalType = _cmsGetTagTrueType(hProfile, tag16); |
780 | | |
781 | | // We need to adjust data for Lab16 on output |
782 | 14.7k | if (OriginalType != cmsSigLut16Type) return Lut; |
783 | | |
784 | | // Here it is possible to get Lab on both sides |
785 | | |
786 | 9.33k | if (cmsGetColorSpace(hProfile) == cmsSigLabData) { |
787 | 6.53k | if (!cmsPipelineInsertStage(Lut, cmsAT_BEGIN, _cmsStageAllocLabV4ToV2(ContextID))) |
788 | 18 | goto Error2; |
789 | 6.53k | } |
790 | | |
791 | 9.32k | if (cmsGetPCS(hProfile) == cmsSigLabData) { |
792 | 7.16k | if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLabV2ToV4(ContextID))) |
793 | 25 | goto Error2; |
794 | 7.16k | } |
795 | | |
796 | 9.29k | return Lut; |
797 | | |
798 | 43 | Error2: |
799 | 43 | cmsPipelineFree(Lut); |
800 | 43 | return NULL; |
801 | 9.32k | } |
802 | | |
803 | | // --------------------------------------------------------------------------------------------------------------- |
804 | | |
805 | | // Returns TRUE if the profile is implemented as matrix-shaper |
806 | | cmsBool CMSEXPORT cmsIsMatrixShaper(cmsHPROFILE hProfile) |
807 | 10.4k | { |
808 | 10.4k | switch (cmsGetColorSpace(hProfile)) { |
809 | | |
810 | 2.90k | case cmsSigGrayData: |
811 | | |
812 | 2.90k | return cmsIsTag(hProfile, cmsSigGrayTRCTag); |
813 | | |
814 | 6.39k | case cmsSigRgbData: |
815 | | |
816 | 6.39k | return (cmsIsTag(hProfile, cmsSigRedColorantTag) && |
817 | 6.39k | cmsIsTag(hProfile, cmsSigGreenColorantTag) && |
818 | 6.39k | cmsIsTag(hProfile, cmsSigBlueColorantTag) && |
819 | 6.39k | cmsIsTag(hProfile, cmsSigRedTRCTag) && |
820 | 6.39k | cmsIsTag(hProfile, cmsSigGreenTRCTag) && |
821 | 6.39k | cmsIsTag(hProfile, cmsSigBlueTRCTag)); |
822 | | |
823 | 1.16k | default: |
824 | | |
825 | 1.16k | return FALSE; |
826 | 10.4k | } |
827 | 10.4k | } |
828 | | |
829 | | // Returns TRUE if the intent is implemented as CLUT |
830 | | cmsBool CMSEXPORT cmsIsCLUT(cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number UsedDirection) |
831 | 9.75k | { |
832 | 9.75k | const cmsTagSignature* TagTable; |
833 | | |
834 | | // For devicelinks, the supported intent is that one stated in the header |
835 | 9.75k | if (cmsGetDeviceClass(hProfile) == cmsSigLinkClass) { |
836 | 0 | return (cmsGetHeaderRenderingIntent(hProfile) == Intent); |
837 | 0 | } |
838 | | |
839 | 9.75k | switch (UsedDirection) { |
840 | | |
841 | 9.28k | case LCMS_USED_AS_INPUT: TagTable = Device2PCS16; break; |
842 | 467 | 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 | 9.75k | } |
853 | | |
854 | | // Extended intents are not strictly CLUT-based |
855 | 9.75k | if (Intent > INTENT_ABSOLUTE_COLORIMETRIC) |
856 | 0 | return FALSE; |
857 | | |
858 | 9.75k | return cmsIsTag(hProfile, TagTable[Intent]); |
859 | | |
860 | 9.75k | } |
861 | | |
862 | | |
863 | | // Return info about supported intents |
864 | | cmsBool CMSEXPORT cmsIsIntentSupported(cmsHPROFILE hProfile, |
865 | | cmsUInt32Number Intent, cmsUInt32Number UsedDirection) |
866 | 9.28k | { |
867 | | |
868 | 9.28k | 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.42k | return cmsIsMatrixShaper(hProfile); |
876 | 9.28k | } |
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 | 612 | { |
920 | 612 | if (!cmsWriteTag(hProfile, cmsSigProfileSequenceDescTag, seq)) return FALSE; |
921 | | |
922 | 612 | if (cmsGetEncodedICCversion(hProfile) >= 0x4000000) { |
923 | | |
924 | 612 | if (!cmsWriteTag(hProfile, cmsSigProfileSequenceIdTag, seq)) return FALSE; |
925 | 612 | } |
926 | | |
927 | 612 | return TRUE; |
928 | 612 | } |
929 | | |
930 | | |
931 | | // Auxiliary, read and duplicate a MLU if found. |
932 | | static |
933 | | cmsMLU* GetMLUFromProfile(cmsHPROFILE h, cmsTagSignature sig) |
934 | 3.09k | { |
935 | 3.09k | cmsMLU* mlu = (cmsMLU*) cmsReadTag(h, sig); |
936 | 3.09k | if (mlu == NULL) return NULL; |
937 | | |
938 | 615 | return cmsMLUdup(mlu); |
939 | 3.09k | } |
940 | | |
941 | | // Create a sequence description out of an array of profiles |
942 | | cmsSEQ* _cmsCompileProfileSequence(cmsContext ContextID, cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[]) |
943 | 517 | { |
944 | 517 | cmsUInt32Number i; |
945 | 517 | cmsSEQ* seq = cmsAllocProfileSequenceDescription(ContextID, nProfiles); |
946 | | |
947 | 517 | if (seq == NULL) return NULL; |
948 | | |
949 | 1.54k | for (i=0; i < nProfiles; i++) { |
950 | | |
951 | 1.03k | cmsPSEQDESC* ps = &seq ->seq[i]; |
952 | 1.03k | cmsHPROFILE h = hProfiles[i]; |
953 | 1.03k | cmsTechnologySignature* techpt; |
954 | | |
955 | 1.03k | cmsGetHeaderAttributes(h, &ps ->attributes); |
956 | 1.03k | cmsGetHeaderProfileID(h, ps ->ProfileID.ID8); |
957 | 1.03k | ps ->deviceMfg = cmsGetHeaderManufacturer(h); |
958 | 1.03k | ps ->deviceModel = cmsGetHeaderModel(h); |
959 | | |
960 | 1.03k | techpt = (cmsTechnologySignature*) cmsReadTag(h, cmsSigTechnologyTag); |
961 | 1.03k | if (techpt == NULL) |
962 | 1.02k | ps ->technology = (cmsTechnologySignature) 0; |
963 | 1 | else |
964 | 1 | ps ->technology = *techpt; |
965 | | |
966 | 1.03k | ps ->Manufacturer = GetMLUFromProfile(h, cmsSigDeviceMfgDescTag); |
967 | 1.03k | ps ->Model = GetMLUFromProfile(h, cmsSigDeviceModelDescTag); |
968 | 1.03k | ps ->Description = GetMLUFromProfile(h, cmsSigProfileDescriptionTag); |
969 | | |
970 | 1.03k | } |
971 | | |
972 | 517 | return seq; |
973 | 517 | } |
974 | | |
975 | | // ------------------------------------------------------------------------------------------------------------------- |
976 | | |
977 | | |
978 | | static |
979 | | const cmsMLU* GetInfo(cmsHPROFILE hProfile, cmsInfoType Info) |
980 | 2.11k | { |
981 | 2.11k | cmsTagSignature sig; |
982 | | |
983 | 2.11k | switch (Info) { |
984 | | |
985 | 1.16k | case cmsInfoDescription: |
986 | 1.16k | sig = cmsSigProfileDescriptionTag; |
987 | 1.16k | break; |
988 | | |
989 | 462 | case cmsInfoManufacturer: |
990 | 462 | sig = cmsSigDeviceMfgDescTag; |
991 | 462 | break; |
992 | | |
993 | 32 | case cmsInfoModel: |
994 | 32 | sig = cmsSigDeviceModelDescTag; |
995 | 32 | break; |
996 | | |
997 | 457 | case cmsInfoCopyright: |
998 | 457 | sig = cmsSigCopyrightTag; |
999 | 457 | break; |
1000 | | |
1001 | 0 | default: return NULL; |
1002 | 2.11k | } |
1003 | | |
1004 | | |
1005 | 2.11k | return (cmsMLU*) cmsReadTag(hProfile, sig); |
1006 | 2.11k | } |
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.11k | { |
1025 | 2.11k | const cmsMLU* mlu = GetInfo(hProfile, Info); |
1026 | 2.11k | if (mlu == NULL) return 0; |
1027 | | |
1028 | 314 | return cmsMLUgetASCII(mlu, LanguageCode, CountryCode, Buffer, BufferSize); |
1029 | 2.11k | } |
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 | } |