Skip to content

Commit

Permalink
Added support for .cube file format
Browse files Browse the repository at this point in the history
Added a very preliminar support for using .cube files as devicelinks
  • Loading branch information
mm2 committed Nov 14, 2023
1 parent 6bf0713 commit f9c55d6
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 10 deletions.
2 changes: 2 additions & 0 deletions include/lcms2.h
Original file line number Diff line number Diff line change
Expand Up @@ -1618,6 +1618,8 @@ CMSAPI cmsHPROFILE CMSEXPORT cmsOpenProfileFromMem(const void * MemPtr, cms
CMSAPI cmsHPROFILE CMSEXPORT cmsOpenProfileFromMemTHR(cmsContext ContextID, const void * MemPtr, cmsUInt32Number dwSize);
CMSAPI cmsHPROFILE CMSEXPORT cmsOpenProfileFromIOhandlerTHR(cmsContext ContextID, cmsIOHANDLER* io);
CMSAPI cmsHPROFILE CMSEXPORT cmsOpenProfileFromIOhandler2THR(cmsContext ContextID, cmsIOHANDLER* io, cmsBool write);
CMSAPI cmsHPROFILE CMSEXPORT cmsOpenCubeFromFile(const char* cFileName);
CMSAPI cmsHPROFILE CMSEXPORT cmsOpenCubeFromFileTHR(cmsContext ContextID, const char* cFileName);
CMSAPI cmsBool CMSEXPORT cmsCloseProfile(cmsHPROFILE hProfile);

CMSAPI cmsBool CMSEXPORT cmsSaveProfileToFile(cmsHPROFILE hProfile, const char* FileName);
Expand Down
287 changes: 279 additions & 8 deletions src/cmscgats.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,27 @@ typedef enum {
SEOF, // End of stream
SSYNERROR, // Syntax error found on stream

// Keywords
// IT8 symbols

SBEGIN_DATA,
SBEGIN_DATA_FORMAT,
SEND_DATA,
SEND_DATA_FORMAT,
SKEYWORD,
SDATA_FORMAT_ID,
SINCLUDE
SINCLUDE,

// Cube symbols

SDOMAIN_MAX,
SDOMAIN_MIN,
S_LUT1D_SIZE,
S_LUT1D_INPUT_RANGE,
S_LUT3D_SIZE,
S_LUT3D_INPUT_RANGE,
S_LUT_IN_VIDEO_RANGE,
S_LUT_OUT_VIDEO_RANGE,
STITLE

} SYMBOL;

Expand Down Expand Up @@ -149,6 +161,10 @@ typedef struct struct_it8 {
cmsUInt32Number TablesCount; // How many tables in this stream
cmsUInt32Number nTable; // The actual table

// Partser type
cmsBool IsCUBE;

// Tables
TABLE Tab[MAXTABLES];

// Memory management
Expand Down Expand Up @@ -208,8 +224,8 @@ typedef struct {

} KEYWORD;

// The keyword->symbol translation table. Sorting is required.
static const KEYWORD TabKeys[] = {
// The keyword->symbol translation tables. Sorting is required.
static const KEYWORD TabKeysIT8[] = {

{"$INCLUDE", SINCLUDE}, // This is an extension!
{".INCLUDE", SINCLUDE}, // This is an extension!
Expand All @@ -222,7 +238,25 @@ static const KEYWORD TabKeys[] = {
{"KEYWORD", SKEYWORD}
};

#define NUMKEYS (sizeof(TabKeys)/sizeof(KEYWORD))
#define NUMKEYS_IT8 (sizeof(TabKeysIT8)/sizeof(KEYWORD))

static const KEYWORD TabKeysCUBE[] = {

{"DOMAIN_MAX", SDOMAIN_MAX },
{"DOMAIN_MIN", SDOMAIN_MIN },
{"LUT_1D_SIZE", S_LUT1D_SIZE },
{"LUT_1D_INPUT_RANGE", S_LUT1D_INPUT_RANGE },
{"LUT_3D_SIZE", S_LUT3D_SIZE },
{"LUT_3D_INPUT_RANGE", S_LUT3D_INPUT_RANGE },
{"LUT_IN_VIDEO_RANGE", S_LUT_IN_VIDEO_RANGE },
{"LUT_OUT_VIDEO_RANGE", S_LUT_OUT_VIDEO_RANGE },
{"TITLE", STITLE }

};

#define NUMKEYS_CUBE (sizeof(TabKeysCUBE)/sizeof(KEYWORD))



// Predefined properties

Expand Down Expand Up @@ -574,10 +608,10 @@ void NextCh(cmsIT8* it8)

// Try to see if current identifier is a keyword, if so return the referred symbol
static
SYMBOL BinSrchKey(const char *id)
SYMBOL BinSrchKey(const char *id, int NumKeys, const KEYWORD* TabKeys)
{
int l = 1;
int r = NUMKEYS;
int r = NumKeys;
int x, res;

while (r >= l)
Expand Down Expand Up @@ -804,7 +838,9 @@ void InSymbol(cmsIT8* it8)
} while (isidchar(it8->ch));


key = BinSrchKey(StringPtr(it8->id));
key = BinSrchKey(StringPtr(it8->id),
it8->IsCUBE ? NUMKEYS_CUBE : NUMKEYS_IT8,
it8->IsCUBE ? TabKeysCUBE : TabKeysIT8);
if (key == SUNDEFINED) it8->sy = SIDENT;
else it8->sy = key;

Expand Down Expand Up @@ -1390,6 +1426,8 @@ cmsHANDLE CMSEXPORT cmsIT8Alloc(cmsContext ContextID)
it8->MemoryBlock = NULL;
it8->MemorySink = NULL;

it8->IsCUBE = FALSE;

it8 ->nTable = 0;

it8->ContextID = ContextID;
Expand Down Expand Up @@ -2963,3 +3001,236 @@ void CMSEXPORT cmsIT8DefineDblFormat(cmsHANDLE hIT8, const char* Formatter)
it8 ->DoubleFormatter[sizeof(it8 ->DoubleFormatter)-1] = 0;
}


static
cmsBool ReadNumbers(cmsIT8* cube, int n, cmsFloat64Number* arr)
{
int i;

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

if (cube->sy == SINUM)
arr[i] = cube->inum;
else
if (cube->sy == SDNUM)
arr[i] = cube->dnum;
else
return SynError(cube, "Number expected");

InSymbol(cube);
}

return CheckEOLN(cube);
}

static
cmsBool ParseCube(cmsIT8* cube, cmsStage** Shaper, cmsStage** CLUT, char title[])
{
cmsFloat64Number domain_min[3] = { 0, 0, 0 };
cmsFloat64Number domain_max[3] = { 1.0, 1.0, 1.0 };
cmsFloat64Number check_0_1[2] = { 0, 1.0 };
int shaper_size = 0;
int lut_size = 0;
int i;

InSymbol(cube);

while (cube->sy != SEOF) {
switch (cube->sy)
{
// Set profile description
case STITLE:
InSymbol(cube);
if (!Check(cube, SSTRING, "Title string expected")) return FALSE;
memcpy(title, StringPtr(cube->str), MAXSTR);
title[MAXSTR - 1] = 0;
InSymbol(cube);
break;

// Define domain
case SDOMAIN_MIN:
InSymbol(cube);
if (!ReadNumbers(cube, 3, domain_min)) return FALSE;
break;

case SDOMAIN_MAX:
InSymbol(cube);
if (!ReadNumbers(cube, 3, domain_max)) return FALSE;
break;

// Define shaper
case S_LUT1D_SIZE:
InSymbol(cube);
if (!Check(cube, SINUM, "Shaper size expected")) return FALSE;
shaper_size = cube->inum;
InSymbol(cube);
break;

// Deefine CLUT
case S_LUT3D_SIZE:
InSymbol(cube);
if (!Check(cube, SINUM, "LUT size expected")) return FALSE;
lut_size = cube->inum;
InSymbol(cube);
break;

// Range. If present, has to be 0..1.0
case S_LUT1D_INPUT_RANGE:
case S_LUT3D_INPUT_RANGE:
InSymbol(cube);
if (!ReadNumbers(cube, 2, check_0_1)) return FALSE;
if (check_0_1[0] != 0 || check_0_1[1] != 1.0) {
return SynError(cube, "Unsupported format");
}
break;

case SEOLN:
InSymbol(cube);
break;

default:
case S_LUT_IN_VIDEO_RANGE:
case S_LUT_OUT_VIDEO_RANGE:
return SynError(cube, "Unsupported format");

// Read and create tables
case SINUM:
case SDNUM:

if (shaper_size > 0) {

cmsToneCurve* curves[3];
cmsFloat32Number* shapers = (cmsFloat32Number*)_cmsMalloc(cube->ContextID, 3 * shaper_size * sizeof(cmsFloat32Number));
if (shapers == NULL) return FALSE;

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

cmsFloat64Number nums[3];

if (!ReadNumbers(cube, 3, nums)) return FALSE;

shapers[i + 0] = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0]));
shapers[i + 1 * shaper_size] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1]));
shapers[i + 2 * shaper_size] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2]));
}

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

