1508 lines
42 KiB
C
1508 lines
42 KiB
C
|
|
/*
|
|
* Argyll Color Correction System
|
|
* Named Color Access Library
|
|
*
|
|
* Author: Graeme W. Gill
|
|
* Date: 3/12/2013
|
|
*
|
|
* Copyright 2013 Graeme W. Gill
|
|
* All rights reserved.
|
|
*
|
|
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
|
|
* see the License.txt file for licencing details.
|
|
*/
|
|
|
|
/*
|
|
* Currently supports CxF2, CxF3 & ICC named color profiles.
|
|
*/
|
|
|
|
/*
|
|
* TTBD:
|
|
*
|
|
* Probably want to add some other formats:
|
|
* (see <http://www.selapa.net/swatchbooker/>
|
|
* and <http://www.selapa.net/swatches/colors/fileformats.php> )
|
|
*
|
|
* Adobe .aco
|
|
* .acb
|
|
* .acbl
|
|
* .ase
|
|
* .bcf
|
|
* corel .cpl
|
|
* corel .xml
|
|
* autocad .acb RGB only
|
|
*
|
|
* and lower quality RGB formats:
|
|
*
|
|
* Adobe .acf ?? May support Lab
|
|
* corel .acb
|
|
*
|
|
* Paint Shop Pro .pal ???
|
|
*
|
|
* RAL ?
|
|
*/
|
|
|
|
#undef DEBUG
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include "copyright.h"
|
|
#include "aconfig.h"
|
|
#ifndef SALONEINSTLIB
|
|
#include "numlib.h"
|
|
#include "icc.h"
|
|
#else
|
|
#include "numsup.h"
|
|
#endif
|
|
#include "conv.h"
|
|
#include "cgats.h"
|
|
#include "xspect.h"
|
|
#include "mxml.h"
|
|
#include "namedc.h"
|
|
#ifdef STANDALONE_TEST
|
|
#include "ui.h"
|
|
#endif /* STANDALONE_TEST */
|
|
|
|
#ifndef STANDALONE_TEST
|
|
|
|
# define DEB6 6
|
|
# define DEB4 4
|
|
|
|
#ifdef NT /* You'd think there might be some standards.... */
|
|
# ifndef __BORLANDC__
|
|
# define stricmp _stricmp
|
|
# endif
|
|
#else
|
|
# define stricmp strcasecmp
|
|
#endif
|
|
|
|
/* Forward declarations */
|
|
static void clear_namedc(namedc *p);
|
|
static void clear_nce(nce *p);
|
|
|
|
static ORD32 do_hash(ORD32 hash, const char *s) {
|
|
int i;
|
|
for (i = 0; s[i] != '\000'; i++) {
|
|
hash += s[i];
|
|
hash += (hash << 10);
|
|
hash ^= (hash >> 6);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static int do_hash2(const char *s1, const char *s2) {
|
|
ORD32 hash;
|
|
|
|
hash = do_hash(0, s1);
|
|
hash = do_hash(hash, s2);
|
|
|
|
return (int)hash;
|
|
}
|
|
|
|
/* Compare while ignoring possible prefix on s1 */
|
|
static int pfxcmp(const char *s1, const char *s2) {
|
|
const char *cp;
|
|
|
|
if ((cp = strchr(s1, ':')) == NULL)
|
|
cp = s1;
|
|
else
|
|
cp++;
|
|
|
|
return strcmp(cp, s2);
|
|
}
|
|
|
|
/* XML data type callback for mxmlLoadFile() */
|
|
static mxml_type_t
|
|
type_cb(mxml_node_t *node) {
|
|
const char *name = node->value.element.name;
|
|
mxml_node_t *parent = mxmlGetParent(node);
|
|
const char *pname = NULL;
|
|
|
|
if (parent != NULL)
|
|
pname = parent->value.element.name;
|
|
|
|
// printf("~1 type_cb got node named '%s', parent '%s'\n",name,pname);
|
|
|
|
if (pname == NULL)
|
|
return MXML_TEXT;
|
|
|
|
if (pfxcmp(pname, "ColorValues") == 0
|
|
&& pfxcmp(name, "ReflectanceSpectrum") == 0)
|
|
return MXML_REAL;
|
|
|
|
if ((pfxcmp(pname, "ColorCIELab") == 0
|
|
|| pfxcmp(pname, "ColorSpaceCIELab") == 0)
|
|
&& (pfxcmp(name, "L") == 0
|
|
|| pfxcmp(name, "A") == 0
|
|
|| pfxcmp(name, "B") == 0))
|
|
return MXML_REAL;
|
|
|
|
if ((pfxcmp(pname, "ColorCIEXYZ") == 0
|
|
|| pfxcmp(pname, "ColorSpaceCIEXYZ") == 0)
|
|
&& (pfxcmp(name, "X") == 0
|
|
|| pfxcmp(name, "Y") == 0
|
|
|| pfxcmp(name, "Z") == 0))
|
|
return MXML_REAL;
|
|
|
|
if ((pfxcmp(pname, "ColorSRGB") == 0
|
|
|| pfxcmp(pname, "ColorSpaceSRGB") == 0)
|
|
&& (pfxcmp(name, "R") == 0
|
|
|| pfxcmp(name, "G") == 0
|
|
|| pfxcmp(name, "B") == 0))
|
|
return MXML_REAL;
|
|
|
|
if ((pfxcmp(pname, "ColorCMYK") == 0
|
|
|| pfxcmp(pname, "ColorSpaceCMYK") == 0)
|
|
&& (pfxcmp(name, "Cyan") == 0
|
|
|| pfxcmp(name, "Magenta") == 0
|
|
|| pfxcmp(name, "Yellow") == 0
|
|
|| pfxcmp(name, "Black") == 0))
|
|
return MXML_REAL;
|
|
|
|
if ((pfxcmp(pname, "FileInformation") == 0
|
|
|| pfxcmp(pname, "Header") == 0)
|
|
&& (pfxcmp(name, "Creator") == 0
|
|
|| pfxcmp(name, "CreationDate") == 0
|
|
|| pfxcmp(name, "Description") == 0))
|
|
return MXML_OPAQUE; /* Don't split strings up */
|
|
|
|
if ((pfxcmp(pname, "IlluminationOptions") == 0
|
|
|| pfxcmp(pname, "TristimulusSpec") == 0
|
|
|| pfxcmp(pname, "ColorSpaceSpecificationSpectrumTristimulus") == 0)
|
|
&& (pfxcmp(name, "Illuminant") == 0
|
|
|| pfxcmp(name, "Observer") == 0
|
|
|| pfxcmp(name, "FieldOfView") == 0))
|
|
return MXML_OPAQUE; /* Don't split strings up */
|
|
|
|
return MXML_TEXT;
|
|
}
|
|
|
|
|
|
static void sax_cb(mxml_node_t *node, mxml_sax_event_t event, void *data) {
|
|
namedc *p = (namedc *)data;
|
|
|
|
if (event == MXML_SAX_ELEMENT_OPEN) {
|
|
const char *pname = mxmlGetElement(node);
|
|
|
|
if (pfxcmp(pname, "Resources") == 0) {
|
|
//printf("~1 open of Resources\n");
|
|
p->indata = 1;
|
|
}
|
|
|
|
if ((p->options & NAMEDC_OP_NODATA) == 0
|
|
|| !p->indata) {
|
|
//printf("~1 options 0x%x, indata %d, retaining '%s'\n",p->options,p->indata,pname);
|
|
a1logd(p->log, 4, "sax_cb: retaining %s\n",pname);
|
|
mxmlRetain(node);
|
|
}
|
|
|
|
} else if (event == MXML_SAX_DIRECTIVE) {
|
|
mxmlRetain(node);
|
|
} else if (event == MXML_SAX_DATA) {
|
|
/* If the parent was retained, then retain this data node as well. */
|
|
if (mxmlGetRefCount(mxmlGetParent(node)) > 1) {
|
|
mxmlRetain(node);
|
|
}
|
|
} else if (event == MXML_SAX_ELEMENT_CLOSE) {
|
|
const char *pname = mxmlGetElement(node);
|
|
if(pfxcmp(pname, "Resources") == 0) {
|
|
//printf("~1 close of Resources\n");
|
|
p->indata = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Return a temporary key with prefix */
|
|
static char *pfx(namedc *p, char *key) {
|
|
if (p->pfx[0] == '\000')
|
|
return key;
|
|
|
|
snprintf(p->prefix, NAMEDC_PLEN, "%s:%s", p->pfx, key);
|
|
|
|
return p->prefix;
|
|
}
|
|
|
|
/* Return a temporary key with prefix at front and after each '/' */
|
|
static char *pfxp(namedc *p, char *path) {
|
|
char *cp = p->prefix;
|
|
char *sp, *ep;
|
|
int plen, slen;
|
|
|
|
if (p->pfx[0] == '\000')
|
|
return path;
|
|
|
|
plen = strlen(p->pfx);
|
|
|
|
/* Figure out the first source key */
|
|
sp = path;
|
|
if ((ep = strchr(sp, '/')) != NULL) {
|
|
slen = ep - sp;
|
|
ep++; /* Point beyond '/' */
|
|
} else {
|
|
slen = strlen(sp);
|
|
ep = sp + slen; /* point at '\000' */
|
|
}
|
|
|
|
for (;;) {
|
|
if ((NAMEDC_PLEN -1 -(cp - p->prefix)) < (plen + 1 + slen))
|
|
break; /* No room for prefix + path element */
|
|
|
|
strcpy(cp, p->pfx);
|
|
cp += plen;
|
|
*cp++ = ':';
|
|
strncpy(cp, sp, slen);
|
|
cp += slen;
|
|
|
|
/* Figure out the next source key */
|
|
sp = ep;
|
|
|
|
if ((ep = strchr(sp, '/')) != NULL) {
|
|
slen = ep - sp;
|
|
ep++; /* Point beyond '/' */
|
|
} else {
|
|
slen = strlen(sp);
|
|
ep = sp + slen; /* point at '\000' */
|
|
}
|
|
|
|
if (slen <= 0) /* No more path elements */
|
|
break;
|
|
|
|
if ((NAMEDC_PLEN -1 -(cp - p->prefix)) < 1)
|
|
break; /* No room for '/' */
|
|
*cp++ = '/';
|
|
}
|
|
*cp++ = '\000';
|
|
|
|
return p->prefix;
|
|
}
|
|
|
|
/* Return a temporary key with suffix */
|
|
static char *sfx(namedc *p, char *key) {
|
|
if (p->pfx[0] == '\000')
|
|
return key;
|
|
|
|
snprintf(p->prefix, 200, "%s:%s", key, p->pfx);
|
|
|
|
return p->prefix;
|
|
}
|
|
|
|
/* Read in a namedc from a cxf2 or cxf3 file */
|
|
/* Return nz on error */
|
|
static int read_cxf(namedc *p, const char *filename, int options) {
|
|
FILE *fp;
|
|
char *pfilename;
|
|
mxml_node_t *tree, *cxf, *pnode, *node;
|
|
const char *attr, *name;
|
|
int cxf2 = 0;
|
|
int specmin = 0, specinc = 0;
|
|
int i, j;
|
|
|
|
a1logd(p->log, 1, "read_cxf: file '%s' options 0x%x\n",filename,options);
|
|
|
|
if (p->filename == NULL) {
|
|
if ((pfilename = strdup(filename)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of filename failed");
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
return p->errc = 2;
|
|
}
|
|
clear_namedc(p);
|
|
p->filename = pfilename;
|
|
}
|
|
p->options = options;
|
|
|
|
if ((fp = fopen(p->filename, "r")) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Opening XML file '%s' failed with %s",
|
|
p->filename,strerror(errno));
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
#ifdef NEVER
|
|
tree = mxmlLoadFile(NULL, fp, type_cb);
|
|
#else
|
|
p->indata = 0;
|
|
tree = mxmlSAXLoadFd(NULL, fileno(fp), type_cb, sax_cb, (void *)p);
|
|
#endif
|
|
fclose(fp);
|
|
|
|
if (tree == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Parsing XML file '%s' failed",p->filename);
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
if ((cxf = mxmlFindElement(tree, tree, NULL, NULL, NULL, MXML_DESCEND_FIRST)) == NULL
|
|
|| mxmlGetType(cxf) != MXML_ELEMENT) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Failed to find top element in '%s'",p->filename);
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 1;
|
|
}
|
|
name = cxf->value.element.name;
|
|
|
|
if ((attr = strchr(cxf->value.element.name, ':')) != NULL) {
|
|
int len = attr - name;
|
|
if (len > 99)
|
|
len = 99;
|
|
strncpy(p->pfx, name, len);
|
|
p->pfx[len] = '\000';
|
|
} else {
|
|
p->pfx[0] = '\000';
|
|
}
|
|
|
|
a1logd(p->log, 4, "read_cxf: prefix '%s'\n",p->pfx);
|
|
|
|
if (strcmp(name, pfx(p, "CxF")) != 0) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Top element not called CxF in '%s'",p->filename);
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
/* Check that its CxF3 */
|
|
if ((attr = mxmlElementGetAttr(cxf, sfx(p, "xmlns"))) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Failed to find CxF attribute %s in '%s'",sfx(p, "xmlns"), p->filename);
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
if (strcmp(attr, "http://colorexchangeformat.com/CxF3-core") != 0) {
|
|
if (strcmp(attr, "http://colorexchangeformat.com/v2") != 0) {
|
|
snprintf(p->err, NAMEDC_ERRL, "File '%s' is not CxF format",p->filename);
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 1;
|
|
} else {
|
|
cxf2 = 1;
|
|
a1logd(p->log, 1, "read_cxf: This is a CxF2 file\n");
|
|
}
|
|
} else {
|
|
a1logd(p->log, 1, "read_cxf: This is a CxF3 file\n");
|
|
}
|
|
|
|
/* Grab the description */
|
|
if (cxf2) {
|
|
if ((node = mxmlFindPathNode(cxf, pfxp(p,"Palette"))) == NULL
|
|
|| (name = mxmlElementGetAttr(node, "PaletteName")) == NULL)
|
|
name = NULL;
|
|
|
|
if (name == NULL)
|
|
name = p->filename;
|
|
if ((p->description = strdup(name)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of description string failed");
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 2;
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: description '%s'\n",p->description);
|
|
|
|
p->hash = do_hash2(p->filename, p->description);
|
|
|
|
} else { /* else cxf3 */
|
|
if ((node = mxmlFindPathNode(cxf, pfxp(p,"FileInformation/Description"))) == NULL)
|
|
name = NULL;
|
|
else
|
|
name = mxmlGetOpaque(node);
|
|
if (name == NULL)
|
|
name = p->filename;
|
|
if ((p->description = strdup(name)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of description string failed");
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 2;
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: description '%s'\n",p->description);
|
|
|
|
p->hash = do_hash2(p->filename, p->description);
|
|
}
|
|
|
|
/* Look through the color specifications and see if there are spectral details */
|
|
pnode = mxmlFindPathNode(cxf, pfxp(p,"Resources/ColorSpecificationCollection/ColorSpecification/WavelengthRange"));
|
|
if (pnode == NULL)
|
|
pnode = mxmlFindPathNode(cxf, pfxp(p,"Resources/ColorSpecificationCollection/ColorSpecification/MeasurementSpec/WavelengthRange"));
|
|
while (pnode != NULL) {
|
|
name = mxmlElementGetAttr(pnode, "StartWL");
|
|
if (name != NULL) {
|
|
specmin = atoi(name);
|
|
a1logd(p->log, 2, "read_cxf: got StartWL %d\n",specmin);
|
|
}
|
|
name = mxmlElementGetAttr(pnode, "Increment");
|
|
if (name != NULL) {
|
|
specinc = atoi(name);
|
|
a1logd(p->log, 2, "read_cxf: got Increment %d\n",specinc);
|
|
}
|
|
pnode = mxmlGetNextSibling(pnode);
|
|
}
|
|
|
|
if ((p->options & NAMEDC_OP_NODATA) == 0) {
|
|
char *SampleKey = NULL;
|
|
char *SampleNameKey = NULL;
|
|
char *SampleLabKey = NULL;
|
|
const char *SpectSpecification = NULL;
|
|
char *SampleXYZKey = NULL;
|
|
char *SampleCMYKKey = NULL;
|
|
const char *colorSpecification = NULL; /* cxf3 ColorSpecification of Lab or XYZ */
|
|
xsp2cie *sp2cie = NULL;
|
|
|
|
if (cxf2) {
|
|
/* Locate the Palette/ColorSet node */
|
|
if ((pnode = mxmlFindPathNode(cxf, pfxp(p,"Palette/ColorSet"))) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Failed to find Resources/ObjectCollection in '%s'",p->filename);
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 1;
|
|
}
|
|
SampleKey = "Color";
|
|
SampleNameKey = "ColorName";
|
|
SampleLabKey = "ColorSpaceCIELab";
|
|
SampleXYZKey = "ColorSpaceCIEXYZ";
|
|
SampleCMYKKey = "ColorSpaceCMYK";
|
|
|
|
} else {
|
|
/* Locate the Resources/ObjectCollection node */
|
|
if ((pnode = mxmlFindPathNode(cxf, pfxp(p,"Resources/ObjectCollection"))) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Failed to find Resources/ObjectCollection in '%s'",p->filename);
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 1;
|
|
}
|
|
SampleKey = "Object";
|
|
SampleNameKey = "Name";
|
|
SampleLabKey = "ColorCIELab";
|
|
SampleXYZKey = "ColorCIEXYZ";
|
|
SampleCMYKKey = "ColorCMYK";
|
|
}
|
|
|
|
/* - - - - - - - - - - - - */
|
|
/* Read all the colors. */
|
|
/* Start with first node */
|
|
node = mxmlFindElement(pnode, pnode, pfx(p,SampleKey), NULL, NULL, MXML_DESCEND_FIRST);
|
|
for (i = 0; node != NULL; i++) {
|
|
int j;
|
|
mxml_node_t *ppvals, *pvals, *val;
|
|
const char *name;
|
|
double Lab[3];
|
|
int Lab_v = 0;
|
|
xspect *sp = NULL;
|
|
double dev[MAX_CHAN];
|
|
int dev_n = 0;
|
|
icColorSpaceSignature devSig = icMaxEnumColorSpace;
|
|
|
|
if (mxmlGetType(node) != MXML_ELEMENT) {
|
|
a1logd(p->log, DEB6, "read_cxf: skipping non element node type %d\n",mxmlGetType(node));
|
|
goto next;
|
|
}
|
|
a1logd(p->log, DEB6, "read_cxf: read node '%s'\n",node->value.element.name);
|
|
|
|
if (strcmp(node->value.element.name, pfx(p,SampleKey)) != 0) {
|
|
a1logd(p->log, DEB6, "read_cxf: skipping non %s node\n",SampleKey);
|
|
goto next;
|
|
}
|
|
|
|
if (!cxf2) {
|
|
/* Get the ObjectType attribute of Object */
|
|
if ((attr = mxmlElementGetAttr(node, "ObjectType")) == NULL) {
|
|
a1logd(p->log, DEB6, "read_cxf: skipping node without ObjectType\n");
|
|
goto next; /* Skip this one */
|
|
}
|
|
|
|
#ifdef NEVER
|
|
/* Check the attribute value (Should we ?) */
|
|
/* Known values are: Standard, Color */
|
|
/* May be Trial, Target, Substrate, Colorant, ... ? */
|
|
if (strcmp(attr, "Standard") != 0
|
|
&& strcmp(attr, "Color") != 0) {
|
|
a1logd(p->log, DEB6, "read_cxf: skipping node with ObjectType = '%s'\n",attr);
|
|
goto next; /* Skip this one */
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ((attr = mxmlElementGetAttr(node, SampleNameKey)) == NULL) {
|
|
a1logd(p->log, DEB6, "read_cxf: skipping without %s\n",SampleNameKey);
|
|
goto next; /* Skip this one */
|
|
}
|
|
name = attr;
|
|
|
|
a1logd(p->log, DEB4, "read_cxf: got color %d name '%s'\n",i,name);
|
|
|
|
if (!cxf2) {
|
|
if ((ppvals = mxmlFindElement(node, node, pfx(p,"ColorValues"), NULL, NULL, MXML_DESCEND_FIRST)) == NULL) {
|
|
a1logd(p->log, DEB4, "read_cxf: no reference ColorValuesname - skipping\n");
|
|
goto next;
|
|
}
|
|
} else {
|
|
ppvals = node;
|
|
}
|
|
|
|
/* Read any spectral values */
|
|
if ((p->options & NAMEDC_OP_NOSPEC) == 0
|
|
&& (pvals = mxmlFindElement(ppvals, ppvals, pfx(p,"ReflectanceSpectrum"), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
|
|
const char *elem = NULL;
|
|
|
|
a1logd(p->log, 4, "read_cxf: got ReflectanceSpectrum\n");
|
|
|
|
/* Check which color specification is being used with XYZ */
|
|
if (SpectSpecification == NULL) {
|
|
SpectSpecification = mxmlElementGetAttr(pvals, "ColorSpecification");
|
|
a1logd(p->log, 4, "read_cxf: got SpectSpecification '%s'\n",SpectSpecification);
|
|
}
|
|
|
|
if ((elem = mxmlElementGetAttr(pvals, "StartWL")) != NULL) {
|
|
int minwl = 0;
|
|
a1logd(p->log, 4, "read_cxf: got StartWL '%s'\n",elem);
|
|
minwl = atoi(elem);
|
|
|
|
if (specmin != 0) {
|
|
if (specmin != minwl) {
|
|
a1logd(p->log,1,"Inconsistent StartWL %d vs. %d from '%s'\n",specmin,minwl,p->filename);
|
|
goto skip_spectral;
|
|
}
|
|
} else {
|
|
specmin = minwl;
|
|
}
|
|
}
|
|
|
|
if ((elem = mxmlElementGetAttr(pvals, "Increment")) != NULL) {
|
|
int inc = 0;
|
|
a1logd(p->log, 4, "read_cxf: got Increment '%s'\n",elem);
|
|
inc = atoi(elem);
|
|
|
|
if (specinc != 0) {
|
|
if (specinc != inc) {
|
|
a1logd(p->log,1,"Inconsistent Increment %d vs. %d from '%s'\n",specinc,inc,p->filename);
|
|
goto skip_spectral;
|
|
}
|
|
} else {
|
|
specinc = inc;
|
|
}
|
|
}
|
|
|
|
if (specmin == 0 || specinc == 0) {
|
|
a1logd(p->log,1,"Missing: specmin %d specinc %d from '%s'\n",specmin,specinc,p->filename);
|
|
goto skip_spectral;
|
|
}
|
|
|
|
/* mxml stashes multiple elements as children.. */
|
|
if ((val = mxmlGetFirstChild(pvals)) == NULL) {
|
|
a1logd(p->log,1,"No spectral values in '%s'\n",p->filename);
|
|
goto skip_spectral;
|
|
}
|
|
|
|
if ((sp = calloc(1, sizeof(xspect))) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of xspect failed");
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 2;
|
|
}
|
|
|
|
for (j = 0; j < XSPECT_MAX_BANDS; j++) {
|
|
sp->spec[j] = 100.0 * mxmlGetReal(val);
|
|
a1logd(p->log, 6, "read_cxf: got spect component %d value %f\n",j,sp->spec[j]);
|
|
val = mxmlGetNextSibling(val);
|
|
if (val == NULL)
|
|
break;
|
|
}
|
|
sp->spec_n = j+1;
|
|
sp->spec_wl_short = (double)specmin;
|
|
sp->spec_wl_long = (double)(specmin + (sp->spec_n-1) * specinc);
|
|
sp->norm = 100.0;
|
|
|
|
a1logd(p->log, 6, "read_cxf: wl num %d short %f long %f\n",sp->spec_n,sp->spec_wl_short,sp->spec_wl_long);
|
|
}
|
|
skip_spectral:;
|
|
|
|
/* See if there is ColorCIELab */
|
|
if ((pvals = mxmlFindElement(ppvals, ppvals, pfx(p,SampleLabKey), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
|
|
char *key[3] = { "L", "A", "B" };
|
|
|
|
/* Check which color specification is being used with Lab */
|
|
if (!cxf2 && colorSpecification == NULL) {
|
|
colorSpecification = mxmlElementGetAttr(pvals, "ColorSpecification");
|
|
}
|
|
|
|
for (j = 0; j < 3; j++) {
|
|
if ((val = mxmlFindElement(pvals, pvals, pfx(p, key[j]), NULL, NULL, MXML_DESCEND_FIRST)) == NULL) {
|
|
a1logd(p->log, DEB6, "read_cxf: failed to find ColorCIELab component %s\n",key[j]);
|
|
break; /* oops */
|
|
}
|
|
Lab[j] = mxmlGetReal(val);
|
|
a1logd(p->log, DEB6, "read_cxf: got ColorCIELab component %s value %f\n",key[j],Lab[j]);
|
|
}
|
|
if (j >= 3)
|
|
Lab_v = 1;
|
|
}
|
|
|
|
if (!Lab_v) {
|
|
|
|
a1logd(p->log, DEB6, "read_cxf: no valid ColorCIELab value, look for XYZ\n");
|
|
|
|
/* See if there is ColorCIEXYZ instead */
|
|
if ((pvals = mxmlFindElement(ppvals, ppvals, pfx(p,SampleXYZKey), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
|
|
char *key[3] = { "X", "Y", "Z" };
|
|
|
|
/* Check which color specification is being used with Lab */
|
|
if (!cxf2 && colorSpecification == NULL) {
|
|
colorSpecification = mxmlElementGetAttr(pvals, "ColorSpecification");
|
|
}
|
|
|
|
for (j = 0; j < 3; j++) {
|
|
if ((val = mxmlFindElement(pvals, pvals, pfx(p, key[j]), NULL, NULL, MXML_DESCEND_FIRST)) == NULL) {
|
|
a1logd(p->log, DEB6, "read_cxf: failed to find ColorCIEXYZ component %s\n",key[j]);
|
|
break; /* oops */
|
|
}
|
|
Lab[j] = mxmlGetReal(val);
|
|
a1logd(p->log, DEB6, "read_cxf: got ColorCIEXYZ component %s value %f\n",key[j],Lab[j]);
|
|
}
|
|
if (j >= 3) {
|
|
icmXYZ2Lab(&icmD50, Lab, Lab);
|
|
Lab_v = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sp == NULL && !Lab_v) {
|
|
a1logd(p->log, DEB6, "read_cxf: no spectral or CIE value found - skipping color\n");
|
|
goto next;
|
|
}
|
|
|
|
/* Read any device color values */
|
|
if (!cxf2) {
|
|
if ((ppvals = mxmlFindElement(node, node, pfx(p,"DeviceColorValues"), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
|
|
|
|
a1logd(p->log, DEB4, "read_cxf: got DeviceColorValues\n");
|
|
}
|
|
}
|
|
|
|
if (ppvals != NULL) {
|
|
|
|
// ~~99 could add read of other device values here
|
|
|
|
/* See if there is ColorCMYK */
|
|
if ((pvals = mxmlFindElement(ppvals, ppvals, pfx(p,SampleCMYKKey), NULL, NULL, MXML_DESCEND_FIRST)) != NULL) {
|
|
char *key[4] = { "Cyan", "Magenta", "Yellow", "Black" };
|
|
|
|
a1logd(p->log, DEB4, "read_cxf: got ColorCMYK\n");
|
|
for (j = 0; j < 4; j++) {
|
|
if ((val = mxmlFindElement(pvals, pvals, pfx(p, key[j]), NULL, NULL, MXML_DESCEND_FIRST)) == NULL) {
|
|
a1logd(p->log, DEB6, "read_cxf: failed to find ColorCMYK component %s\n",key[j]);
|
|
break; /* oops */
|
|
}
|
|
dev[j] = mxmlGetReal(val);
|
|
a1logd(p->log, DEB6, "read_cxf: got ColorCMYK component %s value %f\n",key[j],dev[j]);
|
|
}
|
|
if (j >= 4) {
|
|
dev_n = j;
|
|
devSig = icSigCmykData;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add an entry */
|
|
if (p->count >= p->count_a) {
|
|
unsigned int count_n;
|
|
count_n = p->count_a + 4096/sizeof(nce);
|
|
|
|
a1logd(p->log, 8, "read_cxf: increasing data array size to %d\n",count_n);
|
|
if ((p->data = recalloc(p->data, p->count_a, sizeof(nce), count_n, sizeof(nce))) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of data size %d failed",p->count_a);
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 2;
|
|
}
|
|
p->count_a = count_n;
|
|
}
|
|
clear_nce(&p->data[p->count]);
|
|
|
|
if ((p->data[p->count].name = strdup(name)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of color name string failed");
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 2;
|
|
}
|
|
|
|
if (sp != NULL) {
|
|
p->data[p->count].sp = sp;
|
|
}
|
|
|
|
if (Lab_v) {
|
|
p->data[p->count].Lab[0] = Lab[0];
|
|
p->data[p->count].Lab[1] = Lab[1];
|
|
p->data[p->count].Lab[2] = Lab[2];
|
|
p->data[p->count].Lab_v = 1;
|
|
|
|
} else {
|
|
|
|
/* match() expects lab values */
|
|
if (sp2cie == NULL) {
|
|
if ((sp2cie = new_xsp2cie(p->ill, 0.0, NULL, p->obs, NULL, icSigLabData, 0)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "creating spectral conversion failed");
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 2;
|
|
}
|
|
}
|
|
sp2cie->convert(sp2cie, p->data[p->count].Lab, p->data[p->count].sp);
|
|
p->data[p->count].Lab_v = 1;
|
|
}
|
|
|
|
if (dev_n > 0 && devSig != icMaxEnumColorSpace) {
|
|
for (j = 0; j < dev_n; j++)
|
|
p->data[p->count].dev[j] = dev[j];
|
|
p->data[p->count].dev_n = dev_n;
|
|
p->data[p->count].devSig = devSig;
|
|
} else {
|
|
p->data[p->count].dev_n = 0;
|
|
p->data[p->count].devSig = icMaxEnumColorSpace;
|
|
}
|
|
|
|
a1logd(p->log, 8, "read_cxf: added color %d\n",p->count);
|
|
|
|
p->count++;
|
|
|
|
next:; /* Next color */
|
|
node = mxmlGetNextSibling(node);
|
|
}
|
|
|
|
/* - - - - - - - - - - - - */
|
|
/* Read extra information */
|
|
if (cxf2) {
|
|
/* Grab the creator */
|
|
if ((node = mxmlFindPathNode(cxf, pfxp(p,"Preamble/Header/Creator"))) == NULL)
|
|
name = NULL;
|
|
else
|
|
name = mxmlGetOpaque(node);
|
|
|
|
if (name == NULL)
|
|
name = "[Unknown]";
|
|
if ((p->creator = strdup(name)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of creator string failed");
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 2;
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: creator '%s'\n",p->creator);
|
|
|
|
/* Grab the illuminant type */
|
|
if ((node = mxmlFindPathNode(cxf, pfxp(p,"Palette/ColorSet/CollectionColorSpaceSpecification/ColorSpaceSpecificationSpectrumTristimulus/IlluminationOptions/Illuminant"))) == NULL
|
|
|| (name = mxmlGetOpaque(node)) == NULL) {
|
|
a1logd(p->log, 2, "read_cxf: failed to locate Illuminant - assuming D50\n");
|
|
p->ill = icxIT_D50;
|
|
} else {
|
|
if (strcmp(name, "Illuminant_A") == 0)
|
|
p->ill = icxIT_A;
|
|
else if (strcmp(name, "Illuminant_D50") == 0)
|
|
p->ill = icxIT_D50;
|
|
else if (strcmp(name, "Illuminant_D55") == 0)
|
|
p->ill = icxIT_D55;
|
|
else if (strcmp(name, "Illuminant_D65") == 0)
|
|
p->ill = icxIT_D65;
|
|
else if (strcmp(name, "Illuminant_E") == 0)
|
|
p->ill = icxIT_E;
|
|
else {
|
|
a1logd(p->log, 2, "read_cxf: Illuminant '%s' unrecognised\n",name);
|
|
p->ill = icxIT_D50;
|
|
}
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: illuminant '%s'\n",icm2str(icmIlluminant, p->ill));
|
|
|
|
/* Grab the observer type */
|
|
if ((node = mxmlFindPathNode(cxf, pfxp(p,"Palette/ColorSet/CollectionColorSpaceSpecification/ColorSpaceSpecificationSpectrumTristimulus/FieldOfView"))) == NULL
|
|
|| (name = mxmlGetOpaque(node)) == NULL) {
|
|
a1logd(p->log, 2, "read_cxf: failed to locate FieldOfView - assuming 2 degree\n");
|
|
p->obs = icxOT_CIE_1931_2;
|
|
} else {
|
|
if (strcmp(name, "FieldOfView_2_Degree") == 0)
|
|
p->obs = icxOT_CIE_1931_2;
|
|
else if (strcmp(name, "FieldOfView_10_Degree") == 0)
|
|
p->obs = icxOT_CIE_1964_10;
|
|
else {
|
|
a1logd(p->log, 2, "read_cxf: Observer '%s' unrecognised\n",name);
|
|
p->obs = icxOT_CIE_1931_2;
|
|
}
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: observer '%s'\n",icm2str(icmStandardObserver, p->obs));
|
|
|
|
} else {
|
|
int found_io = 0;
|
|
|
|
/* Grab the creator */
|
|
if ((node = mxmlFindPathNode(cxf, pfxp(p,"FileInformation/Creator"))) == NULL)
|
|
name = NULL;
|
|
else
|
|
name = mxmlGetOpaque(node);
|
|
|
|
if (name == NULL)
|
|
name = "[Unknown]";
|
|
if ((p->creator = strdup(name)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of creator string failed");
|
|
a1logd(p->log, 1, "read_cxf: %s\n",p->err);
|
|
mxmlDelete(tree);
|
|
return p->errc = 2;
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: creator '%s'\n",p->creator);
|
|
|
|
if (colorSpecification != NULL) {
|
|
a1logd(p->log, 2, "read_cxf: colorSpecification '%s'\n",colorSpecification);
|
|
}
|
|
|
|
/* Look through the color specifications and find the one that matches */
|
|
/* the Lab or XYZ color specification */
|
|
pnode = mxmlFindPathNode(cxf, pfxp(p,"Resources/ColorSpecificationCollection/ColorSpecification"));
|
|
while (pnode != NULL) {
|
|
name = mxmlElementGetAttr(pnode, "Id");
|
|
|
|
if (colorSpecification == NULL
|
|
|| (name != NULL && strcmp(name, colorSpecification) == 0)) {
|
|
|
|
a1logd(p->log, 2, "read_cxf: found node matching colorSpecification '%s'\n",colorSpecification);
|
|
|
|
/* Grab the illuminant type */
|
|
if ((node = mxmlFindPathNode(pnode, pfxp(p,"TristimulusSpec/Illuminant"))) == NULL
|
|
|| (name = mxmlGetOpaque(node)) == NULL) {
|
|
a1logd(p->log, 2, "read_cxf: failed to locate Illuminant - assuming D50\n");
|
|
p->ill = icxIT_D50;
|
|
} else {
|
|
if (strcmp(name, "A") == 0)
|
|
p->ill = icxIT_A;
|
|
else if (strcmp(name, "D50") == 0)
|
|
p->ill = icxIT_D50;
|
|
else if (strcmp(name, "D55") == 0)
|
|
p->ill = icxIT_D55;
|
|
else if (strcmp(name, "D65") == 0)
|
|
p->ill = icxIT_D65;
|
|
else if (strcmp(name, "E") == 0)
|
|
p->ill = icxIT_E;
|
|
else {
|
|
a1logd(p->log, 2, "read_cxf: Illuminant '%s' unrecognised\n",name);
|
|
p->ill = icxIT_D50;
|
|
}
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: illuminant '%s'\n",icm2str(icmIlluminant, p->ill));
|
|
|
|
/* Grab the first observer type */
|
|
if ((node = mxmlFindPathNode(cxf, pfxp(p,"Resources/ColorSpecificationCollection/ColorSpecification/TristimulusSpec/Observer"))) == NULL
|
|
|| (name = mxmlGetOpaque(node)) == NULL) {
|
|
a1logd(p->log, 2, "read_cxf: failed to locate Observer - assuming 2 degree\n");
|
|
p->obs = icxOT_CIE_1931_2;
|
|
} else {
|
|
if (strcmp(name, "2_Degree") == 0)
|
|
p->obs = icxOT_CIE_1931_2;
|
|
else if (strcmp(name, "10_Degree") == 0)
|
|
p->obs = icxOT_CIE_1964_10;
|
|
else {
|
|
a1logd(p->log, 2, "read_cxf: Observer '%s' unrecognised\n",name);
|
|
p->obs = icxOT_CIE_1931_2;
|
|
}
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: observer '%s'\n",icm2str(icmStandardObserver, p->obs));
|
|
found_io = 1;
|
|
break;
|
|
}
|
|
pnode = mxmlGetNextSibling(pnode);
|
|
}
|
|
|
|
if (!found_io) {
|
|
p->ill = icxIT_D50;
|
|
p->obs = icxOT_CIE_1931_2;
|
|
a1logd(p->log, 2, "read_cxf: failed to locate ColorSpecification - assuming D50 2 degree observer\n");
|
|
}
|
|
|
|
/* Other values of interest:
|
|
Resources/ColorSpecificationCollection/ColorSpecification/ColorSpecification/MeasurementSpec/MeasurementType ie. "Spectrum_Reflectance"
|
|
Resources/ColorSpecificationCollection/ColorSpecification/ColorSpecification/MeasurementSpec/CalibrationStandard ie. "GMDI" for Gretag Macbeth Calibration, "XRGA" for X-Rite Graphic Arts Standard.
|
|
Resources/ColorSpecificationCollection/ColorSpecification/ColorSpecification/MeasurementSpec/Device/DeviceFilter ie. "Filter_None" "Filter_UVExcluded",
|
|
*/
|
|
}
|
|
}
|
|
|
|
mxmlDelete(tree);
|
|
|
|
a1logd(p->log, 1, "read_cxf: done - %d colors\n",p->count);
|
|
return 0;
|
|
}
|
|
|
|
/* Read in a namedc from a ICC file */
|
|
/* Return nz on error */
|
|
static int read_icc(namedc *p, const char *filename, int options) {
|
|
char *pfilename;
|
|
icmFile *fp;
|
|
icmErr err = { 0, { '\000'} };
|
|
icc *icco;
|
|
|
|
a1logd(p->log, 1, "read_icc: file '%s' options 0x%x\n",filename,options);
|
|
|
|
if (p->filename == NULL) {
|
|
if ((pfilename = strdup(filename)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of filename failed");
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
return p->errc = 2;
|
|
}
|
|
clear_namedc(p);
|
|
p->filename = pfilename;
|
|
}
|
|
p->options = options;
|
|
|
|
/* Open up the file for reading */
|
|
if ((fp = new_icmFileStd_name(&err,p->filename,"r")) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Opening ICC file '%s' failed with %s (0x%x, '%s')",
|
|
p->filename,strerror(errno),err.c,err.m);
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
if ((icco = new_icc(&err)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Creation ICC object failed");
|
|
a1logd(p->log, 1, "read_icc: %s (0x%x, '%s')\n",p->err,err.c,err.m);
|
|
fp->del(fp);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
if (icco->read(icco, fp, 0) != 0) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Failed to read '%s'\n",p->filename);
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
if (icco->header->deviceClass != icSigNamedColorClass) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Not a named color profile");
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
if (icco->header->pcs != icSigXYZData
|
|
&& icco->header->pcs != icSigLabData) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Unrecognised PCS");
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
if (icco->find_tag(icco, icSigNamedColor2Tag) != 0) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Can't find ncl2 tag");
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
{
|
|
icmTextDescription *txd;
|
|
|
|
/* Try and read the tag from the file */
|
|
if ((txd = (icmTextDescription *)icco->read_tag(icco, icSigProfileDescriptionTag)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "No description tag");
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
if ((p->description = strdup(txd->desc)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of description string failed");
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 2;
|
|
}
|
|
a1logd(p->log, 2, "read_icc: description '%s'\n",p->description);
|
|
}
|
|
|
|
p->hash = do_hash2(p->filename, p->description);
|
|
|
|
if ((p->options & NAMEDC_OP_NODATA) == 0) {
|
|
icmNamedColor *tag;
|
|
char name[3 * 32];
|
|
double Lab[3];
|
|
int Lab_v = 0;
|
|
double dev[MAX_CHAN];
|
|
int dev_n = 0;
|
|
icColorSpaceSignature devSig = icMaxEnumColorSpace;
|
|
int i, j;
|
|
|
|
#ifdef NEVER
|
|
/* See if there is a measurementType tag */
|
|
icmMeasurement *meastag;
|
|
if ((meastag = (icmMeasurement *)icco->read_tag(icco, icSigMeasurementTag)) != NULL) {
|
|
p->ill = meastag->illuminant;
|
|
p->obs = meastag->observer;
|
|
a1logd(p->log, 2, "read_cxf: assuming D50 illuminant and 2 degree observer\n");
|
|
} else {
|
|
p->ill = icIlluminantD50;
|
|
p->obs = icStdObs1931TwoDegrees;
|
|
}
|
|
a1logd(p->log, 2, "read_cxf: illuminant '%s'\n",icm2str(icmIlluminant, p->ill));
|
|
a1logd(p->log, 2, "read_cxf: observer '%s'\n",icm2str(icmStandardObserver, p->obs));
|
|
#endif
|
|
|
|
if ((tag = (icmNamedColor *)icco->read_tag(icco, icSigNamedColor2Tag)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Can't read ncl2 tag");
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 1;
|
|
}
|
|
|
|
for (i = 0; i < tag->count; i++) {
|
|
|
|
/* Get the name */
|
|
strcpy(name, tag->prefix);
|
|
strcat(name, tag->data[i].root);
|
|
strcat(name, tag->suffix);
|
|
|
|
a1logd(p->log, DEB4, "read_icc: got color %d name '%s'\n",i,name);
|
|
|
|
if (icco->header->pcs == icSigXYZData) {
|
|
Lab[0] = tag->data[i].pcsCoords[0];
|
|
Lab[1] = tag->data[i].pcsCoords[1];
|
|
Lab[2] = tag->data[i].pcsCoords[2];
|
|
icmXYZ2Lab(&icmD50, Lab, Lab);
|
|
|
|
} else { /* Lab */
|
|
Lab[0] = tag->data[i].pcsCoords[0];
|
|
Lab[1] = tag->data[i].pcsCoords[1];
|
|
Lab[2] = tag->data[i].pcsCoords[2];
|
|
}
|
|
Lab_v = 1;
|
|
a1logd(p->log, DEB6, "read_icc: got ColorCIELab value %s\n",icmPdv(3, Lab));
|
|
|
|
/* Add any device space */
|
|
devSig = icco->header->colorSpace;
|
|
dev_n = icmCSSig2nchan(devSig);
|
|
for (j = 0; j < dev_n; j++)
|
|
dev[j] = tag->data[i].deviceCoords[j];
|
|
|
|
a1logd(p->log, DEB6, "read_icc: got %s value %s\n",
|
|
icm2str(icmColorSpaceSig, devSig), icmPdv(dev_n, dev));
|
|
|
|
/* Add an entry */
|
|
if (p->count >= p->count_a) {
|
|
unsigned int count_n;
|
|
count_n = p->count_a + 4096/sizeof(nce);
|
|
|
|
a1logd(p->log, 8, "read_icc: increasing data array size to %d\n",count_n);
|
|
if ((p->data = recalloc(p->data, p->count_a, sizeof(nce), count_n, sizeof(nce))) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of data size %d failed",p->count_a);
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 2;
|
|
}
|
|
p->count_a = count_n;
|
|
}
|
|
clear_nce(&p->data[p->count]);
|
|
|
|
if ((p->data[p->count].name = strdup(name)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "Malloc of color name string failed");
|
|
a1logd(p->log, 1, "read_icc: %s\n",p->err);
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
return p->errc = 2;
|
|
}
|
|
|
|
if (Lab_v) {
|
|
p->data[p->count].Lab[0] = Lab[0];
|
|
p->data[p->count].Lab[1] = Lab[1];
|
|
p->data[p->count].Lab[2] = Lab[2];
|
|
p->data[p->count].Lab_v = 1;
|
|
}
|
|
|
|
if (dev_n > 0 && devSig != icMaxEnumColorSpace) {
|
|
for (j = 0; j < dev_n; j++)
|
|
p->data[p->count].dev[j] = dev[j];
|
|
p->data[p->count].dev_n = dev_n;
|
|
p->data[p->count].devSig = devSig;
|
|
} else {
|
|
p->data[p->count].dev_n = 0;
|
|
p->data[p->count].devSig = icMaxEnumColorSpace;
|
|
}
|
|
|
|
a1logd(p->log, 8, "read_icc: added color %d\n",p->count);
|
|
|
|
p->count++;
|
|
|
|
next:; /* Next color */
|
|
}
|
|
}
|
|
|
|
icco->del(icco);
|
|
fp->del(fp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Read any format named color files */
|
|
/* Return nz on error */
|
|
static int read_nc(namedc *p, const char *filename, int options) {
|
|
int rv;
|
|
|
|
if ((p->format == 0 || p->format == 1)
|
|
&& (rv = read_cxf(p, filename, options)) == 0) {
|
|
p->format = 1;
|
|
return rv;
|
|
}
|
|
|
|
if ((p->format == 0 || p->format == 2)
|
|
&& (rv = read_icc(p, filename, options)) == 0) {
|
|
p->format = 2;
|
|
return rv;
|
|
}
|
|
|
|
/* Try other file types here */
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
/* Return the index of the best mataching color, -1 on error. */
|
|
/* Lab[] is assumed to be D50, 2 degree standard observer based CIE value, */
|
|
/* and the spec value should only be provided if this is a reflective or */
|
|
/* transmissive measurement, NULL if emissive. */
|
|
/* If named color library is expects other than D50, 2 degree, then */
|
|
/* it will use the spectral value if not NULL, or chromatically */
|
|
/* adapt the Lab value. */
|
|
/* deType == 0 DE76 */
|
|
/* deType == 1 DE94 */
|
|
/* deType == 2 DE2000 */
|
|
/* if de != NULL, return the delta E */
|
|
int match(struct _namedc *p, double *de, double *pLab, xspect *rspect, int deType) {
|
|
int i, bix = -1;
|
|
double bde = 1e99;
|
|
double Lab[3];
|
|
|
|
if (p->filename == NULL) { /* We haven't been opened */
|
|
snprintf(p->err, NAMEDC_ERRL, "We haven't been opened");
|
|
a1logd(p->log, 1, "match: %s\n",p->err);
|
|
return -1;
|
|
}
|
|
|
|
/* If the colors haven't been read yet, read them now */
|
|
if (p->data == NULL || (p->options & NAMEDC_OP_NODATA)) {
|
|
if (read_nc(p, NULL, (p->options & ~NAMEDC_OP_NODATA))) {
|
|
a1logd(p->log, 1, "match: on demand data load failed with '%s'\n",p->err);
|
|
return -1;
|
|
}
|
|
a1logd(p->log, 1, "match: after loading there are %d colors\n",p->count);
|
|
}
|
|
|
|
icmCpy3(Lab, pLab);
|
|
|
|
if (p->ill != (icxIllumeType)icIlluminantD50
|
|
|| p->obs != (icxObserverType)icStdObs1931TwoDegrees) {
|
|
if (rspect != NULL) {
|
|
|
|
if (p->sp2cie == NULL) {
|
|
if ((p->sp2cie = new_xsp2cie(p->ill, 0.0, NULL, p->obs, NULL, icSigLabData, 0)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "creating spectral conversion failed");
|
|
a1logd(p->log, 1, "match: %s\n",p->err);
|
|
return -1;
|
|
|
|
}
|
|
}
|
|
|
|
/* Convert spectrum to the Lab we want */
|
|
p->sp2cie->convert(p->sp2cie, Lab, rspect);
|
|
|
|
} else {
|
|
if (p->chrom[0][0] <= -1e38) {
|
|
double wXYZ[3];
|
|
|
|
// Special case this for a consistent value with ICC profiles
|
|
if (p->obs == icxOT_CIE_1931_2 && p->ill == icxIT_D65) {
|
|
icmCpy3(wXYZ, icmD65_ary3);
|
|
} else {
|
|
xsp2cie *tt;
|
|
xspect ts;
|
|
// Get the XYZ of the given white point for the illuminant and observer
|
|
if ((tt = new_xsp2cie(p->ill, 0.0, NULL, p->obs, NULL, icSigXYZData, 0)) == NULL) {
|
|
snprintf(p->err, NAMEDC_ERRL, "creating spectral conversion failed");
|
|
a1logd(p->log, 1, "match: %s\n",p->err);
|
|
return -1;
|
|
}
|
|
if (standardIlluminant(&ts, icxIT_E, 0.0)) {
|
|
snprintf(p->err, NAMEDC_ERRL, "match: creating E type spectrum failed");
|
|
a1logd(p->log, 1, "match: %s\n",p->err);
|
|
return -1;
|
|
}
|
|
tt->convert(tt, wXYZ, &ts);
|
|
tt->del(tt);
|
|
}
|
|
icmAry2XYZ(p->dXYZ, wXYZ);
|
|
|
|
/* Chreate chromatic adapation matrix from D50 to named color */
|
|
icmChromAdaptMatrix(ICM_CAM_BRADFORD, p->dXYZ, icmD50, p->chrom);
|
|
}
|
|
icmLab2XYZ(&icmD50, Lab, pLab);
|
|
icmMulBy3x3(Lab, p->chrom, Lab);
|
|
icmXYZ2Lab(&p->dXYZ, Lab, Lab);
|
|
}
|
|
}
|
|
|
|
if (deType == 0) {
|
|
for (i = 0; i < p->count; i++) {
|
|
double de = icmLabDEsq(Lab, p->data[i].Lab);
|
|
if (de < bde) {
|
|
bde = de;
|
|
bix = i;
|
|
}
|
|
}
|
|
|
|
} else if (deType == 1) {
|
|
for (i = 0; i < p->count; i++) {
|
|
double de = icmCIE94sq(Lab, p->data[i].Lab);
|
|
if (de < bde) {
|
|
bde = de;
|
|
bix = i;
|
|
}
|
|
}
|
|
|
|
} else if (deType == 2) {
|
|
for (i = 0; i < p->count; i++) {
|
|
double de = icmCIE2Ksq(Lab, p->data[i].Lab);
|
|
if (de < bde) {
|
|
bde = de;
|
|
bix = i;
|
|
}
|
|
}
|
|
} else {
|
|
snprintf(p->err, NAMEDC_ERRL, "Unnown deType %d",deType);
|
|
a1logd(p->log, 1, "match: %s\n",p->err);
|
|
return -1;
|
|
}
|
|
|
|
if (bix < 0) {
|
|
snprintf(p->err, NAMEDC_ERRL, "No colors to match against");
|
|
a1logd(p->log, 1, "match: %s\n",p->err);
|
|
return -1;
|
|
}
|
|
if (de != NULL) {
|
|
*de = sqrt(bde);
|
|
}
|
|
return bix;
|
|
}
|
|
|
|
/* Free an entry */
|
|
static void clear_nce(nce *p) {
|
|
if (p != NULL) {
|
|
if (p->name != NULL)
|
|
free(p->name);
|
|
p->name = NULL;
|
|
if (p->sp != NULL)
|
|
free(p->sp);
|
|
p->sp = NULL;
|
|
}
|
|
}
|
|
|
|
/* Free the contents */
|
|
static void clear_namedc(namedc *p) {
|
|
if (p != NULL) {
|
|
int i;
|
|
if (p->filename != NULL)
|
|
free(p->filename);
|
|
p->filename = NULL;
|
|
if (p->creator != NULL)
|
|
free(p->creator);
|
|
p->creator = NULL;
|
|
if (p->description != NULL)
|
|
free(p->description);
|
|
p->description = NULL;
|
|
if (p->data != NULL) {
|
|
for (i = 0; i < p->count; i++)
|
|
clear_nce(&p->data[i]);
|
|
free(p->data);
|
|
p->data = NULL;
|
|
}
|
|
p->count = 0;
|
|
}
|
|
}
|
|
|
|
/* Delete it */
|
|
static void del_namedc(namedc *p) {
|
|
if (p != NULL) {
|
|
clear_namedc(p);
|
|
p->log = del_a1log(p->log);
|
|
if (p->sp2cie != NULL)
|
|
p->sp2cie->del(p->sp2cie);
|
|
free(p);
|
|
}
|
|
}
|
|
|
|
/* Allocate a new, uninitialised namedc */
|
|
/* Note thate black and white points aren't allocated */
|
|
namedc *new_namedc(a1log *log) {
|
|
namedc *p;
|
|
|
|
a1logd(log, 1, "new_cxf\n");
|
|
|
|
if ((p = (namedc *)calloc(1, sizeof(namedc))) == NULL) {
|
|
a1logd(p->log, 1, "new_cxf: calloc failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
p->log = new_a1log_d(log);
|
|
|
|
/* Init method pointers */
|
|
p->del = del_namedc;
|
|
p->read_cxf = read_cxf;
|
|
p->read_icc = read_icc;
|
|
p->read = read_nc;
|
|
p->match = match;
|
|
|
|
p->chrom[0][0] = -1e38;
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
/* =========================================================================== */
|
|
|
|
#else /* STANDALONE_TEST */
|
|
|
|
void usage(char *diag, ...) {
|
|
fprintf(stderr,"Test namedc library\n");
|
|
fprintf(stderr,"Author: Graeme W. Gill\n");
|
|
if (diag != NULL) {
|
|
va_list args;
|
|
fprintf(stderr," Diagnostic: ");
|
|
va_start(args, diag);
|
|
vfprintf(stderr, diag, args);
|
|
va_end(args);
|
|
fprintf(stderr,"\n");
|
|
}
|
|
fprintf(stderr,"usage: namedc [-D level] infile\n");
|
|
fprintf(stderr," -D level Debug level 1-9\n");
|
|
exit(1);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[]) {
|
|
int fa,nfa; /* argument we're looking at */
|
|
static char inname[MAXNAMEL+1];
|
|
int debug = 0, i;
|
|
namedc *p;
|
|
|
|
/* Process the arguments */
|
|
for(fa = 1;fa < argc;fa++) {
|
|
nfa = fa; /* skip to nfa if next argument is used */
|
|
if (argv[fa][0] == '-') { /* Look for any flags */
|
|
char *na = NULL; /* next argument after flag, null if none */
|
|
|
|
if (argv[fa][2] != '\000')
|
|
na = &argv[fa][2]; /* next is directly after flag */
|
|
else {
|
|
if ((fa+1) < argc) {
|
|
if (argv[fa+1][0] != '-') {
|
|
nfa = fa + 1;
|
|
na = argv[nfa]; /* next is seperate non-flag argument */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (argv[fa][1] == '?')
|
|
usage(NULL);
|
|
|
|
/* Debug level */
|
|
else if (argv[fa][1] == 'D') {
|
|
fa = nfa;
|
|
if (na != NULL)
|
|
debug = atoi(na);
|
|
}
|
|
|
|
else
|
|
usage("Unknown option '%c'",argv[fa][1]);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (fa >= argc || argv[fa][0] == '-') usage("Missing input filename");
|
|
strncpy(inname,argv[fa++],MAXNAMEL); inname[MAXNAMEL] = '\000';
|
|
|
|
g_log->debug = debug;
|
|
|
|
if ((p = new_namedc(g_log)) == NULL)
|
|
error("new_namedc failed\n");
|
|
|
|
/* Open non-data */
|
|
if (p->read(p, inname, NAMEDC_OP_NODATA)) {
|
|
error("read failed with '%s'\n",p->err);
|
|
}
|
|
|
|
printf("Palette is '%s'\n",p->description);
|
|
{
|
|
double Lab[3], de;
|
|
int ix;
|
|
|
|
Lab[0] = 50.0;
|
|
Lab[1] = 20.0;
|
|
Lab[2] = -10.0;
|
|
|
|
if ((ix = p->match(p, &de, Lab, NULL, 0)) < 0)
|
|
error(" match failed with '%s'\n",p->err);
|
|
printf("Matched color '%s' with DE76 %f\n",p->data[ix].name,de);
|
|
|
|
if ((ix = p->match(p, &de, Lab, NULL, 1)) < 0)
|
|
error(" match failed with '%s'\n",p->err);
|
|
printf("Matched color '%s' with DE94 %f\n",p->data[ix].name,de);
|
|
|
|
if ((ix = p->match(p, &de, Lab, NULL, 2)) < 0)
|
|
error(" match failed with '%s'\n",p->err);
|
|
printf("Matched color '%s' with DE00 %f\n",p->data[ix].name,de);
|
|
}
|
|
|
|
#ifdef NEVER
|
|
printf("Loaded %d colors\n",p->count);
|
|
for (i = 0; i < p->count; i++) {
|
|
printf("Color %d name '%s' = %f %f %f\n",
|
|
i, p->data[i].name, p->data[i].Lab[0], p->data[i].Lab[1], p->data[i].Lab[2]);
|
|
if (p->data[i].devSig != icMaxEnumColorSpace) {
|
|
printf("%s = %s\n", icm2str(icmColorSpaceSig, p->data[i].devSig),
|
|
icmPdv(p->data[i].dev_n, p->data[i].dev));
|
|
}
|
|
}
|
|
#endif
|
|
p->del(p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
#endif /* STANDALONE_TEST */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|