//--------------------------------------------------------------------------------- // // Little Color Management System // Copyright (c) 1998-2023 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" // Tone curves are powerful constructs that can contain curves specified in diverse ways. // The curve is stored in segments, where each segment can be sampled or specified by parameters. // a 16.bit simplification of the *whole* curve is kept for optimization purposes. For float operation, // each segment is evaluated separately. Plug-ins may be used to define new parametric schemes, // each plug-in may define up to MAX_TYPES_IN_LCMS_PLUGIN functions types. For defining a function, // the plug-in should provide the type id, how many parameters each type has, and a pointer to // a procedure that evaluates the function. In the case of reverse evaluation, the evaluator will // be called with the type id as a negative value, and a sampled version of the reversed curve // will be built. // ----------------------------------------------------------------- Implementation // Maxim number of nodes #define MAX_NODES_IN_CURVE … #define MINUS_INF … #define PLUS_INF … // The list of supported parametric curves _cmsParametricCurvesCollection; // This is the default (built-in) evaluator static cmsFloat64Number DefaultEvalParametricFn(cmsInt32Number Type, const cmsFloat64Number Params[], cmsFloat64Number R); // The built-in list static const _cmsParametricCurvesCollection DefaultCurves = …; // Duplicates the zone of memory used by the plug-in in the new context static void DupPluginCurvesList(struct _cmsContext_struct* ctx, const struct _cmsContext_struct* src) { … } // The allocator have to follow the chain void _cmsAllocCurvesPluginChunk(struct _cmsContext_struct* ctx, const struct _cmsContext_struct* src) { … } // The linked list head _cmsCurvesPluginChunkType _cmsCurvesPluginChunk = …; // As a way to install new parametric curves cmsBool _cmsRegisterParametricCurvesPlugin(cmsContext ContextID, cmsPluginBase* Data) { … } // Search in type list, return position or -1 if not found static int IsInSet(int Type, const _cmsParametricCurvesCollection* c) { … } // Search for the collection which contains a specific type static const _cmsParametricCurvesCollection *GetParametricCurveByType(cmsContext ContextID, int Type, int* index) { … } // Low level allocate, which takes care of memory details. nEntries may be zero, and in this case // no optimization curve is computed. nSegments may also be zero in the inverse case, where only the // optimization curve is given. Both features simultaneously is an error static cmsToneCurve* AllocateToneCurveStruct(cmsContext ContextID, cmsUInt32Number nEntries, cmsUInt32Number nSegments, const cmsCurveSegment* Segments, const cmsUInt16Number* Values) { … } // Generates a sigmoidal function with desired steepness. cmsINLINE double sigmoid_base(double k, double t) { … } cmsINLINE double inverted_sigmoid_base(double k, double t) { … } cmsINLINE double sigmoid_factory(double k, double t) { … } cmsINLINE double inverse_sigmoid_factory(double k, double t) { … } // Parametric Fn using floating point static cmsFloat64Number DefaultEvalParametricFn(cmsInt32Number Type, const cmsFloat64Number Params[], cmsFloat64Number R) { … } // Evaluate a segmented function for a single value. Return -Inf if no valid segment found . // If fn type is 0, perform an interpolation on the table static cmsFloat64Number EvalSegmentedFn(const cmsToneCurve *g, cmsFloat64Number R) { … } // Access to estimated low-res table cmsUInt32Number CMSEXPORT cmsGetToneCurveEstimatedTableEntries(const cmsToneCurve* t) { … } const cmsUInt16Number* CMSEXPORT cmsGetToneCurveEstimatedTable(const cmsToneCurve* t) { … } // Create an empty gamma curve, by using tables. This specifies only the limited-precision part, and leaves the // floating point description empty. cmsToneCurve* CMSEXPORT cmsBuildTabulatedToneCurve16(cmsContext ContextID, cmsUInt32Number nEntries, const cmsUInt16Number Values[]) { … } static cmsUInt32Number EntriesByGamma(cmsFloat64Number Gamma) { … } // Create a segmented gamma, fill the table cmsToneCurve* CMSEXPORT cmsBuildSegmentedToneCurve(cmsContext ContextID, cmsUInt32Number nSegments, const cmsCurveSegment Segments[]) { … } // Use a segmented curve to store the floating point table cmsToneCurve* CMSEXPORT cmsBuildTabulatedToneCurveFloat(cmsContext ContextID, cmsUInt32Number nEntries, const cmsFloat32Number values[]) { … } // Parametric curves // // Parameters goes as: Curve, a, b, c, d, e, f // Type is the ICC type +1 // if type is negative, then the curve is analytically inverted cmsToneCurve* CMSEXPORT cmsBuildParametricToneCurve(cmsContext ContextID, cmsInt32Number Type, const cmsFloat64Number Params[]) { … } // Build a gamma table based on gamma constant cmsToneCurve* CMSEXPORT cmsBuildGamma(cmsContext ContextID, cmsFloat64Number Gamma) { … } // Free all memory taken by the gamma curve void CMSEXPORT cmsFreeToneCurve(cmsToneCurve* Curve) { … } // Utility function, free 3 gamma tables void CMSEXPORT cmsFreeToneCurveTriple(cmsToneCurve* Curve[3]) { … } // Duplicate a gamma table cmsToneCurve* CMSEXPORT cmsDupToneCurve(const cmsToneCurve* In) { … } // Joins two curves for X and Y. Curves should be monotonic. // We want to get // // y = Y^-1(X(t)) // cmsToneCurve* CMSEXPORT cmsJoinToneCurve(cmsContext ContextID, const cmsToneCurve* X, const cmsToneCurve* Y, cmsUInt32Number nResultingPoints) { … } // Get the surrounding nodes. This is tricky on non-monotonic tables static int GetInterval(cmsFloat64Number In, const cmsUInt16Number LutTable[], const struct _cms_interp_struc* p) { … } // Reverse a gamma table cmsToneCurve* CMSEXPORT cmsReverseToneCurveEx(cmsUInt32Number nResultSamples, const cmsToneCurve* InCurve) { … } // Reverse a gamma table cmsToneCurve* CMSEXPORT cmsReverseToneCurve(const cmsToneCurve* InGamma) { … } // From: Eilers, P.H.C. (1994) Smoothing and interpolation with finite // differences. in: Graphic Gems IV, Heckbert, P.S. (ed.), Academic press. // // Smoothing and interpolation with second differences. // // Input: weights (w), data (y): vector from 1 to m. // Input: smoothing parameter (lambda), length (m). // Output: smoothed vector (z): vector from 1 to m. static cmsBool smooth2(cmsContext ContextID, cmsFloat32Number w[], cmsFloat32Number y[], cmsFloat32Number z[], cmsFloat32Number lambda, int m) { … } // Smooths a curve sampled at regular intervals. cmsBool CMSEXPORT cmsSmoothToneCurve(cmsToneCurve* Tab, cmsFloat64Number lambda) { … } // Is a table linear? Do not use parametric since we cannot guarantee some weird parameters resulting // in a linear table. This way assures it is linear in 12 bits, which should be enough in most cases. cmsBool CMSEXPORT cmsIsToneCurveLinear(const cmsToneCurve* Curve) { … } // Same, but for monotonicity cmsBool CMSEXPORT cmsIsToneCurveMonotonic(const cmsToneCurve* t) { … } // Same, but for descending tables cmsBool CMSEXPORT cmsIsToneCurveDescending(const cmsToneCurve* t) { … } // Another info fn: is out gamma table multisegment? cmsBool CMSEXPORT cmsIsToneCurveMultisegment(const cmsToneCurve* t) { … } cmsInt32Number CMSEXPORT cmsGetToneCurveParametricType(const cmsToneCurve* t) { … } // We need accuracy this time cmsFloat32Number CMSEXPORT cmsEvalToneCurveFloat(const cmsToneCurve* Curve, cmsFloat32Number v) { … } // We need xput over here cmsUInt16Number CMSEXPORT cmsEvalToneCurve16(const cmsToneCurve* Curve, cmsUInt16Number v) { … } // Least squares fitting. // A mathematical procedure for finding the best-fitting curve to a given set of points by // minimizing the sum of the squares of the offsets ("the residuals") of the points from the curve. // The sum of the squares of the offsets is used instead of the offset absolute values because // this allows the residuals to be treated as a continuous differentiable quantity. // // y = f(x) = x ^ g // // R = (yi - (xi^g)) // R2 = (yi - (xi^g))2 // SUM R2 = SUM (yi - (xi^g))2 // // dR2/dg = -2 SUM x^g log(x)(y - x^g) // solving for dR2/dg = 0 // // g = 1/n * SUM(log(y) / log(x)) cmsFloat64Number CMSEXPORT cmsEstimateGamma(const cmsToneCurve* t, cmsFloat64Number Precision) { … } // Retrieve parameters on one-segment tone curves cmsFloat64Number* CMSEXPORT cmsGetToneCurveParams(const cmsToneCurve* t) { … }