curves[i] = cmsBuildTabulatedToneCurveFloat(cube->ContextID, shaper_size,
&shapers[i * shaper_size]);
if (curves[i] == NULL) return FALSE;
}

*Shaper = cmsStageAllocToneCurves(cube->ContextID, 3, curves);

cmsFreeToneCurveTriple(curves);
}

if (lut_size > 0) {

int nodes = lut_size * lut_size * lut_size;

cmsFloat32Number* lut_table = _cmsMalloc(cube->ContextID, nodes * 3 * sizeof(cmsFloat32Number));
if (lut_table == NULL) return FALSE;

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

cmsFloat64Number nums[3];

if (!ReadNumbers(cube, 3, nums)) return FALSE;

lut_table[i * 3 + 0] = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0]));
lut_table[i * 3 + 1] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1]));
lut_table[i * 3 + 2] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2]));
}

*CLUT = cmsStageAllocCLutFloat(cube->ContextID, lut_size, 3, 3, lut_table);
_cmsFree(cube->ContextID, lut_table);
}

if (!Check(cube, SEOF, "Extra symbols found in file")) return FALSE;
}
}

return TRUE;
}

// Share the parser to read .cube format and create RGB devicelink profiles
cmsHPROFILE cmsOpenCubeFromFileTHR(cmsContext ContextID, const char* cFileName)
{
cmsHPROFILE hProfile = NULL;
cmsIT8* cube = NULL;
cmsPipeline* Pipeline = NULL;
cmsStage* CLUT = NULL;
cmsStage* Shaper = NULL;
cmsMLU* DescriptionMLU = NULL;
char title[MAXSTR];

_cmsAssert(cFileName != NULL);

cube = (cmsIT8*) cmsIT8Alloc(ContextID);
if (!cube) return NULL;

cube->IsCUBE = TRUE;
cube->FileStack[0]->Stream = fopen(cFileName, "rt");

if (!cube->FileStack[0]->Stream) goto Done;

strncpy(cube->FileStack[0]->FileName, cFileName, cmsMAX_PATH - 1);
cube->FileStack[0]->FileName[cmsMAX_PATH - 1] = 0;

if (!ParseCube(cube, &Shaper, &CLUT, title)) goto Done;

// Success on parsing, let's create the profile
hProfile = cmsCreateProfilePlaceholder(ContextID);
if (!hProfile) goto Done;

cmsSetProfileVersion(hProfile, 4.4);

cmsSetDeviceClass(hProfile, cmsSigLinkClass);
cmsSetColorSpace(hProfile, cmsSigRgbData);
cmsSetPCS(hProfile, cmsSigRgbData);

cmsSetHeaderRenderingIntent(hProfile, INTENT_PERCEPTUAL);

// Creates a Pipeline to hold CLUT and shaper
Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
if (Pipeline == NULL) goto Done;

// Populates the pipeline
if (Shaper != NULL) {
if (!cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, Shaper))
goto Done;
}

