Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slope Limit #69

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/lcms2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 9 additions & 4 deletions src/cmscnvrt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;


Expand Down Expand Up @@ -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)
Expand Down
26 changes: 23 additions & 3 deletions src/cmsgamma.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down
30 changes: 15 additions & 15 deletions src/cmsio1.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

// ---------------------------------------------------------------------------------------------------------------
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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];
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

// ---------------------------------------------------------------------------------------------------------------
Expand Down
45 changes: 38 additions & 7 deletions src/cmslut.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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[],
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/cmsopt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/cmsps2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down
12 changes: 10 additions & 2 deletions src/lcms2_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -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[],
Expand Down Expand Up @@ -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
Expand Down