aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/lcms2/src/cmscgats.c
diff options
context:
space:
mode:
authorAlexSm <alex@ydb.tech>2023-12-21 15:05:38 +0100
committerGitHub <noreply@github.com>2023-12-21 15:05:38 +0100
commite98bcbc74422492351c51646dba3849a138a8ffc (patch)
tree38ad7a09b1f9c201ce8a7e3d69f2017388769224 /contrib/libs/lcms2/src/cmscgats.c
parent559d7083cd8378cb25b9e966dedcca21d413e338 (diff)
downloadydb-e98bcbc74422492351c51646dba3849a138a8ffc.tar.gz
Import libs 1 (#590)
* Import libs 1 * Add new file without extension * Add file missed in export config
Diffstat (limited to 'contrib/libs/lcms2/src/cmscgats.c')
-rw-r--r--contrib/libs/lcms2/src/cmscgats.c499
1 files changed, 395 insertions, 104 deletions
diff --git a/contrib/libs/lcms2/src/cmscgats.c b/contrib/libs/lcms2/src/cmscgats.c
index 51fc2a6e28..bccbf58c10 100644
--- a/contrib/libs/lcms2/src/cmscgats.c
+++ b/contrib/libs/lcms2/src/cmscgats.c
@@ -58,7 +58,7 @@ typedef enum {
SEOF, // End of stream
SSYNERROR, // Syntax error found on stream
- // Keywords
+ // IT8 symbols
SBEGIN_DATA,
SBEGIN_DATA_FORMAT,
@@ -66,7 +66,19 @@ typedef enum {
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;
@@ -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
@@ -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!
@@ -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
@@ -426,7 +460,7 @@ void StringCat(string* s, const char* c)
static
cmsBool isseparator(int c)
{
- return (c == ' ') || (c == '\t') ;
+ return (c == ' ') || (c == '\t');
}
// Checks whatever c is a valid identifier char
@@ -447,7 +481,7 @@ cmsBool isidchar(int c)
static
cmsBool isfirstidchar(int c)
{
- return !isdigit(c) && ismiddle(c);
+ return c != '-' && !isdigit(c) && ismiddle(c);
}
// Guess whether the supplied path looks like an absolute path
@@ -486,13 +520,13 @@ cmsBool BuildAbsolutePath(const char *relPath, const char *basePath, char *buffe
// Already absolute?
if (isabsolutepath(relPath)) {
- strncpy(buffer, relPath, MaxLen);
+ memcpy(buffer, relPath, MaxLen);
buffer[MaxLen-1] = 0;
return TRUE;
}
// No, search for last
- strncpy(buffer, basePath, MaxLen);
+ memcpy(buffer, basePath, MaxLen);
buffer[MaxLen-1] = 0;
tail = strrchr(buffer, DIR_CHAR);
@@ -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)
@@ -747,7 +781,7 @@ cmsFloat64Number ParseFloatNumber(const char *Buffer)
}
-// Reads a string, special case to avoid infinite resursion on .include
+// Reads a string, special case to avoid infinite recursion on .include
static
void InStringSymbol(cmsIT8* it8)
{
@@ -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;
@@ -913,6 +949,7 @@ void InSymbol(cmsIT8* it8)
snprintf(buffer, sizeof(buffer), it8 ->DoubleFormatter, it8->dnum);
}
+ StringClear(it8->id);
StringCat(it8->id, buffer);
do {
@@ -942,7 +979,7 @@ void InSymbol(cmsIT8* it8)
// Next line
case '\r':
NextCh(it8);
- if (it8 ->ch == '\n')
+ if (it8->ch == '\n')
NextCh(it8);
it8->sy = SEOLN;
it8->lineno++;
@@ -1263,7 +1300,12 @@ KEYVALUE* AddToList(cmsIT8* it8, KEYVALUE** Head, const char *Key, const char *S
// This may work for editing properties
- // return SynError(it8, "duplicate key <%s>", Key);
+ if (cmsstrcasecmp(Key, "NUMBER_OF_FIELDS") == 0 ||
+ cmsstrcasecmp(Key, "NUMBER_OF_SETS") == 0) {
+
+ SynError(it8, "duplicate key <%s>", Key);
+ return NULL;
+ }
}
else {
@@ -1384,6 +1426,8 @@ cmsHANDLE CMSEXPORT cmsIT8Alloc(cmsContext ContextID)
it8->MemoryBlock = NULL;
it8->MemorySink = NULL;
+ it8->IsCUBE = FALSE;
+
it8 ->nTable = 0;
it8->ContextID = ContextID;
@@ -1665,7 +1709,7 @@ char* GetData(cmsIT8* it8, int nSet, int nField)
int nSamples = t -> nSamples;
int nPatches = t -> nPatches;
- if (nSet >= nPatches || nField >= nSamples)
+ if (nSet < 0 || nSet >= nPatches || nField < 0 || nField >= nSamples)
return NULL;
if (!t->Data) return NULL;
@@ -1850,11 +1894,14 @@ void WriteDataFormat(SAVESTREAM* fp, cmsIT8* it8)
WriteStr(fp, " ");
nSamples = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
- for (i = 0; i < nSamples; i++) {
+ if (nSamples <= t->nSamples) {
- WriteStr(fp, t->DataFormat[i]);
- WriteStr(fp, ((i == (nSamples-1)) ? "\n" : "\t"));
- }
+ for (i = 0; i < nSamples; i++) {
+
+ WriteStr(fp, t->DataFormat[i]);
+ WriteStr(fp, ((i == (nSamples - 1)) ? "\n" : "\t"));
+ }
+ }
WriteStr (fp, "END_DATA_FORMAT\n");
}
@@ -1864,39 +1911,42 @@ void WriteDataFormat(SAVESTREAM* fp, cmsIT8* it8)
static
void WriteData(SAVESTREAM* fp, cmsIT8* it8)
{
- int i, j;
+ int i, j, nPatches;
TABLE* t = GetTable(it8);
if (!t->Data) return;
WriteStr (fp, "BEGIN_DATA\n");
- t->nPatches = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
+ nPatches = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
- for (i = 0; i < t-> nPatches; i++) {
+ if (nPatches <= t->nPatches) {
- WriteStr(fp, " ");
+ for (i = 0; i < nPatches; i++) {
- for (j = 0; j < t->nSamples; j++) {
+ WriteStr(fp, " ");
- char *ptr = t->Data[i*t->nSamples+j];
+ for (j = 0; j < t->nSamples; j++) {
- if (ptr == NULL) WriteStr(fp, "\"\"");
- else {
- // If value contains whitespace, enclose within quote
+ char* ptr = t->Data[i * t->nSamples + j];
- if (strchr(ptr, ' ') != NULL) {
+ if (ptr == NULL) WriteStr(fp, "\"\"");
+ else {
+ // If value contains whitespace, enclose within quote
- WriteStr(fp, "\"");
- WriteStr(fp, ptr);
- WriteStr(fp, "\"");
- }
- else
- WriteStr(fp, ptr);
- }
+ if (strchr(ptr, ' ') != NULL) {
- WriteStr(fp, ((j == (t->nSamples-1)) ? "\n" : "\t"));
- }
+ WriteStr(fp, "\"");
+ WriteStr(fp, ptr);
+ WriteStr(fp, "\"");
+ }
+ else
+ WriteStr(fp, ptr);
+ }
+
+ WriteStr(fp, ((j == (t->nSamples - 1)) ? "\n" : "\t"));
+ }
+ }
}
WriteStr (fp, "END_DATA\n");
}
@@ -1917,15 +1967,29 @@ cmsBool CMSEXPORT cmsIT8SaveToFile(cmsHANDLE hIT8, const char* cFileName)
for (i=0; i < it8 ->TablesCount; i++) {
- cmsIT8SetTable(hIT8, i);
- WriteHeader(it8, &sd);
- WriteDataFormat(&sd, it8);
- WriteData(&sd, it8);
+ TABLE* t;
+
+ if (cmsIT8SetTable(hIT8, i) < 0) goto Error;
+
+ /**
+ * Check for wrong data
+ */
+ t = GetTable(it8);
+ if (t->Data == NULL) goto Error;
+ if (t->DataFormat == NULL) goto Error;
+
+ WriteHeader(it8, &sd);
+ WriteDataFormat(&sd, it8);
+ WriteData(&sd, it8);
}
if (fclose(sd.stream) != 0) return FALSE;
-
return TRUE;
+
+Error:
+ fclose(sd.stream);
+ return FALSE;
+
}
@@ -2302,78 +2366,72 @@ void CookPointers(cmsIT8* it8)
int idField, i;
char* Fld;
cmsUInt32Number j;
- cmsUInt32Number nOldTable = it8 ->nTable;
+ cmsUInt32Number nOldTable = it8->nTable;
- for (j=0; j < it8 ->TablesCount; j++) {
+ for (j = 0; j < it8->TablesCount; j++) {
- TABLE* t = it8 ->Tab + j;
+ TABLE* t = it8->Tab + j;
- t -> SampleID = 0;
- it8 ->nTable = j;
-
- for (idField = 0; idField < t -> nSamples; idField++)
- {
- if (t ->DataFormat == NULL){
- SynError(it8, "Undefined DATA_FORMAT");
- return;
- }
-
- Fld = t->DataFormat[idField];
- if (!Fld) continue;
-
-
- if (cmsstrcasecmp(Fld, "SAMPLE_ID") == 0) {
-
- t -> SampleID = idField;
- }
+ t->SampleID = 0;
+ it8->nTable = j;
- // "LABEL" is an extension. It keeps references to forward tables
-
- if ((cmsstrcasecmp(Fld, "LABEL") == 0) || Fld[0] == '$') {
+ for (idField = 0; idField < t->nSamples; idField++)
+ {
+ if (t->DataFormat == NULL) {
+ SynError(it8, "Undefined DATA_FORMAT");
+ return;
+ }
- // Search for table references...
- for (i = 0; i < t->nPatches; i++) {
+ Fld = t->DataFormat[idField];
+ if (!Fld) continue;
- char* Label = GetData(it8, i, idField);
- if (Label) {
+ if (cmsstrcasecmp(Fld, "SAMPLE_ID") == 0) {
- cmsUInt32Number k;
+ t->SampleID = idField;
+ }
- // This is the label, search for a table containing
- // this property
+ // "LABEL" is an extension. It keeps references to forward tables
- for (k = 0; k < it8->TablesCount; k++) {
+ if ((cmsstrcasecmp(Fld, "LABEL") == 0) || Fld[0] == '$') {
- TABLE* Table = it8->Tab + k;
- KEYVALUE* p;
+ // Search for table references...
+ for (i = 0; i < t->nPatches; i++) {
- if (IsAvailableOnList(Table->HeaderList, Label, NULL, &p)) {
+ char* Label = GetData(it8, i, idField);
- // Available, keep type and table
- char Buffer[256];
+ if (Label) {
- char* Type = p->Value;
- int nTable = (int)k;
+ cmsUInt32Number k;
- snprintf(Buffer, 255, "%s %d %s", Label, nTable, Type);
+ // This is the label, search for a table containing
+ // this property
- SetData(it8, i, idField, Buffer);
- }
- }
+ for (k = 0; k < it8->TablesCount; k++) {
+ TABLE* Table = it8->Tab + k;
+ KEYVALUE* p;
- }
+ if (IsAvailableOnList(Table->HeaderList, Label, NULL, &p)) {
- }
+ // Available, keep type and table
+ char Buffer[256];
+ char* Type = p->Value;
+ int nTable = (int)k;
- }
+ snprintf(Buffer, 255, "%s %d %s", Label, nTable, Type);
- }
- }
+ SetData(it8, i, idField, Buffer);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
- it8 ->nTable = nOldTable;
+ it8->nTable = nOldTable;
}
// Try to infere if the file is a CGATS/IT8 file at all. Read first line
@@ -2464,7 +2522,7 @@ cmsHANDLE CMSEXPORT cmsIT8LoadFromMem(cmsContext ContextID, const void *Ptr, cm
if (it8->MemoryBlock == NULL)
{
cmsIT8Free(hIT8);
- return FALSE;
+ return NULL;
}
strncpy(it8 ->MemoryBlock, (const char*) Ptr, len);
@@ -2476,7 +2534,7 @@ cmsHANDLE CMSEXPORT cmsIT8LoadFromMem(cmsContext ContextID, const void *Ptr, cm
if (!ParseIT8(it8, type-1)) {
cmsIT8Free(hIT8);
- return FALSE;
+ return NULL;
}
CookPointers(it8);
@@ -2573,17 +2631,17 @@ cmsUInt32Number CMSEXPORT cmsIT8EnumProperties(cmsHANDLE hIT8, char ***PropertyN
}
- Props = (char**)AllocChunk(it8, sizeof(char*) * n);
- if (Props != NULL) {
+ Props = (char**)AllocChunk(it8, sizeof(char*) * n);
+ if (Props != NULL) {
- // Pass#2 - Fill pointers
- n = 0;
- for (p = t->HeaderList; p != NULL; p = p->Next) {
- Props[n++] = p->Keyword;
- }
+ // Pass#2 - Fill pointers
+ n = 0;
+ for (p = t->HeaderList; p != NULL; p = p->Next) {
+ Props[n++] = p->Keyword;
+ }
- }
- *PropertyNames = Props;
+ }
+ *PropertyNames = Props;
return n;
}
@@ -2943,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 + 2] = (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 + 0] = (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 CMSEXPORT cmsCreateDeviceLinkFromCubeFileTHR(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 cmsCreateDeviceLinkFromCubeFile(const char* cFileName)
+{
+ return cmsCreateDeviceLinkFromCubeFileTHR(NULL, cFileName);
+}