1427 lines
40 KiB
C
Executable File
1427 lines
40 KiB
C
Executable File
//---------------------------------------------------------------------------------
|
|
//
|
|
// Little Color Management System
|
|
// Copyright (c) 1998-2017 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 "testcms2.h"
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Auxiliary, duplicate a context and mark the block as non-debug because in this case the allocator
|
|
// and deallocator have different context owners
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
static
|
|
cmsContext DupContext(cmsContext src, void* Data)
|
|
{
|
|
cmsContext cpy = cmsDupContext(src, Data);
|
|
|
|
DebugMemDontCheckThis(cpy);
|
|
|
|
return cpy;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Simple context functions
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
// Allocation order
|
|
cmsInt32Number CheckAllocContext(void)
|
|
{
|
|
cmsContext c1, c2, c3, c4;
|
|
|
|
c1 = cmsCreateContext(NULL, NULL); // This creates a context by using the normal malloc
|
|
DebugMemDontCheckThis(c1);
|
|
cmsDeleteContext(c1);
|
|
|
|
c2 = cmsCreateContext(PluginMemHandler(), NULL); // This creates a context by using the debug malloc
|
|
DebugMemDontCheckThis(c2);
|
|
cmsDeleteContext(c2);
|
|
|
|
c1 = cmsCreateContext(NULL, NULL);
|
|
DebugMemDontCheckThis(c1);
|
|
|
|
c2 = cmsCreateContext(PluginMemHandler(), NULL);
|
|
DebugMemDontCheckThis(c2);
|
|
|
|
cmsPlugin(c1, PluginMemHandler()); // Now the context have custom allocators
|
|
|
|
c3 = DupContext(c1, NULL);
|
|
c4 = DupContext(c2, NULL);
|
|
|
|
cmsDeleteContext(c1); // Should be deleted by using nomal malloc
|
|
cmsDeleteContext(c2); // Should be deleted by using debug malloc
|
|
cmsDeleteContext(c3); // Should be deleted by using nomal malloc
|
|
cmsDeleteContext(c4); // Should be deleted by using debug malloc
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Test the very basic context capabilities
|
|
cmsInt32Number CheckSimpleContext(void)
|
|
{
|
|
int a = 1;
|
|
int b = 32;
|
|
cmsInt32Number rc = 0;
|
|
|
|
cmsContext c1, c2, c3;
|
|
|
|
// This function creates a context with a special
|
|
// memory manager that check allocation
|
|
c1 = WatchDogContext(&a);
|
|
cmsDeleteContext(c1);
|
|
|
|
c1 = WatchDogContext(&a);
|
|
|
|
// Let's check duplication
|
|
c2 = DupContext(c1, NULL);
|
|
c3 = DupContext(c2, NULL);
|
|
|
|
// User data should have been propagated
|
|
rc = (*(int*) cmsGetContextUserData(c3)) == 1 ;
|
|
|
|
// Free resources
|
|
cmsDeleteContext(c1);
|
|
cmsDeleteContext(c2);
|
|
cmsDeleteContext(c3);
|
|
|
|
if (!rc) {
|
|
Fail("Creation of user data failed");
|
|
return 0;
|
|
}
|
|
|
|
// Back to create 3 levels of inherance
|
|
c1 = cmsCreateContext(NULL, &a);
|
|
DebugMemDontCheckThis(c1);
|
|
|
|
c2 = DupContext(c1, NULL);
|
|
c3 = DupContext(c2, &b);
|
|
|
|
rc = (*(int*) cmsGetContextUserData(c3)) == 32 ;
|
|
|
|
cmsDeleteContext(c1);
|
|
cmsDeleteContext(c2);
|
|
cmsDeleteContext(c3);
|
|
|
|
if (!rc) {
|
|
Fail("Modification of user data failed");
|
|
return 0;
|
|
}
|
|
|
|
// All seems ok
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
//Alarm color functions
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
// This function tests the alarm codes across contexts
|
|
cmsInt32Number CheckAlarmColorsContext(void)
|
|
{
|
|
cmsInt32Number rc = 0;
|
|
const cmsUInt16Number codes[] = {0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888, 0x9999, 0xaaaa, 0xbbbb, 0xcccc, 0xdddd, 0xeeee, 0xffff};
|
|
cmsUInt16Number out[16];
|
|
cmsContext c1, c2, c3;
|
|
int i;
|
|
|
|
c1 = WatchDogContext(NULL);
|
|
|
|
cmsSetAlarmCodes(c1, codes);
|
|
c2 = DupContext(c1, NULL);
|
|
c3 = DupContext(c2, NULL);
|
|
|
|
cmsGetAlarmCodes(c3, out);
|
|
|
|
rc = 1;
|
|
for (i=0; i < 16; i++) {
|
|
if (out[i] != codes[i]) {
|
|
Fail("Bad alarm code %x != %x", out[i], codes[i]);
|
|
rc = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
cmsDeleteContext(c1);
|
|
cmsDeleteContext(c2);
|
|
cmsDeleteContext(c3);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
//Adaptation state functions
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
// Similar to the previous, but for adaptation state
|
|
cmsInt32Number CheckAdaptationStateContext(void)
|
|
{
|
|
cmsInt32Number rc = 0;
|
|
cmsContext c1, c2, c3;
|
|
cmsFloat64Number old1, old2;
|
|
|
|
old1 = cmsSetAdaptationState(NULL, -1);
|
|
|
|
c1 = WatchDogContext(NULL);
|
|
|
|
cmsSetAdaptationState(c1, 0.7);
|
|
|
|
c2 = DupContext(c1, NULL);
|
|
c3 = DupContext(c2, NULL);
|
|
|
|
rc = IsGoodVal("Adaptation state", cmsSetAdaptationState(c3, -1), 0.7, 0.001);
|
|
|
|
cmsDeleteContext(c1);
|
|
cmsDeleteContext(c2);
|
|
cmsDeleteContext(c3);
|
|
|
|
old2 = cmsSetAdaptationState(NULL, -1);
|
|
|
|
if (old1 != old2) {
|
|
Fail("Adaptation state has changed");
|
|
return 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Interpolation plugin check: A fake 1D and 3D interpolation will be used to test the functionality.
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
// This fake interpolation takes always the closest lower node in the interpolation table for 1D
|
|
static
|
|
void Fake1Dfloat(cmsContext ContextID, const cmsFloat32Number Value[],
|
|
cmsFloat32Number Output[],
|
|
const cmsInterpParams* p)
|
|
{
|
|
cmsFloat32Number val2;
|
|
int cell;
|
|
const cmsFloat32Number* LutTable = (const cmsFloat32Number*) p ->Table;
|
|
|
|
// Clip upper values
|
|
if (Value[0] >= 1.0) {
|
|
Output[0] = LutTable[p -> Domain[0]];
|
|
return;
|
|
}
|
|
|
|
val2 = p -> Domain[0] * Value[0];
|
|
cell = (int) floor(val2);
|
|
Output[0] = LutTable[cell] ;
|
|
}
|
|
|
|
// This fake interpolation just uses scrambled negated indexes for output
|
|
static
|
|
void Fake3D16(cmsContext ContextID, register const cmsUInt16Number Input[],
|
|
register cmsUInt16Number Output[],
|
|
register const struct _cms_interp_struc* p)
|
|
{
|
|
Output[0] = 0xFFFF - Input[2];
|
|
Output[1] = 0xFFFF - Input[1];
|
|
Output[2] = 0xFFFF - Input[0];
|
|
}
|
|
|
|
// The factory chooses interpolation routines on depending on certain conditions.
|
|
cmsInterpFunction my_Interpolators_Factory(cmsContext ContextID, cmsUInt32Number nInputChannels,
|
|
cmsUInt32Number nOutputChannels,
|
|
cmsUInt32Number dwFlags)
|
|
{
|
|
cmsInterpFunction Interpolation;
|
|
cmsBool IsFloat = (dwFlags & CMS_LERP_FLAGS_FLOAT);
|
|
|
|
// Initialize the return to zero as a non-supported mark
|
|
memset(&Interpolation, 0, sizeof(Interpolation));
|
|
|
|
// For 1D to 1D and floating point
|
|
if (nInputChannels == 1 && nOutputChannels == 1 && IsFloat) {
|
|
|
|
Interpolation.LerpFloat = Fake1Dfloat;
|
|
}
|
|
else
|
|
if (nInputChannels == 3 && nOutputChannels == 3 && !IsFloat) {
|
|
|
|
// For 3D to 3D and 16 bits
|
|
Interpolation.Lerp16 = Fake3D16;
|
|
}
|
|
|
|
// Here is the interpolation
|
|
return Interpolation;
|
|
}
|
|
|
|
// Interpolation plug-in
|
|
static
|
|
cmsPluginInterpolation InterpPluginSample = {
|
|
|
|
{ cmsPluginMagicNumber, 2060, cmsPluginInterpolationSig, NULL },
|
|
my_Interpolators_Factory
|
|
};
|
|
|
|
|
|
// This is the check code for 1D interpolation plug-in
|
|
cmsInt32Number CheckInterp1DPlugin(void)
|
|
{
|
|
cmsToneCurve* Sampled1D = NULL;
|
|
cmsContext ctx = NULL;
|
|
cmsContext cpy = NULL;
|
|
const cmsFloat32Number tab[] = { 0.0f, 0.10f, 0.20f, 0.30f, 0.40f, 0.50f, 0.60f, 0.70f, 0.80f, 0.90f, 1.00f }; // A straight line
|
|
|
|
// 1st level context
|
|
ctx = WatchDogContext(NULL);
|
|
if (ctx == NULL) {
|
|
Fail("Cannot create context");
|
|
goto Error;
|
|
}
|
|
|
|
cmsPlugin(ctx, &InterpPluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
if (cpy == NULL) {
|
|
Fail("Cannot create context (2)");
|
|
goto Error;
|
|
}
|
|
|
|
Sampled1D = cmsBuildTabulatedToneCurveFloat(cpy, 11, tab);
|
|
if (Sampled1D == NULL) {
|
|
Fail("Cannot create tone curve (1)");
|
|
goto Error;
|
|
}
|
|
|
|
// Do some interpolations with the plugin
|
|
if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.10f), 0.10, 0.01)) goto Error;
|
|
if (!IsGoodVal("0.13", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.13f), 0.10, 0.01)) goto Error;
|
|
if (!IsGoodVal("0.55", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.55f), 0.50, 0.01)) goto Error;
|
|
if (!IsGoodVal("0.9999", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.9999f), 0.90, 0.01)) goto Error;
|
|
|
|
cmsFreeToneCurve(cpy, Sampled1D);
|
|
cmsDeleteContext(ctx);
|
|
cmsDeleteContext(cpy);
|
|
|
|
// Now in global context
|
|
Sampled1D = cmsBuildTabulatedToneCurveFloat(NULL, 11, tab);
|
|
if (Sampled1D == NULL) {
|
|
Fail("Cannot create tone curve (2)");
|
|
goto Error;
|
|
}
|
|
|
|
// Now without the plug-in
|
|
if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.10f), 0.10, 0.001)) goto Error;
|
|
if (!IsGoodVal("0.13", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.13f), 0.13, 0.001)) goto Error;
|
|
if (!IsGoodVal("0.55", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.55f), 0.55, 0.001)) goto Error;
|
|
if (!IsGoodVal("0.9999", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.9999f), 0.9999, 0.001)) goto Error;
|
|
|
|
cmsFreeToneCurve(NULL, Sampled1D);
|
|
return 1;
|
|
|
|
Error:
|
|
if (ctx != NULL) cmsDeleteContext(ctx);
|
|
if (cpy != NULL) cmsDeleteContext(ctx);
|
|
if (Sampled1D != NULL) cmsFreeToneCurve(NULL, Sampled1D);
|
|
return 0;
|
|
|
|
}
|
|
|
|
// Checks the 3D interpolation
|
|
cmsInt32Number CheckInterp3DPlugin(void)
|
|
{
|
|
|
|
cmsPipeline* p;
|
|
cmsStage* clut;
|
|
cmsContext ctx;
|
|
cmsUInt16Number In[3], Out[3];
|
|
cmsUInt16Number identity[] = {
|
|
|
|
0, 0, 0,
|
|
0, 0, 0xffff,
|
|
0, 0xffff, 0,
|
|
0, 0xffff, 0xffff,
|
|
0xffff, 0, 0,
|
|
0xffff, 0, 0xffff,
|
|
0xffff, 0xffff, 0,
|
|
0xffff, 0xffff, 0xffff
|
|
};
|
|
|
|
|
|
ctx = WatchDogContext(NULL);
|
|
if (ctx == NULL) {
|
|
Fail("Cannot create context");
|
|
return 0;
|
|
}
|
|
|
|
cmsPlugin(ctx, &InterpPluginSample);
|
|
|
|
p = cmsPipelineAlloc(ctx, 3, 3);
|
|
clut = cmsStageAllocCLut16bit(ctx, 2, 3, 3, identity);
|
|
cmsPipelineInsertStage(ctx, p, cmsAT_BEGIN, clut);
|
|
|
|
// Do some interpolations with the plugin
|
|
|
|
In[0] = 0; In[1] = 0; In[2] = 0;
|
|
cmsPipelineEval16(ctx, In, Out, p);
|
|
|
|
if (!IsGoodWord("0", Out[0], 0xFFFF - 0)) goto Error;
|
|
if (!IsGoodWord("1", Out[1], 0xFFFF - 0)) goto Error;
|
|
if (!IsGoodWord("2", Out[2], 0xFFFF - 0)) goto Error;
|
|
|
|
In[0] = 0x1234; In[1] = 0x5678; In[2] = 0x9ABC;
|
|
cmsPipelineEval16(ctx, In, Out, p);
|
|
|
|
if (!IsGoodWord("0", 0xFFFF - 0x9ABC, Out[0])) goto Error;
|
|
if (!IsGoodWord("1", 0xFFFF - 0x5678, Out[1])) goto Error;
|
|
if (!IsGoodWord("2", 0xFFFF - 0x1234, Out[2])) goto Error;
|
|
|
|
cmsPipelineFree(ctx, p);
|
|
cmsDeleteContext(ctx);
|
|
|
|
// Now without the plug-in
|
|
|
|
p = cmsPipelineAlloc(NULL, 3, 3);
|
|
clut = cmsStageAllocCLut16bit(NULL, 2, 3, 3, identity);
|
|
cmsPipelineInsertStage(NULL, p, cmsAT_BEGIN, clut);
|
|
|
|
In[0] = 0; In[1] = 0; In[2] = 0;
|
|
cmsPipelineEval16(NULL, In, Out, p);
|
|
|
|
if (!IsGoodWord("0", 0, Out[0])) goto Error;
|
|
if (!IsGoodWord("1", 0, Out[1])) goto Error;
|
|
if (!IsGoodWord("2", 0, Out[2])) goto Error;
|
|
|
|
In[0] = 0x1234; In[1] = 0x5678; In[2] = 0x9ABC;
|
|
cmsPipelineEval16(NULL, In, Out, p);
|
|
|
|
if (!IsGoodWord("0", 0x1234, Out[0])) goto Error;
|
|
if (!IsGoodWord("1", 0x5678, Out[1])) goto Error;
|
|
if (!IsGoodWord("2", 0x9ABC, Out[2])) goto Error;
|
|
|
|
cmsPipelineFree(NULL, p);
|
|
return 1;
|
|
|
|
Error:
|
|
cmsPipelineFree(NULL, p);
|
|
return 0;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Parametric curve plugin check: sin(x)/cos(x) function will be used to test the functionality.
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
#define TYPE_SIN 1000
|
|
#define TYPE_COS 1010
|
|
#define TYPE_TAN 1020
|
|
#define TYPE_709 709
|
|
|
|
static cmsFloat64Number my_fns(cmsContext ContextID, cmsInt32Number Type,
|
|
const cmsFloat64Number Params[],
|
|
cmsFloat64Number R)
|
|
{
|
|
cmsFloat64Number Val;
|
|
switch (Type) {
|
|
|
|
case TYPE_SIN:
|
|
Val = Params[0]* sin(R * M_PI);
|
|
break;
|
|
|
|
case -TYPE_SIN:
|
|
Val = asin(R) / (M_PI * Params[0]);
|
|
break;
|
|
|
|
case TYPE_COS:
|
|
Val = Params[0]* cos(R * M_PI);
|
|
break;
|
|
|
|
case -TYPE_COS:
|
|
Val = acos(R) / (M_PI * Params[0]);
|
|
break;
|
|
|
|
default: return -1.0;
|
|
|
|
}
|
|
|
|
return Val;
|
|
}
|
|
|
|
static
|
|
cmsFloat64Number my_fns2(cmsContext ContextID, cmsInt32Number Type,
|
|
const cmsFloat64Number Params[],
|
|
cmsFloat64Number R)
|
|
{
|
|
cmsFloat64Number Val;
|
|
switch (Type) {
|
|
|
|
case TYPE_TAN:
|
|
Val = Params[0]* tan(R * M_PI);
|
|
break;
|
|
|
|
case -TYPE_TAN:
|
|
Val = atan(R) / (M_PI * Params[0]);
|
|
break;
|
|
|
|
default: return -1.0;
|
|
}
|
|
|
|
return Val;
|
|
}
|
|
|
|
|
|
static double Rec709Math(cmsContext ContextID, int Type, const double Params[], double R)
|
|
{
|
|
double Fun = 0;
|
|
|
|
switch (Type)
|
|
{
|
|
case 709:
|
|
|
|
if (R <= (Params[3]*Params[4])) Fun = R / Params[3];
|
|
else Fun = pow(((R - Params[2])/Params[1]), Params[0]);
|
|
break;
|
|
|
|
case -709:
|
|
|
|
if (R <= Params[4]) Fun = R * Params[3];
|
|
else Fun = Params[1] * pow(R, (1/Params[0])) + Params[2];
|
|
break;
|
|
}
|
|
return Fun;
|
|
}
|
|
|
|
|
|
// Add nonstandard TRC curves -> Rec709
|
|
|
|
cmsPluginParametricCurves Rec709Plugin = {
|
|
|
|
{ cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL },
|
|
|
|
1, {TYPE_709}, {5}, Rec709Math
|
|
|
|
};
|
|
|
|
|
|
static
|
|
cmsPluginParametricCurves CurvePluginSample = {
|
|
{ cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL },
|
|
|
|
2, // nFunctions
|
|
{ TYPE_SIN, TYPE_COS }, // Function Types
|
|
{ 1, 1 }, // ParameterCount
|
|
my_fns // Evaluator
|
|
};
|
|
|
|
static
|
|
cmsPluginParametricCurves CurvePluginSample2 = {
|
|
{ cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL },
|
|
|
|
1, // nFunctions
|
|
{ TYPE_TAN}, // Function Types
|
|
{ 1 }, // ParameterCount
|
|
my_fns2 // Evaluator
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// In this test, the DupContext function will be checked as well
|
|
// --------------------------------------------------------------------------------------------------
|
|
cmsInt32Number CheckParametricCurvePlugin(void)
|
|
{
|
|
cmsContext ctx = NULL;
|
|
cmsContext cpy = NULL;
|
|
cmsToneCurve* sinus;
|
|
cmsToneCurve* cosinus;
|
|
cmsToneCurve* tangent;
|
|
cmsToneCurve* reverse_sinus;
|
|
cmsToneCurve* reverse_cosinus;
|
|
cmsFloat64Number scale = 1.0;
|
|
|
|
ctx = WatchDogContext(NULL);
|
|
|
|
cmsPlugin(ctx, &CurvePluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
|
|
cmsPlugin(cpy, &CurvePluginSample2);
|
|
|
|
sinus = cmsBuildParametricToneCurve(cpy, TYPE_SIN, &scale);
|
|
cosinus = cmsBuildParametricToneCurve(cpy, TYPE_COS, &scale);
|
|
tangent = cmsBuildParametricToneCurve(cpy, TYPE_TAN, &scale);
|
|
reverse_sinus = cmsReverseToneCurve(cpy, sinus);
|
|
reverse_cosinus = cmsReverseToneCurve(cpy, cosinus);
|
|
|
|
|
|
if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, sinus, 0.10f), sin(0.10 * M_PI) , 0.001)) goto Error;
|
|
if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, sinus, 0.60f), sin(0.60* M_PI), 0.001)) goto Error;
|
|
if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, sinus, 0.90f), sin(0.90* M_PI), 0.001)) goto Error;
|
|
|
|
if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, cosinus, 0.10f), cos(0.10* M_PI), 0.001)) goto Error;
|
|
if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, cosinus, 0.60f), cos(0.60* M_PI), 0.001)) goto Error;
|
|
if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, cosinus, 0.90f), cos(0.90* M_PI), 0.001)) goto Error;
|
|
|
|
if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, tangent, 0.10f), tan(0.10* M_PI), 0.001)) goto Error;
|
|
if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, tangent, 0.60f), tan(0.60* M_PI), 0.001)) goto Error;
|
|
if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, tangent, 0.90f), tan(0.90* M_PI), 0.001)) goto Error;
|
|
|
|
|
|
if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, reverse_sinus, 0.10f), asin(0.10)/M_PI, 0.001)) goto Error;
|
|
if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, reverse_sinus, 0.60f), asin(0.60)/M_PI, 0.001)) goto Error;
|
|
if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, reverse_sinus, 0.90f), asin(0.90)/M_PI, 0.001)) goto Error;
|
|
|
|
if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cpy, reverse_cosinus, 0.10f), acos(0.10)/M_PI, 0.001)) goto Error;
|
|
if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cpy, reverse_cosinus, 0.60f), acos(0.60)/M_PI, 0.001)) goto Error;
|
|
if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cpy, reverse_cosinus, 0.90f), acos(0.90)/M_PI, 0.001)) goto Error;
|
|
|
|
cmsFreeToneCurve(cpy, sinus);
|
|
cmsFreeToneCurve(cpy, cosinus);
|
|
cmsFreeToneCurve(cpy, tangent);
|
|
cmsFreeToneCurve(cpy, reverse_sinus);
|
|
cmsFreeToneCurve(cpy, reverse_cosinus);
|
|
|
|
cmsDeleteContext(ctx);
|
|
cmsDeleteContext(cpy);
|
|
|
|
return 1;
|
|
|
|
Error:
|
|
|
|
cmsFreeToneCurve(cpy, sinus);
|
|
cmsFreeToneCurve(cpy, reverse_sinus);
|
|
cmsFreeToneCurve(cpy, cosinus);
|
|
cmsFreeToneCurve(cpy, reverse_cosinus);
|
|
|
|
if (ctx != NULL) cmsDeleteContext(ctx);
|
|
if (cpy != NULL) cmsDeleteContext(cpy);
|
|
return 0;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// formatters plugin check: 5-6-5 RGB format
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
// We define this special type as 0 bytes not float, and set the upper bit
|
|
|
|
#define TYPE_RGB_565 (COLORSPACE_SH(PT_RGB)|CHANNELS_SH(3)|BYTES_SH(0) | (1 << 23))
|
|
|
|
cmsUInt8Number* my_Unroll565(cmsContext ContextID, register struct _cmstransform_struct* nfo,
|
|
register cmsUInt16Number wIn[],
|
|
register cmsUInt8Number* accum,
|
|
register cmsUInt32Number Stride)
|
|
{
|
|
cmsUInt16Number pixel = *(cmsUInt16Number*) accum; // Take whole pixel
|
|
|
|
double r = floor(((double) (pixel & 31) * 65535.0) / 31.0 + 0.5);
|
|
double g = floor((((pixel >> 5) & 63) * 65535.0) / 63.0 + 0.5);
|
|
double b = floor((((pixel >> 11) & 31) * 65535.0) / 31.0 + 0.5);
|
|
|
|
wIn[2] = (cmsUInt16Number) r;
|
|
wIn[1] = (cmsUInt16Number) g;
|
|
wIn[0] = (cmsUInt16Number) b;
|
|
|
|
return accum + 2;
|
|
}
|
|
|
|
cmsUInt8Number* my_Pack565(cmsContext ContextID, register _cmsTRANSFORM* info,
|
|
register cmsUInt16Number wOut[],
|
|
register cmsUInt8Number* output,
|
|
register cmsUInt32Number Stride)
|
|
{
|
|
|
|
register cmsUInt16Number pixel;
|
|
int r, g, b;
|
|
|
|
r = (int) floor(( wOut[2] * 31) / 65535.0 + 0.5);
|
|
g = (int) floor(( wOut[1] * 63) / 65535.0 + 0.5);
|
|
b = (int) floor(( wOut[0] * 31) / 65535.0 + 0.5);
|
|
|
|
|
|
pixel = (r & 31) | (( g & 63) << 5) | ((b & 31) << 11);
|
|
|
|
|
|
*(cmsUInt16Number*) output = pixel;
|
|
return output + 2;
|
|
}
|
|
|
|
|
|
cmsFormatter my_FormatterFactory(cmsContext ContextID, cmsUInt32Number Type,
|
|
cmsFormatterDirection Dir,
|
|
cmsUInt32Number dwFlags)
|
|
{
|
|
cmsFormatter Result = { NULL };
|
|
|
|
if ((Type == TYPE_RGB_565) &&
|
|
!(dwFlags & CMS_PACK_FLAGS_FLOAT) &&
|
|
(Dir == cmsFormatterInput)) {
|
|
Result.Fmt16 = my_Unroll565;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
|
|
cmsFormatter my_FormatterFactory2(cmsContext ContextID, cmsUInt32Number Type,
|
|
cmsFormatterDirection Dir,
|
|
cmsUInt32Number dwFlags)
|
|
{
|
|
cmsFormatter Result = { NULL };
|
|
|
|
if ((Type == TYPE_RGB_565) &&
|
|
!(dwFlags & CMS_PACK_FLAGS_FLOAT) &&
|
|
(Dir == cmsFormatterOutput)) {
|
|
Result.Fmt16 = my_Pack565;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static
|
|
cmsPluginFormatters FormattersPluginSample = { {cmsPluginMagicNumber,
|
|
2060,
|
|
cmsPluginFormattersSig,
|
|
NULL},
|
|
my_FormatterFactory };
|
|
|
|
|
|
|
|
static
|
|
cmsPluginFormatters FormattersPluginSample2 = { {cmsPluginMagicNumber,
|
|
2060,
|
|
cmsPluginFormattersSig,
|
|
NULL},
|
|
my_FormatterFactory2 };
|
|
|
|
|
|
cmsInt32Number CheckFormattersPlugin(void)
|
|
{
|
|
cmsContext ctx = WatchDogContext(NULL);
|
|
cmsContext cpy;
|
|
cmsHTRANSFORM xform;
|
|
cmsUInt16Number stream[]= { 0xffffU, 0x1234U, 0x0000U, 0x33ddU };
|
|
cmsUInt16Number result[4];
|
|
int i;
|
|
|
|
cmsPlugin(ctx, &FormattersPluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
|
|
cmsPlugin(cpy, &FormattersPluginSample2);
|
|
|
|
xform = cmsCreateTransform(cpy, NULL, TYPE_RGB_565, NULL, TYPE_RGB_565, INTENT_PERCEPTUAL, cmsFLAGS_NULLTRANSFORM);
|
|
|
|
cmsDoTransform(cpy, xform, stream, result, 4);
|
|
|
|
cmsDeleteTransform(cpy, xform);
|
|
cmsDeleteContext(ctx);
|
|
cmsDeleteContext(cpy);
|
|
|
|
for (i=0; i < 4; i++)
|
|
if (stream[i] != result[i]) return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// TagTypePlugin plugin check
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
#define SigIntType ((cmsTagTypeSignature) 0x74747448) // 'tttH'
|
|
#define SigInt ((cmsTagSignature) 0x74747448) // 'tttH'
|
|
|
|
static
|
|
void *Type_int_Read(cmsContext ContextID, struct _cms_typehandler_struct* self,
|
|
cmsIOHANDLER* io,
|
|
cmsUInt32Number* nItems,
|
|
cmsUInt32Number SizeOfTag)
|
|
{
|
|
cmsUInt32Number* Ptr = (cmsUInt32Number*) _cmsMalloc(ContextID, sizeof(cmsUInt32Number));
|
|
if (Ptr == NULL) return NULL;
|
|
if (!_cmsReadUInt32Number(ContextID, io, Ptr)) return NULL;
|
|
*nItems = 1;
|
|
return Ptr;
|
|
}
|
|
|
|
static
|
|
cmsBool Type_int_Write(cmsContext ContextID, struct _cms_typehandler_struct* self,
|
|
cmsIOHANDLER* io,
|
|
void* Ptr, cmsUInt32Number nItems)
|
|
{
|
|
return _cmsWriteUInt32Number(ContextID, io, *(cmsUInt32Number*) Ptr);
|
|
}
|
|
|
|
static
|
|
void* Type_int_Dup(cmsContext ContextID, struct _cms_typehandler_struct* self,
|
|
const void *Ptr, cmsUInt32Number n)
|
|
{
|
|
return _cmsDupMem(ContextID, Ptr, n * sizeof(cmsUInt32Number));
|
|
}
|
|
|
|
void Type_int_Free(cmsContext ContextID, struct _cms_typehandler_struct* self,
|
|
void* Ptr)
|
|
{
|
|
_cmsFree(ContextID, Ptr);
|
|
}
|
|
|
|
|
|
static cmsPluginTag HiddenTagPluginSample = {
|
|
|
|
{ cmsPluginMagicNumber, 2060, cmsPluginTagSig, NULL},
|
|
SigInt, { 1, 1, { SigIntType }, NULL }
|
|
};
|
|
|
|
static cmsPluginTagType TagTypePluginSample = {
|
|
|
|
{ cmsPluginMagicNumber, 2060, cmsPluginTagTypeSig, (cmsPluginBase*) &HiddenTagPluginSample},
|
|
{ SigIntType, Type_int_Read, Type_int_Write, Type_int_Dup, Type_int_Free, 0 }
|
|
};
|
|
|
|
|
|
cmsInt32Number CheckTagTypePlugin(void)
|
|
{
|
|
cmsContext ctx = NULL;
|
|
cmsContext cpy = NULL;
|
|
cmsHPROFILE h = NULL;
|
|
cmsUInt32Number myTag = 1234;
|
|
cmsUInt32Number rc = 0;
|
|
char* data = NULL;
|
|
cmsUInt32Number *ptr = NULL;
|
|
cmsUInt32Number clen = 0;
|
|
|
|
ctx = WatchDogContext(NULL);
|
|
cmsPlugin(ctx, &TagTypePluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
|
|
h = cmsCreateProfilePlaceholder(cpy);
|
|
if (h == NULL) {
|
|
Fail("Create placeholder failed");
|
|
goto Error;
|
|
}
|
|
|
|
|
|
if (!cmsWriteTag(cpy, h, SigInt, &myTag)) {
|
|
Fail("Plug-in failed");
|
|
goto Error;
|
|
}
|
|
|
|
rc = cmsSaveProfileToMem(cpy, h, NULL, &clen);
|
|
if (!rc) {
|
|
Fail("Fetch mem size failed");
|
|
goto Error;
|
|
}
|
|
|
|
|
|
data = (char*) malloc(clen);
|
|
if (data == NULL) {
|
|
Fail("malloc failed ?!?");
|
|
goto Error;
|
|
}
|
|
|
|
|
|
rc = cmsSaveProfileToMem(cpy, h, data, &clen);
|
|
if (!rc) {
|
|
Fail("Save to mem failed");
|
|
goto Error;
|
|
}
|
|
|
|
cmsCloseProfile(cpy, h);
|
|
|
|
cmsSetLogErrorHandler(cpy, NULL);
|
|
h = cmsOpenProfileFromMem(cpy, data, clen);
|
|
if (h == NULL) {
|
|
Fail("Open profile failed");
|
|
goto Error;
|
|
}
|
|
|
|
ptr = (cmsUInt32Number*) cmsReadTag(cpy, h, SigInt);
|
|
if (ptr != NULL) {
|
|
|
|
Fail("read tag/context switching failed");
|
|
goto Error;
|
|
}
|
|
|
|
cmsCloseProfile(cpy, h);
|
|
ResetFatalError(cpy);
|
|
|
|
h = cmsOpenProfileFromMem(cpy, data, clen);
|
|
if (h == NULL) {
|
|
Fail("Open profile from mem failed");
|
|
goto Error;
|
|
}
|
|
|
|
// Get rid of data
|
|
free(data); data = NULL;
|
|
|
|
ptr = (cmsUInt32Number*) cmsReadTag(cpy, h, SigInt);
|
|
if (ptr == NULL) {
|
|
Fail("Read tag/conext switching failed (2)");
|
|
return 0;
|
|
}
|
|
|
|
rc = (*ptr == 1234);
|
|
|
|
cmsCloseProfile(cpy, h);
|
|
cmsDeleteContext(ctx);
|
|
cmsDeleteContext(cpy);
|
|
|
|
return rc;
|
|
|
|
Error:
|
|
|
|
if (h != NULL) cmsCloseProfile(cpy, h);
|
|
if (ctx != NULL) cmsDeleteContext(ctx);
|
|
if (cpy != NULL) cmsDeleteContext(cpy);
|
|
if (data) free(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// MPE plugin check:
|
|
// --------------------------------------------------------------------------------------------------
|
|
#define SigNegateType ((cmsStageSignature)0x6E202020)
|
|
|
|
static
|
|
void EvaluateNegate(cmsContext ContextID, const cmsFloat32Number In[],
|
|
cmsFloat32Number Out[],
|
|
const cmsStage *mpe)
|
|
{
|
|
Out[0] = 1.0f - In[0];
|
|
Out[1] = 1.0f - In[1];
|
|
Out[2] = 1.0f - In[2];
|
|
}
|
|
|
|
static
|
|
cmsStage* StageAllocNegate(cmsContext ContextID)
|
|
{
|
|
return _cmsStageAllocPlaceholder(ContextID,
|
|
SigNegateType, 3, 3, EvaluateNegate,
|
|
NULL, NULL, NULL);
|
|
}
|
|
|
|
static
|
|
void *Type_negate_Read(cmsContext ContextID, struct _cms_typehandler_struct* self,
|
|
cmsIOHANDLER* io,
|
|
cmsUInt32Number* nItems,
|
|
cmsUInt32Number SizeOfTag)
|
|
{
|
|
cmsUInt16Number Chans;
|
|
if (!_cmsReadUInt16Number(ContextID, io, &Chans)) return NULL;
|
|
if (Chans != 3) return NULL;
|
|
|
|
*nItems = 1;
|
|
return StageAllocNegate(ContextID);
|
|
}
|
|
|
|
static
|
|
cmsBool Type_negate_Write(cmsContext ContextID, struct _cms_typehandler_struct* self,
|
|
cmsIOHANDLER* io,
|
|
void* Ptr, cmsUInt32Number nItems)
|
|
{
|
|
|
|
if (!_cmsWriteUInt16Number(ContextID, io, 3)) return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static
|
|
cmsPluginMultiProcessElement MPEPluginSample = {
|
|
|
|
{cmsPluginMagicNumber, 2060, cmsPluginMultiProcessElementSig, NULL},
|
|
|
|
{ (cmsTagTypeSignature) SigNegateType, Type_negate_Read, Type_negate_Write, NULL, NULL, 0 }
|
|
};
|
|
|
|
|
|
cmsInt32Number CheckMPEPlugin(void)
|
|
{
|
|
cmsContext ctx = NULL;
|
|
cmsContext cpy = NULL;
|
|
cmsHPROFILE h = NULL;
|
|
cmsUInt32Number myTag = 1234;
|
|
cmsUInt32Number rc = 0;
|
|
char* data = NULL;
|
|
cmsUInt32Number clen = 0;
|
|
cmsFloat32Number In[3], Out[3];
|
|
cmsPipeline* pipe;
|
|
|
|
ctx = WatchDogContext(NULL);
|
|
cmsPlugin(ctx, &MPEPluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
|
|
h = cmsCreateProfilePlaceholder(cpy);
|
|
if (h == NULL) {
|
|
Fail("Create placeholder failed");
|
|
goto Error;
|
|
}
|
|
|
|
pipe = cmsPipelineAlloc(cpy, 3, 3);
|
|
cmsPipelineInsertStage(cpy, pipe, cmsAT_BEGIN, StageAllocNegate(cpy));
|
|
|
|
|
|
In[0] = 0.3f; In[1] = 0.2f; In[2] = 0.9f;
|
|
cmsPipelineEvalFloat(cpy, In, Out, pipe);
|
|
|
|
rc = (IsGoodVal("0", Out[0], 1.0-In[0], 0.001) &&
|
|
IsGoodVal("1", Out[1], 1.0-In[1], 0.001) &&
|
|
IsGoodVal("2", Out[2], 1.0-In[2], 0.001));
|
|
|
|
if (!rc) {
|
|
Fail("Pipeline failed");
|
|
goto Error;
|
|
}
|
|
|
|
if (!cmsWriteTag(cpy, h, cmsSigDToB3Tag, pipe)) {
|
|
Fail("Plug-in failed");
|
|
goto Error;
|
|
}
|
|
|
|
// This cleans the stage as well
|
|
cmsPipelineFree(cpy, pipe);
|
|
|
|
rc = cmsSaveProfileToMem(cpy, h, NULL, &clen);
|
|
if (!rc) {
|
|
Fail("Fetch mem size failed");
|
|
goto Error;
|
|
}
|
|
|
|
|
|
data = (char*) malloc(clen);
|
|
if (data == NULL) {
|
|
Fail("malloc failed ?!?");
|
|
goto Error;
|
|
}
|
|
|
|
|
|
rc = cmsSaveProfileToMem(cpy, h, data, &clen);
|
|
if (!rc) {
|
|
Fail("Save to mem failed");
|
|
goto Error;
|
|
}
|
|
|
|
cmsCloseProfile(cpy, h);
|
|
|
|
cmsSetLogErrorHandler(cpy, NULL);
|
|
h = cmsOpenProfileFromMem(cpy, data, clen);
|
|
if (h == NULL) {
|
|
Fail("Open profile failed");
|
|
goto Error;
|
|
}
|
|
|
|
pipe = (cmsPipeline*) cmsReadTag(cpy, h, cmsSigDToB3Tag);
|
|
if (pipe != NULL) {
|
|
|
|
// Unsupported stage, should fail
|
|
Fail("read tag/context switching failed");
|
|
goto Error;
|
|
}
|
|
|
|
cmsCloseProfile(cpy, h);
|
|
|
|
ResetFatalError(cpy);
|
|
|
|
h = cmsOpenProfileFromMem(cpy, data, clen);
|
|
if (h == NULL) {
|
|
Fail("Open profile from mem failed");
|
|
goto Error;
|
|
}
|
|
|
|
// Get rid of data
|
|
free(data); data = NULL;
|
|
|
|
pipe = (cmsPipeline*) cmsReadTag(cpy, h, cmsSigDToB3Tag);
|
|
if (pipe == NULL) {
|
|
Fail("Read tag/conext switching failed (2)");
|
|
return 0;
|
|
}
|
|
|
|
// Evaluate for negation
|
|
In[0] = 0.3f; In[1] = 0.2f; In[2] = 0.9f;
|
|
cmsPipelineEvalFloat(cpy, In, Out, pipe);
|
|
|
|
rc = (IsGoodVal("0", Out[0], 1.0-In[0], 0.001) &&
|
|
IsGoodVal("1", Out[1], 1.0-In[1], 0.001) &&
|
|
IsGoodVal("2", Out[2], 1.0-In[2], 0.001));
|
|
|
|
cmsCloseProfile(cpy, h);
|
|
cmsDeleteContext(ctx);
|
|
cmsDeleteContext(cpy);
|
|
return rc;
|
|
|
|
Error:
|
|
|
|
if (h != NULL) cmsCloseProfile(ctx, h);
|
|
if (ctx != NULL) cmsDeleteContext(ctx);
|
|
if (cpy != NULL) cmsDeleteContext(cpy);
|
|
if (data) free(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Optimization plugin check:
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
static
|
|
void FastEvaluateCurves(cmsContext ContextID, register const cmsUInt16Number In[],
|
|
register cmsUInt16Number Out[],
|
|
register const void* Data)
|
|
{
|
|
Out[0] = In[0];
|
|
}
|
|
|
|
static
|
|
cmsBool MyOptimize(cmsContext ContextID, cmsPipeline** Lut,
|
|
cmsUInt32Number Intent,
|
|
cmsUInt32Number* InputFormat,
|
|
cmsUInt32Number* OutputFormat,
|
|
cmsUInt32Number* dwFlags)
|
|
{
|
|
cmsStage* mpe;
|
|
_cmsStageToneCurvesData* Data;
|
|
|
|
// Only curves in this LUT? All are identities?
|
|
for (mpe = cmsPipelineGetPtrToFirstStage(ContextID, *Lut);
|
|
mpe != NULL;
|
|
mpe = cmsStageNext(ContextID, mpe)) {
|
|
|
|
if (cmsStageType(ContextID, mpe) != cmsSigCurveSetElemType) return FALSE;
|
|
|
|
// Check for identity
|
|
Data = (_cmsStageToneCurvesData*) cmsStageData(ContextID, mpe);
|
|
if (Data ->nCurves != 1) return FALSE;
|
|
if (cmsEstimateGamma(ContextID, Data->TheCurves[0], 0.1) > 1.0) return FALSE;
|
|
|
|
}
|
|
|
|
*dwFlags |= cmsFLAGS_NOCACHE;
|
|
_cmsPipelineSetOptimizationParameters(ContextID, *Lut, FastEvaluateCurves, NULL, NULL, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
cmsPluginOptimization OptimizationPluginSample = {
|
|
|
|
{cmsPluginMagicNumber, 2060, cmsPluginOptimizationSig, NULL},
|
|
MyOptimize
|
|
};
|
|
|
|
|
|
cmsInt32Number CheckOptimizationPlugin(void)
|
|
{
|
|
cmsContext ctx = WatchDogContext(NULL);
|
|
cmsContext cpy;
|
|
cmsHTRANSFORM xform;
|
|
cmsUInt8Number In[]= { 10, 20, 30, 40 };
|
|
cmsUInt8Number Out[4];
|
|
cmsToneCurve* Linear[1];
|
|
cmsHPROFILE h;
|
|
int i;
|
|
|
|
cmsPlugin(ctx, &OptimizationPluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
|
|
Linear[0] = cmsBuildGamma(cpy, 1.0);
|
|
h = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, Linear);
|
|
cmsFreeToneCurve(cpy, Linear[0]);
|
|
|
|
xform = cmsCreateTransform(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
|
|
cmsCloseProfile(cpy, h);
|
|
|
|
cmsDoTransform(cpy, xform, In, Out, 4);
|
|
|
|
cmsDeleteTransform(cpy, xform);
|
|
cmsDeleteContext(ctx);
|
|
cmsDeleteContext(cpy);
|
|
|
|
for (i=0; i < 4; i++)
|
|
if (In[i] != Out[i]) return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Check the intent plug-in
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
/*
|
|
This example creates a new rendering intent, at intent number 300, that is identical to perceptual
|
|
intent for all color spaces but gray to gray transforms, in this case it bypasses the data.
|
|
Note that it has to clear all occurrences of intent 300 in the intents array to avoid
|
|
infinite recursion.
|
|
*/
|
|
|
|
#define INTENT_DECEPTIVE 300
|
|
|
|
static
|
|
cmsPipeline* MyNewIntent(cmsContext ContextID,
|
|
cmsUInt32Number nProfiles,
|
|
cmsUInt32Number TheIntents[],
|
|
cmsHPROFILE hProfiles[],
|
|
cmsBool BPC[],
|
|
cmsFloat64Number AdaptationStates[],
|
|
cmsUInt32Number dwFlags)
|
|
{
|
|
cmsPipeline* Result;
|
|
cmsUInt32Number ICCIntents[256];
|
|
cmsUInt32Number i;
|
|
|
|
for (i=0; i < nProfiles; i++)
|
|
ICCIntents[i] = (TheIntents[i] == INTENT_DECEPTIVE) ? INTENT_PERCEPTUAL :
|
|
TheIntents[i];
|
|
|
|
if (cmsGetColorSpace(ContextID, hProfiles[0]) != cmsSigGrayData ||
|
|
cmsGetColorSpace(ContextID, hProfiles[nProfiles-1]) != cmsSigGrayData)
|
|
return _cmsDefaultICCintents(ContextID, nProfiles,
|
|
ICCIntents, hProfiles,
|
|
BPC, AdaptationStates,
|
|
dwFlags);
|
|
|
|
Result = cmsPipelineAlloc(ContextID, 1, 1);
|
|
if (Result == NULL) return NULL;
|
|
|
|
cmsPipelineInsertStage(ContextID, Result, cmsAT_BEGIN,
|
|
cmsStageAllocIdentity(ContextID, 1));
|
|
|
|
return Result;
|
|
}
|
|
|
|
static cmsPluginRenderingIntent IntentPluginSample = {
|
|
|
|
{cmsPluginMagicNumber, 2060, cmsPluginRenderingIntentSig, NULL},
|
|
|
|
INTENT_DECEPTIVE, MyNewIntent, "bypass gray to gray rendering intent"
|
|
};
|
|
|
|
cmsInt32Number CheckIntentPlugin(void)
|
|
{
|
|
cmsContext ctx = WatchDogContext(NULL);
|
|
cmsContext cpy;
|
|
cmsHTRANSFORM xform;
|
|
cmsHPROFILE h1, h2;
|
|
cmsToneCurve* Linear1;
|
|
cmsToneCurve* Linear2;
|
|
cmsUInt8Number In[]= { 10, 20, 30, 40 };
|
|
cmsUInt8Number Out[4];
|
|
int i;
|
|
|
|
cmsPlugin(ctx, &IntentPluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
|
|
Linear1 = cmsBuildGamma(cpy, 3.0);
|
|
Linear2 = cmsBuildGamma(cpy, 0.1);
|
|
h1 = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, &Linear1);
|
|
h2 = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, &Linear2);
|
|
|
|
cmsFreeToneCurve(cpy, Linear1);
|
|
cmsFreeToneCurve(cpy, Linear2);
|
|
|
|
xform = cmsCreateTransform(cpy, h1, TYPE_GRAY_8, h2, TYPE_GRAY_8, INTENT_DECEPTIVE, 0);
|
|
cmsCloseProfile(cpy,h1); cmsCloseProfile(cpy, h2);
|
|
|
|
cmsDoTransform(cpy, xform, In, Out, 4);
|
|
|
|
cmsDeleteTransform(cpy, xform);
|
|
cmsDeleteContext(cpy);
|
|
cmsDeleteContext(ctx);
|
|
|
|
for (i=0; i < 4; i++)
|
|
if (Out[i] != In[i]) return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Check the full transform plug-in
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
// This is a sample intent that only works for gray8 as output, and always returns '42'
|
|
static
|
|
void TrancendentalTransform(cmsContext ContextID, struct _cmstransform_struct * CMM,
|
|
const void* InputBuffer,
|
|
void* OutputBuffer,
|
|
cmsUInt32Number Size,
|
|
cmsUInt32Number Stride)
|
|
{
|
|
cmsUInt32Number i;
|
|
|
|
for (i=0; i < Size; i++)
|
|
{
|
|
((cmsUInt8Number*) OutputBuffer)[i] = 0x42;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
cmsBool TransformFactory(cmsContext ContextID, _cmsTransformFn* xformPtr,
|
|
void** UserData,
|
|
_cmsFreeUserDataFn* FreePrivateDataFn,
|
|
cmsPipeline** Lut,
|
|
cmsUInt32Number* InputFormat,
|
|
cmsUInt32Number* OutputFormat,
|
|
cmsUInt32Number* dwFlags)
|
|
|
|
{
|
|
if (*OutputFormat == TYPE_GRAY_8)
|
|
{
|
|
// *Lut holds the pipeline to be applied
|
|
*xformPtr = TrancendentalTransform;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// The Plug-in entry point
|
|
static cmsPluginTransform FullTransformPluginSample = {
|
|
|
|
{ cmsPluginMagicNumber, 2060, cmsPluginTransformSig, NULL},
|
|
|
|
TransformFactory
|
|
};
|
|
|
|
cmsInt32Number CheckTransformPlugin(void)
|
|
{
|
|
cmsContext ctx = WatchDogContext(NULL);
|
|
cmsContext cpy;
|
|
cmsHTRANSFORM xform;
|
|
cmsUInt8Number In[]= { 10, 20, 30, 40 };
|
|
cmsUInt8Number Out[4];
|
|
cmsToneCurve* Linear;
|
|
cmsHPROFILE h;
|
|
int i;
|
|
|
|
cmsPlugin(ctx, &FullTransformPluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
|
|
Linear = cmsBuildGamma(cpy, 1.0);
|
|
h = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, &Linear);
|
|
cmsFreeToneCurve(cpy, Linear);
|
|
|
|
xform = cmsCreateTransform(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
|
|
cmsCloseProfile(cpy, h);
|
|
|
|
cmsDoTransform(cpy, xform, In, Out, 4);
|
|
|
|
cmsDeleteTransform(cpy, xform);
|
|
cmsDeleteContext(ctx);
|
|
cmsDeleteContext(cpy);
|
|
|
|
for (i=0; i < 4; i++)
|
|
if (Out[i] != 0x42) return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Check the mutex plug-in
|
|
// --------------------------------------------------------------------------------------------------
|
|
|
|
typedef struct {
|
|
int nlocks;
|
|
} MyMtx;
|
|
|
|
|
|
static
|
|
void* MyMtxCreate(cmsContext id)
|
|
{
|
|
MyMtx* mtx = (MyMtx*) _cmsMalloc(id, sizeof(MyMtx));
|
|
mtx ->nlocks = 0;
|
|
return mtx;
|
|
}
|
|
|
|
static
|
|
void MyMtxDestroy(cmsContext id, void* mtx)
|
|
{
|
|
MyMtx* mtx_ = (MyMtx*) mtx;
|
|
|
|
if (mtx_->nlocks != 0)
|
|
Die("Locks != 0 when setting free a mutex");
|
|
|
|
_cmsFree(id, mtx);
|
|
|
|
}
|
|
|
|
static
|
|
cmsBool MyMtxLock(cmsContext id, void* mtx)
|
|
{
|
|
MyMtx* mtx_ = (MyMtx*) mtx;
|
|
mtx_->nlocks++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static
|
|
void MyMtxUnlock(cmsContext id, void* mtx)
|
|
{
|
|
MyMtx* mtx_ = (MyMtx*) mtx;
|
|
mtx_->nlocks--;
|
|
|
|
}
|
|
|
|
|
|
static cmsPluginMutex MutexPluginSample = {
|
|
|
|
{ cmsPluginMagicNumber, 2060, cmsPluginMutexSig, NULL},
|
|
|
|
MyMtxCreate, MyMtxDestroy, MyMtxLock, MyMtxUnlock
|
|
};
|
|
|
|
|
|
cmsInt32Number CheckMutexPlugin(void)
|
|
{
|
|
cmsContext ctx = WatchDogContext(NULL);
|
|
cmsContext cpy;
|
|
cmsHTRANSFORM xform;
|
|
cmsUInt8Number In[]= { 10, 20, 30, 40 };
|
|
cmsUInt8Number Out[4];
|
|
cmsToneCurve* Linear;
|
|
cmsHPROFILE h;
|
|
int i;
|
|
|
|
|
|
cmsPlugin(ctx, &MutexPluginSample);
|
|
|
|
cpy = DupContext(ctx, NULL);
|
|
|
|
Linear = cmsBuildGamma(cpy, 1.0);
|
|
h = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, &Linear);
|
|
cmsFreeToneCurve(cpy, Linear);
|
|
|
|
xform = cmsCreateTransform(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
|
|
cmsCloseProfile(cpy, h);
|
|
|
|
cmsDoTransform(cpy, xform, In, Out, 4);
|
|
|
|
cmsDeleteTransform(cpy, xform);
|
|
cmsDeleteContext(ctx);
|
|
cmsDeleteContext(cpy);
|
|
|
|
for (i=0; i < 4; i++)
|
|
if (Out[i] != In[i]) return 0;
|
|
|
|
return 1;
|
|
}
|