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 |
61 | static const cmsTagSignature Device2PCS16[] = {cmsSigAToB0Tag, // Perceptual |
62 | cmsSigAToB1Tag, // Relative colorimetric |
63 | cmsSigAToB2Tag, // Saturation |
64 | cmsSigAToB1Tag }; // Absolute colorimetric |
65 | |
66 | static const cmsTagSignature Device2PCSFloat[] = {cmsSigDToB0Tag, // Perceptual |
67 | cmsSigDToB1Tag, // Relative colorimetric |
68 | cmsSigDToB2Tag, // Saturation |
69 | cmsSigDToB3Tag }; // Absolute colorimetric |
70 | |
71 | static const cmsTagSignature PCS2Device16[] = {cmsSigBToA0Tag, // Perceptual |
72 | cmsSigBToA1Tag, // Relative colorimetric |
73 | cmsSigBToA2Tag, // Saturation |
74 | cmsSigBToA1Tag }; // Absolute colorimetric |
75 | |
76 | static 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. |
87 | static const cmsFloat64Number GrayInputMatrix[] = { (InpAdj*cmsD50X), (InpAdj*cmsD50Y), (InpAdj*cmsD50Z) }; |
88 | static const cmsFloat64Number OneToThreeInputMatrix[] = { 1, 1, 1 }; |
89 | static const cmsFloat64Number PickYMatrix[] = { 0, (OutpAdj*cmsD50Y), 0 }; |
90 | static const cmsFloat64Number PickLstarMatrix[] = { 1, 0, 0 }; |
91 | |
92 | // Get a media white point fixing some issues found in certain old profiles |
93 | cmsBool _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 |
123 | cmsBool _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 |
161 | static |
162 | cmsBool 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 |
184 | static |
185 | cmsPipeline* 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 | |
232 | Error: |
233 | cmsFreeToneCurve(GrayTRC); |
234 | cmsPipelineFree(Lut); |
235 | return NULL; |
236 | } |
237 | |
238 | // RGB Matrix shaper |
239 | static |
240 | cmsPipeline* 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 | |
286 | Error: |
287 | cmsPipelineFree(Lut); |
288 | return NULL; |
289 | } |
290 | |
291 | |
292 | |
293 | // Read the DToAX tag, adjusting the encoding of Lab or XYZ if neded |
294 | static |
295 | cmsPipeline* _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 | |
330 | Error: |
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 |
339 | cmsPipeline* 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; |
415 | Error: |
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 | |
442 | static |
443 | cmsPipeline* 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 | |
477 | Error: |
478 | cmsFreeToneCurve(RevGrayTRC); |
479 | cmsPipelineFree(Lut); |
480 | return NULL; |
481 | } |
482 | |
483 | |
484 | static |
485 | cmsPipeline* 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; |
541 | Error: |
542 | cmsFreeToneCurveTriple(InvShapes); |
543 | cmsPipelineFree(Lut); |
544 | return NULL; |
545 | } |
546 | |
547 | |
548 | // Change CLUT interpolation to trilinear |
549 | static |
550 | void 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 |
570 | static |
571 | cmsPipeline* _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 | |
608 | Error: |
609 | cmsPipelineFree(Lut); |
610 | return NULL; |
611 | } |
612 | |
613 | // Create an output MPE LUT from agiven profile. Version mismatches are handled here |
614 | cmsPipeline* 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; |
672 | Error: |
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 |
695 | static |
696 | cmsPipeline* _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; |
730 | Error: |
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 |
737 | cmsPipeline* 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 | |
831 | Error2: |
832 | cmsPipelineFree(Lut); |
833 | return NULL; |
834 | } |
835 | |
836 | // --------------------------------------------------------------------------------------------------------------- |
837 | |
838 | // Returns TRUE if the profile is implemented as matrix-shaper |
839 | cmsBool 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 |
863 | cmsBool 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 |
893 | cmsBool 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. |
912 | cmsSEQ* _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) |
947 | cmsBool _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. |
961 | static |
962 | cmsMLU* 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 |
971 | cmsSEQ* _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 | |
1007 | static |
1008 | const 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 | |
1039 | cmsUInt32Number 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 | |
1050 | cmsUInt32Number 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 | |