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-2016 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 = -1 as a way to read matrix shaper always, no matter of other LUT 339 cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int 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 = -1 means just this. 370 if (Intent >= INTENT_PERCEPTUAL && 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* _cmsReadOutputLUT(cmsHPROFILE hProfile, int Intent) 615 { 616 cmsTagTypeSignature OriginalType; 617 cmsTagSignature tag16; 618 cmsTagSignature tagFloat; 619 cmsContext ContextID = cmsGetProfileContextID(hProfile); 620 621 622 if (Intent >= INTENT_PERCEPTUAL && 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* _cmsReadDevicelinkLUT(cmsHPROFILE hProfile, int 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_PERCEPTUAL || 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 }