| //--------------------------------------------------------------------------------- |
| // |
| // Little Color Management System |
| // Copyright (c) 1998-2020 Marti Maria Saguer |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining |
| // a copy of this software and associated documentation files (the "Software"), |
| // to deal in the Software without restriction, including without limitation |
| // the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| // and/or sell copies of the Software, and to permit persons to whom the Software |
| // is furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO |
| // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| // |
| //--------------------------------------------------------------------------------- |
| // |
| |
| #include "lcms2_internal.h" |
| |
| |
| // Allocates an empty multi profile element |
| cmsStage* CMSEXPORT _cmsStageAllocPlaceholder(cmsContext ContextID, |
| cmsStageSignature Type, |
| cmsUInt32Number InputChannels, |
| cmsUInt32Number OutputChannels, |
| _cmsStageEvalFn EvalPtr, |
| _cmsStageDupElemFn DupElemPtr, |
| _cmsStageFreeElemFn FreePtr, |
| void* Data) |
| { |
| cmsStage* ph = (cmsStage*) _cmsMallocZero(ContextID, sizeof(cmsStage)); |
| |
| if (ph == NULL) return NULL; |
| |
| |
| ph ->ContextID = ContextID; |
| |
| ph ->Type = Type; |
| ph ->Implements = Type; // By default, no clue on what is implementing |
| |
| ph ->InputChannels = InputChannels; |
| ph ->OutputChannels = OutputChannels; |
| ph ->EvalPtr = EvalPtr; |
| ph ->DupElemPtr = DupElemPtr; |
| ph ->FreePtr = FreePtr; |
| ph ->Data = Data; |
| |
| return ph; |
| } |
| |
| |
| static |
| void EvaluateIdentity(const cmsFloat32Number In[], |
| cmsFloat32Number Out[], |
| const cmsStage *mpe) |
| { |
| memmove(Out, In, mpe ->InputChannels * sizeof(cmsFloat32Number)); |
| } |
| |
| |
| cmsStage* CMSEXPORT cmsStageAllocIdentity(cmsContext ContextID, cmsUInt32Number nChannels) |
| { |
| return _cmsStageAllocPlaceholder(ContextID, |
| cmsSigIdentityElemType, |
| nChannels, nChannels, |
| EvaluateIdentity, |
| NULL, |
| NULL, |
| NULL); |
| } |
| |
| // Conversion functions. From floating point to 16 bits |
| static |
| void FromFloatTo16(const cmsFloat32Number In[], cmsUInt16Number Out[], cmsUInt32Number n) |
| { |
| cmsUInt32Number i; |
| |
| for (i=0; i < n; i++) { |
| Out[i] = _cmsQuickSaturateWord(In[i] * 65535.0); |
| } |
| } |
| |
| // From 16 bits to floating point |
| static |
| void From16ToFloat(const cmsUInt16Number In[], cmsFloat32Number Out[], cmsUInt32Number n) |
| { |
| cmsUInt32Number i; |
| |
| for (i=0; i < n; i++) { |
| Out[i] = (cmsFloat32Number) In[i] / 65535.0F; |
| } |
| } |
| |
| |
| // This function is quite useful to analyze the structure of a LUT and retrieve the MPE elements |
| // that conform the LUT. It should be called with the LUT, the number of expected elements and |
| // then a list of expected types followed with a list of cmsFloat64Number pointers to MPE elements. If |
| // the function founds a match with current pipeline, it fills the pointers and returns TRUE |
| // if not, returns FALSE without touching anything. Setting pointers to NULL does bypass |
| // the storage process. |
| cmsBool CMSEXPORT cmsPipelineCheckAndRetreiveStages(const cmsPipeline* Lut, cmsUInt32Number n, ...) |
| { |
| va_list args; |
| cmsUInt32Number i; |
| cmsStage* mpe; |
| cmsStageSignature Type; |
| void** ElemPtr; |
| |
| // Make sure same number of elements |
| if (cmsPipelineStageCount(Lut) != n) return FALSE; |
| |
| va_start(args, n); |
| |
| // Iterate across asked types |
| mpe = Lut ->Elements; |
| for (i=0; i < n; i++) { |
| |
| // Get asked type. cmsStageSignature is promoted to int by compiler |
| Type = (cmsStageSignature)va_arg(args, int); |
| if (mpe ->Type != Type) { |
| |
| va_end(args); // Mismatch. We are done. |
| return FALSE; |
| } |
| mpe = mpe ->Next; |
| } |
| |
| // Found a combination, fill pointers if not NULL |
| mpe = Lut ->Elements; |
| for (i=0; i < n; i++) { |
| |
| ElemPtr = va_arg(args, void**); |
| if (ElemPtr != NULL) |
| *ElemPtr = mpe; |
| |
| mpe = mpe ->Next; |
| } |
| |
| va_end(args); |
| return TRUE; |
| } |
| |
| // Below there are implementations for several types of elements. Each type may be implemented by a |
| // evaluation function, a duplication function, a function to free resources and a constructor. |
| |
| // ************************************************************************************************* |
| // Type cmsSigCurveSetElemType (curves) |
| // ************************************************************************************************* |
| |
| cmsToneCurve** _cmsStageGetPtrToCurveSet(const cmsStage* mpe) |
| { |
| _cmsStageToneCurvesData* Data = (_cmsStageToneCurvesData*) mpe ->Data; |
| |
| return Data ->TheCurves; |
| } |
| |
| static |
| void EvaluateCurves(const cmsFloat32Number In[], |
| cmsFloat32Number Out[], |
| const cmsStage *mpe) |
| { |
| _cmsStageToneCurvesData* Data; |
| cmsUInt32Number i; |
| |
| _cmsAssert(mpe != NULL); |
| |
| Data = (_cmsStageToneCurvesData*) mpe ->Data; |
| if (Data == NULL) return; |
| |
| if (Data ->TheCurves == NULL) return; |
| |
| for (i=0; i < Data ->nCurves; i++) { |
| Out[i] = cmsEvalToneCurveFloat(Data ->TheCurves[i], In[i]); |
| } |
| } |
| |
| static |
| void CurveSetElemTypeFree(cmsStage* mpe) |
| { |
| _cmsStageToneCurvesData* Data; |
| cmsUInt32Number i; |
| |
| _cmsAssert(mpe != NULL); |
| |
| Data = (_cmsStageToneCurvesData*) mpe ->Data; |
| if (Data == NULL) return; |
| |
| if (Data ->TheCurves != NULL) { |
| for (i=0; i < Data ->nCurves; i++) { |
| if (Data ->TheCurves[i] != NULL) |
| cmsFreeToneCurve(Data ->TheCurves[i]); |
| } |
| } |
| _cmsFree(mpe ->ContextID, Data ->TheCurves); |
| _cmsFree(mpe ->ContextID, Data); |
| } |
| |
| |
| static |
| void* CurveSetDup(cmsStage* mpe) |
| { |
| _cmsStageToneCurvesData* Data = (_cmsStageToneCurvesData*) mpe ->Data; |
| _cmsStageToneCurvesData* NewElem; |
| cmsUInt32Number i; |
| |
| NewElem = (_cmsStageToneCurvesData*) _cmsMallocZero(mpe ->ContextID, sizeof(_cmsStageToneCurvesData)); |
| if (NewElem == NULL) return NULL; |
| |
| NewElem ->nCurves = Data ->nCurves; |
| NewElem ->TheCurves = (cmsToneCurve**) _cmsCalloc(mpe ->ContextID, NewElem ->nCurves, sizeof(cmsToneCurve*)); |
| |
| if (NewElem ->TheCurves == NULL) goto Error; |
| |
| for (i=0; i < NewElem ->nCurves; i++) { |
| |
| // Duplicate each curve. It may fail. |
| NewElem ->TheCurves[i] = cmsDupToneCurve(Data ->TheCurves[i]); |
| if (NewElem ->TheCurves[i] == NULL) goto Error; |
| |
| |
| } |
| return (void*) NewElem; |
| |
| Error: |
| |
| if (NewElem ->TheCurves != NULL) { |
| for (i=0; i < NewElem ->nCurves; i++) { |
| if (NewElem ->TheCurves[i]) |
| cmsFreeToneCurve(NewElem ->TheCurves[i]); |
| } |
| } |
| _cmsFree(mpe ->ContextID, NewElem ->TheCurves); |
| _cmsFree(mpe ->ContextID, NewElem); |
| return NULL; |
| } |
| |
| |
| // Curves == NULL forces identity curves |
| cmsStage* CMSEXPORT cmsStageAllocToneCurves(cmsContext ContextID, cmsUInt32Number nChannels, cmsToneCurve* const Curves[]) |
| { |
| cmsUInt32Number i; |
| _cmsStageToneCurvesData* NewElem; |
| cmsStage* NewMPE; |
| |
| |
| NewMPE = _cmsStageAllocPlaceholder(ContextID, cmsSigCurveSetElemType, nChannels, nChannels, |
| EvaluateCurves, CurveSetDup, CurveSetElemTypeFree, NULL ); |
| if (NewMPE == NULL) return NULL; |
| |
| NewElem = (_cmsStageToneCurvesData*) _cmsMallocZero(ContextID, sizeof(_cmsStageToneCurvesData)); |
| if (NewElem == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| NewMPE ->Data = (void*) NewElem; |
| |
| NewElem ->nCurves = nChannels; |
| NewElem ->TheCurves = (cmsToneCurve**) _cmsCalloc(ContextID, nChannels, sizeof(cmsToneCurve*)); |
| if (NewElem ->TheCurves == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| for (i=0; i < nChannels; i++) { |
| |
| if (Curves == NULL) { |
| NewElem ->TheCurves[i] = cmsBuildGamma(ContextID, 1.0); |
| } |
| else { |
| NewElem ->TheCurves[i] = cmsDupToneCurve(Curves[i]); |
| } |
| |
| if (NewElem ->TheCurves[i] == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| } |
| |
| return NewMPE; |
| } |
| |
| |
| // Create a bunch of identity curves |
| cmsStage* CMSEXPORT _cmsStageAllocIdentityCurves(cmsContext ContextID, cmsUInt32Number nChannels) |
| { |
| cmsStage* mpe = cmsStageAllocToneCurves(ContextID, nChannels, NULL); |
| |
| if (mpe == NULL) return NULL; |
| mpe ->Implements = cmsSigIdentityElemType; |
| return mpe; |
| } |
| |
| |
| // ************************************************************************************************* |
| // Type cmsSigMatrixElemType (Matrices) |
| // ************************************************************************************************* |
| |
| |
| // Special care should be taken here because precision loss. A temporary cmsFloat64Number buffer is being used |
| static |
| void EvaluateMatrix(const cmsFloat32Number In[], |
| cmsFloat32Number Out[], |
| const cmsStage *mpe) |
| { |
| cmsUInt32Number i, j; |
| _cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data; |
| cmsFloat64Number Tmp; |
| |
| // Input is already in 0..1.0 notation |
| for (i=0; i < mpe ->OutputChannels; i++) { |
| |
| Tmp = 0; |
| for (j=0; j < mpe->InputChannels; j++) { |
| Tmp += In[j] * Data->Double[i*mpe->InputChannels + j]; |
| } |
| |
| if (Data ->Offset != NULL) |
| Tmp += Data->Offset[i]; |
| |
| Out[i] = (cmsFloat32Number) Tmp; |
| } |
| |
| |
| // Output in 0..1.0 domain |
| } |
| |
| |
| // Duplicate a yet-existing matrix element |
| static |
| void* MatrixElemDup(cmsStage* mpe) |
| { |
| _cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data; |
| _cmsStageMatrixData* NewElem; |
| cmsUInt32Number sz; |
| |
| NewElem = (_cmsStageMatrixData*) _cmsMallocZero(mpe ->ContextID, sizeof(_cmsStageMatrixData)); |
| if (NewElem == NULL) return NULL; |
| |
| sz = mpe ->InputChannels * mpe ->OutputChannels; |
| |
| NewElem ->Double = (cmsFloat64Number*) _cmsDupMem(mpe ->ContextID, Data ->Double, sz * sizeof(cmsFloat64Number)) ; |
| |
| if (Data ->Offset) |
| NewElem ->Offset = (cmsFloat64Number*) _cmsDupMem(mpe ->ContextID, |
| Data ->Offset, mpe -> OutputChannels * sizeof(cmsFloat64Number)) ; |
| |
| return (void*) NewElem; |
| } |
| |
| |
| static |
| void MatrixElemTypeFree(cmsStage* mpe) |
| { |
| _cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data; |
| if (Data == NULL) |
| return; |
| if (Data ->Double) |
| _cmsFree(mpe ->ContextID, Data ->Double); |
| |
| if (Data ->Offset) |
| _cmsFree(mpe ->ContextID, Data ->Offset); |
| |
| _cmsFree(mpe ->ContextID, mpe ->Data); |
| } |
| |
| |
| |
| cmsStage* CMSEXPORT cmsStageAllocMatrix(cmsContext ContextID, cmsUInt32Number Rows, cmsUInt32Number Cols, |
| const cmsFloat64Number* Matrix, const cmsFloat64Number* Offset) |
| { |
| cmsUInt32Number i, n; |
| _cmsStageMatrixData* NewElem; |
| cmsStage* NewMPE; |
| |
| n = Rows * Cols; |
| |
| // Check for overflow |
| if (n == 0) return NULL; |
| if (n >= UINT_MAX / Cols) return NULL; |
| if (n >= UINT_MAX / Rows) return NULL; |
| if (n < Rows || n < Cols) return NULL; |
| |
| NewMPE = _cmsStageAllocPlaceholder(ContextID, cmsSigMatrixElemType, Cols, Rows, |
| EvaluateMatrix, MatrixElemDup, MatrixElemTypeFree, NULL ); |
| if (NewMPE == NULL) return NULL; |
| |
| |
| NewElem = (_cmsStageMatrixData*) _cmsMallocZero(ContextID, sizeof(_cmsStageMatrixData)); |
| if (NewElem == NULL) goto Error; |
| NewMPE->Data = (void*)NewElem; |
| |
| NewElem ->Double = (cmsFloat64Number*) _cmsCalloc(ContextID, n, sizeof(cmsFloat64Number)); |
| if (NewElem->Double == NULL) goto Error; |
| |
| for (i=0; i < n; i++) { |
| NewElem ->Double[i] = Matrix[i]; |
| } |
| |
| if (Offset != NULL) { |
| |
| NewElem ->Offset = (cmsFloat64Number*) _cmsCalloc(ContextID, Rows, sizeof(cmsFloat64Number)); |
| if (NewElem->Offset == NULL) goto Error; |
| |
| for (i=0; i < Rows; i++) { |
| NewElem ->Offset[i] = Offset[i]; |
| } |
| } |
| |
| return NewMPE; |
| |
| Error: |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| |
| // ************************************************************************************************* |
| // Type cmsSigCLutElemType |
| // ************************************************************************************************* |
| |
| |
| // Evaluate in true floating point |
| static |
| void EvaluateCLUTfloat(const cmsFloat32Number In[], cmsFloat32Number Out[], const cmsStage *mpe) |
| { |
| _cmsStageCLutData* Data = (_cmsStageCLutData*) mpe ->Data; |
| |
| Data -> Params ->Interpolation.LerpFloat(In, Out, Data->Params); |
| } |
| |
| |
| // Convert to 16 bits, evaluate, and back to floating point |
| static |
| void EvaluateCLUTfloatIn16(const cmsFloat32Number In[], cmsFloat32Number Out[], const cmsStage *mpe) |
| { |
| _cmsStageCLutData* Data = (_cmsStageCLutData*) mpe ->Data; |
| cmsUInt16Number In16[MAX_STAGE_CHANNELS], Out16[MAX_STAGE_CHANNELS]; |
| |
| _cmsAssert(mpe ->InputChannels <= MAX_STAGE_CHANNELS); |
| _cmsAssert(mpe ->OutputChannels <= MAX_STAGE_CHANNELS); |
| |
| FromFloatTo16(In, In16, mpe ->InputChannels); |
| Data -> Params ->Interpolation.Lerp16(In16, Out16, Data->Params); |
| From16ToFloat(Out16, Out, mpe ->OutputChannels); |
| } |
| |
| |
| // Given an hypercube of b dimensions, with Dims[] number of nodes by dimension, calculate the total amount of nodes |
| static |
| cmsUInt32Number CubeSize(const cmsUInt32Number Dims[], cmsUInt32Number b) |
| { |
| cmsUInt32Number rv, dim; |
| |
| _cmsAssert(Dims != NULL); |
| |
| for (rv = 1; b > 0; b--) { |
| |
| dim = Dims[b-1]; |
| if (dim == 0) return 0; // Error |
| |
| rv *= dim; |
| |
| // Check for overflow |
| if (rv > UINT_MAX / dim) return 0; |
| } |
| |
| return rv; |
| } |
| |
| static |
| void* CLUTElemDup(cmsStage* mpe) |
| { |
| _cmsStageCLutData* Data = (_cmsStageCLutData*) mpe ->Data; |
| _cmsStageCLutData* NewElem; |
| |
| |
| NewElem = (_cmsStageCLutData*) _cmsMallocZero(mpe ->ContextID, sizeof(_cmsStageCLutData)); |
| if (NewElem == NULL) return NULL; |
| |
| NewElem ->nEntries = Data ->nEntries; |
| NewElem ->HasFloatValues = Data ->HasFloatValues; |
| |
| if (Data ->Tab.T) { |
| |
| if (Data ->HasFloatValues) { |
| NewElem ->Tab.TFloat = (cmsFloat32Number*) _cmsDupMem(mpe ->ContextID, Data ->Tab.TFloat, Data ->nEntries * sizeof (cmsFloat32Number)); |
| if (NewElem ->Tab.TFloat == NULL) |
| goto Error; |
| } else { |
| NewElem ->Tab.T = (cmsUInt16Number*) _cmsDupMem(mpe ->ContextID, Data ->Tab.T, Data ->nEntries * sizeof (cmsUInt16Number)); |
| if (NewElem ->Tab.T == NULL) |
| goto Error; |
| } |
| } |
| |
| NewElem ->Params = _cmsComputeInterpParamsEx(mpe ->ContextID, |
| Data ->Params ->nSamples, |
| Data ->Params ->nInputs, |
| Data ->Params ->nOutputs, |
| NewElem ->Tab.T, |
| Data ->Params ->dwFlags); |
| if (NewElem->Params != NULL) |
| return (void*) NewElem; |
| Error: |
| if (NewElem->Tab.T) |
| // This works for both types |
| _cmsFree(mpe ->ContextID, NewElem -> Tab.T); |
| _cmsFree(mpe ->ContextID, NewElem); |
| return NULL; |
| } |
| |
| |
| static |
| void CLutElemTypeFree(cmsStage* mpe) |
| { |
| |
| _cmsStageCLutData* Data = (_cmsStageCLutData*) mpe ->Data; |
| |
| // Already empty |
| if (Data == NULL) return; |
| |
| // This works for both types |
| if (Data -> Tab.T) |
| _cmsFree(mpe ->ContextID, Data -> Tab.T); |
| |
| _cmsFreeInterpParams(Data ->Params); |
| _cmsFree(mpe ->ContextID, mpe ->Data); |
| } |
| |
| |
| // Allocates a 16-bit multidimensional CLUT. This is evaluated at 16-bit precision. Table may have different |
| // granularity on each dimension. |
| cmsStage* CMSEXPORT cmsStageAllocCLut16bitGranular(cmsContext ContextID, |
| const cmsUInt32Number clutPoints[], |
| cmsUInt32Number inputChan, |
| cmsUInt32Number outputChan, |
| const cmsUInt16Number* Table) |
| { |
| cmsUInt32Number i, n; |
| _cmsStageCLutData* NewElem; |
| cmsStage* NewMPE; |
| |
| _cmsAssert(clutPoints != NULL); |
| |
| if (inputChan > MAX_INPUT_DIMENSIONS) { |
| cmsSignalError(ContextID, cmsERROR_RANGE, "Too many input channels (%d channels, max=%d)", inputChan, MAX_INPUT_DIMENSIONS); |
| return NULL; |
| } |
| |
| NewMPE = _cmsStageAllocPlaceholder(ContextID, cmsSigCLutElemType, inputChan, outputChan, |
| EvaluateCLUTfloatIn16, CLUTElemDup, CLutElemTypeFree, NULL ); |
| |
| if (NewMPE == NULL) return NULL; |
| |
| NewElem = (_cmsStageCLutData*) _cmsMallocZero(ContextID, sizeof(_cmsStageCLutData)); |
| if (NewElem == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| NewMPE ->Data = (void*) NewElem; |
| |
| NewElem -> nEntries = n = outputChan * CubeSize(clutPoints, inputChan); |
| NewElem -> HasFloatValues = FALSE; |
| |
| if (n == 0) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| |
| NewElem ->Tab.T = (cmsUInt16Number*) _cmsCalloc(ContextID, n, sizeof(cmsUInt16Number)); |
| if (NewElem ->Tab.T == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| if (Table != NULL) { |
| for (i=0; i < n; i++) { |
| NewElem ->Tab.T[i] = Table[i]; |
| } |
| } |
| |
| NewElem ->Params = _cmsComputeInterpParamsEx(ContextID, clutPoints, inputChan, outputChan, NewElem ->Tab.T, CMS_LERP_FLAGS_16BITS); |
| if (NewElem ->Params == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| return NewMPE; |
| } |
| |
| cmsStage* CMSEXPORT cmsStageAllocCLut16bit(cmsContext ContextID, |
| cmsUInt32Number nGridPoints, |
| cmsUInt32Number inputChan, |
| cmsUInt32Number outputChan, |
| const cmsUInt16Number* Table) |
| { |
| cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS]; |
| int i; |
| |
| // Our resulting LUT would be same gridpoints on all dimensions |
| for (i=0; i < MAX_INPUT_DIMENSIONS; i++) |
| Dimensions[i] = nGridPoints; |
| |
| return cmsStageAllocCLut16bitGranular(ContextID, Dimensions, inputChan, outputChan, Table); |
| } |
| |
| |
| cmsStage* CMSEXPORT cmsStageAllocCLutFloat(cmsContext ContextID, |
| cmsUInt32Number nGridPoints, |
| cmsUInt32Number inputChan, |
| cmsUInt32Number outputChan, |
| const cmsFloat32Number* Table) |
| { |
| cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS]; |
| int i; |
| |
| // Our resulting LUT would be same gridpoints on all dimensions |
| for (i=0; i < MAX_INPUT_DIMENSIONS; i++) |
| Dimensions[i] = nGridPoints; |
| |
| return cmsStageAllocCLutFloatGranular(ContextID, Dimensions, inputChan, outputChan, Table); |
| } |
| |
| |
| |
| cmsStage* CMSEXPORT cmsStageAllocCLutFloatGranular(cmsContext ContextID, const cmsUInt32Number clutPoints[], cmsUInt32Number inputChan, cmsUInt32Number outputChan, const cmsFloat32Number* Table) |
| { |
| cmsUInt32Number i, n; |
| _cmsStageCLutData* NewElem; |
| cmsStage* NewMPE; |
| |
| _cmsAssert(clutPoints != NULL); |
| |
| if (inputChan > MAX_INPUT_DIMENSIONS) { |
| cmsSignalError(ContextID, cmsERROR_RANGE, "Too many input channels (%d channels, max=%d)", inputChan, MAX_INPUT_DIMENSIONS); |
| return NULL; |
| } |
| |
| NewMPE = _cmsStageAllocPlaceholder(ContextID, cmsSigCLutElemType, inputChan, outputChan, |
| EvaluateCLUTfloat, CLUTElemDup, CLutElemTypeFree, NULL); |
| if (NewMPE == NULL) return NULL; |
| |
| |
| NewElem = (_cmsStageCLutData*) _cmsMallocZero(ContextID, sizeof(_cmsStageCLutData)); |
| if (NewElem == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| NewMPE ->Data = (void*) NewElem; |
| |
| // There is a potential integer overflow on conputing n and nEntries. |
| NewElem -> nEntries = n = outputChan * CubeSize(clutPoints, inputChan); |
| NewElem -> HasFloatValues = TRUE; |
| |
| if (n == 0) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| NewElem ->Tab.TFloat = (cmsFloat32Number*) _cmsCalloc(ContextID, n, sizeof(cmsFloat32Number)); |
| if (NewElem ->Tab.TFloat == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| if (Table != NULL) { |
| for (i=0; i < n; i++) { |
| NewElem ->Tab.TFloat[i] = Table[i]; |
| } |
| } |
| |
| NewElem ->Params = _cmsComputeInterpParamsEx(ContextID, clutPoints, inputChan, outputChan, NewElem ->Tab.TFloat, CMS_LERP_FLAGS_FLOAT); |
| if (NewElem ->Params == NULL) { |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| return NewMPE; |
| } |
| |
| |
| static |
| int IdentitySampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void * Cargo) |
| { |
| int nChan = *(int*) Cargo; |
| int i; |
| |
| for (i=0; i < nChan; i++) |
| Out[i] = In[i]; |
| |
| return 1; |
| } |
| |
| // Creates an MPE that just copies input to output |
| cmsStage* CMSEXPORT _cmsStageAllocIdentityCLut(cmsContext ContextID, cmsUInt32Number nChan) |
| { |
| cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS]; |
| cmsStage* mpe ; |
| int i; |
| |
| for (i=0; i < MAX_INPUT_DIMENSIONS; i++) |
| Dimensions[i] = 2; |
| |
| mpe = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, nChan, nChan, NULL); |
| if (mpe == NULL) return NULL; |
| |
| if (!cmsStageSampleCLut16bit(mpe, IdentitySampler, &nChan, 0)) { |
| cmsStageFree(mpe); |
| return NULL; |
| } |
| |
| mpe ->Implements = cmsSigIdentityElemType; |
| return mpe; |
| } |
| |
| |
| |
| // Quantize a value 0 <= i < MaxSamples to 0..0xffff |
| cmsUInt16Number CMSEXPORT _cmsQuantizeVal(cmsFloat64Number i, cmsUInt32Number MaxSamples) |
| { |
| cmsFloat64Number x; |
| |
| x = ((cmsFloat64Number) i * 65535.) / (cmsFloat64Number) (MaxSamples - 1); |
| return _cmsQuickSaturateWord(x); |
| } |
| |
| |
| // This routine does a sweep on whole input space, and calls its callback |
| // function on knots. returns TRUE if all ok, FALSE otherwise. |
| cmsBool CMSEXPORT cmsStageSampleCLut16bit(cmsStage* mpe, cmsSAMPLER16 Sampler, void * Cargo, cmsUInt32Number dwFlags) |
| { |
| int i, t, index, rest; |
| cmsUInt32Number nTotalPoints; |
| cmsUInt32Number nInputs, nOutputs; |
| cmsUInt32Number* nSamples; |
| cmsUInt16Number In[MAX_INPUT_DIMENSIONS+1], Out[MAX_STAGE_CHANNELS]; |
| _cmsStageCLutData* clut; |
| |
| if (mpe == NULL) return FALSE; |
| |
| clut = (_cmsStageCLutData*) mpe->Data; |
| |
| if (clut == NULL) return FALSE; |
| |
| nSamples = clut->Params ->nSamples; |
| nInputs = clut->Params ->nInputs; |
| nOutputs = clut->Params ->nOutputs; |
| |
| if (nInputs <= 0) return FALSE; |
| if (nOutputs <= 0) return FALSE; |
| if (nInputs > MAX_INPUT_DIMENSIONS) return FALSE; |
| if (nOutputs >= MAX_STAGE_CHANNELS) return FALSE; |
| |
| memset(In, 0, sizeof(In)); |
| memset(Out, 0, sizeof(Out)); |
| |
| nTotalPoints = CubeSize(nSamples, nInputs); |
| if (nTotalPoints == 0) return FALSE; |
| |
| index = 0; |
| for (i = 0; i < (int) nTotalPoints; i++) { |
| |
| rest = i; |
| for (t = (int)nInputs - 1; t >= 0; --t) { |
| |
| cmsUInt32Number Colorant = rest % nSamples[t]; |
| |
| rest /= nSamples[t]; |
| |
| In[t] = _cmsQuantizeVal(Colorant, nSamples[t]); |
| } |
| |
| if (clut ->Tab.T != NULL) { |
| for (t = 0; t < (int)nOutputs; t++) |
| Out[t] = clut->Tab.T[index + t]; |
| } |
| |
| if (!Sampler(In, Out, Cargo)) |
| return FALSE; |
| |
| if (!(dwFlags & SAMPLER_INSPECT)) { |
| |
| if (clut ->Tab.T != NULL) { |
| for (t=0; t < (int) nOutputs; t++) |
| clut->Tab.T[index + t] = Out[t]; |
| } |
| } |
| |
| index += nOutputs; |
| } |
| |
| return TRUE; |
| } |
| |
| // Same as anterior, but for floating point |
| cmsBool CMSEXPORT cmsStageSampleCLutFloat(cmsStage* mpe, cmsSAMPLERFLOAT Sampler, void * Cargo, cmsUInt32Number dwFlags) |
| { |
| int i, t, index, rest; |
| cmsUInt32Number nTotalPoints; |
| cmsUInt32Number nInputs, nOutputs; |
| cmsUInt32Number* nSamples; |
| cmsFloat32Number In[MAX_INPUT_DIMENSIONS+1], Out[MAX_STAGE_CHANNELS]; |
| _cmsStageCLutData* clut = (_cmsStageCLutData*) mpe->Data; |
| |
| nSamples = clut->Params ->nSamples; |
| nInputs = clut->Params ->nInputs; |
| nOutputs = clut->Params ->nOutputs; |
| |
| if (nInputs <= 0) return FALSE; |
| if (nOutputs <= 0) return FALSE; |
| if (nInputs > MAX_INPUT_DIMENSIONS) return FALSE; |
| if (nOutputs >= MAX_STAGE_CHANNELS) return FALSE; |
| |
| nTotalPoints = CubeSize(nSamples, nInputs); |
| if (nTotalPoints == 0) return FALSE; |
| |
| index = 0; |
| for (i = 0; i < (int)nTotalPoints; i++) { |
| |
| rest = i; |
| for (t = (int) nInputs-1; t >=0; --t) { |
| |
| cmsUInt32Number Colorant = rest % nSamples[t]; |
| |
| rest /= nSamples[t]; |
| |
| In[t] = (cmsFloat32Number) (_cmsQuantizeVal(Colorant, nSamples[t]) / 65535.0); |
| } |
| |
| if (clut ->Tab.TFloat != NULL) { |
| for (t=0; t < (int) nOutputs; t++) |
| Out[t] = clut->Tab.TFloat[index + t]; |
| } |
| |
| if (!Sampler(In, Out, Cargo)) |
| return FALSE; |
| |
| if (!(dwFlags & SAMPLER_INSPECT)) { |
| |
| if (clut ->Tab.TFloat != NULL) { |
| for (t=0; t < (int) nOutputs; t++) |
| clut->Tab.TFloat[index + t] = Out[t]; |
| } |
| } |
| |
| index += nOutputs; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| |
| // This routine does a sweep on whole input space, and calls its callback |
| // function on knots. returns TRUE if all ok, FALSE otherwise. |
| cmsBool CMSEXPORT cmsSliceSpace16(cmsUInt32Number nInputs, const cmsUInt32Number clutPoints[], |
| cmsSAMPLER16 Sampler, void * Cargo) |
| { |
| int i, t, rest; |
| cmsUInt32Number nTotalPoints; |
| cmsUInt16Number In[cmsMAXCHANNELS]; |
| |
| if (nInputs >= cmsMAXCHANNELS) return FALSE; |
| |
| nTotalPoints = CubeSize(clutPoints, nInputs); |
| if (nTotalPoints == 0) return FALSE; |
| |
| for (i = 0; i < (int) nTotalPoints; i++) { |
| |
| rest = i; |
| for (t = (int) nInputs-1; t >=0; --t) { |
| |
| cmsUInt32Number Colorant = rest % clutPoints[t]; |
| |
| rest /= clutPoints[t]; |
| In[t] = _cmsQuantizeVal(Colorant, clutPoints[t]); |
| |
| } |
| |
| if (!Sampler(In, NULL, Cargo)) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| cmsInt32Number CMSEXPORT cmsSliceSpaceFloat(cmsUInt32Number nInputs, const cmsUInt32Number clutPoints[], |
| cmsSAMPLERFLOAT Sampler, void * Cargo) |
| { |
| int i, t, rest; |
| cmsUInt32Number nTotalPoints; |
| cmsFloat32Number In[cmsMAXCHANNELS]; |
| |
| if (nInputs >= cmsMAXCHANNELS) return FALSE; |
| |
| nTotalPoints = CubeSize(clutPoints, nInputs); |
| if (nTotalPoints == 0) return FALSE; |
| |
| for (i = 0; i < (int) nTotalPoints; i++) { |
| |
| rest = i; |
| for (t = (int) nInputs-1; t >=0; --t) { |
| |
| cmsUInt32Number Colorant = rest % clutPoints[t]; |
| |
| rest /= clutPoints[t]; |
| In[t] = (cmsFloat32Number) (_cmsQuantizeVal(Colorant, clutPoints[t]) / 65535.0); |
| |
| } |
| |
| if (!Sampler(In, NULL, Cargo)) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| // ******************************************************************************** |
| // Type cmsSigLab2XYZElemType |
| // ******************************************************************************** |
| |
| |
| static |
| void EvaluateLab2XYZ(const cmsFloat32Number In[], |
| cmsFloat32Number Out[], |
| const cmsStage *mpe) |
| { |
| cmsCIELab Lab; |
| cmsCIEXYZ XYZ; |
| const cmsFloat64Number XYZadj = MAX_ENCODEABLE_XYZ; |
| |
| // V4 rules |
| Lab.L = In[0] * 100.0; |
| Lab.a = In[1] * 255.0 - 128.0; |
| Lab.b = In[2] * 255.0 - 128.0; |
| |
| cmsLab2XYZ(NULL, &XYZ, &Lab); |
| |
| // From XYZ, range 0..19997 to 0..1.0, note that 1.99997 comes from 0xffff |
| // encoded as 1.15 fixed point, so 1 + (32767.0 / 32768.0) |
| |
| Out[0] = (cmsFloat32Number) ((cmsFloat64Number) XYZ.X / XYZadj); |
| Out[1] = (cmsFloat32Number) ((cmsFloat64Number) XYZ.Y / XYZadj); |
| Out[2] = (cmsFloat32Number) ((cmsFloat64Number) XYZ.Z / XYZadj); |
| return; |
| |
| cmsUNUSED_PARAMETER(mpe); |
| } |
| |
| |
| // No dup or free routines needed, as the structure has no pointers in it. |
| cmsStage* CMSEXPORT _cmsStageAllocLab2XYZ(cmsContext ContextID) |
| { |
| return _cmsStageAllocPlaceholder(ContextID, cmsSigLab2XYZElemType, 3, 3, EvaluateLab2XYZ, NULL, NULL, NULL); |
| } |
| |
| // ******************************************************************************** |
| |
| // v2 L=100 is supposed to be placed on 0xFF00. There is no reasonable |
| // number of gridpoints that would make exact match. However, a prelinearization |
| // of 258 entries, would map 0xFF00 exactly on entry 257, and this is good to avoid scum dot. |
| // Almost all what we need but unfortunately, the rest of entries should be scaled by |
| // (255*257/256) and this is not exact. |
| |
| cmsStage* _cmsStageAllocLabV2ToV4curves(cmsContext ContextID) |
| { |
| cmsStage* mpe; |
| cmsToneCurve* LabTable[3]; |
| int i, j; |
| |
| LabTable[0] = cmsBuildTabulatedToneCurve16(ContextID, 258, NULL); |
| LabTable[1] = cmsBuildTabulatedToneCurve16(ContextID, 258, NULL); |
| LabTable[2] = cmsBuildTabulatedToneCurve16(ContextID, 258, NULL); |
| |
| for (j=0; j < 3; j++) { |
| |
| if (LabTable[j] == NULL) { |
| cmsFreeToneCurveTriple(LabTable); |
| return NULL; |
| } |
| |
| // We need to map * (0xffff / 0xff00), that's same as (257 / 256) |
| // So we can use 258-entry tables to do the trick (i / 257) * (255 * 257) * (257 / 256); |
| for (i=0; i < 257; i++) { |
| |
| LabTable[j]->Table16[i] = (cmsUInt16Number) ((i * 0xffff + 0x80) >> 8); |
| } |
| |
| LabTable[j] ->Table16[257] = 0xffff; |
| } |
| |
| mpe = cmsStageAllocToneCurves(ContextID, 3, LabTable); |
| cmsFreeToneCurveTriple(LabTable); |
| |
| if (mpe == NULL) return NULL; |
| mpe ->Implements = cmsSigLabV2toV4; |
| return mpe; |
| } |
| |
| // ******************************************************************************** |
| |
| // Matrix-based conversion, which is more accurate, but slower and cannot properly be saved in devicelink profiles |
| cmsStage* CMSEXPORT _cmsStageAllocLabV2ToV4(cmsContext ContextID) |
| { |
| static const cmsFloat64Number V2ToV4[] = { 65535.0/65280.0, 0, 0, |
| 0, 65535.0/65280.0, 0, |
| 0, 0, 65535.0/65280.0 |
| }; |
| |
| cmsStage *mpe = cmsStageAllocMatrix(ContextID, 3, 3, V2ToV4, NULL); |
| |
| if (mpe == NULL) return mpe; |
| mpe ->Implements = cmsSigLabV2toV4; |
| return mpe; |
| } |
| |
| |
| // Reverse direction |
| cmsStage* CMSEXPORT _cmsStageAllocLabV4ToV2(cmsContext ContextID) |
| { |
| static const cmsFloat64Number V4ToV2[] = { 65280.0/65535.0, 0, 0, |
| 0, 65280.0/65535.0, 0, |
| 0, 0, 65280.0/65535.0 |
| }; |
| |
| cmsStage *mpe = cmsStageAllocMatrix(ContextID, 3, 3, V4ToV2, NULL); |
| |
| if (mpe == NULL) return mpe; |
| mpe ->Implements = cmsSigLabV4toV2; |
| return mpe; |
| } |
| |
| |
| // To Lab to float. Note that the MPE gives numbers in normal Lab range |
| // and we need 0..1.0 range for the formatters |
| // L* : 0...100 => 0...1.0 (L* / 100) |
| // ab* : -128..+127 to 0..1 ((ab* + 128) / 255) |
| |
| cmsStage* _cmsStageNormalizeFromLabFloat(cmsContext ContextID) |
| { |
| static const cmsFloat64Number a1[] = { |
| 1.0/100.0, 0, 0, |
| 0, 1.0/255.0, 0, |
| 0, 0, 1.0/255.0 |
| }; |
| |
| static const cmsFloat64Number o1[] = { |
| 0, |
| 128.0/255.0, |
| 128.0/255.0 |
| }; |
| |
| cmsStage *mpe = cmsStageAllocMatrix(ContextID, 3, 3, a1, o1); |
| |
| if (mpe == NULL) return mpe; |
| mpe ->Implements = cmsSigLab2FloatPCS; |
| return mpe; |
| } |
| |
| // Fom XYZ to floating point PCS |
| cmsStage* _cmsStageNormalizeFromXyzFloat(cmsContext ContextID) |
| { |
| #define n (32768.0/65535.0) |
| static const cmsFloat64Number a1[] = { |
| n, 0, 0, |
| 0, n, 0, |
| 0, 0, n |
| }; |
| #undef n |
| |
| cmsStage *mpe = cmsStageAllocMatrix(ContextID, 3, 3, a1, NULL); |
| |
| if (mpe == NULL) return mpe; |
| mpe ->Implements = cmsSigXYZ2FloatPCS; |
| return mpe; |
| } |
| |
| cmsStage* _cmsStageNormalizeToLabFloat(cmsContext ContextID) |
| { |
| static const cmsFloat64Number a1[] = { |
| 100.0, 0, 0, |
| 0, 255.0, 0, |
| 0, 0, 255.0 |
| }; |
| |
| static const cmsFloat64Number o1[] = { |
| 0, |
| -128.0, |
| -128.0 |
| }; |
| |
| cmsStage *mpe = cmsStageAllocMatrix(ContextID, 3, 3, a1, o1); |
| if (mpe == NULL) return mpe; |
| mpe ->Implements = cmsSigFloatPCS2Lab; |
| return mpe; |
| } |
| |
| cmsStage* _cmsStageNormalizeToXyzFloat(cmsContext ContextID) |
| { |
| #define n (65535.0/32768.0) |
| |
| static const cmsFloat64Number a1[] = { |
| n, 0, 0, |
| 0, n, 0, |
| 0, 0, n |
| }; |
| #undef n |
| |
| cmsStage *mpe = cmsStageAllocMatrix(ContextID, 3, 3, a1, NULL); |
| if (mpe == NULL) return mpe; |
| mpe ->Implements = cmsSigFloatPCS2XYZ; |
| return mpe; |
| } |
| |
| // Clips values smaller than zero |
| static |
| void Clipper(const cmsFloat32Number In[], cmsFloat32Number Out[], const cmsStage *mpe) |
| { |
| cmsUInt32Number i; |
| for (i = 0; i < mpe->InputChannels; i++) { |
| |
| cmsFloat32Number n = In[i]; |
| Out[i] = n < 0 ? 0 : n; |
| } |
| } |
| |
| cmsStage* _cmsStageClipNegatives(cmsContext ContextID, cmsUInt32Number nChannels) |
| { |
| return _cmsStageAllocPlaceholder(ContextID, cmsSigClipNegativesElemType, |
| nChannels, nChannels, Clipper, NULL, NULL, NULL); |
| } |
| |
| // ******************************************************************************** |
| // Type cmsSigXYZ2LabElemType |
| // ******************************************************************************** |
| |
| static |
| void EvaluateXYZ2Lab(const cmsFloat32Number In[], cmsFloat32Number Out[], const cmsStage *mpe) |
| { |
| cmsCIELab Lab; |
| cmsCIEXYZ XYZ; |
| const cmsFloat64Number XYZadj = MAX_ENCODEABLE_XYZ; |
| |
| // From 0..1.0 to XYZ |
| |
| XYZ.X = In[0] * XYZadj; |
| XYZ.Y = In[1] * XYZadj; |
| XYZ.Z = In[2] * XYZadj; |
| |
| cmsXYZ2Lab(NULL, &Lab, &XYZ); |
| |
| // From V4 Lab to 0..1.0 |
| |
| Out[0] = (cmsFloat32Number) (Lab.L / 100.0); |
| Out[1] = (cmsFloat32Number) ((Lab.a + 128.0) / 255.0); |
| Out[2] = (cmsFloat32Number) ((Lab.b + 128.0) / 255.0); |
| return; |
| |
| cmsUNUSED_PARAMETER(mpe); |
| } |
| |
| cmsStage* CMSEXPORT _cmsStageAllocXYZ2Lab(cmsContext ContextID) |
| { |
| return _cmsStageAllocPlaceholder(ContextID, cmsSigXYZ2LabElemType, 3, 3, EvaluateXYZ2Lab, NULL, NULL, NULL); |
| |
| } |
| |
| // ******************************************************************************** |
| |
| // For v4, S-Shaped curves are placed in a/b axis to increase resolution near gray |
| |
| cmsStage* _cmsStageAllocLabPrelin(cmsContext ContextID) |
| { |
| cmsToneCurve* LabTable[3]; |
| cmsFloat64Number Params[1] = {2.4} ; |
| |
| LabTable[0] = cmsBuildGamma(ContextID, 1.0); |
| LabTable[1] = cmsBuildParametricToneCurve(ContextID, 108, Params); |
| LabTable[2] = cmsBuildParametricToneCurve(ContextID, 108, Params); |
| |
| return cmsStageAllocToneCurves(ContextID, 3, LabTable); |
| } |
| |
| |
| // Free a single MPE |
| void CMSEXPORT cmsStageFree(cmsStage* mpe) |
| { |
| if (mpe ->FreePtr) |
| mpe ->FreePtr(mpe); |
| |
| _cmsFree(mpe ->ContextID, mpe); |
| } |
| |
| |
| cmsUInt32Number CMSEXPORT cmsStageInputChannels(const cmsStage* mpe) |
| { |
| return mpe ->InputChannels; |
| } |
| |
| cmsUInt32Number CMSEXPORT cmsStageOutputChannels(const cmsStage* mpe) |
| { |
| return mpe ->OutputChannels; |
| } |
| |
| cmsStageSignature CMSEXPORT cmsStageType(const cmsStage* mpe) |
| { |
| return mpe -> Type; |
| } |
| |
| void* CMSEXPORT cmsStageData(const cmsStage* mpe) |
| { |
| return mpe -> Data; |
| } |
| |
| cmsStage* CMSEXPORT cmsStageNext(const cmsStage* mpe) |
| { |
| return mpe -> Next; |
| } |
| |
| |
| // Duplicates an MPE |
| cmsStage* CMSEXPORT cmsStageDup(cmsStage* mpe) |
| { |
| cmsStage* NewMPE; |
| |
| if (mpe == NULL) return NULL; |
| NewMPE = _cmsStageAllocPlaceholder(mpe ->ContextID, |
| mpe ->Type, |
| mpe ->InputChannels, |
| mpe ->OutputChannels, |
| mpe ->EvalPtr, |
| mpe ->DupElemPtr, |
| mpe ->FreePtr, |
| NULL); |
| if (NewMPE == NULL) return NULL; |
| |
| NewMPE ->Implements = mpe ->Implements; |
| |
| if (mpe ->DupElemPtr) { |
| |
| NewMPE ->Data = mpe ->DupElemPtr(mpe); |
| |
| if (NewMPE->Data == NULL) { |
| |
| cmsStageFree(NewMPE); |
| return NULL; |
| } |
| |
| } else { |
| |
| NewMPE ->Data = NULL; |
| } |
| |
| return NewMPE; |
| } |
| |
| |
| // *********************************************************************************************************** |
| |
| // This function sets up the channel count |
| static |
| cmsBool BlessLUT(cmsPipeline* lut) |
| { |
| // We can set the input/output channels only if we have elements. |
| if (lut ->Elements != NULL) { |
| |
| cmsStage* prev; |
| cmsStage* next; |
| cmsStage* First; |
| cmsStage* Last; |
| |
| First = cmsPipelineGetPtrToFirstStage(lut); |
| Last = cmsPipelineGetPtrToLastStage(lut); |
| |
| if (First == NULL || Last == NULL) return FALSE; |
| |
| lut->InputChannels = First->InputChannels; |
| lut->OutputChannels = Last->OutputChannels; |
| |
| // Check chain consistency |
| prev = First; |
| next = prev->Next; |
| |
| while (next != NULL) |
| { |
| if (next->InputChannels != prev->OutputChannels) |
| return FALSE; |
| |
| next = next->Next; |
| prev = prev->Next; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| |
| // Default to evaluate the LUT on 16 bit-basis. Precision is retained. |
| static |
| void _LUTeval16(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER const void* D) |
| { |
| cmsPipeline* lut = (cmsPipeline*) D; |
| cmsStage *mpe; |
| cmsFloat32Number Storage[2][MAX_STAGE_CHANNELS] = {0.0f}; |
| int Phase = 0, NextPhase; |
| |
| From16ToFloat(In, &Storage[Phase][0], lut ->InputChannels); |
| |
| for (mpe = lut ->Elements; |
| mpe != NULL; |
| mpe = mpe ->Next) { |
| |
| NextPhase = Phase ^ 1; |
| mpe ->EvalPtr(&Storage[Phase][0], &Storage[NextPhase][0], mpe); |
| Phase = NextPhase; |
| } |
| |
| |
| FromFloatTo16(&Storage[Phase][0], Out, lut ->OutputChannels); |
| } |
| |
| |
| |
| // Does evaluate the LUT on cmsFloat32Number-basis. |
| static |
| void _LUTevalFloat(CMSREGISTER const cmsFloat32Number In[], CMSREGISTER cmsFloat32Number Out[], const void* D) |
| { |
| cmsPipeline* lut = (cmsPipeline*) D; |
| cmsStage *mpe; |
| cmsFloat32Number Storage[2][MAX_STAGE_CHANNELS] = {0.0f}; |
| int Phase = 0, NextPhase; |
| |
| memmove(&Storage[Phase][0], In, lut ->InputChannels * sizeof(cmsFloat32Number)); |
| |
| for (mpe = lut ->Elements; |
| mpe != NULL; |
| mpe = mpe ->Next) { |
| |
| NextPhase = Phase ^ 1; |
| mpe ->EvalPtr(&Storage[Phase][0], &Storage[NextPhase][0], mpe); |
| Phase = NextPhase; |
| } |
| |
| memmove(Out, &Storage[Phase][0], lut ->OutputChannels * sizeof(cmsFloat32Number)); |
| } |
| |
| |
| // LUT Creation & Destruction |
| cmsPipeline* CMSEXPORT cmsPipelineAlloc(cmsContext ContextID, cmsUInt32Number InputChannels, cmsUInt32Number OutputChannels) |
| { |
| cmsPipeline* NewLUT; |
| |
| // A value of zero in channels is allowed as placeholder |
| if (InputChannels >= cmsMAXCHANNELS || |
| OutputChannels >= cmsMAXCHANNELS) return NULL; |
| |
| NewLUT = (cmsPipeline*) _cmsMallocZero(ContextID, sizeof(cmsPipeline)); |
| if (NewLUT == NULL) return NULL; |
| |
| NewLUT -> InputChannels = InputChannels; |
| NewLUT -> OutputChannels = OutputChannels; |
| |
| NewLUT ->Eval16Fn = _LUTeval16; |
| NewLUT ->EvalFloatFn = _LUTevalFloat; |
| NewLUT ->DupDataFn = NULL; |
| NewLUT ->FreeDataFn = NULL; |
| NewLUT ->Data = NewLUT; |
| NewLUT ->ContextID = ContextID; |
| |
| if (!BlessLUT(NewLUT)) |
| { |
| _cmsFree(ContextID, NewLUT); |
| return NULL; |
| } |
| |
| return NewLUT; |
| } |
| |
| cmsContext CMSEXPORT cmsGetPipelineContextID(const cmsPipeline* lut) |
| { |
| _cmsAssert(lut != NULL); |
| return lut ->ContextID; |
| } |
| |
| cmsUInt32Number CMSEXPORT cmsPipelineInputChannels(const cmsPipeline* lut) |
| { |
| _cmsAssert(lut != NULL); |
| return lut ->InputChannels; |
| } |
| |
| cmsUInt32Number CMSEXPORT cmsPipelineOutputChannels(const cmsPipeline* lut) |
| { |
| _cmsAssert(lut != NULL); |
| return lut ->OutputChannels; |
| } |
| |
| // Free a profile elements LUT |
| void CMSEXPORT cmsPipelineFree(cmsPipeline* lut) |
| { |
| cmsStage *mpe, *Next; |
| |
| if (lut == NULL) return; |
| |
| for (mpe = lut ->Elements; |
| mpe != NULL; |
| mpe = Next) { |
| |
| Next = mpe ->Next; |
| cmsStageFree(mpe); |
| } |
| |
| if (lut ->FreeDataFn) lut ->FreeDataFn(lut ->ContextID, lut ->Data); |
| |
| _cmsFree(lut ->ContextID, lut); |
| } |
| |
| |
| // Default to evaluate the LUT on 16 bit-basis. |
| void CMSEXPORT cmsPipelineEval16(const cmsUInt16Number In[], cmsUInt16Number Out[], const cmsPipeline* lut) |
| { |
| _cmsAssert(lut != NULL); |
| lut ->Eval16Fn(In, Out, lut->Data); |
| } |
| |
| |
| // Does evaluate the LUT on cmsFloat32Number-basis. |
| void CMSEXPORT cmsPipelineEvalFloat(const cmsFloat32Number In[], cmsFloat32Number Out[], const cmsPipeline* lut) |
| { |
| _cmsAssert(lut != NULL); |
| lut ->EvalFloatFn(In, Out, lut); |
| } |
| |
| |
| |
| // Duplicates a LUT |
| cmsPipeline* CMSEXPORT cmsPipelineDup(const cmsPipeline* lut) |
| { |
| cmsPipeline* NewLUT; |
| cmsStage *NewMPE, *Anterior = NULL, *mpe; |
| cmsBool First = TRUE; |
| |
| if (lut == NULL) return NULL; |
| |
| NewLUT = cmsPipelineAlloc(lut ->ContextID, lut ->InputChannels, lut ->OutputChannels); |
| if (NewLUT == NULL) return NULL; |
| |
| for (mpe = lut ->Elements; |
| mpe != NULL; |
| mpe = mpe ->Next) { |
| |
| NewMPE = cmsStageDup(mpe); |
| |
| if (NewMPE == NULL) { |
| cmsPipelineFree(NewLUT); |
| return NULL; |
| } |
| |
| if (First) { |
| NewLUT ->Elements = NewMPE; |
| First = FALSE; |
| } |
| else { |
| if (Anterior != NULL) |
| Anterior ->Next = NewMPE; |
| } |
| |
| Anterior = NewMPE; |
| } |
| |
| NewLUT ->Eval16Fn = lut ->Eval16Fn; |
| NewLUT ->EvalFloatFn = lut ->EvalFloatFn; |
| NewLUT ->DupDataFn = lut ->DupDataFn; |
| NewLUT ->FreeDataFn = lut ->FreeDataFn; |
| |
| if (NewLUT ->DupDataFn != NULL) |
| NewLUT ->Data = NewLUT ->DupDataFn(lut ->ContextID, lut->Data); |
| |
| |
| NewLUT ->SaveAs8Bits = lut ->SaveAs8Bits; |
| |
| if (!BlessLUT(NewLUT)) |
| { |
| _cmsFree(lut->ContextID, NewLUT); |
| return NULL; |
| } |
| |
| return NewLUT; |
| } |
| |
| |
| int CMSEXPORT cmsPipelineInsertStage(cmsPipeline* lut, cmsStageLoc loc, cmsStage* mpe) |
| { |
| cmsStage* Anterior = NULL, *pt; |
| |
| if (lut == NULL || mpe == NULL) |
| return FALSE; |
| |
| switch (loc) { |
| |
| case cmsAT_BEGIN: |
| mpe ->Next = lut ->Elements; |
| lut ->Elements = mpe; |
| break; |
| |
| case cmsAT_END: |
| |
| if (lut ->Elements == NULL) |
| lut ->Elements = mpe; |
| else { |
| |
| for (pt = lut ->Elements; |
| pt != NULL; |
| pt = pt -> Next) Anterior = pt; |
| |
| Anterior ->Next = mpe; |
| mpe ->Next = NULL; |
| } |
| break; |
| default:; |
| return FALSE; |
| } |
| |
| return BlessLUT(lut); |
| } |
| |
| // Unlink an element and return the pointer to it |
| void CMSEXPORT cmsPipelineUnlinkStage(cmsPipeline* lut, cmsStageLoc loc, cmsStage** mpe) |
| { |
| cmsStage *Anterior, *pt, *Last; |
| cmsStage *Unlinked = NULL; |
| |
| |
| // If empty LUT, there is nothing to remove |
| if (lut ->Elements == NULL) { |
| if (mpe) *mpe = NULL; |
| return; |
| } |
| |
| // On depending on the strategy... |
| switch (loc) { |
| |
| case cmsAT_BEGIN: |
| { |
| cmsStage* elem = lut ->Elements; |
| |
| lut ->Elements = elem -> Next; |
| elem ->Next = NULL; |
| Unlinked = elem; |
| |
| } |
| break; |
| |
| case cmsAT_END: |
| Anterior = Last = NULL; |
| for (pt = lut ->Elements; |
| pt != NULL; |
| pt = pt -> Next) { |
| Anterior = Last; |
| Last = pt; |
| } |
| |
| Unlinked = Last; // Next already points to NULL |
| |
| // Truncate the chain |
| if (Anterior) |
| Anterior ->Next = NULL; |
| else |
| lut ->Elements = NULL; |
| break; |
| default:; |
| } |
| |
| if (mpe) |
| *mpe = Unlinked; |
| else |
| cmsStageFree(Unlinked); |
| |
| // May fail, but we ignore it |
| BlessLUT(lut); |
| } |
| |
| |
| // Concatenate two LUT into a new single one |
| cmsBool CMSEXPORT cmsPipelineCat(cmsPipeline* l1, const cmsPipeline* l2) |
| { |
| cmsStage* mpe; |
| |
| // If both LUTS does not have elements, we need to inherit |
| // the number of channels |
| if (l1 ->Elements == NULL && l2 ->Elements == NULL) { |
| l1 ->InputChannels = l2 ->InputChannels; |
| l1 ->OutputChannels = l2 ->OutputChannels; |
| } |
| |
| // Cat second |
| for (mpe = l2 ->Elements; |
| mpe != NULL; |
| mpe = mpe ->Next) { |
| |
| // We have to dup each element |
| if (!cmsPipelineInsertStage(l1, cmsAT_END, cmsStageDup(mpe))) |
| return FALSE; |
| } |
| |
| return BlessLUT(l1); |
| } |
| |
| |
| cmsBool CMSEXPORT cmsPipelineSetSaveAs8bitsFlag(cmsPipeline* lut, cmsBool On) |
| { |
| cmsBool Anterior = lut ->SaveAs8Bits; |
| |
| lut ->SaveAs8Bits = On; |
| return Anterior; |
| } |
| |
| |
| cmsStage* CMSEXPORT cmsPipelineGetPtrToFirstStage(const cmsPipeline* lut) |
| { |
| return lut ->Elements; |
| } |
| |
| cmsStage* CMSEXPORT cmsPipelineGetPtrToLastStage(const cmsPipeline* lut) |
| { |
| cmsStage *mpe, *Anterior = NULL; |
| |
| for (mpe = lut ->Elements; mpe != NULL; mpe = mpe ->Next) |
| Anterior = mpe; |
| |
| return Anterior; |
| } |
| |
| cmsUInt32Number CMSEXPORT cmsPipelineStageCount(const cmsPipeline* lut) |
| { |
| cmsStage *mpe; |
| cmsUInt32Number n; |
| |
| for (n=0, mpe = lut ->Elements; mpe != NULL; mpe = mpe ->Next) |
| n++; |
| |
| return n; |
| } |
| |
| // This function may be used to set the optional evaluator and a block of private data. If private data is being used, an optional |
| // duplicator and free functions should also be specified in order to duplicate the LUT construct. Use NULL to inhibit such functionality. |
| void CMSEXPORT _cmsPipelineSetOptimizationParameters(cmsPipeline* Lut, |
| _cmsOPTeval16Fn Eval16, |
| void* PrivateData, |
| _cmsFreeUserDataFn FreePrivateDataFn, |
| _cmsDupUserDataFn DupPrivateDataFn) |
| { |
| |
| Lut ->Eval16Fn = Eval16; |
| Lut ->DupDataFn = DupPrivateDataFn; |
| Lut ->FreeDataFn = FreePrivateDataFn; |
| Lut ->Data = PrivateData; |
| } |
| |
| |
| // ----------------------------------------------------------- Reverse interpolation |
| // Here's how it goes. The derivative Df(x) of the function f is the linear |
| // transformation that best approximates f near the point x. It can be represented |
| // by a matrix A whose entries are the partial derivatives of the components of f |
| // with respect to all the coordinates. This is know as the Jacobian |
| // |
| // The best linear approximation to f is given by the matrix equation: |
| // |
| // y-y0 = A (x-x0) |
| // |
| // So, if x0 is a good "guess" for the zero of f, then solving for the zero of this |
| // linear approximation will give a "better guess" for the zero of f. Thus let y=0, |
| // and since y0=f(x0) one can solve the above equation for x. This leads to the |
| // Newton's method formula: |
| // |
| // xn+1 = xn - A-1 f(xn) |
| // |
| // where xn+1 denotes the (n+1)-st guess, obtained from the n-th guess xn in the |
| // fashion described above. Iterating this will give better and better approximations |
| // if you have a "good enough" initial guess. |
| |
| |
| #define JACOBIAN_EPSILON 0.001f |
| #define INVERSION_MAX_ITERATIONS 30 |
| |
| // Increment with reflexion on boundary |
| static |
| void IncDelta(cmsFloat32Number *Val) |
| { |
| if (*Val < (1.0 - JACOBIAN_EPSILON)) |
| |
| *Val += JACOBIAN_EPSILON; |
| |
| else |
| *Val -= JACOBIAN_EPSILON; |
| |
| } |
| |
| |
| |
| // Euclidean distance between two vectors of n elements each one |
| static |
| cmsFloat32Number EuclideanDistance(cmsFloat32Number a[], cmsFloat32Number b[], int n) |
| { |
| cmsFloat32Number sum = 0; |
| int i; |
| |
| for (i=0; i < n; i++) { |
| cmsFloat32Number dif = b[i] - a[i]; |
| sum += dif * dif; |
| } |
| |
| return sqrtf(sum); |
| } |
| |
| |
| // Evaluate a LUT in reverse direction. It only searches on 3->3 LUT. Uses Newton method |
| // |
| // x1 <- x - [J(x)]^-1 * f(x) |
| // |
| // lut: The LUT on where to do the search |
| // Target: LabK, 3 values of Lab plus destination K which is fixed |
| // Result: The obtained CMYK |
| // Hint: Location where begin the search |
| |
| cmsBool CMSEXPORT cmsPipelineEvalReverseFloat(cmsFloat32Number Target[], |
| cmsFloat32Number Result[], |
| cmsFloat32Number Hint[], |
| const cmsPipeline* lut) |
| { |
| cmsUInt32Number i, j; |
| cmsFloat64Number error, LastError = 1E20; |
| cmsFloat32Number fx[4], x[4], xd[4], fxd[4]; |
| cmsVEC3 tmp, tmp2; |
| cmsMAT3 Jacobian; |
| |
| // Only 3->3 and 4->3 are supported |
| if (lut ->InputChannels != 3 && lut ->InputChannels != 4) return FALSE; |
| if (lut ->OutputChannels != 3) return FALSE; |
| |
| // Take the hint as starting point if specified |
| if (Hint == NULL) { |
| |
| // Begin at any point, we choose 1/3 of CMY axis |
| x[0] = x[1] = x[2] = 0.3f; |
| } |
| else { |
| |
| // Only copy 3 channels from hint... |
| for (j=0; j < 3; j++) |
| x[j] = Hint[j]; |
| } |
| |
| // If Lut is 4-dimensions, then grab target[3], which is fixed |
| if (lut ->InputChannels == 4) { |
| x[3] = Target[3]; |
| } |
| else x[3] = 0; // To keep lint happy |
| |
| |
| // Iterate |
| for (i = 0; i < INVERSION_MAX_ITERATIONS; i++) { |
| |
| // Get beginning fx |
| cmsPipelineEvalFloat(x, fx, lut); |
| |
| // Compute error |
| error = EuclideanDistance(fx, Target, 3); |
| |
| // If not convergent, return last safe value |
| if (error >= LastError) |
| break; |
| |
| // Keep latest values |
| LastError = error; |
| for (j=0; j < lut ->InputChannels; j++) |
| Result[j] = x[j]; |
| |
| // Found an exact match? |
| if (error <= 0) |
| break; |
| |
| // Obtain slope (the Jacobian) |
| for (j = 0; j < 3; j++) { |
| |
| xd[0] = x[0]; |
| xd[1] = x[1]; |
| xd[2] = x[2]; |
| xd[3] = x[3]; // Keep fixed channel |
| |
| IncDelta(&xd[j]); |
| |
| cmsPipelineEvalFloat(xd, fxd, lut); |
| |
| Jacobian.v[0].n[j] = ((fxd[0] - fx[0]) / JACOBIAN_EPSILON); |
| Jacobian.v[1].n[j] = ((fxd[1] - fx[1]) / JACOBIAN_EPSILON); |
| Jacobian.v[2].n[j] = ((fxd[2] - fx[2]) / JACOBIAN_EPSILON); |
| } |
| |
| // Solve system |
| tmp2.n[0] = fx[0] - Target[0]; |
| tmp2.n[1] = fx[1] - Target[1]; |
| tmp2.n[2] = fx[2] - Target[2]; |
| |
| if (!_cmsMAT3solve(&tmp, &Jacobian, &tmp2)) |
| return FALSE; |
| |
| // Move our guess |
| x[0] -= (cmsFloat32Number) tmp.n[0]; |
| x[1] -= (cmsFloat32Number) tmp.n[1]; |
| x[2] -= (cmsFloat32Number) tmp.n[2]; |
| |
| // Some clipping.... |
| for (j=0; j < 3; j++) { |
| if (x[j] < 0) x[j] = 0; |
| else |
| if (x[j] > 1.0) x[j] = 1.0; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| |