/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
// This file is available under and governed by the GNU General Public // License version 2 only, as published by the Free Software Foundation. // However, the following notice accompanied the original version of this // file: // //--------------------------------------------------------------------------------- // // Little Color Management System // Copyright (c) 1998-2022 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. // //--------------------------------------------------------------------------------- //
// Number of channels
cmsUInt32Number nInputs;
cmsUInt32Number nOutputs;
_cmsInterpFn16 EvalCurveIn16[MAX_INPUT_DIMENSIONS]; // The maximum number of input channels is known in advance
cmsInterpParams* ParamsCurveIn16[MAX_INPUT_DIMENSIONS];
_cmsInterpFn16 EvalCLUT; // The evaluator for 3D grid const cmsInterpParams* CLUTparams; // (not-owned pointer)
_cmsInterpFn16* EvalCurveOut16; // Points to an array of curve evaluators in 16 bits (not-owned pointer)
cmsInterpParams** ParamsCurveOut16; // Points to an array of references to interpolation params (not-owned pointer)
} Prelin16Data;
// Optimization for matrix-shaper in 8 bits. Numbers are operated in n.14 signed, tables are stored in 1.14 fixed
typedef cmsInt32Number cmsS1Fixed14Number; // Note that this may hold more than 16 bits!
cmsS1Fixed14Number Shaper1R[256]; // from 0..255 to 1.14 (0.0...1.0)
cmsS1Fixed14Number Shaper1G[256];
cmsS1Fixed14Number Shaper1B[256];
cmsS1Fixed14Number Mat[3][3]; // n.14 to n.14 (needs a saturation after that)
cmsS1Fixed14Number Off[3];
cmsUInt16Number Shaper2R[16385]; // 1.14 to 0..255
cmsUInt16Number Shaper2G[16385];
cmsUInt16Number Shaper2B[16385];
} MatShaper8Data;
// Curves, optimization is shared between 8 and 16 bits typedefstruct {
cmsContext ContextID;
cmsUInt32Number nCurves; // Number of curves
cmsUInt32Number nElements; // Elements in curves
cmsUInt16Number** Curves; // Points to a dynamically allocated array
// Remove an element in linked chain static void _RemoveElement(cmsStage** head)
{
cmsStage* mpe = *head;
cmsStage* next = mpe ->Next;
*head = next;
cmsStageFree(mpe);
}
// Remove all identities in chain. Note that pt actually is a double pointer to the element that holds the pointer. static
cmsBool _Remove1Op(cmsPipeline* Lut, cmsStageSignature UnaryOp)
{
cmsStage** pt = &Lut ->Elements;
cmsBool AnyOpt = FALSE;
// Same, but only if two adjacent elements are found static
cmsBool _Remove2Op(cmsPipeline* Lut, cmsStageSignature Op1, cmsStageSignature Op2)
{
cmsStage** pt1;
cmsStage** pt2;
cmsBool AnyOpt = FALSE;
pt1 = &Lut ->Elements; if (*pt1 == NULL) return AnyOpt;
static
cmsBool CloseEnoughFloat(cmsFloat64Number a, cmsFloat64Number b)
{ return fabs(b - a) < 0.00001f;
}
static
cmsBool isFloatMatrixIdentity(const cmsMAT3* a)
{
cmsMAT3 Identity; int i, j;
_cmsMAT3identity(&Identity);
for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) if (!CloseEnoughFloat(a->v[i].n[j], Identity.v[i].n[j])) returnFALSE;
returnTRUE;
} // if two adjacent matrices are found, multiply them. static
cmsBool _MultiplyMatrix(cmsPipeline* Lut)
{
cmsStage** pt1;
cmsStage** pt2;
cmsStage* chain;
cmsBool AnyOpt = FALSE;
pt1 = &Lut->Elements; if (*pt1 == NULL) return AnyOpt;
while (*pt1 != NULL) {
pt2 = &((*pt1)->Next); if (*pt2 == NULL) return AnyOpt;
if ((*pt1)->Implements == cmsSigMatrixElemType && (*pt2)->Implements == cmsSigMatrixElemType) {
// Get both matrices
_cmsStageMatrixData* m1 = (_cmsStageMatrixData*) cmsStageData(*pt1);
_cmsStageMatrixData* m2 = (_cmsStageMatrixData*) cmsStageData(*pt2);
cmsMAT3 res;
// Input offset and output offset should be zero to use this optimization if (m1->Offset != NULL || m2 ->Offset != NULL ||
cmsStageInputChannels(*pt1) != 3 || cmsStageOutputChannels(*pt1) != 3 ||
cmsStageInputChannels(*pt2) != 3 || cmsStageOutputChannels(*pt2) != 3) returnFALSE;
// Multiply both matrices to get the result
_cmsMAT3per(&res, (cmsMAT3*)m2->Double, (cmsMAT3*)m1->Double);
// Get the next in chain after the matrices
chain = (*pt2)->Next;
// Remove both matrices
_RemoveElement(pt2);
_RemoveElement(pt1);
// Now what if the result is a plain identity? if (!isFloatMatrixIdentity(&res)) {
// We can not get rid of full matrix
cmsStage* Multmat = cmsStageAllocMatrix(Lut->ContextID, 3, 3, (const cmsFloat64Number*) &res, NULL); if (Multmat == NULL) returnFALSE; // Should never happen
// Preoptimize just gets rif of no-ops coming paired. Conversion from v2 to v4 followed // by a v4 to v2 and vice-versa. The elements are then discarded. static
cmsBool PreOptimize(cmsPipeline* Lut)
{
cmsBool AnyOpt = FALSE, Opt;
do {
Opt = FALSE;
// Remove all identities
Opt |= _Remove1Op(Lut, cmsSigIdentityElemType);
// Remove XYZ2Lab followed by Lab2XYZ
Opt |= _Remove2Op(Lut, cmsSigXYZ2LabElemType, cmsSigLab2XYZElemType);
// Remove Lab2XYZ followed by XYZ2Lab
Opt |= _Remove2Op(Lut, cmsSigLab2XYZElemType, cmsSigXYZ2LabElemType);
// Remove V4 to V2 followed by V2 to V4
Opt |= _Remove2Op(Lut, cmsSigLabV4toV2, cmsSigLabV2toV4);
// Remove V2 to V4 followed by V4 to V2
Opt |= _Remove2Op(Lut, cmsSigLabV2toV4, cmsSigLabV4toV2);
// Sampler implemented by another LUT. This is a clean way to precalculate the devicelink 3D CLUT for // almost any transform. We use floating point precision and then convert from floating point to 16 bits. static
cmsInt32Number XFormSampler16(CMSREGISTER const cmsUInt16Number In[],
CMSREGISTER cmsUInt16Number Out[],
CMSREGISTER void* Cargo)
{
cmsPipeline* Lut = (cmsPipeline*) Cargo;
cmsFloat32Number InFloat[cmsMAXCHANNELS], OutFloat[cmsMAXCHANNELS];
cmsUInt32Number i;
// From 16 bit to floating point for (i=0; i < Lut ->InputChannels; i++)
InFloat[i] = (cmsFloat32Number) (In[i] / 65535.0);
// Evaluate in floating point
cmsPipelineEvalFloat(InFloat, OutFloat, Lut);
// Back to 16 bits representation for (i=0; i < Lut ->OutputChannels; i++)
Out[i] = _cmsQuickSaturateWord(OutFloat[i] * 65535.0);
// Always succeed returnTRUE;
}
// Try to see if the curves of a given MPE are linear static
cmsBool AllCurvesAreLinear(cmsStage* mpe)
{
cmsToneCurve** Curves;
cmsUInt32Number i, n;
Curves = _cmsStageGetPtrToCurveSet(mpe); if (Curves == NULL) returnFALSE;
n = cmsStageOutputChannels(mpe);
for (i=0; i < n; i++) { if (!cmsIsToneCurveLinear(Curves[i])) returnFALSE;
}
returnTRUE;
}
// This function replaces a specific node placed in "At" by the "Value" numbers. Its purpose // is to fix scum dot on broken profiles/transforms. Works on 1, 3 and 4 channels static
cmsBool PatchLUT(cmsStage* CLUT, cmsUInt16Number At[], cmsUInt16Number Value[],
cmsUInt32Number nChannelsOut, cmsUInt32Number nChannelsIn)
{
_cmsStageCLutData* Grid = (_cmsStageCLutData*) CLUT ->Data;
cmsInterpParams* p16 = Grid ->Params;
cmsFloat64Number px, py, pz, pw; int x0, y0, z0, w0; int i, index;
if (CLUT -> Type != cmsSigCLutElemType) {
cmsSignalError(CLUT->ContextID, cmsERROR_INTERNAL, "(internal) Attempt to PatchLUT on non-lut stage"); returnFALSE;
}
if (((px - x0) != 0)) returnFALSE; // Not on exact node
index = (int) p16 -> opta[0] * x0;
} else {
cmsSignalError(CLUT->ContextID, cmsERROR_INTERNAL, "(internal) %d Channels are not supported on PatchLUT", nChannelsIn); returnFALSE;
}
for (i = 0; i < (int) nChannelsOut; i++)
Grid->Tab.T[index + i] = Value[i];
returnTRUE;
}
// Auxiliary, to see if two values are equal or very different static
cmsBool WhitesAreEqual(cmsUInt32Number n, cmsUInt16Number White1[], cmsUInt16Number White2[] )
{
cmsUInt32Number i;
for (i=0; i < n; i++) {
if (abs(White1[i] - White2[i]) > 0xf000) returnTRUE; // Values are so extremely different that the fixup should be avoided if (White1[i] != White2[i]) returnFALSE;
} returnTRUE;
}
// Locate the node for the white point and fix it to pure white in order to avoid scum dot. static
cmsBool FixWhiteMisalignment(cmsPipeline* Lut, cmsColorSpaceSignature EntryColorSpace, cmsColorSpaceSignature ExitColorSpace)
{
cmsUInt16Number *WhitePointIn, *WhitePointOut;
cmsUInt16Number WhiteIn[cmsMAXCHANNELS], WhiteOut[cmsMAXCHANNELS], ObtainedOut[cmsMAXCHANNELS];
cmsUInt32Number i, nOuts, nIns;
cmsStage *PreLin = NULL, *CLUT = NULL, *PostLin = NULL;
if (!_cmsEndPointsBySpace(EntryColorSpace,
&WhitePointIn, NULL, &nIns)) returnFALSE;
if (!_cmsEndPointsBySpace(ExitColorSpace,
&WhitePointOut, NULL, &nOuts)) returnFALSE;
// It needs to be fixed? if (Lut ->InputChannels != nIns) returnFALSE; if (Lut ->OutputChannels != nOuts) returnFALSE;
if (WhitesAreEqual(nOuts, WhitePointOut, ObtainedOut)) returnTRUE; // whites already match
// Check if the LUT comes as Prelin, CLUT or Postlin. We allow all combinations if (!cmsPipelineCheckAndRetreiveStages(Lut, 3, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType, &PreLin, &CLUT, &PostLin)) if (!cmsPipelineCheckAndRetreiveStages(Lut, 2, cmsSigCurveSetElemType, cmsSigCLutElemType, &PreLin, &CLUT)) if (!cmsPipelineCheckAndRetreiveStages(Lut, 2, cmsSigCLutElemType, cmsSigCurveSetElemType, &CLUT, &PostLin)) if (!cmsPipelineCheckAndRetreiveStages(Lut, 1, cmsSigCLutElemType, &CLUT)) returnFALSE;
// We need to interpolate white points of both, pre and post curves if (PreLin) {
WhiteOut[i] = cmsEvalToneCurve16(InversePostLin, WhitePointOut[i]);
cmsFreeToneCurve(InversePostLin);
}
}
} else { for (i=0; i < nOuts; i++)
WhiteOut[i] = WhitePointOut[i];
}
// Ok, proceed with patching. May fail and we don't care if it fails
PatchLUT(CLUT, WhiteIn, WhiteOut, nOuts, nIns);
returnTRUE;
}
// ----------------------------------------------------------------------------------------------------------------------------------------------- // This function creates simple LUT from complex ones. The generated LUT has an optional set of // prelinearization curves, a CLUT of nGridPoints and optional postlinearization tables. // These curves have to exist in the original LUT in order to be used in the simplified output. // Caller may also use the flags to allow this feature. // LUTS with all curves will be simplified to a single curve. Parametric curves are lost. // This function should be used on 16-bits LUTS only, as floating point losses precision when simplified // -----------------------------------------------------------------------------------------------------------------------------------------------
// This is a lossy optimization! does not apply in floating-point cases if (_cmsFormatterIsFloat(*InputFormat) || _cmsFormatterIsFloat(*OutputFormat)) returnFALSE;
// Remove prelinearization. Since we have duplicated the curve // in destination LUT, the sampling should be applied after this stage.
cmsPipelineUnlinkStage(Src, cmsAT_BEGIN, &KeepPreLin);
}
}
}
// Allocate the CLUT
CLUT = cmsStageAllocCLut16bit(Src ->ContextID, nGridPoints, Src ->InputChannels, Src->OutputChannels, NULL); if (CLUT == NULL) goto Error;
// Add the CLUT to the destination LUT if (!cmsPipelineInsertStage(Dest, cmsAT_END, CLUT)) { goto Error;
}
// Postlinearization tables are kept unless indicated by flags if (*dwFlags & cmsFLAGS_CLUT_POST_LINEARIZATION) {
// Get a pointer to the postlinearization if present
cmsStage* PostLin = cmsPipelineGetPtrToLastStage(Src);
// Check if suitable if (PostLin && cmsStageType(PostLin) == cmsSigCurveSetElemType) {
// Maybe this is a linear tram, so we can avoid the whole stuff if (!AllCurvesAreLinear(PostLin)) {
// All seems ok, proceed.
NewPostLin = cmsStageDup(PostLin); if (!cmsPipelineInsertStage(Dest, cmsAT_END, NewPostLin)) goto Error;
// In destination LUT, the sampling should be applied after this stage.
cmsPipelineUnlinkStage(Src, cmsAT_END, &KeepPostLin);
}
}
}
// Now its time to do the sampling. We have to ignore pre/post linearization // The source LUT without pre/post curves is passed as parameter. if (!cmsStageSampleCLut16bit(CLUT, XFormSampler16, (void*) Src, 0)) {
Error: // Ops, something went wrong, Restore stages if (KeepPreLin != NULL) { if (!cmsPipelineInsertStage(Src, cmsAT_BEGIN, KeepPreLin)) {
_cmsAssert(0); // This never happens
}
} if (KeepPostLin != NULL) { if (!cmsPipelineInsertStage(Src, cmsAT_END, KeepPostLin)) {
_cmsAssert(0); // This never happens
}
}
cmsPipelineFree(Dest); returnFALSE;
}
// Done.
if (KeepPreLin != NULL) cmsStageFree(KeepPreLin); if (KeepPostLin != NULL) cmsStageFree(KeepPostLin);
cmsPipelineFree(Src);
// ----------------------------------------------------------------------------------------------------------------------------------------------- // Fixes the gamma balancing of transform. This is described in my paper "Prelinearization Stages on // Color-Management Application-Specific Integrated Circuits (ASICs)" presented at NIP24. It only works // for RGB transforms. See the paper for more details // -----------------------------------------------------------------------------------------------------------------------------------------------
// Normalize endpoints by slope limiting max and min. This assures endpoints as well. // Descending curves are handled as well. static void SlopeLimiting(cmsToneCurve* g)
{ int BeginVal, EndVal; int AtBegin = (int) floor((cmsFloat64Number) g ->nEntries * 0.02 + 0.5); // Cutoff at 2% int AtEnd = (int) g ->nEntries - AtBegin - 1; // And 98%
cmsFloat64Number Val, Slope, beta; int i;
// Compute slope and offset for begin of curve
Val = g ->Table16[AtBegin];
Slope = (Val - BeginVal) / AtBegin;
beta = Val - Slope * AtBegin;
for (i=0; i < AtBegin; i++)
g ->Table16[i] = _cmsQuickSaturateWord(i * Slope + beta);
// Compute slope and offset for the end
Val = g ->Table16[AtEnd];
Slope = (EndVal - Val) / AtBegin; // AtBegin holds the X interval, which is same in both cases
beta = Val - Slope * AtEnd;
for (i = AtEnd; i < (int) g ->nEntries; i++)
g ->Table16[i] = _cmsQuickSaturateWord(i * Slope + beta);
}
// Precomputes tables for 8-bit on input devicelink. static
Prelin8Data* PrelinOpt8alloc(cmsContext ContextID, const cmsInterpParams* p, cmsToneCurve* G[3])
{ int i;
cmsUInt16Number Input[3];
cmsS15Fixed16Number v1, v2, v3;
Prelin8Data* p8;
p8 = (Prelin8Data*)_cmsMallocZero(ContextID, sizeof(Prelin8Data)); if (p8 == NULL) return NULL;
// Since this only works for 8 bit input, values comes always as x * 257, // we can safely take msb byte (x << 8 + x)
// Curves that contain wide empty areas are not optimizeable static
cmsBool IsDegenerated(const cmsToneCurve* g)
{
cmsUInt32Number i, Zeros = 0, Poles = 0;
cmsUInt32Number nEntries = g ->nEntries;
for (i=0; i < nEntries; i++) {
if (g ->Table16[i] == 0x0000) Zeros++; if (g ->Table16[i] == 0xffff) Poles++;
}
if (Zeros == 1 && Poles == 1) returnFALSE; // For linear tables if (Zeros > (nEntries / 20)) returnTRUE; // Degenerated, many zeros if (Poles > (nEntries / 20)) returnTRUE; // Degenerated, many poles
returnFALSE;
}
// -------------------------------------------------------------------------------------------------------------- // We need xput over here
// This is a lossy optimization! does not apply in floating-point cases if (_cmsFormatterIsFloat(*InputFormat) || _cmsFormatterIsFloat(*OutputFormat)) returnFALSE;
// Only on chunky RGB if (T_COLORSPACE(*InputFormat) != PT_RGB) returnFALSE; if (T_PLANAR(*InputFormat)) returnFALSE;
if (T_COLORSPACE(*OutputFormat) != PT_RGB) returnFALSE; if (T_PLANAR(*OutputFormat)) returnFALSE;
// On 16 bits, user has to specify the feature if (!_cmsFormatterIs8bit(*InputFormat)) { if (!(*dwFlags & cmsFLAGS_CLUT_PRE_LINEARIZATION)) returnFALSE;
}
// If the last stage of the original lut are curves, and those curves are // degenerated, it is likely the transform is squeezing and clipping // the output from previous CLUT. We cannot optimize this case
{
cmsStage* last = cmsPipelineGetPtrToLastStage(OriginalLut);
if (last == NULL) goto Error; if (cmsStageType(last) == cmsSigCurveSetElemType) {
_cmsStageToneCurvesData* Data = (_cmsStageToneCurvesData*)cmsStageData(last); for (i = 0; i < Data->nCurves; i++) { if (IsDegenerated(Data->TheCurves[i])) goto Error;
}
}
}
for (t = 0; t < OriginalLut ->InputChannels; t++) {
Trans[t] = cmsBuildTabulatedToneCurve16(OriginalLut ->ContextID, PRELINEARIZATION_POINTS, NULL); if (Trans[t] == NULL) goto Error;
}
// Populate the curves for (i=0; i < PRELINEARIZATION_POINTS; i++) {
v = (cmsFloat32Number) ((cmsFloat64Number) i / (PRELINEARIZATION_POINTS - 1));
// Feed input with a gray ramp for (t=0; t < OriginalLut ->InputChannels; t++)
In[t] = v;
// Evaluate the gray value
cmsPipelineEvalFloat(In, Out, OriginalLut);
// Store result in curve for (t=0; t < OriginalLut ->InputChannels; t++)
Trans[t] ->Table16[i] = _cmsQuickSaturateWord(Out[t] * 65535.0);
}
// Slope-limit the obtained curves for (t = 0; t < OriginalLut ->InputChannels; t++)
SlopeLimiting(Trans[t]);
// Check for validity
lIsSuitable = TRUE;
lIsLinear = TRUE; for (t=0; (lIsSuitable && (t < OriginalLut ->InputChannels)); t++) {
// Exclude if already linear if (!cmsIsToneCurveLinear(Trans[t]))
lIsLinear = FALSE;
// Exclude if non-monotonic if (!cmsIsToneCurveMonotonic(Trans[t]))
lIsSuitable = FALSE;
if (IsDegenerated(Trans[t]))
lIsSuitable = FALSE;
}
// If it is not suitable, just quit if (!lIsSuitable) goto Error;
// Invert curves if possible for (t = 0; t < OriginalLut ->InputChannels; t++) {
TransReverse[t] = cmsReverseToneCurveEx(PRELINEARIZATION_POINTS, Trans[t]); if (TransReverse[t] == NULL) goto Error;
}
// Now inset the reversed curves at the begin of transform
LutPlusCurves = cmsPipelineDup(OriginalLut); if (LutPlusCurves == NULL) goto Error;
if (!cmsPipelineInsertStage(LutPlusCurves, cmsAT_BEGIN, cmsStageAllocToneCurves(OriginalLut ->ContextID, OriginalLut ->InputChannels, TransReverse))) goto Error;
// Create the result LUT
OptimizedLUT = cmsPipelineAlloc(OriginalLut ->ContextID, OriginalLut ->InputChannels, OriginalLut ->OutputChannels); if (OptimizedLUT == NULL) goto Error;
for (i=0; i < Lut ->InputChannels; i++) {
Out[i] = In[i];
}
}
// If the target LUT holds only curves, the optimization procedure is to join all those // curves together. That only works on curves and does not work on matrices. static
cmsBool OptimizeByJoiningCurves(cmsPipeline** Lut, cmsUInt32Number Intent, cmsUInt32Number* InputFormat, cmsUInt32Number* OutputFormat, cmsUInt32Number* dwFlags)
{
cmsToneCurve** GammaTables = NULL;
cmsFloat32Number InFloat[cmsMAXCHANNELS], OutFloat[cmsMAXCHANNELS];
cmsUInt32Number i, j;
cmsPipeline* Src = *Lut;
cmsPipeline* Dest = NULL;
cmsStage* mpe;
cmsStage* ObtainedCurves = NULL;
// This is a lossy optimization! does not apply in floating-point cases if (_cmsFormatterIsFloat(*InputFormat) || _cmsFormatterIsFloat(*OutputFormat)) returnFALSE;
// Only curves in this LUT? for (mpe = cmsPipelineGetPtrToFirstStage(Src);
mpe != NULL;
mpe = cmsStageNext(mpe)) { if (cmsStageType(mpe) != cmsSigCurveSetElemType) returnFALSE;
}
// Allocate an empty LUT
Dest = cmsPipelineAlloc(Src ->ContextID, Src ->InputChannels, Src ->OutputChannels); if (Dest == NULL) returnFALSE;
// Maybe the curves are linear at the end if (!AllCurvesAreLinear(ObtainedCurves)) {
_cmsStageToneCurvesData* Data;
if (!cmsPipelineInsertStage(Dest, cmsAT_BEGIN, ObtainedCurves)) goto Error;
Data = (_cmsStageToneCurvesData*) cmsStageData(ObtainedCurves);
ObtainedCurves = NULL;
// If the curves are to be applied in 8 bits, we can save memory if (_cmsFormatterIs8bit(*InputFormat)) {
Curves16Data* c16 = CurvesAlloc(Dest ->ContextID, Data ->nCurves, 256, Data ->TheCurves);
// We are done.
cmsPipelineFree(Src);
*Lut = Dest; returnTRUE;
Error:
if (ObtainedCurves != NULL) cmsStageFree(ObtainedCurves); if (GammaTables != NULL) { for (i=0; i < Src ->InputChannels; i++) { if (GammaTables[i] != NULL) cmsFreeToneCurve(GammaTables[i]);
}
_cmsFree(Src ->ContextID, GammaTables);
}
if (Dest != NULL) cmsPipelineFree(Dest); returnFALSE;
// ------------------------------------------------------------------------------------------------------------------------------------- // LUT is Shaper - Matrix - Matrix - Shaper, which is very frequent when combining two matrix-shaper profiles
// A fast matrix-shaper evaluator for 8 bits. This is a bit tricky since I'm using 1.14 signed fixed point // to accomplish some performance. Actually it takes 256x3 16 bits tables and 16385 x 3 tables of 8 bits, // in total about 50K, and the performance boost is huge! static CMS_NO_SANITIZE void MatShaperEval16(CMSREGISTER const cmsUInt16Number In[],
CMSREGISTER cmsUInt16Number Out[],
CMSREGISTER constvoid* D)
{
MatShaper8Data* p = (MatShaper8Data*) D;
cmsS1Fixed14Number l1, l2, l3, r, g, b;
cmsUInt32Number ri, gi, bi;
// In this case (and only in this case!) we can use this simplification since // In[] is assured to come from a 8 bit number. (a << 8 | a)
ri = In[0] & 0xFFU;
gi = In[1] & 0xFFU;
bi = In[2] & 0xFFU;
// Across first shaper, which also converts to 1.14 fixed point
r = p->Shaper1R[ri];
g = p->Shaper1G[gi];
b = p->Shaper1B[bi];
// Evaluate the matrix in 1.14 fixed point
l1 = (p->Mat[0][0] * r + p->Mat[0][1] * g + p->Mat[0][2] * b + p->Off[0] + 0x2000) >> 14;
l2 = (p->Mat[1][0] * r + p->Mat[1][1] * g + p->Mat[1][2] * b + p->Off[1] + 0x2000) >> 14;
l3 = (p->Mat[2][0] * r + p->Mat[2][1] * g + p->Mat[2][2] * b + p->Off[2] + 0x2000) >> 14;
// Now we have to clip to 0..1.0 range
ri = (l1 < 0) ? 0 : ((l1 > 16384) ? 16384U : (cmsUInt32Number) l1);
gi = (l2 < 0) ? 0 : ((l2 > 16384) ? 16384U : (cmsUInt32Number) l2);
bi = (l3 < 0) ? 0 : ((l3 > 16384) ? 16384U : (cmsUInt32Number) l3);
// And across second shaper,
Out[0] = p->Shaper2R[ri];
Out[1] = p->Shaper2G[gi];
Out[2] = p->Shaper2B[bi];
}
// This table converts from 8 bits to 1.14 after applying the curve static void FillFirstShaper(cmsS1Fixed14Number* Table, cmsToneCurve* Curve)
{ int i;
cmsFloat32Number R, y;
for (i=0; i < 256; i++) {
R = (cmsFloat32Number) (i / 255.0);
y = cmsEvalToneCurveFloat(Curve, R);
// This table converts form 1.14 (being 0x4000 the last entry) to 8 bits after applying the curve static void FillSecondShaper(cmsUInt16Number* Table, cmsToneCurve* Curve, cmsBool Is8BitsOutput)
{ int i;
cmsFloat32Number R, Val;
for (i=0; i < 16385; i++) {
R = (cmsFloat32Number) (i / 16384.0);
Val = cmsEvalToneCurveFloat(Curve, R); // Val comes 0..1.0
if (Val < 0)
Val = 0;
if (Val > 1.0)
Val = 1.0;
if (Is8BitsOutput) {
// If 8 bits output, we can optimize further by computing the / 257 part. // first we compute the resulting byte and then we store the byte times // 257. This quantization allows to round very quick by doing a >> 8, but // since the low byte is always equal to msb, we can do a & 0xff and this works!
cmsUInt16Number w = _cmsQuickSaturateWord(Val * 65535.0);
cmsUInt8Number b = FROM_16_TO_8(w);
// Compute the matrix-shaper structure static
cmsBool SetMatShaper(cmsPipeline* Dest, cmsToneCurve* Curve1[3], cmsMAT3* Mat, cmsVEC3* Off, cmsToneCurve* Curve2[3], cmsUInt32Number* OutputFormat)
{
MatShaper8Data* p; int i, j;
cmsBool Is8Bits = _cmsFormatterIs8bit(*OutputFormat);
// Allocate a big chuck of memory to store precomputed tables
p = (MatShaper8Data*) _cmsMalloc(Dest ->ContextID, sizeof(MatShaper8Data)); if (p == NULL) returnFALSE;
// Convert matrix to nFixed14. Note that those values may take more than 16 bits for (i=0; i < 3; i++) { for (j=0; j < 3; j++) {
p ->Mat[i][j] = DOUBLE_TO_1FIXED14(Mat->v[i].n[j]);
}
}
for (i=0; i < 3; i++) {
if (Off == NULL) {
p ->Off[i] = 0;
} else {
p ->Off[i] = DOUBLE_TO_1FIXED14(Off->n[i]);
}
}
// Mark as optimized for faster formatter if (Is8Bits)
*OutputFormat |= OPTIMIZED_SH(1);
// Fill function pointers
_cmsPipelineSetOptimizationParameters(Dest, MatShaperEval16, (void*) p, FreeMatShaper, DupMatShaper); returnTRUE;
}
// 8 bits on input allows matrix-shaper boot up to 25 Mpixels per second on RGB. That's fast! static
cmsBool OptimizeMatrixShaper(cmsPipeline** Lut, cmsUInt32Number Intent, cmsUInt32Number* InputFormat, cmsUInt32Number* OutputFormat, cmsUInt32Number* dwFlags)
{
cmsStage* Curve1, *Curve2;
cmsStage* Matrix1, *Matrix2;
cmsMAT3 res;
cmsBool IdentityMat;
cmsPipeline* Dest, *Src;
cmsFloat64Number* Offset;
// Only works on RGB to RGB if (T_CHANNELS(*InputFormat) != 3 || T_CHANNELS(*OutputFormat) != 3) returnFALSE;
// Only works on 8 bit input if (!_cmsFormatterIs8bit(*InputFormat)) returnFALSE;
// Seems suitable, proceed
Src = *Lut;
// Check for: // // shaper-matrix-matrix-shaper // shaper-matrix-shaper // // Both of those constructs are possible (first because abs. colorimetric). // additionally, In the first case, the input matrix offset should be zero.
// In this particular optimization, cache does not help as it takes more time to deal with // the cache that with the pixel handling
*dwFlags |= cmsFLAGS_NOCACHE;
// List of optimizations typedefstruct _cmsOptimizationCollection_st {
_cmsOPToptimizeFn OptimizePtr;
struct _cmsOptimizationCollection_st *Next;
} _cmsOptimizationCollection;
// The built-in list. We currently implement 4 types of optimizations. Joining of curves, matrix-shaper, linearization and resampling static _cmsOptimizationCollection DefaultOptimization[] = {
// The linked list head
_cmsOptimizationPluginChunkType _cmsOptimizationPluginChunk = { NULL };
// Duplicates the zone of memory used by the plug-in in the new context static void DupPluginOptimizationList(struct _cmsContext_struct* ctx, conststruct _cmsContext_struct* src)
{
_cmsOptimizationPluginChunkType newHead = { NULL };
_cmsOptimizationCollection* entry;
_cmsOptimizationCollection* Anterior = NULL;
_cmsOptimizationPluginChunkType* head = (_cmsOptimizationPluginChunkType*) src->chunks[OptimizationPlugin];
// Anything to optimize? if ((*PtrLut) ->Elements == NULL) {
_cmsPipelineSetOptimizationParameters(*PtrLut, FastIdentity16, (void*) *PtrLut, NULL, NULL); returnTRUE;
}
// Named color pipelines cannot be optimized for (mpe = cmsPipelineGetPtrToFirstStage(*PtrLut);
mpe != NULL;
mpe = cmsStageNext(mpe)) { if (cmsStageType(mpe) == cmsSigNamedColorElemType) returnFALSE;
}
// Try to get rid of identities and trivial conversions.
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.26 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.