Skip to content
Closed
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
109 changes: 109 additions & 0 deletions include/lcms2.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@
// Uncomment to get rid of the tables for "half" float support
// #define CMS_NO_HALF_SUPPORT 1

// Uncomment to enable reading iccMAX (ICC.2) spectral data embedded in ICC.1
// profiles. Off by default: it widens the set of profiles, tags and processing
// elements the parser accepts.
// #define CMS_USE_ICCMAX_SPECTRAL 1

// iccMAX support cannot be built without half float support: ICC.2 encodes the
// header's spectralRange as float16Number (ICC.2:2023 4.2.8), so with the tables
// gone a spectral PCS cannot be read or written at all, float16ArrayType is
// unavailable, and the spectral white point tag cannot be parsed.
#if defined(CMS_USE_ICCMAX_SPECTRAL) && defined(CMS_NO_HALF_SUPPORT)
# error "CMS_USE_ICCMAX_SPECTRAL requires half float support: ICC.2 encodes spectralRange as float16Number, so CMS_NO_HALF_SUPPORT and CMS_USE_ICCMAX_SPECTRAL are mutually exclusive. Undefine one of them."
#endif

// Uncomment to get rid of pthreads/windows dependency
// #define CMS_NO_PTHREADS 1

Expand Down Expand Up @@ -345,6 +358,13 @@ typedef enum {
cmsSigViewingConditionsType = 0x76696577, // 'view'
cmsSigXYZType = 0x58595A20, // 'XYZ '
cmsSigMHC2Type = 0x4D484332 // 'MHC2'
#ifdef CMS_USE_ICCMAX_SPECTRAL
,
cmsSigEmbeddedProfileType = 0x49434370, // 'ICCp', holds an embedded ICC.2 profile
cmsSigFloat16ArrayType = 0x666C3136, // 'fl16', ICC.2 10.2.9 float16ArrayType
cmsSigFloat32ArrayType = 0x666C3332, // 'fl32', ICC.2 10.2.10 float32ArrayType
cmsSigSpectralViewingConditionsType = 0x7376636E // 'svcn', ICC.2 10.2.22
#endif


} cmsTagTypeSignature;
Expand Down Expand Up @@ -424,6 +444,12 @@ typedef enum {
cmsSigcicpTag = 0x63696370, // 'cicp'
cmsSigArgyllArtsTag = 0x61727473, // 'arts'
cmsSigMHC2Tag = 0x4D484332 // 'MHC2'
#ifdef CMS_USE_ICCMAX_SPECTRAL
,
cmsSigEmbeddedV5ProfileTag = 0x49434335, // 'ICC5', an embedded ICC.2 profile
cmsSigSpectralWhitePointTag = 0x73777074, // 'swpt', ICC.2 9.2.112
cmsSigSpectralViewingConditionsTag = 0x7376636E // 'svcn', ICC.2 9.2.111
#endif

} cmsTagSignature;

