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