diff --git a/include/lcms2.h b/include/lcms2.h index a691f5257..08dda0718 100644 --- a/include/lcms2.h +++ b/include/lcms2.h @@ -1639,6 +1639,10 @@ CMSAPI cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID #define cmsFLAGS_HIGHRESPRECALC 0x0400 // Use more memory to give better accurancy #define cmsFLAGS_LOWRESPRECALC 0x0800 // Use less memory to minimize resouces +// Slope Limit +#define cmsFLAGS_SLOPE_LIMIT_16 0x40000000 // Enable Slope Limit 16 (emulate ColorSync & Kodak CMM) +#define cmsFLAGS_SLOPE_LIMIT_32 0x80000000 // Enable Slope Limit 32 (emulate Adobe CMM) + // For devicelink creation #define cmsFLAGS_8BITS_DEVICELINK 0x0008 // Create 8 bits devicelinks #define cmsFLAGS_GUESSDEVICECLASS 0x0020 // Guess device class (for transform2devicelink) diff --git a/src/cmscnvrt.c b/src/cmscnvrt.c index 04f844a3f..5fd5b5e22 100644 --- a/src/cmscnvrt.c +++ b/src/cmscnvrt.c @@ -536,10 +536,15 @@ cmsPipeline* DefaultICCintents(cmsContext ContextID, cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut = cmsSigLabData, CurrentColorSpace; cmsProfileClassSignature ClassSig; cmsUInt32Number i, Intent; - + int SlopeLimit = 0; + // For safety if (nProfiles == 0) return NULL; + // Register slope limit flags + if (dwFlags & cmsFLAGS_SLOPE_LIMIT_16) SlopeLimit = 16; + else if (dwFlags & cmsFLAGS_SLOPE_LIMIT_32) SlopeLimit = 32; + // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined' Result = cmsPipelineAlloc(ContextID, 0, 0); if (Result == NULL) return NULL; @@ -608,13 +613,13 @@ cmsPipeline* DefaultICCintents(cmsContext ContextID, if (lIsInput) { // Input direction means non-pcs connection, so proceed like devicelinks - Lut = _cmsReadInputLUT(hProfile, Intent); + Lut = _cmsReadInputLUT(hProfile, Intent, -SlopeLimit); // negative slope limit means input slope limiting if (Lut == NULL) goto Error; } else { // Output direction means PCS connection. Intent may apply here - Lut = _cmsReadOutputLUT(hProfile, Intent); + Lut = _cmsReadOutputLUT(hProfile, Intent, SlopeLimit); if (Lut == NULL) goto Error; @@ -958,7 +963,7 @@ cmsPipeline* BlackPreservingKPlaneIntents(cmsContext ContextID, // We need the input LUT of the last profile, assuming this one is responsible of // black generation. This LUT will be seached in inverse order. - bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC); + bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC, 0); if (bp.LabK2cmyk == NULL) goto Cleanup; // Get total area coverage (in 0..1 domain) diff --git a/src/cmsgamma.c b/src/cmsgamma.c index 78691668a..b264ca4a6 100644 --- a/src/cmsgamma.c +++ b/src/cmsgamma.c @@ -1191,8 +1191,15 @@ cmsInt32Number CMSEXPORT cmsGetToneCurveParametricType(const cmsToneCurve* t) // We need accuracy this time cmsFloat32Number CMSEXPORT cmsEvalToneCurveFloat(const cmsToneCurve* Curve, cmsFloat32Number v) +{ + return _cmsEvalToneCurveFloatWithSlopeLimit(Curve, v, 0); +} + +cmsFloat32Number _cmsEvalToneCurveFloatWithSlopeLimit(const cmsToneCurve* Curve, cmsFloat32Number v, int SlopeLimit) { _cmsAssert(Curve != NULL); + + cmsFloat32Number result; // Check for 16 bits table. If so, this is a limited-precision tone curve if (Curve ->nSegments == 0) { @@ -1202,10 +1209,23 @@ cmsFloat32Number CMSEXPORT cmsEvalToneCurveFloat(const cmsToneCurve* Curve, cmsF In = (cmsUInt16Number) _cmsQuickSaturateWord(v * 65535.0); Out = cmsEvalToneCurve16(Curve, In); - return (cmsFloat32Number) (Out / 65535.0); + result = (cmsFloat32Number) (Out / 65535.0); } - - return (cmsFloat32Number) EvalSegmentedFn(Curve, v); + else result = (cmsFloat32Number) EvalSegmentedFn(Curve, v); + + // Apply slope limit, if set to do so + if (SlopeLimit < 0) { // < 0 means input tone curve + + cmsFloat32Number factor = (cmsFloat32Number)(-SlopeLimit); + result = fmaxf(result, v / factor); + } + else if (SlopeLimit > 0) { // > 0 means output tone curve + + cmsFloat32Number factor = (cmsFloat32Number)(SlopeLimit); + result = fminf(result, v * factor); + } + + return result; } // We need xput over here diff --git a/src/cmsio1.c b/src/cmsio1.c index 1f002fafb..6a61a43b6 100644 --- a/src/cmsio1.c +++ b/src/cmsio1.c @@ -153,7 +153,7 @@ cmsBool ReadICCMatrixRGB2XYZ(cmsMAT3* r, cmsHPROFILE hProfile) // Gray input pipeline static -cmsPipeline* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile) +cmsPipeline* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile, int SlopeLimit) { cmsToneCurve *GrayTRC; cmsPipeline* Lut; @@ -183,7 +183,7 @@ cmsPipeline* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile) LabCurves[2] = EmptyTab; if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 1, OneToThreeInputMatrix, NULL)) || - !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, LabCurves))) { + !cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocToneCurvesWithSlopeLimit(ContextID, 3, LabCurves, SlopeLimit))) { cmsFreeToneCurve(EmptyTab); goto Error; } @@ -193,7 +193,7 @@ cmsPipeline* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile) } else { - if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 1, &GrayTRC)) || + if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocToneCurvesWithSlopeLimit(ContextID, 1, &GrayTRC, SlopeLimit)) || !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 1, GrayInputMatrix, NULL))) goto Error; } @@ -208,7 +208,7 @@ cmsPipeline* BuildGrayInputMatrixPipeline(cmsHPROFILE hProfile) // RGB Matrix shaper static -cmsPipeline* BuildRGBInputMatrixShaper(cmsHPROFILE hProfile) +cmsPipeline* BuildRGBInputMatrixShaper(cmsHPROFILE hProfile, int SlopeLimit) { cmsPipeline* Lut; cmsMAT3 Mat; @@ -237,7 +237,7 @@ cmsPipeline* BuildRGBInputMatrixShaper(cmsHPROFILE hProfile) Lut = cmsPipelineAlloc(ContextID, 3, 3); if (Lut != NULL) { - if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, Shapes)) || + if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocToneCurvesWithSlopeLimit(ContextID, 3, Shapes, SlopeLimit)) || !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Mat, NULL))) goto Error; @@ -307,7 +307,7 @@ cmsPipeline* _cmsReadFloatInputTag(cmsHPROFILE hProfile, cmsTagSignature tagFloa // Read and create a BRAND NEW MPE LUT from a given profile. All stuff dependent of version, etc // is adjusted here in order to create a LUT that takes care of all those details. // We add intent = -1 as a way to read matrix shaper always, no matter of other LUT -cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent) +cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent, int SlopeLimit) { cmsTagTypeSignature OriginalType; cmsTagSignature tag16; @@ -396,11 +396,11 @@ cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent) // if so, build appropiate conversion tables. // The tables are the PCS iluminant, scaled across GrayTRC - return BuildGrayInputMatrixPipeline(hProfile); + return BuildGrayInputMatrixPipeline(hProfile, SlopeLimit); } // Not gray, create a normal matrix-shaper - return BuildRGBInputMatrixShaper(hProfile); + return BuildRGBInputMatrixShaper(hProfile, SlopeLimit); } // --------------------------------------------------------------------------------------------------------------- @@ -411,7 +411,7 @@ cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent) // The complete pipeline on XYZ is Matrix[3:1] -> Tone curve and in Lab Matrix[3:1] -> Tone Curve as well. static -cmsPipeline* BuildGrayOutputPipeline(cmsHPROFILE hProfile) +cmsPipeline* BuildGrayOutputPipeline(cmsHPROFILE hProfile, int SlopeLimit) { cmsToneCurve *GrayTRC, *RevGrayTRC; cmsPipeline* Lut; @@ -439,7 +439,7 @@ cmsPipeline* BuildGrayOutputPipeline(cmsHPROFILE hProfile) goto Error; } - if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 1, &RevGrayTRC))) + if (!cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocToneCurvesWithSlopeLimit(ContextID, 1, &RevGrayTRC, SlopeLimit))) goto Error; cmsFreeToneCurve(RevGrayTRC); @@ -453,7 +453,7 @@ cmsPipeline* BuildGrayOutputPipeline(cmsHPROFILE hProfile) static -cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile) +cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile, int SlopeLimit) { cmsPipeline* Lut; cmsToneCurve *Shapes[3], *InvShapes[3]; @@ -503,7 +503,7 @@ cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile) } if (!cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Inv, NULL)) || - !cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, InvShapes))) + !cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocToneCurvesWithSlopeLimit(ContextID, 3, InvShapes, SlopeLimit))) goto Error; } @@ -582,7 +582,7 @@ cmsPipeline* _cmsReadFloatOutputTag(cmsHPROFILE hProfile, cmsTagSignature tagFlo } // Create an output MPE LUT from agiven profile. Version mismatches are handled here -cmsPipeline* _cmsReadOutputLUT(cmsHPROFILE hProfile, int Intent) +cmsPipeline* _cmsReadOutputLUT(cmsHPROFILE hProfile, int Intent, int SlopeLimit) { cmsTagTypeSignature OriginalType; cmsTagSignature tag16; @@ -653,11 +653,11 @@ cmsPipeline* _cmsReadOutputLUT(cmsHPROFILE hProfile, int Intent) // if so, build appropiate conversion tables. // The tables are the PCS iluminant, scaled across GrayTRC - return BuildGrayOutputPipeline(hProfile); + return BuildGrayOutputPipeline(hProfile, SlopeLimit); } // Not gray, create a normal matrix-shaper, which only operates in XYZ space - return BuildRGBOutputMatrixShaper(hProfile); + return BuildRGBOutputMatrixShaper(hProfile, SlopeLimit); } // --------------------------------------------------------------------------------------------------------------- diff --git a/src/cmslut.c b/src/cmslut.c index e2525477c..91fb9bf2b 100644 --- a/src/cmslut.c +++ b/src/cmslut.c @@ -28,14 +28,16 @@ // Allocates an empty multi profile element -cmsStage* CMSEXPORT _cmsStageAllocPlaceholder(cmsContext ContextID, +static +cmsStage* _cmsStageAllocPlaceholderWithSlopeLimit(cmsContext ContextID, cmsStageSignature Type, cmsUInt32Number InputChannels, cmsUInt32Number OutputChannels, _cmsStageEvalFn EvalPtr, _cmsStageDupElemFn DupElemPtr, _cmsStageFreeElemFn FreePtr, - void* Data) + void* Data, + int SlopeLimit) { cmsStage* ph = (cmsStage*) _cmsMallocZero(ContextID, sizeof(cmsStage)); @@ -53,11 +55,33 @@ cmsStage* CMSEXPORT _cmsStageAllocPlaceholder(cmsContext ContextID, ph ->DupElemPtr = DupElemPtr; ph ->FreePtr = FreePtr; ph ->Data = Data; + ph ->SlopeLimit = SlopeLimit; return ph; } +cmsStage* CMSEXPORT _cmsStageAllocPlaceholder(cmsContext ContextID, + cmsStageSignature Type, + cmsUInt32Number InputChannels, + cmsUInt32Number OutputChannels, + _cmsStageEvalFn EvalPtr, + _cmsStageDupElemFn DupElemPtr, + _cmsStageFreeElemFn FreePtr, + void* Data) +{ + return _cmsStageAllocPlaceholderWithSlopeLimit(ContextID, + Type, + InputChannels, + OutputChannels, + EvalPtr, + DupElemPtr, + FreePtr, + Data, + 0); +} + + static void EvaluateIdentity(const cmsFloat32Number In[], cmsFloat32Number Out[], @@ -179,7 +203,7 @@ void EvaluateCurves(const cmsFloat32Number In[], if (Data ->TheCurves == NULL) return; for (i=0; i < Data ->nCurves; i++) { - Out[i] = cmsEvalToneCurveFloat(Data ->TheCurves[i], In[i]); + Out[i] = _cmsEvalToneCurveFloatWithSlopeLimit(Data ->TheCurves[i], In[i], mpe ->SlopeLimit); } } @@ -246,14 +270,20 @@ void* CurveSetDup(cmsStage* mpe) // Curves == NULL forces identity curves cmsStage* CMSEXPORT cmsStageAllocToneCurves(cmsContext ContextID, cmsUInt32Number nChannels, cmsToneCurve* const Curves[]) +{ + return _cmsStageAllocToneCurvesWithSlopeLimit(ContextID, nChannels, Curves, 0); +} + + +cmsStage* _cmsStageAllocToneCurvesWithSlopeLimit(cmsContext ContextID, cmsUInt32Number nChannels, cmsToneCurve* const Curves[], int SlopeLimit) { cmsUInt32Number i; _cmsStageToneCurvesData* NewElem; cmsStage* NewMPE; - NewMPE = _cmsStageAllocPlaceholder(ContextID, cmsSigCurveSetElemType, nChannels, nChannels, - EvaluateCurves, CurveSetDup, CurveSetElemTypeFree, NULL ); + NewMPE = _cmsStageAllocPlaceholderWithSlopeLimit(ContextID, cmsSigCurveSetElemType, nChannels, nChannels, + EvaluateCurves, CurveSetDup, CurveSetElemTypeFree, NULL, SlopeLimit ); if (NewMPE == NULL) return NULL; NewElem = (_cmsStageToneCurvesData*) _cmsMallocZero(ContextID, sizeof(_cmsStageToneCurvesData)); @@ -1237,14 +1267,15 @@ cmsStage* CMSEXPORT cmsStageDup(cmsStage* mpe) cmsStage* NewMPE; if (mpe == NULL) return NULL; - NewMPE = _cmsStageAllocPlaceholder(mpe ->ContextID, + NewMPE = _cmsStageAllocPlaceholderWithSlopeLimit(mpe ->ContextID, mpe ->Type, mpe ->InputChannels, mpe ->OutputChannels, mpe ->EvalPtr, mpe ->DupElemPtr, mpe ->FreePtr, - NULL); + NULL, + mpe ->SlopeLimit); if (NewMPE == NULL) return NULL; NewMPE ->Implements = mpe ->Implements; diff --git a/src/cmsopt.c b/src/cmsopt.c index 8acaa469b..de073bf29 100644 --- a/src/cmsopt.c +++ b/src/cmsopt.c @@ -1900,6 +1900,11 @@ cmsBool _cmsOptimizePipeline(cmsContext ContextID, _cmsPipelineSetOptimizationParameters(*PtrLut, FastIdentity16, (void*) *PtrLut, NULL, NULL); return TRUE; } + + // Tone Curve Stages with a slope limit cannot be optimized. The following line switches off optimization + // completely and is a workaround until _cmsStage_struct.Implements correctly signifies that this + // stage cannot be optimized + if (*dwFlags & (cmsFLAGS_SLOPE_LIMIT_16 | cmsFLAGS_SLOPE_LIMIT_32)) *dwFlags |= cmsFLAGS_NOOPTIMIZE; // Do not optimize, keep all precision if (*dwFlags & cmsFLAGS_NOOPTIMIZE) diff --git a/src/cmsps2.c b/src/cmsps2.c index 224b44b54..815cce807 100644 --- a/src/cmsps2.c +++ b/src/cmsps2.c @@ -1073,7 +1073,7 @@ cmsUInt32Number GenerateCSA(cmsContext ContextID, // Read the lut with all necessary conversion stages - lut = _cmsReadInputLUT(hProfile, Intent); + lut = _cmsReadInputLUT(hProfile, Intent, 0); if (lut == NULL) goto Error; diff --git a/src/lcms2_internal.h b/src/lcms2_internal.h index b90b8bdb8..c79f668e3 100644 --- a/src/lcms2_internal.h +++ b/src/lcms2_internal.h @@ -818,6 +818,9 @@ struct _cmsStage_struct { // A generic pointer to whatever memory needed by the stage void* Data; + // Slope limit setting for tone curves (used internally) + int SlopeLimit; + // Maintains linked list (used internally) struct _cmsStage_struct* Next; }; @@ -844,6 +847,11 @@ cmsStage* _cmsStageClipNegatives(cmsContext ContextID, int nChannels); cmsToneCurve** _cmsStageGetPtrToCurveSet(const cmsStage* mpe); +// Curve evaluation with slope limit +cmsStage* _cmsStageAllocToneCurvesWithSlopeLimit(cmsContext ContextID, cmsUInt32Number nChannels, cmsToneCurve* const Curves[], int SlopeLimit); +cmsFloat32Number _cmsEvalToneCurveFloatWithSlopeLimit(const cmsToneCurve* Curve, cmsFloat32Number v, int SlopeLimit); + + // Pipeline Evaluator (in floating point) typedef void (* _cmsPipelineEvalFloatFn)(const cmsFloat32Number In[], cmsFloat32Number Out[], @@ -872,8 +880,8 @@ struct _cmsPipeline_struct { // Read tags using low-level function, provide necessary glue code to adapt versions, etc. All those return a brand new copy // of the LUTS, since ownership of original is up to the profile. The user should free allocated resources. -cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent); -cmsPipeline* _cmsReadOutputLUT(cmsHPROFILE hProfile, int Intent); +cmsPipeline* _cmsReadInputLUT(cmsHPROFILE hProfile, int Intent, int SlopeLimit); +cmsPipeline* _cmsReadOutputLUT(cmsHPROFILE hProfile, int Intent, int SlopeLimit); cmsPipeline* _cmsReadDevicelinkLUT(cmsHPROFILE hProfile, int Intent); // Special values