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 // Transformations stuff
  59 // -----------------------------------------------------------------------
  60 
  61 #define DEFAULT_OBSERVER_ADAPTATION_STATE 1.0
  62 
  63 // The Context0 observer adaptation state.
  64 _cmsAdaptationStateChunkType _cmsAdaptationStateChunk = { DEFAULT_OBSERVER_ADAPTATION_STATE };
  65 
  66 // Init and duplicate observer adaptation state
  67 void _cmsAllocAdaptationStateChunk(struct _cmsContext_struct* ctx,
  68                                    const struct _cmsContext_struct* src)
  69 {
  70     static _cmsAdaptationStateChunkType AdaptationStateChunk = { DEFAULT_OBSERVER_ADAPTATION_STATE };
  71     void* from;
  72 
  73     if (src != NULL) {
  74         from = src ->chunks[AdaptationStateContext];
  75     }
  76     else {
  77        from = &AdaptationStateChunk;
  78     }
  79 
  80     ctx ->chunks[AdaptationStateContext] = _cmsSubAllocDup(ctx ->MemPool, from, sizeof(_cmsAdaptationStateChunkType));
  81 }
  82 
  83 
  84 // Sets adaptation state for absolute colorimetric intent in the given context.  Adaptation state applies on all
  85 // but cmsCreateExtendedTransformTHR().  Little CMS can handle incomplete adaptation states.
  86 cmsFloat64Number CMSEXPORT cmsSetAdaptationStateTHR(cmsContext ContextID, cmsFloat64Number d)
  87 {
  88     cmsFloat64Number prev;
  89     _cmsAdaptationStateChunkType* ptr = (_cmsAdaptationStateChunkType*) _cmsContextGetClientChunk(ContextID, AdaptationStateContext);
  90 
  91     // Get previous value for return
  92     prev = ptr ->AdaptationState;
  93 
  94     // Set the value if d is positive or zero
  95     if (d >= 0.0) {
  96 
  97         ptr ->AdaptationState = d;
  98     }
  99 
 100     // Always return previous value
 101     return prev;
 102 }
 103 
 104 
 105 // The adaptation state may be defaulted by this function. If you don't like it, use the extended transform routine
 106 cmsFloat64Number CMSEXPORT cmsSetAdaptationState(cmsFloat64Number d)
 107 {
 108     return cmsSetAdaptationStateTHR(NULL, d);
 109 }
 110 
 111 // -----------------------------------------------------------------------
 112 
 113 // Alarm codes for 16-bit transformations, because the fixed range of containers there are
 114 // no values left to mark out of gamut.
 115 
 116 #define DEFAULT_ALARM_CODES_VALUE {0x7F00, 0x7F00, 0x7F00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 117 
 118 _cmsAlarmCodesChunkType _cmsAlarmCodesChunk = { DEFAULT_ALARM_CODES_VALUE };
 119 
 120 // Sets the codes used to mark out-out-gamut on Proofing transforms for a given context. Values are meant to be
 121 // encoded in 16 bits.
 122 void CMSEXPORT cmsSetAlarmCodesTHR(cmsContext ContextID, const cmsUInt16Number AlarmCodesP[cmsMAXCHANNELS])
 123 {
 124     _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(ContextID, AlarmCodesContext);
 125 
 126     _cmsAssert(ContextAlarmCodes != NULL); // Can't happen
 127 
 128     memcpy(ContextAlarmCodes->AlarmCodes, AlarmCodesP, sizeof(ContextAlarmCodes->AlarmCodes));
 129 }
 130 
 131 // Gets the current codes used to mark out-out-gamut on Proofing transforms for the given context.
 132 // Values are meant to be encoded in 16 bits.
 133 void CMSEXPORT cmsGetAlarmCodesTHR(cmsContext ContextID, cmsUInt16Number AlarmCodesP[cmsMAXCHANNELS])
 134 {
 135     _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(ContextID, AlarmCodesContext);
 136 
 137     _cmsAssert(ContextAlarmCodes != NULL); // Can't happen
 138 
 139     memcpy(AlarmCodesP, ContextAlarmCodes->AlarmCodes, sizeof(ContextAlarmCodes->AlarmCodes));
 140 }
 141 
 142 void CMSEXPORT cmsSetAlarmCodes(const cmsUInt16Number NewAlarm[cmsMAXCHANNELS])
 143 {
 144     _cmsAssert(NewAlarm != NULL);
 145 
 146     cmsSetAlarmCodesTHR(NULL, NewAlarm);
 147 }
 148 
 149 void CMSEXPORT cmsGetAlarmCodes(cmsUInt16Number OldAlarm[cmsMAXCHANNELS])
 150 {
 151     _cmsAssert(OldAlarm != NULL);
 152     cmsGetAlarmCodesTHR(NULL, OldAlarm);
 153 }
 154 
 155 
 156 // Init and duplicate alarm codes
 157 void _cmsAllocAlarmCodesChunk(struct _cmsContext_struct* ctx,
 158                               const struct _cmsContext_struct* src)
 159 {
 160     static _cmsAlarmCodesChunkType AlarmCodesChunk = { DEFAULT_ALARM_CODES_VALUE };
 161     void* from;
 162 
 163     if (src != NULL) {
 164         from = src ->chunks[AlarmCodesContext];
 165     }
 166     else {
 167        from = &AlarmCodesChunk;
 168     }
 169 
 170     ctx ->chunks[AlarmCodesContext] = _cmsSubAllocDup(ctx ->MemPool, from, sizeof(_cmsAlarmCodesChunkType));
 171 }
 172 
 173 // -----------------------------------------------------------------------
 174 
 175 // Get rid of transform resources
 176 void CMSEXPORT cmsDeleteTransform(cmsHTRANSFORM hTransform)
 177 {
 178     _cmsTRANSFORM* p = (_cmsTRANSFORM*) hTransform;
 179 
 180     _cmsAssert(p != NULL);
 181 
 182     if (p -> GamutCheck)
 183         cmsPipelineFree(p -> GamutCheck);
 184 
 185     if (p -> Lut)
 186         cmsPipelineFree(p -> Lut);
 187 
 188     if (p ->InputColorant)
 189         cmsFreeNamedColorList(p ->InputColorant);
 190 
 191     if (p -> OutputColorant)
 192         cmsFreeNamedColorList(p ->OutputColorant);
 193 
 194     if (p ->Sequence)
 195         cmsFreeProfileSequenceDescription(p ->Sequence);
 196 
 197     if (p ->UserData)
 198         p ->FreeUserData(p ->ContextID, p ->UserData);
 199 
 200     _cmsFree(p ->ContextID, (void *) p);
 201 }
 202 
 203 // Apply transform.
 204 void CMSEXPORT cmsDoTransform(cmsHTRANSFORM  Transform,
 205                               const void* InputBuffer,
 206                               void* OutputBuffer,
 207                               cmsUInt32Number Size)
 208 
 209 {
 210     _cmsTRANSFORM* p = (_cmsTRANSFORM*) Transform;
 211     cmsStride stride;
 212 
 213     stride.BytesPerLineIn = 0;  // Not used
 214     stride.BytesPerLineOut = 0;
 215     stride.BytesPerPlaneIn = Size;
 216     stride.BytesPerPlaneOut = Size;
 217 
 218     p -> xform(p, InputBuffer, OutputBuffer, Size, 1, &stride);
 219 }
 220 
 221 
 222 // This is a legacy stride for planar
 223 void CMSEXPORT cmsDoTransformStride(cmsHTRANSFORM  Transform,
 224                               const void* InputBuffer,
 225                               void* OutputBuffer,
 226                               cmsUInt32Number Size, cmsUInt32Number Stride)
 227 
 228 {
 229     _cmsTRANSFORM* p = (_cmsTRANSFORM*) Transform;
 230     cmsStride stride;
 231 
 232     stride.BytesPerLineIn = 0;
 233     stride.BytesPerLineOut = 0;
 234     stride.BytesPerPlaneIn = Stride;
 235     stride.BytesPerPlaneOut = Stride;
 236 
 237     p -> xform(p, InputBuffer, OutputBuffer, Size, 1, &stride);
 238 }
 239 
 240 // This is the "fast" function for plugins
 241 void CMSEXPORT cmsDoTransformLineStride(cmsHTRANSFORM  Transform,
 242                               const void* InputBuffer,
 243                               void* OutputBuffer,
 244                               cmsUInt32Number PixelsPerLine,
 245                               cmsUInt32Number LineCount,
 246                               cmsUInt32Number BytesPerLineIn,
 247                               cmsUInt32Number BytesPerLineOut,
 248                               cmsUInt32Number BytesPerPlaneIn,
 249                               cmsUInt32Number BytesPerPlaneOut)
 250 
 251 {
 252     _cmsTRANSFORM* p = (_cmsTRANSFORM*) Transform;
 253     cmsStride stride;
 254 
 255     stride.BytesPerLineIn = BytesPerLineIn;
 256     stride.BytesPerLineOut = BytesPerLineOut;
 257     stride.BytesPerPlaneIn = BytesPerPlaneIn;
 258     stride.BytesPerPlaneOut = BytesPerPlaneOut;
 259 
 260     p->xform(p, InputBuffer, OutputBuffer, PixelsPerLine, LineCount, &stride);
 261 }
 262 
 263 
 264 
 265 // Transform routines ----------------------------------------------------------------------------------------------------------
 266 
 267 // Float xform converts floats. Since there are no performance issues, one routine does all job, including gamut check.
 268 // Note that because extended range, we can use a -1.0 value for out of gamut in this case.
 269 static
 270 void FloatXFORM(_cmsTRANSFORM* p,
 271                 const void* in,
 272                 void* out,
 273                 cmsUInt32Number PixelsPerLine,
 274                 cmsUInt32Number LineCount,
 275                 const cmsStride* Stride)
 276 {
 277     cmsUInt8Number* accum;
 278     cmsUInt8Number* output;
 279     cmsFloat32Number fIn[cmsMAXCHANNELS], fOut[cmsMAXCHANNELS];
 280     cmsFloat32Number OutOfGamut;
 281     cmsUInt32Number i, j, c, strideIn, strideOut;
 282 
 283     _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
 284 
 285     strideIn = 0;
 286     strideOut = 0;
 287     memset(fIn, 0, sizeof(fIn));
 288     memset(fOut, 0, sizeof(fIn));
 289 
 290     for (i = 0; i < LineCount; i++) {
 291 
 292         accum = (cmsUInt8Number*)in + strideIn;
 293         output = (cmsUInt8Number*)out + strideOut;
 294 
 295         for (j = 0; j < PixelsPerLine; j++) {
 296 
 297             accum = p->FromInputFloat(p, fIn, accum, Stride->BytesPerPlaneIn);
 298 
 299             // Any gamut chack to do?
 300             if (p->GamutCheck != NULL) {
 301 
 302                 // Evaluate gamut marker.
 303                 cmsPipelineEvalFloat(fIn, &OutOfGamut, p->GamutCheck);
 304 
 305                 // Is current color out of gamut?
 306                 if (OutOfGamut > 0.0) {
 307 
 308                     // Certainly, out of gamut
 309                     for (c = 0; c < cmsMAXCHANNELS; c++)
 310                         fOut[c] = -1.0;
 311 
 312                 }
 313                 else {
 314                     // No, proceed normally
 315                     cmsPipelineEvalFloat(fIn, fOut, p->Lut);
 316                 }
 317             }
 318             else {
 319 
 320                 // No gamut check at all
 321                 cmsPipelineEvalFloat(fIn, fOut, p->Lut);
 322             }
 323 
 324 
 325             output = p->ToOutputFloat(p, fOut, output, Stride->BytesPerPlaneOut);
 326         }
 327 
 328         strideIn += Stride->BytesPerLineIn;
 329         strideOut += Stride->BytesPerLineOut;
 330     }
 331 
 332 }
 333 
 334 
 335 static
 336 void NullFloatXFORM(_cmsTRANSFORM* p,
 337                     const void* in,
 338                     void* out,
 339                     cmsUInt32Number PixelsPerLine,
 340                     cmsUInt32Number LineCount,
 341                     const cmsStride* Stride)
 342 
 343 {
 344     cmsUInt8Number* accum;
 345     cmsUInt8Number* output;
 346     cmsFloat32Number fIn[cmsMAXCHANNELS];
 347     cmsUInt32Number i, j, strideIn, strideOut;
 348 
 349     _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
 350 
 351     strideIn = 0;
 352     strideOut = 0;
 353     memset(fIn, 0, sizeof(fIn));
 354 
 355     for (i = 0; i < LineCount; i++) {
 356 
 357            accum = (cmsUInt8Number*) in + strideIn;
 358            output = (cmsUInt8Number*) out + strideOut;
 359 
 360            for (j = 0; j < PixelsPerLine; j++) {
 361 
 362                   accum = p->FromInputFloat(p, fIn, accum, Stride ->BytesPerPlaneIn);
 363                   output = p->ToOutputFloat(p, fIn, output, Stride->BytesPerPlaneOut);
 364            }
 365 
 366            strideIn += Stride->BytesPerLineIn;
 367            strideOut += Stride->BytesPerLineOut;
 368     }
 369 }
 370 
 371 // 16 bit precision -----------------------------------------------------------------------------------------------------------
 372 
 373 // Null transformation, only applies formatters. No caché
 374 static
 375 void NullXFORM(_cmsTRANSFORM* p,
 376                const void* in,
 377                void* out,
 378                cmsUInt32Number PixelsPerLine,
 379                cmsUInt32Number LineCount,
 380                const cmsStride* Stride)
 381 {
 382     cmsUInt8Number* accum;
 383     cmsUInt8Number* output;
 384     cmsUInt16Number wIn[cmsMAXCHANNELS];
 385     cmsUInt32Number i, j, strideIn, strideOut;
 386 
 387     _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
 388 
 389     strideIn = 0;
 390     strideOut = 0;
 391     memset(wIn, 0, sizeof(wIn));
 392 
 393     for (i = 0; i < LineCount; i++) {
 394 
 395            accum = (cmsUInt8Number*)in + strideIn;
 396            output = (cmsUInt8Number*)out + strideOut;
 397 
 398            for (j = 0; j < PixelsPerLine; j++) {
 399 
 400                   accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
 401                   output = p->ToOutput(p, wIn, output, Stride->BytesPerPlaneOut);
 402     }
 403 
 404            strideIn += Stride->BytesPerLineIn;
 405            strideOut += Stride->BytesPerLineOut;
 406     }
 407 
 408 }
 409 
 410 
 411 // No gamut check, no cache, 16 bits
 412 static
 413 void PrecalculatedXFORM(_cmsTRANSFORM* p,
 414                         const void* in,
 415                         void* out,
 416                         cmsUInt32Number PixelsPerLine,
 417                         cmsUInt32Number LineCount,
 418                         const cmsStride* Stride)
 419 {
 420     register cmsUInt8Number* accum;
 421     register cmsUInt8Number* output;
 422     cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
 423     cmsUInt32Number i, j, strideIn, strideOut;
 424 
 425     _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
 426 
 427     strideIn = 0;
 428     strideOut = 0;
 429     memset(wIn, 0, sizeof(wIn));
 430     memset(wOut, 0, sizeof(wOut));
 431 
 432     for (i = 0; i < LineCount; i++) {
 433 
 434         accum = (cmsUInt8Number*)in + strideIn;
 435         output = (cmsUInt8Number*)out + strideOut;
 436 
 437         for (j = 0; j < PixelsPerLine; j++) {
 438 
 439             accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
 440             p->Lut->Eval16Fn(wIn, wOut, p->Lut->Data);
 441             output = p->ToOutput(p, wOut, output, Stride->BytesPerPlaneOut);
 442         }
 443 
 444         strideIn += Stride->BytesPerLineIn;
 445         strideOut += Stride->BytesPerLineOut;
 446     }
 447 
 448 }
 449 
 450 
 451 // Auxiliary: Handle precalculated gamut check. The retrieval of context may be alittle bit slow, but this function is not critical.
 452 static
 453 void TransformOnePixelWithGamutCheck(_cmsTRANSFORM* p,
 454                                      const cmsUInt16Number wIn[],
 455                                      cmsUInt16Number wOut[])
 456 {
 457     cmsUInt16Number wOutOfGamut;
 458 
 459     p ->GamutCheck ->Eval16Fn(wIn, &wOutOfGamut, p ->GamutCheck ->Data);
 460     if (wOutOfGamut >= 1) {
 461 
 462         cmsUInt16Number i;
 463         _cmsAlarmCodesChunkType* ContextAlarmCodes = (_cmsAlarmCodesChunkType*) _cmsContextGetClientChunk(p->ContextID, AlarmCodesContext);
 464 
 465         for (i=0; i < p ->Lut->OutputChannels; i++) {
 466 
 467             wOut[i] = ContextAlarmCodes ->AlarmCodes[i];
 468         }
 469     }
 470     else
 471         p ->Lut ->Eval16Fn(wIn, wOut, p -> Lut->Data);
 472 }
 473 
 474 // Gamut check, No caché, 16 bits.
 475 static
 476 void PrecalculatedXFORMGamutCheck(_cmsTRANSFORM* p,
 477                                   const void* in,
 478                                   void* out,
 479                                   cmsUInt32Number PixelsPerLine,
 480                                   cmsUInt32Number LineCount,
 481                                   const cmsStride* Stride)
 482 {
 483     cmsUInt8Number* accum;
 484     cmsUInt8Number* output;
 485     cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
 486     cmsUInt32Number i, j, strideIn, strideOut;
 487 
 488     _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
 489 
 490     strideIn = 0;
 491     strideOut = 0;
 492     memset(wIn, 0, sizeof(wIn));
 493     memset(wOut, 0, sizeof(wOut));
 494 
 495     for (i = 0; i < LineCount; i++) {
 496 
 497            accum = (cmsUInt8Number*)in + strideIn;
 498            output = (cmsUInt8Number*)out + strideOut;
 499 
 500            for (j = 0; j < PixelsPerLine; j++) {
 501 
 502                   accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
 503                   TransformOnePixelWithGamutCheck(p, wIn, wOut);
 504                   output = p->ToOutput(p, wOut, output, Stride->BytesPerPlaneOut);
 505            }
 506 
 507            strideIn += Stride->BytesPerLineIn;
 508            strideOut += Stride->BytesPerLineOut;
 509     }
 510 }
 511 
 512 
 513 // No gamut check, Caché, 16 bits,
 514 static
 515 void CachedXFORM(_cmsTRANSFORM* p,
 516                  const void* in,
 517                  void* out,
 518                  cmsUInt32Number PixelsPerLine,
 519                  cmsUInt32Number LineCount,
 520                  const cmsStride* Stride)
 521 {
 522     cmsUInt8Number* accum;
 523     cmsUInt8Number* output;
 524     cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
 525     _cmsCACHE Cache;
 526     cmsUInt32Number i, j, strideIn, strideOut;
 527 
 528     _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
 529 
 530     // Empty buffers for quick memcmp
 531     memset(wIn, 0, sizeof(wIn));
 532     memset(wOut, 0, sizeof(wOut));
 533 
 534     // Get copy of zero cache
 535     memcpy(&Cache, &p->Cache, sizeof(Cache));
 536 
 537     strideIn = 0;
 538     strideOut = 0;
 539 
 540     for (i = 0; i < LineCount; i++) {
 541 
 542         accum = (cmsUInt8Number*)in + strideIn;
 543         output = (cmsUInt8Number*)out + strideOut;
 544 
 545         for (j = 0; j < PixelsPerLine; j++) {
 546 
 547             accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
 548 
 549             if (memcmp(wIn, Cache.CacheIn, sizeof(Cache.CacheIn)) == 0) {
 550 
 551                 memcpy(wOut, Cache.CacheOut, sizeof(Cache.CacheOut));
 552             }
 553             else {
 554                 p->Lut->Eval16Fn(wIn, wOut, p->Lut->Data);
 555 
 556                 memcpy(Cache.CacheIn, wIn, sizeof(Cache.CacheIn));
 557                 memcpy(Cache.CacheOut, wOut, sizeof(Cache.CacheOut));
 558             }
 559 
 560             output = p->ToOutput(p, wOut, output, Stride->BytesPerPlaneOut);
 561         }
 562 
 563         strideIn += Stride->BytesPerLineIn;
 564         strideOut += Stride->BytesPerLineOut;
 565     }
 566 }
 567 
 568 // All those nice features together
 569 static
 570 void CachedXFORMGamutCheck(_cmsTRANSFORM* p,
 571                            const void* in,
 572                            void* out,
 573                            cmsUInt32Number PixelsPerLine,
 574                            cmsUInt32Number LineCount,
 575                            const cmsStride* Stride)
 576 {
 577     cmsUInt8Number* accum;
 578     cmsUInt8Number* output;
 579     cmsUInt16Number wIn[cmsMAXCHANNELS], wOut[cmsMAXCHANNELS];
 580     _cmsCACHE Cache;
 581     cmsUInt32Number i, j, strideIn, strideOut;
 582 
 583     _cmsHandleExtraChannels(p, in, out, PixelsPerLine, LineCount, Stride);
 584 
 585     // Empty buffers for quick memcmp
 586     memset(wIn, 0, sizeof(wIn));
 587     memset(wOut, 0, sizeof(wOut));
 588 
 589     // Get copy of zero cache
 590     memcpy(&Cache, &p->Cache, sizeof(Cache));
 591 
 592     strideIn = 0;
 593     strideOut = 0;
 594 
 595     for (i = 0; i < LineCount; i++) {
 596 
 597         accum = (cmsUInt8Number*)in + strideIn;
 598         output = (cmsUInt8Number*)out + strideOut;
 599 
 600         for (j = 0; j < PixelsPerLine; j++) {
 601 
 602             accum = p->FromInput(p, wIn, accum, Stride->BytesPerPlaneIn);
 603 
 604             if (memcmp(wIn, Cache.CacheIn, sizeof(Cache.CacheIn)) == 0) {
 605 
 606                 memcpy(wOut, Cache.CacheOut, sizeof(Cache.CacheOut));
 607             }
 608             else {
 609                 TransformOnePixelWithGamutCheck(p, wIn, wOut);
 610 
 611                 memcpy(Cache.CacheIn, wIn, sizeof(Cache.CacheIn));
 612                 memcpy(Cache.CacheOut, wOut, sizeof(Cache.CacheOut));
 613             }
 614 
 615             output = p->ToOutput(p, wOut, output, Stride->BytesPerPlaneOut);
 616         }
 617 
 618         strideIn += Stride->BytesPerLineIn;
 619         strideOut += Stride->BytesPerLineOut;
 620     }
 621 }
 622 
 623 // Transform plug-ins ----------------------------------------------------------------------------------------------------
 624 
 625 // List of used-defined transform factories
 626 typedef struct _cmsTransformCollection_st {
 627 
 628     _cmsTransform2Factory  Factory;
 629     cmsBool                OldXform;   // Factory returns xform function in the old style
 630 
 631     struct _cmsTransformCollection_st *Next;
 632 
 633 } _cmsTransformCollection;
 634 
 635 // The linked list head
 636 _cmsTransformPluginChunkType _cmsTransformPluginChunk = { NULL };
 637 
 638 
 639 // Duplicates the zone of memory used by the plug-in in the new context
 640 static
 641 void DupPluginTransformList(struct _cmsContext_struct* ctx,
 642                                                const struct _cmsContext_struct* src)
 643 {
 644    _cmsTransformPluginChunkType newHead = { NULL };
 645    _cmsTransformCollection*  entry;
 646    _cmsTransformCollection*  Anterior = NULL;
 647    _cmsTransformPluginChunkType* head = (_cmsTransformPluginChunkType*) src->chunks[TransformPlugin];
 648 
 649     // Walk the list copying all nodes
 650    for (entry = head->TransformCollection;
 651         entry != NULL;
 652         entry = entry ->Next) {
 653 
 654             _cmsTransformCollection *newEntry = ( _cmsTransformCollection *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(_cmsTransformCollection));
 655 
 656             if (newEntry == NULL)
 657                 return;
 658 
 659             // We want to keep the linked list order, so this is a little bit tricky
 660             newEntry -> Next = NULL;
 661             if (Anterior)
 662                 Anterior -> Next = newEntry;
 663 
 664             Anterior = newEntry;
 665 
 666             if (newHead.TransformCollection == NULL)
 667                 newHead.TransformCollection = newEntry;
 668     }
 669 
 670   ctx ->chunks[TransformPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsTransformPluginChunkType));
 671 }
 672 
 673 // Allocates memory for transform plugin factory
 674 void _cmsAllocTransformPluginChunk(struct _cmsContext_struct* ctx,
 675                                         const struct _cmsContext_struct* src)
 676 {
 677     if (src != NULL) {
 678 
 679         // Copy all linked list
 680         DupPluginTransformList(ctx, src);
 681     }
 682     else {
 683         static _cmsTransformPluginChunkType TransformPluginChunkType = { NULL };
 684         ctx ->chunks[TransformPlugin] = _cmsSubAllocDup(ctx ->MemPool, &TransformPluginChunkType, sizeof(_cmsTransformPluginChunkType));
 685     }
 686 }
 687 
 688 // Adaptor for old versions of plug-in
 689 static
 690 void _cmsTransform2toTransformAdaptor(struct _cmstransform_struct *CMMcargo,
 691                                       const void* InputBuffer,
 692                                       void* OutputBuffer,
 693                                       cmsUInt32Number PixelsPerLine,
 694                                       cmsUInt32Number LineCount,
 695                                       const cmsStride* Stride)
 696 {
 697 
 698        cmsUInt32Number i, strideIn, strideOut;
 699 
 700        _cmsHandleExtraChannels(CMMcargo, InputBuffer, OutputBuffer, PixelsPerLine, LineCount, Stride);
 701 
 702        strideIn = 0;
 703        strideOut = 0;
 704 
 705        for (i = 0; i < LineCount; i++) {
 706 
 707               void *accum = (cmsUInt8Number*)InputBuffer + strideIn;
 708               void *output = (cmsUInt8Number*)OutputBuffer + strideOut;
 709 
 710               CMMcargo->OldXform(CMMcargo, accum, output, PixelsPerLine, Stride->BytesPerPlaneIn);
 711 
 712               strideIn += Stride->BytesPerLineIn;
 713               strideOut += Stride->BytesPerLineOut;
 714        }
 715 }
 716 
 717 
 718 
 719 // Register new ways to transform
 720 cmsBool  _cmsRegisterTransformPlugin(cmsContext ContextID, cmsPluginBase* Data)
 721 {
 722     cmsPluginTransform* Plugin = (cmsPluginTransform*) Data;
 723     _cmsTransformCollection* fl;
 724     _cmsTransformPluginChunkType* ctx = ( _cmsTransformPluginChunkType*) _cmsContextGetClientChunk(ContextID,TransformPlugin);
 725 
 726     if (Data == NULL) {
 727 
 728         // Free the chain. Memory is safely freed at exit
 729         ctx->TransformCollection = NULL;
 730         return TRUE;
 731     }
 732 
 733     // Factory callback is required
 734     if (Plugin->factories.xform == NULL) return FALSE;
 735 
 736 
 737     fl = (_cmsTransformCollection*) _cmsPluginMalloc(ContextID, sizeof(_cmsTransformCollection));
 738     if (fl == NULL) return FALSE;
 739 
 740     // Check for full xform plug-ins previous to 2.8, we would need an adapter in that case
 741     if (Plugin->base.ExpectedVersion < 2080) {
 742 
 743            fl->OldXform = TRUE;
 744     }
 745     else
 746            fl->OldXform = FALSE;
 747 
 748     // Copy the parameters
 749     fl->Factory = Plugin->factories.xform;
 750 
 751     // Keep linked list
 752     fl ->Next = ctx->TransformCollection;
 753     ctx->TransformCollection = fl;
 754 
 755     // All is ok
 756     return TRUE;
 757 }
 758 
 759 
 760 void CMSEXPORT _cmsSetTransformUserData(struct _cmstransform_struct *CMMcargo, void* ptr, _cmsFreeUserDataFn FreePrivateDataFn)
 761 {
 762     _cmsAssert(CMMcargo != NULL);
 763     CMMcargo ->UserData = ptr;
 764     CMMcargo ->FreeUserData = FreePrivateDataFn;
 765 }
 766 
 767 // returns the pointer defined by the plug-in to store private data
 768 void * CMSEXPORT _cmsGetTransformUserData(struct _cmstransform_struct *CMMcargo)
 769 {
 770     _cmsAssert(CMMcargo != NULL);
 771     return CMMcargo ->UserData;
 772 }
 773 
 774 // returns the current formatters
 775 void CMSEXPORT _cmsGetTransformFormatters16(struct _cmstransform_struct *CMMcargo, cmsFormatter16* FromInput, cmsFormatter16* ToOutput)
 776 {
 777      _cmsAssert(CMMcargo != NULL);
 778      if (FromInput) *FromInput = CMMcargo ->FromInput;
 779      if (ToOutput)  *ToOutput  = CMMcargo ->ToOutput;
 780 }
 781 
 782 void CMSEXPORT _cmsGetTransformFormattersFloat(struct _cmstransform_struct *CMMcargo, cmsFormatterFloat* FromInput, cmsFormatterFloat* ToOutput)
 783 {
 784      _cmsAssert(CMMcargo != NULL);
 785      if (FromInput) *FromInput = CMMcargo ->FromInputFloat;
 786      if (ToOutput)  *ToOutput  = CMMcargo ->ToOutputFloat;
 787 }
 788 
 789 
 790 // Allocate transform struct and set it to defaults. Ask the optimization plug-in about if those formats are proper
 791 // for separated transforms. If this is the case,
 792 static
 793 _cmsTRANSFORM* AllocEmptyTransform(cmsContext ContextID, cmsPipeline* lut,
 794                                                cmsUInt32Number Intent, cmsUInt32Number* InputFormat, cmsUInt32Number* OutputFormat, cmsUInt32Number* dwFlags)
 795 {
 796      _cmsTransformPluginChunkType* ctx = ( _cmsTransformPluginChunkType*) _cmsContextGetClientChunk(ContextID, TransformPlugin);
 797      _cmsTransformCollection* Plugin;
 798 
 799        // Allocate needed memory
 800        _cmsTRANSFORM* p = (_cmsTRANSFORM*)_cmsMallocZero(ContextID, sizeof(_cmsTRANSFORM));
 801        if (!p) {
 802               cmsPipelineFree(lut);
 803               return NULL;
 804        }
 805 
 806        // Store the proposed pipeline
 807        p->Lut = lut;
 808 
 809        // Let's see if any plug-in want to do the transform by itself
 810        if (p->Lut != NULL) {
 811 
 812               for (Plugin = ctx->TransformCollection;
 813                      Plugin != NULL;
 814                      Plugin = Plugin->Next) {
 815 
 816                      if (Plugin->Factory(&p->xform, &p->UserData, &p->FreeUserData, &p->Lut, InputFormat, OutputFormat, dwFlags)) {
 817 
 818                             // Last plugin in the declaration order takes control. We just keep
 819                             // the original parameters as a logging.
 820                             // Note that cmsFLAGS_CAN_CHANGE_FORMATTER is not set, so by default
 821                             // an optimized transform is not reusable. The plug-in can, however, change
 822                             // the flags and make it suitable.
 823 
 824                             p->ContextID = ContextID;
 825                             p->InputFormat = *InputFormat;
 826                             p->OutputFormat = *OutputFormat;
 827                             p->dwOriginalFlags = *dwFlags;
 828 
 829                             // Fill the formatters just in case the optimized routine is interested.
 830                             // No error is thrown if the formatter doesn't exist. It is up to the optimization
 831                             // factory to decide what to do in those cases.
 832                             p->FromInput = _cmsGetFormatter(ContextID, *InputFormat, cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
 833                             p->ToOutput = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
 834                             p->FromInputFloat = _cmsGetFormatter(ContextID, *InputFormat, cmsFormatterInput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
 835                             p->ToOutputFloat = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
 836 
 837                             // Save the day? (Ignore the warning)
 838                             if (Plugin->OldXform) {
 839                                    p->OldXform = (_cmsTransformFn) p->xform;
 840                                    p->xform = _cmsTransform2toTransformAdaptor;
 841                             }
 842 
 843                             return p;
 844                      }
 845               }
 846 
 847               // Not suitable for the transform plug-in, let's check  the pipeline plug-in
 848               _cmsOptimizePipeline(ContextID, &p->Lut, Intent, InputFormat, OutputFormat, dwFlags);
 849        }
 850 
 851     // Check whatever this is a true floating point transform
 852     if (_cmsFormatterIsFloat(*InputFormat) && _cmsFormatterIsFloat(*OutputFormat)) {
 853 
 854         // Get formatter function always return a valid union, but the contents of this union may be NULL.
 855         p ->FromInputFloat = _cmsGetFormatter(ContextID, *InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
 856         p ->ToOutputFloat  = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_FLOAT).FmtFloat;
 857         *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
 858 
 859         if (p ->FromInputFloat == NULL || p ->ToOutputFloat == NULL) {
 860 
 861             cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
 862             cmsDeleteTransform(p);
 863             return NULL;
 864         }
 865 
 866         if (*dwFlags & cmsFLAGS_NULLTRANSFORM) {
 867 
 868             p ->xform = NullFloatXFORM;
 869         }
 870         else {
 871             // Float transforms don't use caché, always are non-NULL
 872             p ->xform = FloatXFORM;
 873         }
 874 
 875     }
 876     else {
 877 
 878         if (*InputFormat == 0 && *OutputFormat == 0) {
 879             p ->FromInput = p ->ToOutput = NULL;
 880             *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
 881         }
 882         else {
 883 
 884             cmsUInt32Number BytesPerPixelInput;
 885 
 886             p ->FromInput = _cmsGetFormatter(ContextID, *InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
 887             p ->ToOutput  = _cmsGetFormatter(ContextID, *OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
 888 
 889             if (p ->FromInput == NULL || p ->ToOutput == NULL) {
 890 
 891                 cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
 892                 cmsDeleteTransform(p);
 893                 return NULL;
 894             }
 895 
 896             BytesPerPixelInput = T_BYTES(p ->InputFormat);
 897             if (BytesPerPixelInput == 0 || BytesPerPixelInput >= 2)
 898                    *dwFlags |= cmsFLAGS_CAN_CHANGE_FORMATTER;
 899 
 900         }
 901 
 902         if (*dwFlags & cmsFLAGS_NULLTRANSFORM) {
 903 
 904             p ->xform = NullXFORM;
 905         }
 906         else {
 907             if (*dwFlags & cmsFLAGS_NOCACHE) {
 908 
 909                 if (*dwFlags & cmsFLAGS_GAMUTCHECK)
 910                     p ->xform = PrecalculatedXFORMGamutCheck;  // Gamut check, no caché
 911                 else
 912                     p ->xform = PrecalculatedXFORM;  // No caché, no gamut check
 913             }
 914             else {
 915 
 916                 if (*dwFlags & cmsFLAGS_GAMUTCHECK)
 917                     p ->xform = CachedXFORMGamutCheck;    // Gamut check, caché
 918                 else
 919                     p ->xform = CachedXFORM;  // No gamut check, caché
 920 
 921             }
 922         }
 923     }
 924 
 925     p ->InputFormat     = *InputFormat;
 926     p ->OutputFormat    = *OutputFormat;
 927     p ->dwOriginalFlags = *dwFlags;
 928     p ->ContextID       = ContextID;
 929     p ->UserData        = NULL;
 930     return p;
 931 }
 932 
 933 static
 934 cmsBool GetXFormColorSpaces(cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[], cmsColorSpaceSignature* Input, cmsColorSpaceSignature* Output)
 935 {
 936     cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut;
 937     cmsColorSpaceSignature PostColorSpace;
 938     cmsUInt32Number i;
 939 
 940     if (nProfiles == 0) return FALSE;
 941     if (hProfiles[0] == NULL) return FALSE;
 942 
 943     *Input = PostColorSpace = cmsGetColorSpace(hProfiles[0]);
 944 
 945     for (i=0; i < nProfiles; i++) {
 946 
 947         cmsProfileClassSignature cls;
 948         cmsHPROFILE hProfile = hProfiles[i];
 949 
 950         int lIsInput = (PostColorSpace != cmsSigXYZData) &&
 951                        (PostColorSpace != cmsSigLabData);
 952 
 953         if (hProfile == NULL) return FALSE;
 954 
 955         cls = cmsGetDeviceClass(hProfile);
 956 
 957         if (cls == cmsSigNamedColorClass) {
 958 
 959             ColorSpaceIn    = cmsSig1colorData;
 960             ColorSpaceOut   = (nProfiles > 1) ? cmsGetPCS(hProfile) : cmsGetColorSpace(hProfile);
 961         }
 962         else
 963         if (lIsInput || (cls == cmsSigLinkClass)) {
 964 
 965             ColorSpaceIn    = cmsGetColorSpace(hProfile);
 966             ColorSpaceOut   = cmsGetPCS(hProfile);
 967         }
 968         else
 969         {
 970             ColorSpaceIn    = cmsGetPCS(hProfile);
 971             ColorSpaceOut   = cmsGetColorSpace(hProfile);
 972         }
 973 
 974         if (i==0)
 975             *Input = ColorSpaceIn;
 976 
 977         PostColorSpace = ColorSpaceOut;
 978     }
 979 
 980     *Output = PostColorSpace;
 981 
 982     return TRUE;
 983 }
 984 
 985 // Check colorspace
 986 static
 987 cmsBool  IsProperColorSpace(cmsColorSpaceSignature Check, cmsUInt32Number dwFormat)
 988 {
 989     int Space1 = (int) T_COLORSPACE(dwFormat);
 990     int Space2 = _cmsLCMScolorSpace(Check);
 991 
 992     if (Space1 == PT_ANY) return TRUE;
 993     if (Space1 == Space2) return TRUE;
 994 
 995     if (Space1 == PT_LabV2 && Space2 == PT_Lab) return TRUE;
 996     if (Space1 == PT_Lab   && Space2 == PT_LabV2) return TRUE;
 997 
 998     return FALSE;
 999 }
1000 
1001 // ----------------------------------------------------------------------------------------------------------------
1002 
1003 // Jun-21-2000: Some profiles (those that comes with W2K) comes
1004 // with the media white (media black?) x 100. Add a sanity check
1005 
1006 static
1007 void NormalizeXYZ(cmsCIEXYZ* Dest)
1008 {
1009     while (Dest -> X > 2. &&
1010            Dest -> Y > 2. &&
1011            Dest -> Z > 2.) {
1012 
1013                Dest -> X /= 10.;
1014                Dest -> Y /= 10.;
1015                Dest -> Z /= 10.;
1016        }
1017 }
1018 
1019 static
1020 void SetWhitePoint(cmsCIEXYZ* wtPt, const cmsCIEXYZ* src)
1021 {
1022     if (src == NULL) {
1023         wtPt ->X = cmsD50X;
1024         wtPt ->Y = cmsD50Y;
1025         wtPt ->Z = cmsD50Z;
1026     }
1027     else {
1028         wtPt ->X = src->X;
1029         wtPt ->Y = src->Y;
1030         wtPt ->Z = src->Z;
1031 
1032         NormalizeXYZ(wtPt);
1033     }
1034 
1035 }
1036 
1037 // New to lcms 2.0 -- have all parameters available.
1038 cmsHTRANSFORM CMSEXPORT cmsCreateExtendedTransform(cmsContext ContextID,
1039                                                    cmsUInt32Number nProfiles, cmsHPROFILE hProfiles[],
1040                                                    cmsBool  BPC[],
1041                                                    cmsUInt32Number Intents[],
1042                                                    cmsFloat64Number AdaptationStates[],
1043                                                    cmsHPROFILE hGamutProfile,
1044                                                    cmsUInt32Number nGamutPCSposition,
1045                                                    cmsUInt32Number InputFormat,
1046                                                    cmsUInt32Number OutputFormat,
1047                                                    cmsUInt32Number dwFlags)
1048 {
1049     _cmsTRANSFORM* xform;
1050     cmsColorSpaceSignature EntryColorSpace;
1051     cmsColorSpaceSignature ExitColorSpace;
1052     cmsPipeline* Lut;
1053     cmsUInt32Number LastIntent = Intents[nProfiles-1];
1054 
1055     // If it is a fake transform
1056     if (dwFlags & cmsFLAGS_NULLTRANSFORM)
1057     {
1058         return AllocEmptyTransform(ContextID, NULL, INTENT_PERCEPTUAL, &InputFormat, &OutputFormat, &dwFlags);
1059     }
1060 
1061     // If gamut check is requested, make sure we have a gamut profile
1062     if (dwFlags & cmsFLAGS_GAMUTCHECK) {
1063         if (hGamutProfile == NULL) dwFlags &= ~cmsFLAGS_GAMUTCHECK;
1064     }
1065 
1066     // On floating point transforms, inhibit cache
1067     if (_cmsFormatterIsFloat(InputFormat) || _cmsFormatterIsFloat(OutputFormat))
1068         dwFlags |= cmsFLAGS_NOCACHE;
1069 
1070     // Mark entry/exit spaces
1071     if (!GetXFormColorSpaces(nProfiles, hProfiles, &EntryColorSpace, &ExitColorSpace)) {
1072         cmsSignalError(ContextID, cmsERROR_NULL, "NULL input profiles on transform");
1073         return NULL;
1074     }
1075 
1076     // Check if proper colorspaces
1077     if (!IsProperColorSpace(EntryColorSpace, InputFormat)) {
1078         cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Wrong input color space on transform");
1079         return NULL;
1080     }
1081 
1082     if (!IsProperColorSpace(ExitColorSpace, OutputFormat)) {
1083         cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Wrong output color space on transform");
1084         return NULL;
1085     }
1086 
1087     // Create a pipeline with all transformations
1088     Lut = _cmsLinkProfiles(ContextID, nProfiles, Intents, hProfiles, BPC, AdaptationStates, dwFlags);
1089     if (Lut == NULL) {
1090         cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Couldn't link the profiles");
1091         return NULL;
1092     }
1093 
1094     // Check channel count
1095     if ((cmsChannelsOf(EntryColorSpace) != cmsPipelineInputChannels(Lut)) ||
1096         (cmsChannelsOf(ExitColorSpace)  != cmsPipelineOutputChannels(Lut))) {
1097         cmsPipelineFree(Lut);
1098         cmsSignalError(ContextID, cmsERROR_NOT_SUITABLE, "Channel count doesn't match. Profile is corrupted");
1099         return NULL;
1100     }
1101 
1102 
1103     // All seems ok
1104     xform = AllocEmptyTransform(ContextID, Lut, LastIntent, &InputFormat, &OutputFormat, &dwFlags);
1105     if (xform == NULL) {
1106         return NULL;
1107     }
1108 
1109     // Keep values
1110     xform ->EntryColorSpace = EntryColorSpace;
1111     xform ->ExitColorSpace  = ExitColorSpace;
1112     xform ->RenderingIntent = Intents[nProfiles-1];
1113 
1114     // Take white points
1115     SetWhitePoint(&xform->EntryWhitePoint, (cmsCIEXYZ*) cmsReadTag(hProfiles[0], cmsSigMediaWhitePointTag));
1116     SetWhitePoint(&xform->ExitWhitePoint,  (cmsCIEXYZ*) cmsReadTag(hProfiles[nProfiles-1], cmsSigMediaWhitePointTag));
1117 
1118 
1119     // Create a gamut check LUT if requested
1120     if (hGamutProfile != NULL && (dwFlags & cmsFLAGS_GAMUTCHECK))
1121         xform ->GamutCheck  = _cmsCreateGamutCheckPipeline(ContextID, hProfiles,
1122                                                         BPC, Intents,
1123                                                         AdaptationStates,
1124                                                         nGamutPCSposition,
1125                                                         hGamutProfile);
1126 
1127 
1128     // Try to read input and output colorant table
1129     if (cmsIsTag(hProfiles[0], cmsSigColorantTableTag)) {
1130 
1131         // Input table can only come in this way.
1132         xform ->InputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[0], cmsSigColorantTableTag));
1133     }
1134 
1135     // Output is a little bit more complex.
1136     if (cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigLinkClass) {
1137 
1138         // This tag may exist only on devicelink profiles.
1139         if (cmsIsTag(hProfiles[nProfiles-1], cmsSigColorantTableOutTag)) {
1140 
1141             // It may be NULL if error
1142             xform ->OutputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[nProfiles-1], cmsSigColorantTableOutTag));
1143         }
1144 
1145     } else {
1146 
1147         if (cmsIsTag(hProfiles[nProfiles-1], cmsSigColorantTableTag)) {
1148 
1149             xform -> OutputColorant = cmsDupNamedColorList((cmsNAMEDCOLORLIST*) cmsReadTag(hProfiles[nProfiles-1], cmsSigColorantTableTag));
1150         }
1151     }
1152 
1153     // Store the sequence of profiles
1154     if (dwFlags & cmsFLAGS_KEEP_SEQUENCE) {
1155         xform ->Sequence = _cmsCompileProfileSequence(ContextID, nProfiles, hProfiles);
1156     }
1157     else
1158         xform ->Sequence = NULL;
1159 
1160     // If this is a cached transform, init first value, which is zero (16 bits only)
1161     if (!(dwFlags & cmsFLAGS_NOCACHE)) {
1162 
1163         memset(&xform ->Cache.CacheIn, 0, sizeof(xform ->Cache.CacheIn));
1164 
1165         if (xform ->GamutCheck != NULL) {
1166             TransformOnePixelWithGamutCheck(xform, xform ->Cache.CacheIn, xform->Cache.CacheOut);
1167         }
1168         else {
1169 
1170             xform ->Lut ->Eval16Fn(xform ->Cache.CacheIn, xform->Cache.CacheOut, xform -> Lut->Data);
1171         }
1172 
1173     }
1174 
1175     return (cmsHTRANSFORM) xform;
1176 }
1177 
1178 // Multiprofile transforms: Gamut check is not available here, as it is unclear from which profile the gamut comes.
1179 cmsHTRANSFORM CMSEXPORT cmsCreateMultiprofileTransformTHR(cmsContext ContextID,
1180                                                        cmsHPROFILE hProfiles[],
1181                                                        cmsUInt32Number nProfiles,
1182                                                        cmsUInt32Number InputFormat,
1183                                                        cmsUInt32Number OutputFormat,
1184                                                        cmsUInt32Number Intent,
1185                                                        cmsUInt32Number dwFlags)
1186 {
1187     cmsUInt32Number i;
1188     cmsBool BPC[256];
1189     cmsUInt32Number Intents[256];
1190     cmsFloat64Number AdaptationStates[256];
1191 
1192     if (nProfiles <= 0 || nProfiles > 255) {
1193          cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong number of profiles. 1..255 expected, %d found.", nProfiles);
1194         return NULL;
1195     }
1196 
1197     for (i=0; i < nProfiles; i++) {
1198         BPC[i] = dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION ? TRUE : FALSE;
1199         Intents[i] = Intent;
1200         AdaptationStates[i] = cmsSetAdaptationStateTHR(ContextID, -1);
1201     }
1202 
1203 
1204     return cmsCreateExtendedTransform(ContextID, nProfiles, hProfiles, BPC, Intents, AdaptationStates, NULL, 0, InputFormat, OutputFormat, dwFlags);
1205 }
1206 
1207 
1208 
1209 cmsHTRANSFORM CMSEXPORT cmsCreateMultiprofileTransform(cmsHPROFILE hProfiles[],
1210                                                   cmsUInt32Number nProfiles,
1211                                                   cmsUInt32Number InputFormat,
1212                                                   cmsUInt32Number OutputFormat,
1213                                                   cmsUInt32Number Intent,
1214                                                   cmsUInt32Number dwFlags)
1215 {
1216 
1217     if (nProfiles <= 0 || nProfiles > 255) {
1218          cmsSignalError(NULL, cmsERROR_RANGE, "Wrong number of profiles. 1..255 expected, %d found.", nProfiles);
1219          return NULL;
1220     }
1221 
1222     return cmsCreateMultiprofileTransformTHR(cmsGetProfileContextID(hProfiles[0]),
1223                                                   hProfiles,
1224                                                   nProfiles,
1225                                                   InputFormat,
1226                                                   OutputFormat,
1227                                                   Intent,
1228                                                   dwFlags);
1229 }
1230 
1231 cmsHTRANSFORM CMSEXPORT cmsCreateTransformTHR(cmsContext ContextID,
1232                                               cmsHPROFILE Input,
1233                                               cmsUInt32Number InputFormat,
1234                                               cmsHPROFILE Output,
1235                                               cmsUInt32Number OutputFormat,
1236                                               cmsUInt32Number Intent,
1237                                               cmsUInt32Number dwFlags)
1238 {
1239 
1240     cmsHPROFILE hArray[2];
1241 
1242     hArray[0] = Input;
1243     hArray[1] = Output;
1244 
1245     return cmsCreateMultiprofileTransformTHR(ContextID, hArray, Output == NULL ? 1U : 2U, InputFormat, OutputFormat, Intent, dwFlags);
1246 }
1247 
1248 CMSAPI cmsHTRANSFORM CMSEXPORT cmsCreateTransform(cmsHPROFILE Input,
1249                                                   cmsUInt32Number InputFormat,
1250                                                   cmsHPROFILE Output,
1251                                                   cmsUInt32Number OutputFormat,
1252                                                   cmsUInt32Number Intent,
1253                                                   cmsUInt32Number dwFlags)
1254 {
1255     return cmsCreateTransformTHR(cmsGetProfileContextID(Input), Input, InputFormat, Output, OutputFormat, Intent, dwFlags);
1256 }
1257 
1258 
1259 cmsHTRANSFORM CMSEXPORT cmsCreateProofingTransformTHR(cmsContext ContextID,
1260                                                    cmsHPROFILE InputProfile,
1261                                                    cmsUInt32Number InputFormat,
1262                                                    cmsHPROFILE OutputProfile,
1263                                                    cmsUInt32Number OutputFormat,
1264                                                    cmsHPROFILE ProofingProfile,
1265                                                    cmsUInt32Number nIntent,
1266                                                    cmsUInt32Number ProofingIntent,
1267                                                    cmsUInt32Number dwFlags)
1268 {
1269     cmsHPROFILE hArray[4];
1270     cmsUInt32Number Intents[4];
1271     cmsBool  BPC[4];
1272     cmsFloat64Number Adaptation[4];
1273     cmsBool  DoBPC = (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION) ? TRUE : FALSE;
1274 
1275 
1276     hArray[0]  = InputProfile; hArray[1] = ProofingProfile; hArray[2]  = ProofingProfile;               hArray[3] = OutputProfile;
1277     Intents[0] = nIntent;      Intents[1] = nIntent;        Intents[2] = INTENT_RELATIVE_COLORIMETRIC;  Intents[3] = ProofingIntent;
1278     BPC[0]     = DoBPC;        BPC[1] = DoBPC;              BPC[2] = 0;                                 BPC[3] = 0;
1279 
1280     Adaptation[0] = Adaptation[1] = Adaptation[2] = Adaptation[3] = cmsSetAdaptationStateTHR(ContextID, -1);
1281 
1282     if (!(dwFlags & (cmsFLAGS_SOFTPROOFING|cmsFLAGS_GAMUTCHECK)))
1283         return cmsCreateTransformTHR(ContextID, InputProfile, InputFormat, OutputProfile, OutputFormat, nIntent, dwFlags);
1284 
1285     return cmsCreateExtendedTransform(ContextID, 4, hArray, BPC, Intents, Adaptation,
1286                                         ProofingProfile, 1, InputFormat, OutputFormat, dwFlags);
1287 
1288 }
1289 
1290 
1291 cmsHTRANSFORM CMSEXPORT cmsCreateProofingTransform(cmsHPROFILE InputProfile,
1292                                                    cmsUInt32Number InputFormat,
1293                                                    cmsHPROFILE OutputProfile,
1294                                                    cmsUInt32Number OutputFormat,
1295                                                    cmsHPROFILE ProofingProfile,
1296                                                    cmsUInt32Number nIntent,
1297                                                    cmsUInt32Number ProofingIntent,
1298                                                    cmsUInt32Number dwFlags)
1299 {
1300     return cmsCreateProofingTransformTHR(cmsGetProfileContextID(InputProfile),
1301                                                    InputProfile,
1302                                                    InputFormat,
1303                                                    OutputProfile,
1304                                                    OutputFormat,
1305                                                    ProofingProfile,
1306                                                    nIntent,
1307                                                    ProofingIntent,
1308                                                    dwFlags);
1309 }
1310 
1311 
1312 // Grab the ContextID from an open transform. Returns NULL if a NULL transform is passed
1313 cmsContext CMSEXPORT cmsGetTransformContextID(cmsHTRANSFORM hTransform)
1314 {
1315     _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1316 
1317     if (xform == NULL) return NULL;
1318     return xform -> ContextID;
1319 }
1320 
1321 // Grab the input/output formats
1322 cmsUInt32Number CMSEXPORT cmsGetTransformInputFormat(cmsHTRANSFORM hTransform)
1323 {
1324     _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1325 
1326     if (xform == NULL) return 0;
1327     return xform->InputFormat;
1328 }
1329 
1330 cmsUInt32Number CMSEXPORT cmsGetTransformOutputFormat(cmsHTRANSFORM hTransform)
1331 {
1332     _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1333 
1334     if (xform == NULL) return 0;
1335     return xform->OutputFormat;
1336 }
1337 
1338 // For backwards compatibility
1339 cmsBool CMSEXPORT cmsChangeBuffersFormat(cmsHTRANSFORM hTransform,
1340                                          cmsUInt32Number InputFormat,
1341                                          cmsUInt32Number OutputFormat)
1342 {
1343     _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1344     cmsFormatter16 FromInput, ToOutput;
1345 
1346 
1347     // We only can afford to change formatters if previous transform is at least 16 bits
1348     if (!(xform ->dwOriginalFlags & cmsFLAGS_CAN_CHANGE_FORMATTER)) {
1349 
1350         cmsSignalError(xform ->ContextID, cmsERROR_NOT_SUITABLE, "cmsChangeBuffersFormat works only on transforms created originally with at least 16 bits of precision");
1351         return FALSE;
1352     }
1353 
1354     FromInput = _cmsGetFormatter(xform->ContextID, InputFormat,  cmsFormatterInput, CMS_PACK_FLAGS_16BITS).Fmt16;
1355     ToOutput  = _cmsGetFormatter(xform->ContextID, OutputFormat, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS).Fmt16;
1356 
1357     if (FromInput == NULL || ToOutput == NULL) {
1358 
1359         cmsSignalError(xform -> ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported raster format");
1360         return FALSE;
1361     }
1362 
1363     xform ->InputFormat  = InputFormat;
1364     xform ->OutputFormat = OutputFormat;
1365     xform ->FromInput    = FromInput;
1366     xform ->ToOutput     = ToOutput;
1367     return TRUE;
1368 }