Expand Down Expand Up @@ -552,6 +578,9 @@ typedef enum {
cmsSigCurveSetElemType = 0x63767374, //'cvst'
cmsSigMatrixElemType = 0x6D617466, //'matf'
cmsSigCLutElemType = 0x636C7574, //'clut'
#ifdef CMS_USE_ICCMAX_SPECTRAL
cmsSigExtCLutElemType = 0x78636C74, //'xclt' (ICC.2 extendedCLUTElement)
#endif

cmsSigBAcsElemType = 0x62414353, // 'bACS'
cmsSigEAcsElemType = 0x65414353, // 'eACS'
Expand Down Expand Up @@ -581,6 +610,10 @@ typedef enum {
cmsSigFormulaCurveSeg = 0x70617266, // 'parf'
cmsSigSampledCurveSeg = 0x73616D66, // 'samf'
cmsSigSegmentedCurve = 0x63757266 // 'curf'
#ifdef CMS_USE_ICCMAX_SPECTRAL
,
cmsSigSingleSampledCurve = 0x736E6766 // 'sngf' (ICC.2 singleSampledCurve)
#endif

} cmsCurveSegSignature;

Expand Down Expand Up @@ -1564,6 +1597,82 @@ CMSAPI void CMSEXPORT cmsSetHeaderRenderingIntent(cmsHPROFILE hProf
CMSAPI cmsColorSpaceSignature
CMSEXPORT cmsGetPCS(cmsHPROFILE hProfile);
CMSAPI void CMSEXPORT cmsSetPCS(cmsHPROFILE hProfile, cmsColorSpaceSignature pcs);

#ifdef CMS_USE_ICCMAX_SPECTRAL
// An array of spectral values, as carried by the ICC.2 float16ArrayType and
// float32ArrayType tag types. The count lives here rather than in a tag
// descriptor, because ICC.2 derives it from the tag size.
typedef struct {
cmsContext ContextID;
cmsUInt32Number nValues;
cmsFloat32Number* Values;

// Both fl16 and fl32 are read; the tag is always written back as fl32,
// because fl32 is lossless from fl16 and Little-CMS does not preserve
// tag encodings.

} cmsFloatArray;

CMSAPI cmsFloatArray* CMSEXPORT cmsAllocFloatArray(cmsContext ContextID, cmsUInt32Number nValues);
CMSAPI void CMSEXPORT cmsFreeFloatArray(cmsFloatArray* v);

// Observer and illuminant for a spectrally-based PCS (ICC.2:2023 Table 69). The
// observer step count N and the illuminant step count M are independent of each
// other and of the spectral PCS channel count -- nothing here may assume they agree.
typedef struct {
cmsContext ContextID;

cmsUInt32Number ObserverType; // Table 70: 0 custom, 1 CIE 1931, 2 CIE 1964
cmsFloat32Number ObserverStart; // nm
cmsFloat32Number ObserverEnd; // nm
cmsUInt16Number ObserverSteps; // N
cmsFloat32Number* Observer; // 3N values: all X, then all Y, then all Z

cmsUInt32Number IlluminantType; // Table 71: 1 D50, 2 D65, 9 black body by CCT, ...
cmsFloat32Number CCT; // only meaningful for types 9 and 0Ah
cmsFloat32Number IlluminantStart; // nm
cmsFloat32Number IlluminantEnd; // nm
cmsUInt16Number IlluminantSteps; // M
cmsFloat32Number* Illuminant; // M values

cmsCIEXYZ IlluminantXYZ; // un-normalised, Y in cd/m2
cmsCIEXYZ SurroundXYZ; // un-normalised

} cmsSpectralViewingConditions;

CMSAPI cmsSpectralViewingConditions*
CMSEXPORT cmsAllocSpectralViewingConditions(cmsContext ContextID,
cmsUInt16Number ObserverSteps,
cmsUInt16Number IlluminantSteps);
CMSAPI void CMSEXPORT cmsFreeSpectralViewingConditions(cmsSpectralViewingConditions* v);

// Read or write spectralWhitePointTag in any of the three encodings ICC.2 9.2.112
// permits, including uInt16ArrayType, which has no registered type handler because
// its 1/65535 scaling belongs to this tag rather than to the generic ui16 signature.
// These dispatch on the tag's own type signature. On success the caller owns *Out
// and releases it with cmsFreeFloatArray.
CMSAPI cmsBool CMSEXPORT cmsReadSpectralWhitePoint(cmsHPROFILE hProfile,
cmsFloatArray** Out);
CMSAPI cmsBool CMSEXPORT cmsWriteSpectralWhitePoint(cmsHPROFILE hProfile,
const cmsFloatArray* In,
cmsTagTypeSignature AsType);
#endif

#ifdef CMS_USE_ICCMAX_SPECTRAL
// Spectral PCS, from the ICC.2 (iccMAX) header. Zero when the profile does not
// declare one, which is the case for every ICC.1 profile.
CMSAPI cmsUInt32Number CMSEXPORT cmsGetSpectralPCS(cmsHPROFILE hProfile);
CMSAPI cmsBool CMSEXPORT cmsSetSpectralPCS(cmsHPROFILE hProfile, cmsUInt32Number SpectralPCS);
CMSAPI cmsUInt16Number CMSEXPORT cmsGetSpectralPCSChannels(cmsHPROFILE hProfile);
CMSAPI cmsBool CMSEXPORT cmsGetSpectralPCSRange(cmsHPROFILE hProfile,
cmsFloat32Number* Start,
cmsFloat32Number* End,
cmsUInt16Number* Steps);
CMSAPI cmsBool CMSEXPORT cmsSetSpectralPCSRange(cmsHPROFILE hProfile,
cmsFloat32Number Start,
cmsFloat32Number End,
cmsUInt16Number Steps);
#endif
CMSAPI cmsColorSpaceSignature
CMSEXPORT cmsGetColorSpace(cmsHPROFILE hProfile);
CMSAPI void CMSEXPORT cmsSetColorSpace(cmsHPROFILE hProfile, cmsColorSpaceSignature sig);
Expand Down
127 changes: 125 additions & 2 deletions src/cmsgamma.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,39 @@ typedef struct _cmsParametricCurvesCollection_st {

} _cmsParametricCurvesCollection;

#ifdef CMS_USE_ICCMAX_SPECTRAL

// Exponentiation guarded against a non-positive base, which pow() would turn into
// NaN for a fractional exponent. Mirrors clipPow in the iccMAX reference
// implementation, and is used for every power in the ICC.2 formula segment types,
// including each X^gamma: a segmented curve's outer segments extend to +/-infinity,
// so a negative X is reachable.
static
cmsFloat64Number clipPow(cmsFloat64Number v, cmsFloat64Number g)
{
if (v <= 0) return 0.0;

return pow(v, g);
}

#endif

// This is the default (built-in) evaluator
static cmsFloat64Number DefaultEvalParametricFn(cmsInt32Number Type, const cmsFloat64Number Params[], cmsFloat64Number R);

// The built-in list
// The built-in list. The iccMAX types 9..13 are appended rather than inserted in
// numeric order: IsInSet returns a positional index that callers use against
// ParameterCount[], so existing positions must not shift.
static _cmsParametricCurvesCollection DefaultCurves = {
#ifdef CMS_USE_ICCMAX_SPECTRAL
15, // # of curve types
{ 1, 2, 3, 4, 5, 6, 7, 8, 108, 109, 9, 10, 11, 12, 13 }, // Parametric curve ID
{ 1, 3, 4, 5, 7, 4, 5, 5, 1, 1, 5, 5, 6, 7, 6 }, // Parameters by type
#else
10, // # of curve types
{ 1, 2, 3, 4, 5, 6, 7, 8, 108, 109 }, // Parametric curve ID
{ 1, 3, 4, 5, 7, 4, 5, 5, 1, 1 }, // Parameters by type
#endif
DefaultEvalParametricFn, // Evaluator
NULL // Next in chain
};
Expand Down Expand Up @@ -228,6 +253,30 @@ cmsToneCurve* AllocateToneCurveStruct(cmsContext ContextID, cmsUInt32Number nEnt
return NULL;
}

#ifdef CMS_USE_ICCMAX_SPECTRAL
// IsInSet matches on abs(Type), so a negative type is accepted whenever the
// positive one is registered, on the understanding that the evaluator implements
// the analytic inverse. For the ICC.2 formula segment types 9..13 it deliberately
// does not, so a curve built with -9..-13 would evaluate to 0 everywhere. Reject
// it here -- the common path for both cmsBuildParametricToneCurve and
// cmsBuildSegmentedToneCurve -- rather than hand back a silently useless curve.
if (Segments != NULL) {

for (i = 0; i < nSegments; i++) {

if (Segments[i].Type <= -9 && Segments[i].Type >= -13) {

cmsSignalError(ContextID, cmsERROR_RANGE,
"Parametric curve type %d has no analytic inverse in Little-CMS: the ICC.2 "
"formula segment types 9 to 13 are forward-only. Build the forward curve with "
"type %d and reverse it numerically with cmsReverseToneCurveEx.",
Segments[i].Type, -Segments[i].Type);
return NULL;
}
}
}
#endif // CMS_USE_ICCMAX_SPECTRAL

// Allocate all required pointers, etc.
p = (cmsToneCurve*) _cmsMallocZero(ContextID, sizeof(cmsToneCurve));
if (!p) return NULL;
Expand Down Expand Up @@ -685,6 +734,70 @@ cmsFloat64Number DefaultEvalParametricFn(cmsInt32Number Type, const cmsFloat64Nu
break;


#ifdef CMS_USE_ICCMAX_SPECTRAL

// ICC.2 formulaCurveSegment function types 0003h..0007h, which ICC.1 does not
// define. Parameter order follows ICC.2:2023 Table 111; note that omega precedes
// gamma in types 12 and 13. Degenerate cases return a finite value rather than
// NaN or an infinity, matching how types 6 and 7 above behave: a NaN reaching a
// CLUT index is far worse than a clamped number. The analytic inverses (-9..-13)
// are deliberately not implemented -- curve reversal in lcms is numerical, so no
// profile path needs them, and a caller asking for a negative type here falls
// through to the default arm and receives 0.

// Y = a * (b * X + c)^g + d : g a b c d
case 9:
Val = Params[1] * clipPow(Params[2] * R + Params[3], Params[0]) + Params[4];
break;

// Y = a * ln(d * X^g - b) + c : g a b c d
case 10:
e = Params[4] * clipPow(R, Params[0]) - Params[2];
if (e <= 0)
Val = Params[3];
else
Val = Params[1] * log(e) + Params[3];
break;

// Y = e * exp((d * X^g - c) / a) + b : g a b c d e
case 11:
if (fabs(Params[1]) < MATRIX_DET_TOLERANCE)
Val = Params[2];
else
Val = Params[5] * exp((Params[4] * clipPow(R, Params[0]) - Params[3]) / Params[1]) + Params[2];
break;

// Y = d * (max(e * X^g - a, 0) / (b - c * X^g))^w : w g a b c d e
case 12:
{
cmsFloat64Number u = clipPow(R, Params[1]);
cmsFloat64Number den = Params[3] - Params[4] * u;
cmsFloat64Number num = Params[6] * u - Params[2];

if (num < 0) num = 0;

if (fabs(den) < MATRIX_DET_TOLERANCE)
Val = 0;
else
Val = Params[5] * clipPow(num / den, Params[0]);
}
break;

// Y = d * ((a + b * X^g) / (1 + c * X^g))^w : w g a b c d
case 13:
{
cmsFloat64Number u = clipPow(R, Params[1]);
cmsFloat64Number den = 1.0 + Params[4] * u;

if (fabs(den) < MATRIX_DET_TOLERANCE)
Val = 0;
else
Val = Params[5] * clipPow((Params[2] + Params[3] * u) / den, Params[0]);
}
break;

#endif

// S-Shaped: (1 - (1-x)^1/g)^1/g
case 108:
if (fabs(Params[0]) < MATRIX_DET_TOLERANCE)
Expand Down Expand Up @@ -967,9 +1080,19 @@ void CMSEXPORT cmsFreeToneCurveTriple(cmsToneCurve* Curve[3])
// Duplicate a gamma table
cmsToneCurve* CMSEXPORT cmsDupToneCurve(const cmsToneCurve* In)
{
cmsToneCurve* Out;

if (In == NULL) return NULL;

return AllocateToneCurveStruct(In ->InterpParams ->ContextID, In ->nEntries, In ->nSegments, In ->Segments, In ->Table16);
Out = AllocateToneCurveStruct(In ->InterpParams ->ContextID, In ->nEntries, In ->nSegments, In ->Segments, In ->Table16);

#ifdef CMS_USE_ICCMAX_SPECTRAL
// Carry the encoding across, so a duplicated curve is still written back in the
// form it arrived in. AllocateToneCurveStruct does not know about it.
if (Out != NULL) Out ->CurveType = In ->CurveType;
#endif

return Out;
}

// Joins two curves for X and Y. Curves should be monotonic.
Expand Down
Loading