if (CLUT != NULL) {
if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT))
goto Done;
}

// Propagate the description. We put no copyright because we know
// nothing on the copyrighted state of the .cube
DescriptionMLU = cmsMLUalloc(ContextID, 1);
if (!cmsMLUsetUTF8(DescriptionMLU, cmsNoLanguage, cmsNoCountry, title)) goto Done;

// Flush the tags
if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Done;
if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, (void*)Pipeline)) goto Done;

Done:

if (DescriptionMLU != NULL)
cmsMLUfree(DescriptionMLU);

if (Pipeline != NULL)
cmsPipelineFree(Pipeline);

cmsIT8Free((cmsHANDLE) cube);

return hProfile;
}

cmsHPROFILE CMSEXPORT cmsOpenCubeFromFile(const char* cFileName)
{
return cmsOpenCubeFromFileTHR(0, cFileName);
}
2 changes: 2 additions & 0 deletions src/lcms2.def
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ cmsOpenProfileFromMem = cmsOpenProfileFromMem
cmsOpenProfileFromMemTHR = cmsOpenProfileFromMemTHR
cmsOpenProfileFromStream = cmsOpenProfileFromStream
cmsOpenProfileFromStreamTHR = cmsOpenProfileFromStreamTHR
cmsOpenCubeFromFileTHR = cmsOpenCubeFromFileTHR
cmsOpenCubeFromFile = cmsOpenCubeFromFile
cmsPlugin = cmsPlugin
_cmsRead15Fixed16Number = _cmsRead15Fixed16Number
_cmsReadAlignment = _cmsReadAlignment
Expand Down
Loading

0 comments on commit f9c55d6

Please sign in to comment.