summaryrefslogtreecommitdiff
path: root/icc/icc.c
diff options
context:
space:
mode:
Diffstat (limited to 'icc/icc.c')
-rw-r--r--icc/icc.c987
1 files changed, 636 insertions, 351 deletions
diff --git a/icc/icc.c b/icc/icc.c
index 7dfe519..87ce500 100644
--- a/icc/icc.c
+++ b/icc/icc.c
@@ -373,13 +373,13 @@ const char *format,
int rv;
va_list args;
icmFileMem *p = (icmFileMem *)pp;
- int len;
+ int alen, len;
va_start(args, format);
rv = 1;
- len = 100; /* Initial allocation for printf */
- icmFileMem_filemem_resize(p, p->cur + len);
+ alen = 100; /* Initial allocation for printf */
+ icmFileMem_filemem_resize(p, p->cur + alen);
/* We have to use the available printf functions to resize the buffer if needed. */
for (;rv != 0;) {
@@ -391,15 +391,15 @@ const char *format,
break;
if (len > -1) /* vsnprintf returned needed size-1 */
- len = len+2; /* (In case vsnprintf returned 1 less than it needs) */
+ alen = len+2; /* (In case vsnprintf returned 1 less than it needs) */
else
- len *= 2; /* We just have to guess */
+ alen *= 2; /* We just have to guess */
/* Attempt to resize */
- icmFileMem_filemem_resize(p, p->cur + len);
+ icmFileMem_filemem_resize(p, p->cur + alen);
/* If resize failed */
- if ((p->aend - p->cur) < len) {
+ if ((p->aend - p->cur) < alen) {
rv = 0;
break;
}
@@ -710,12 +710,17 @@ static int write_S15Fixed16Number(double d, char *p) {
return 0;
}
+/* Round a number to the same quantization as a S15Fixed16 */
static double round_S15Fixed16Number(double d) {
- d = floor(d * 65536.0 + 0.5); /* Beware! (int)(d + 0.5) doesn't work! */
+ d = floor(d * 65536.0 + 0.5); /* Beware! (int)(d + 0.5) doesn't work for -ve nummbets ! */
d = d/65536.0;
return d;
}
+/* Macro version */
+#define RND_S15FIXED16(xxx) ((xxx) > 0.0 ? (int)((xxx) * 65536.0 + 0.5)/65536.0 \
+ : (int)((xxx) * 65536.0 - 0.5)/65536.0)
+
/* Device coordinate as 8 bit value range 0.0 - 1.0 */
static double read_DCS8Number(char *p) {
unsigned int rv;
@@ -774,7 +779,6 @@ static void Lut_L2LutV2_16(double *out, double *in);
static void Lut_Lut2LV4_16(double *out, double *in);
static void Lut_L2LutV4_16(double *out, double *in);
-//~~~~888888
/* read a PCS number. PCS can be profile PCS, profile version Lab, */
/* or a specific type of Lab, depending on the value of csig: */
/* icmSigPCSData, icSigXYZData, icmSigLab8Data, icSigLabData, */
@@ -1397,6 +1401,8 @@ static const char *string_TagSignature(icTagSignature sig) {
return "BToA2 Multidimentional Transform";
case icSigCalibrationDateTimeTag:
return "Calibration Date & Time";
+ case icSigChromaticAdaptationTag:
+ return "Chromatic Adaptation";
case icSigCharTargetTag:
return "Characterization Target";
case icSigCopyrightTag:
@@ -1467,6 +1473,11 @@ static const char *string_TagSignature(icTagSignature sig) {
return "Viewing Condition Description";
case icSigViewingConditionsTag:
return "Viewing Condition Paramaters";
+
+ /* ArgyllCMS private tag: */
+ case icmSigAbsToRelTransSpace:
+ return "Absolute to Media Relative Transformation Space matrix";
+
default:
sprintf(buf,"Unrecognized - %s",tag2str(sig));
return buf;
@@ -3071,7 +3082,7 @@ static void icmU16Fixed16Array_dump(
if (verb >= 2) {
unsigned int i;
for (i = 0; i < p->size; i++)
- op->gprintf(op," %lu: %f\n",i,p->data[i]);
+ op->gprintf(op," %lu: %.8f\n",i,p->data[i]);
}
}
@@ -3265,7 +3276,7 @@ static void icmS15Fixed16Array_dump(
if (verb >= 2) {
unsigned int i;
for (i = 0; i < p->size; i++)
- op->gprintf(op," %lu: %f\n",i,p->data[i]);
+ op->gprintf(op," %lu: %.8f\n",i,p->data[i]);
}
}
@@ -3350,7 +3361,7 @@ static int read_XYZNumber(icmXYZNumber *p, char *d) {
static char *string_XYZNumber(icmXYZNumber *p) {
static char buf[40];
- sprintf(buf,"%f, %f, %f", p->X, p->Y, p->Z);
+ sprintf(buf,"%.8f, %.8f, %.8f", p->X, p->Y, p->Z);
return buf;
}
@@ -3363,7 +3374,7 @@ static char *string_XYZNumber_and_Lab(icmXYZNumber *p) {
lab[1] = p->Y;
lab[2] = p->Z;
icmXYZ2Lab(&icmD50, lab, lab);
- snprintf(buf,sizeof(buf),"%f, %f, %f [Lab %f, %f, %f]", p->X, p->Y, p->Z, lab[0], lab[1], lab[2]);
+ snprintf(buf,sizeof(buf),"%.8f, %.8f, %.8f [Lab %f, %f, %f]", p->X, p->Y, p->Z, lab[0], lab[1], lab[2]);
return buf;
}
@@ -3772,6 +3783,8 @@ static int icmTable_lookup_bwd(
/* Do a reverse lookup through the curve */
/* Return 0 on success, 1 if clipping occured, 2 on other error */
+/* (Note that clipping means mathematical clipping, and is not */
+/* set just because a device value is out of gamut. */
static int icmCurve_lookup_bwd(
icmCurve *p,
double *out,
@@ -3950,7 +3963,7 @@ static int icmCurve_write(
return icp->errc = 1;
}
if ((rv = write_U8Fixed8Number(p->data[0],bp)) != 0) {
- sprintf(icp->err,"icmCurve_write: write_U8Fixed8umber(%f) failed",p->data[0]);
+ sprintf(icp->err,"icmCurve_write: write_U8Fixed8umber(%.8f) failed",p->data[0]);
icp->al->free(icp->al, buf);
return icp->errc = rv;
}
@@ -3962,7 +3975,7 @@ static int icmCurve_write(
}
for (i = 0; i < p->size; i++, bp += 2) {
if ((rv = write_DCS16Number(p->data[i],bp)) != 0) {
- sprintf(icp->err,"icmCurve_write: write_UInt16umber(%f) failed",p->data[i]);
+ sprintf(icp->err,"icmCurve_write: write_UInt16umber(%.8f) failed",p->data[i]);
icp->al->free(icp->al, buf);
return icp->errc = rv;
}
@@ -3995,13 +4008,13 @@ static void icmCurve_dump(
if (p->flag == icmCurveLin) {
op->gprintf(op," Curve is linear\n");
} else if (p->flag == icmCurveGamma) {
- op->gprintf(op," Curve is gamma of %f\n",p->data[0]);
+ op->gprintf(op," Curve is gamma of %.8f\n",p->data[0]);
} else {
op->gprintf(op," No. elements = %lu\n",p->size);
if (verb >= 2) {
unsigned int i;
for (i = 0; i < p->size; i++)
- op->gprintf(op," %3lu: %f\n",i,p->data[i]);
+ op->gprintf(op," %3lu: %.8f\n",i,p->data[i]);
}
}
}
@@ -4979,7 +4992,7 @@ double *in /* Input array[inputChan] */
return 0;
}
-/* Convert normalized numbers though this Luts input tables. */
+/* Convert normalized numbers though this Luts per channel input tables. */
/* Return 0 on success, 1 if clipping occured, 2 on other error */
static int icmLut_lookup_input(
icmLut *p, /* Pointer to Lut object */
@@ -5212,140 +5225,7 @@ double *in /* Input array[outputChan] */
return rv;
}
-#ifdef NEVER // ~~~99 development code
-
-/* Convert normalized numbers though this Luts multi-dimensional table */
-/* using optimised simplex interpolation. */
-/* This version optimses the simplex split axis depending on the input */
-/* colorspace. */
-static int icmLut_lookup_clut_osx(
-/* Return 0 on success, 1 if clipping occured, 2 on other error */
-icmLut *p, /* Pointer to Lut object */
-double *out, /* Output array[inputChan] */
-double *in /* Input array[outputChan] */
-) {
- int rv = 0;
- double *gp; /* Pointer to grid cube base */
- double co[MAX_CHAN]; /* Coordinate offset with the grid cell */
- int si[MAX_CHAN]; /* co[] Sort index, [0] = smalest */
- char xflip[MAX_CHAN]; /* Optimised simplex axis flip flags */
-
- /* Compute base index into grid and coordinate offsets */
- {
- unsigned int e;
- double clutPoints_1 = (double)(p->clutPoints-1);
- int clutPoints_2 = p->clutPoints-2;
- gp = p->clutTable; /* Base of grid array */
-
- for (e = 0; e < p->inputChan; e++) {
- unsigned int x;
- double val;
-// ~~~999
-#ifdef NEVER
- xflip[e] = p->finfo[e].bthff;
- if (in[e] >= p->finfo[e].fth)
- xflip[e] = p->finfo[e].athff;
-#else
-
- xflip[e] = 0;
- if (e == 0)
- xflip[e] = 1;
-#endif
- val = in[e] * clutPoints_1;
- if (val < 0.0) {
- val = 0.0;
- rv |= 1;
- } else if (val > clutPoints_1) {
- val = clutPoints_1;
- rv |= 1;
- }
- x = (unsigned int)floor(val); /* Grid coordinate */
- if (x > clutPoints_2)
- x = clutPoints_2;
- co[e] = val - (double)x; /* 1.0 - weight */
- gp += x * p->dinc[e]; /* Add index offset for base of cube */
- if (xflip[e]) { /* Reverse sense of direction for this axis */
- co[e] = 1.0 - co[e];
- gp += p->dinc[e];
- }
- }
- }
-//printf("*");fflush(stdout);
-#ifdef NEVER
- /* Do selection sort on coordinates, smallest to largest. */
- {
- int e, f;
- for (e = 0; e < p->inputChan; e++)
- si[e] = e; /* Initial unsorted indexes */
- for (e = 0; e < (p->inputChan-1); e++) {
- double cosn;
- cosn = co[si[e]]; /* Current smallest value */
- for (f = e+1; f < p->inputChan; f++) { /* Check against rest */
- int tt;
- tt = si[f];
- if (cosn > co[tt]) {
- si[f] = si[e]; /* Exchange */
- si[e] = tt;
- cosn = co[tt];
- }
- }
- }
- }
-#else
- /* Do insertion sort on coordinates, smallest to largest. */
- {
- int f, vf;
- unsigned int e;
- double v;
- for (e = 0; e < p->inputChan; e++)
- si[e] = e; /* Initial unsorted indexes */
-
- for (e = 1; e < p->inputChan; e++) {
- f = e;
- v = co[si[f]];
- vf = f;
- while (f > 0 && co[si[f-1]] > v) {
- si[f] = si[f-1];
- f--;
- }
- si[f] = vf;
- }
- }
-#endif
- /* Now compute the weightings, simplex vertices and output values */
- {
- unsigned int e, f;
- double w; /* Current vertex weight */
-
- w = 1.0 - co[si[p->inputChan-1]]; /* Vertex at base of cell */
- for (f = 0; f < p->outputChan; f++)
- out[f] = w * gp[f];
-
- for (e = p->inputChan-1; e > 0; e--) { /* Middle verticies */
- w = co[si[e]] - co[si[e-1]];
- if (xflip[e])
- gp -= p->dinc[si[e]]; /* Move to top of cell in next largest dimension */
- else
- gp += p->dinc[si[e]]; /* Move to top of cell in next largest dimension */
- for (f = 0; f < p->outputChan; f++)
- out[f] += w * gp[f];
- }
-
- w = co[si[0]];
- if (xflip[0])
- gp -= p->dinc[si[0]]; /* Far corner from base of cell */
- else
- gp += p->dinc[si[0]]; /* Far corner from base of cell */
- for (f = 0; f < p->outputChan; f++)
- out[f] += w * gp[f];
- }
- return rv;
-}
-
-#endif /* NEVER */ // ~~~99 development code
-
-
-/* Convert normalized numbers though this Luts output tables. */
+/* Convert normalized numbers though this Luts per channel output tables. */
/* Return 0 on success, 1 if clipping occured, 2 on other error */
static int icmLut_lookup_output(
icmLut *p, /* Pointer to Lut object */
@@ -5509,7 +5389,6 @@ double *in /* Input array[outputChan] */
double *gp; /* Pointer to grid cube base */
double co[MAX_CHAN]; /* Coordinate offset with the grid cell */
int si[MAX_CHAN]; /* co[] Sort index, [0] = smalest */
- double cout[MAX_CHAN]; /* Current output value */
/* We are using a simplex (ie. tetrahedral for 3D input) interpolation. */
/* This method is more appropriate for XYZ/RGB/CMYK input spaces, */
@@ -5562,6 +5441,7 @@ double *in /* Input array[outputChan] */
{
unsigned int e, f;
double w, ww = 0.0; /* Current vertex weight, sum of weights squared */
+ double cout[MAX_CHAN]; /* Current output value */
double *ogp = gp; /* Pointer to grid cube base */
w = 1.0 - co[si[p->inputChan-1]]; /* Vertex at base of cell */
@@ -6461,8 +6341,8 @@ int icmSetMultiLutTables(
} /* Next pass */
} /* Next table */
- free(clutTable1);
- free(clutTable3);
+ icp->al->free(icp->al, clutTable1);
+ icp->al->free(icp->al, clutTable3);
}
/* Create the 1D output table entry values */
@@ -6815,7 +6695,7 @@ static int icmLut_write(
} else {
for (i = 0; i < size; i++, bp += 2) {
if ((rv = write_DCS16Number(p->inputTable[i], bp)) != 0) {
- sprintf(icp->err,"icmLut_write: inputTable write_DCS16Number(%f) failed",p->inputTable[i]);
+ sprintf(icp->err,"icmLut_write: inputTable write_DCS16Number(%.8f) failed",p->inputTable[i]);
icp->al->free(icp->al, buf);
return icp->errc = rv;
}
@@ -6835,7 +6715,7 @@ static int icmLut_write(
} else {
for (i = 0; i < size; i++, bp += 2) {
if ((rv = write_DCS16Number(p->clutTable[i], bp)) != 0) {
- sprintf(icp->err,"icmLut_write: clutTable write_DCS16Number(%f) failed",p->clutTable[i]);
+ sprintf(icp->err,"icmLut_write: clutTable write_DCS16Number(%.8f) failed",p->clutTable[i]);
icp->al->free(icp->al, buf);
return icp->errc = rv;
}
@@ -6855,7 +6735,7 @@ static int icmLut_write(
} else {
for (i = 0; i < size; i++, bp += 2) {
if ((rv = write_DCS16Number(p->outputTable[i], bp)) != 0) {
- sprintf(icp->err,"icmLut_write: outputTable write_DCS16Number(%f) failed",p->outputTable[i]);
+ sprintf(icp->err,"icmLut_write: outputTable write_DCS16Number(%.8f) failed",p->outputTable[i]);
icp->al->free(icp->al, buf);
return icp->errc = rv;
}
@@ -6893,9 +6773,9 @@ static void icmLut_dump(
op->gprintf(op," CLUT resolution = %u\n",p->clutPoints);
op->gprintf(op," Input Table entries = %u\n",p->inputEnt);
op->gprintf(op," Output Table entries = %u\n",p->outputEnt);
- op->gprintf(op," XYZ matrix = %f, %f, %f\n",p->e[0][0],p->e[0][1],p->e[0][2]);
- op->gprintf(op," %f, %f, %f\n",p->e[1][0],p->e[1][1],p->e[1][2]);
- op->gprintf(op," %f, %f, %f\n",p->e[2][0],p->e[2][1],p->e[2][2]);
+ op->gprintf(op," XYZ matrix = %.8f, %.8f, %.8f\n",p->e[0][0],p->e[0][1],p->e[0][2]);
+ op->gprintf(op," %.8f, %.8f, %.8f\n",p->e[1][0],p->e[1][1],p->e[1][2]);
+ op->gprintf(op," %.8f, %.8f, %.8f\n",p->e[2][0],p->e[2][1],p->e[2][2]);
if (verb >= 2) {
unsigned int i, j, size;
@@ -7819,7 +7699,7 @@ static void icmNamedColor_dump(
if (p->ttype == icSigNamedColor2Type) {
switch(icp->header->pcs) {
case icSigXYZData:
- op->gprintf(op," XYZ = %f, %f, %f\n",
+ op->gprintf(op," XYZ = %.8f, %.8f, %.8f\n",
vp->pcsCoords[0],vp->pcsCoords[1],vp->pcsCoords[2]);
break;
case icSigLabData:
@@ -7836,7 +7716,7 @@ static void icmNamedColor_dump(
for (n = 0; n < p->nDeviceCoords; n++) {
if (n > 0)
op->gprintf(op,", ");
- op->gprintf(op,"%f",vp->deviceCoords[n]);
+ op->gprintf(op,"%.8f",vp->deviceCoords[n]);
}
op->gprintf(op,"\n");
}
@@ -8188,7 +8068,7 @@ static void icmColorantTable_dump(
switch(pcs) {
case icSigXYZData:
- op->gprintf(op," XYZ = %f, %f, %f\n",
+ op->gprintf(op," XYZ = %.8f, %.8f, %.8f\n",
vp->pcsCoords[0],vp->pcsCoords[1],vp->pcsCoords[2]);
break;
case icSigLabData:
@@ -8214,7 +8094,8 @@ static int icmColorantTable_allocate(
if (p->count != p->_count) {
unsigned int i;
if (ovr_mul(p->count, sizeof(icmColorantTableVal))) {
- sprintf(icp->err,"icmColorantTable_alloc: count overflow (%d of %lu bytes)",p->count,sizeof(icmColorantTableVal));
+ sprintf(icp->err,"icmColorantTable_alloc: count overflow (%d of %lu bytes)",
+ p->count,(unsigned long)sizeof(icmColorantTableVal));
return icp->errc = 1;
}
if (p->data != NULL)
@@ -9788,7 +9669,7 @@ static int icmUcrBg_write(
}
} else {
if ((rv = write_DCS16Number(p->UCRcurve[i],bp)) != 0) {
- sprintf(icp->err,"icmUcrBg_write: write_DCS16umber(%f) failed",p->UCRcurve[i]);
+ sprintf(icp->err,"icmUcrBg_write: write_DCS16umber(%.8f) failed",p->UCRcurve[i]);
icp->al->free(icp->al, buf);
return icp->errc = rv;
}
@@ -9812,7 +9693,7 @@ static int icmUcrBg_write(
}
} else {
if ((rv = write_DCS16Number(p->BGcurve[i],bp)) != 0) {
- sprintf(icp->err,"icmUcrBg_write: write_DCS16umber(%f) failed",p->BGcurve[i]);
+ sprintf(icp->err,"icmUcrBg_write: write_DCS16umber(%.8f) failed",p->BGcurve[i]);
icp->al->free(icp->al, buf);
return icp->errc = rv;
}
@@ -10302,15 +10183,15 @@ static void icmVideoCardGamma_dump(
}
} else if (p->tagType == icmVideoCardGammaFormulaType) {
op->gprintf(op,"VideoCardGammaFormula:\n");
- op->gprintf(op," red gamma = %f\n", p->u.formula.redGamma);
- op->gprintf(op," red min = %f\n", p->u.formula.redMin);
- op->gprintf(op," red max = %f\n", p->u.formula.redMax);
- op->gprintf(op," green gamma = %f\n", p->u.formula.greenGamma);
- op->gprintf(op," green min = %f\n", p->u.formula.greenMin);
- op->gprintf(op," green max = %f\n", p->u.formula.greenMax);
- op->gprintf(op," blue gamma = %f\n", p->u.formula.blueGamma);
- op->gprintf(op," blue min = %f\n", p->u.formula.blueMin);
- op->gprintf(op," blue max = %f\n", p->u.formula.blueMax);
+ op->gprintf(op," red gamma = %.8f\n", p->u.formula.redGamma);
+ op->gprintf(op," red min = %.8f\n", p->u.formula.redMin);
+ op->gprintf(op," red max = %.8f\n", p->u.formula.redMax);
+ op->gprintf(op," green gamma = %.8f\n", p->u.formula.greenGamma);
+ op->gprintf(op," green min = %.8f\n", p->u.formula.greenMin);
+ op->gprintf(op," green max = %.8f\n", p->u.formula.greenMax);
+ op->gprintf(op," blue gamma = %.8f\n", p->u.formula.blueGamma);
+ op->gprintf(op," blue min = %.8f\n", p->u.formula.blueMin);
+ op->gprintf(op," blue max = %.8f\n", p->u.formula.blueMax);
} else {
op->gprintf(op," Unknown tag format\n");
}
@@ -11364,6 +11245,7 @@ static struct {
{icSigBToA1Tag, {icSigLut8Type,icSigLut16Type,icMaxEnumType}},
{icSigBToA2Tag, {icSigLut8Type,icSigLut16Type,icMaxEnumType}},
{icSigCalibrationDateTimeTag, {icSigDateTimeType,icMaxEnumType}},
+ {icSigChromaticAdaptationTag, {icSigS15Fixed16ArrayType,icMaxEnumType}},
{icSigCharTargetTag, {icSigTextType,icMaxEnumType}},
{icSigColorantTableTag, {icSigColorantTableType,icmSigAltColorantTableType,
icMaxEnumType}},
@@ -11403,6 +11285,9 @@ static struct {
{icSigVideoCardGammaTag, {icSigVideoCardGammaType,icMaxEnumType}},
{icSigViewingCondDescTag, {icSigTextDescriptionType,icMaxEnumType}},
{icSigViewingConditionsTag, {icSigViewingConditionsType,icMaxEnumType}},
+
+ {icmSigAbsToRelTransSpace, {icSigS15Fixed16ArrayType,icMaxEnumType}},
+
{icMaxEnumTag, {icMaxEnumType}}
};
@@ -11550,13 +11435,13 @@ static struct {
{icSigNamedColorClass, -200, icMaxEnumData, icMaxEnumData,
{icSigProfileDescriptionTag,
- icSigNamedColor2Tag,
+ icSigNamedColorTag, /* Not strictly V3.4 */
icSigMediaWhitePointTag,
icSigCopyrightTag, icMaxEnumTag}},
{icSigNamedColorClass, -100, icMaxEnumData, icMaxEnumData,
{icSigProfileDescriptionTag,
- icSigNamedColorTag, /* Not strictly V3.4 */
+ icSigNamedColor2Tag,
icSigMediaWhitePointTag,
icSigCopyrightTag, icMaxEnumTag}},
@@ -11652,7 +11537,7 @@ static int check_icc_legal(
for (j = 0; tagchecktable[i].tags[j] != icMaxEnumType; j++) {
if (p->find_tag(p, tagchecktable[i].tags[j]) != 0) { /* Not present! */
#ifdef NEVER
- printf("icc_check_legal: deviceClass %s is missing required tag %s", tag2str(sig), tag2str(tagchecktable[i].tags[j]));
+ printf("icc_check_legal: deviceClass %s is missing required tag %s\n", tag2str(sig), tag2str(tagchecktable[i].tags[j]));
#endif
if (tagchecktable[i].chans == -200
|| tagchecktable[i].chans == -dchans) { /* But can try next table */
@@ -11778,7 +11663,7 @@ static int icc_read_x(
|| p->data[i].size > (maxoff - minoff)
|| (p->data[i].offset + p->data[i].size) < p->data[i].offset /* Overflow */
|| (p->data[i].offset + p->data[i].size) > p->header->size) {
- sprintf(p->err,"icc_read: tag %d is out of range of the nominated file size",i);
+ sprintf(p->err,"icc_read: tag %d sig %s offset %d size %d is out of range of the nominated file size %d",i,tag2str(p->data[i].sig),p->data[i].offset,p->data[i].size,maxoff);
p->al->free(p->al, p->data);
p->data = NULL;
return p->errc = 1;
@@ -11799,6 +11684,82 @@ static int icc_read_x(
}
} /* p->count > 0 */
+ /* Check if there is an ArgyllCMS 'arts' tag, and setup the wpchtmx[][] matrix from it. */
+ {
+ icmS15Fixed16Array *artsTag;
+
+ if ((artsTag = (icmS15Fixed16Array *)p->read_tag(p, icmSigAbsToRelTransSpace)) != NULL
+ && artsTag->ttype == icSigS15Fixed16ArrayType
+ && artsTag->size >= 9) {
+
+ p->wpchtmx[0][0] = artsTag->data[0];
+ p->wpchtmx[0][1] = artsTag->data[1];
+ p->wpchtmx[0][2] = artsTag->data[2];
+ p->wpchtmx[1][0] = artsTag->data[3];
+ p->wpchtmx[1][1] = artsTag->data[4];
+ p->wpchtmx[1][2] = artsTag->data[5];
+ p->wpchtmx[2][0] = artsTag->data[6];
+ p->wpchtmx[2][1] = artsTag->data[7];
+ p->wpchtmx[2][2] = artsTag->data[8];
+
+ icmInverse3x3(p->iwpchtmx, p->wpchtmx);
+
+ p->useArts = 1; /* Save it if it was in profile */
+
+ } else {
+ /* If an ArgyllCMS created profile, or if it's a Display profile, */
+ /* use Bradford. This makes sRGB and AdobeRGB etc. work correctly */
+ /* for absolute colorimetic. Note that for display profiles that set */
+ /* the WP to D50 and store their chromatic transform in the 'chad' tag, */
+ /* (i.e. some V2 profiles and all V4 profiles) this will have no effect */
+ /* on the Media Relative WP Transformation since D50 -> D50, and */
+ /* the 'chad' tag will be used to set the internal MediaWhite value */
+ /* and transform matrix. */
+ if (p->header->creator == str2tag("argl")
+ || p->header->deviceClass == icSigDisplayClass) {
+
+ icmCpy3x3(p->wpchtmx, icmBradford);
+ icmInverse3x3(p->iwpchtmx, p->wpchtmx);
+
+ /* Default to ICC standard Wrong Von Kries */
+ /* for non-ArgyllCMS, non-Display profiles. */
+ } else {
+ icmCpy3x3(p->wpchtmx, icmWrongVonKries);
+ icmCpy3x3(p->iwpchtmx, icmWrongVonKries);
+ }
+
+ p->useArts = 0; /* Don't save it if it wasn't in profile */
+ }
+
+ p->autoWpchtmx = 0; /* It's been set on reading - don't set automatically */
+ }
+
+ /* If this is a Display profile, check if there is a 'chad' tag, and read it */
+ /* in if it exists. We will use this latter. */
+ {
+ icmS15Fixed16Array *chadTag;
+
+ if (p->header->deviceClass == icSigDisplayClass
+ && (chadTag = (icmS15Fixed16Array *)p->read_tag(p, icSigChromaticAdaptationTag)) != NULL
+ && chadTag->ttype == icSigS15Fixed16ArrayType
+ && chadTag->size == 9) {
+
+ p->chadmx[0][0] = chadTag->data[0];
+ p->chadmx[0][1] = chadTag->data[1];
+ p->chadmx[0][2] = chadTag->data[2];
+ p->chadmx[1][0] = chadTag->data[3];
+ p->chadmx[1][1] = chadTag->data[4];
+ p->chadmx[1][2] = chadTag->data[5];
+ p->chadmx[2][0] = chadTag->data[6];
+ p->chadmx[2][1] = chadTag->data[7];
+ p->chadmx[2][2] = chadTag->data[8];
+
+ p->chadValid = 1;
+
+ p->useChad = 1; /* Use it when writing */
+ }
+ }
+
return er;
}
@@ -11946,6 +11907,8 @@ static unsigned int icc_get_size(
return size; /* Total size needed, or UINT_MAX if overflow */
}
+void quantize3x3S15Fixed16(double targ[3], double mat[3][3], double in[3]);
+
/* Write the contents of the object. Return 0 on sucess, error code on failure */
/* NOTE: fp ownership is taken even if the function fails. */
static int icc_write_x(
@@ -11965,19 +11928,121 @@ static int icc_write_x(
p->del_fp = 1;
p->of = of; /* Offset of ICC profile */
- for (i = 0; i < ALIGN_SIZE; i++)
- pbuf[i] = 0;
+ /* Compute the total size and tag element data offsets */
+ if (p->header == NULL) {
+ sprintf(p->err,"icc_write: No header defined");
+ return p->errc = 1;
+ }
+
+ /* If we're using the ArgyllCMS 'arts' tag to record the chromatic */
+ /* adapation cone matrix used for the Media Relative WP Transformation, */
+ /* create it and set it from the wpchtmx[][] matrix. */
+ /* Don't write it if there is to 'wtpt' tag (i.e. it's a device link) */
+ if (p->useArts
+ && p->find_tag(p, icSigMediaWhitePointTag) == 0) {
+ icmS15Fixed16Array *artsTag;
+
+ /* Make sure no 'arts' tag currently exists */
+ if (p->delete_tag(p, icmSigAbsToRelTransSpace) != 0
+ && p->errc != 2) {
+ sprintf(p->err,"icc_write: Deleting existing 'arts' tag failed");
+ return p->errc = 1;
+ }
+
+ /* Add one */
+ if ((artsTag = (icmS15Fixed16Array *)p->add_tag(p, icmSigAbsToRelTransSpace,
+ icSigS15Fixed16ArrayType)) == NULL) {
+ sprintf(p->err,"icc_write: Adding 'arts' tag failed");
+ return p->errc = 1;
+ }
+ artsTag->size = 9;
+ if ((rv = artsTag->allocate((icmBase *)artsTag) ) != 0) {
+ sprintf(p->err,"icc_write: Allocating 'arts' tag failed");
+ return p->errc = 1;
+ }
+
+ /* The cone matrix is assumed to be arranged conventionaly for matrix */
+ /* times vector multiplication. */
+ /* Consistent with ICC usage, the dimension corresponding to the matrix */
+ /* rows varies least rapidly while the one corresponding to the matrix */
+ /* columns varies most rapidly. */
+ artsTag->data[0] = p->wpchtmx[0][0];
+ artsTag->data[1] = p->wpchtmx[0][1];
+ artsTag->data[2] = p->wpchtmx[0][2];
+ artsTag->data[3] = p->wpchtmx[1][0];
+ artsTag->data[4] = p->wpchtmx[1][1];
+ artsTag->data[5] = p->wpchtmx[1][2];
+ artsTag->data[6] = p->wpchtmx[2][0];
+ artsTag->data[7] = p->wpchtmx[2][1];
+ artsTag->data[8] = p->wpchtmx[2][2];
+ }
+
+ /* If this is a Display profile, and we have been told to save it in */
+ /* ICCV4 style, then set the media white point tag to D50 and save */
+ /* the chromatic adapation matrix to the 'chad' tag. */
+ {
+ icmXYZArray *whitePointTag;
+ icmS15Fixed16Array *chadTag;
+
+ if (p->header->deviceClass == icSigDisplayClass
+ && p->useChad
+ && (whitePointTag = (icmXYZArray *)p->read_tag(p, icSigMediaWhitePointTag)) != NULL
+ && whitePointTag->ttype == icSigXYZType
+ && whitePointTag->size >= 1) {
+
+ /* If we've set this profile, not just read it, */
+ /* compute the fromAbs/chad matrix from media white point and cone matrix */
+ if (!p->chadValid) {
+ double wp[3];
+ p->chromAdaptMatrix(p, ICM_CAM_NONE, icmD50, whitePointTag->data[0], p->chadmx);
+
+ /* Optimally quantize chad matrix to preserver white point */
+ icmXYZ2Ary(wp, whitePointTag->data[0]);
+ quantize3x3S15Fixed16(icmD50_ary3, p->chadmx, wp);
+ }
+
+ /* Make sure no 'chad' tag currently exists */
+ if (p->delete_tag(p, icSigChromaticAdaptationTag) != 0
+ && p->errc != 2) {
+ sprintf(p->err,"icc_write: Deleting existing 'chad' tag failed");
+ return p->errc = 1;
+ }
+
+ /* Add one */
+ if ((chadTag = (icmS15Fixed16Array *)p->add_tag(p, icSigChromaticAdaptationTag,
+ icSigS15Fixed16ArrayType)) == NULL) {
+ sprintf(p->err,"icc_write: Adding 'chad' tag failed");
+ return p->errc = 1;
+ }
+ chadTag->size = 9;
+ if ((rv = chadTag->allocate((icmBase *)chadTag) ) != 0) {
+ sprintf(p->err,"icc_write: Allocating 'chad' tag failed");
+ return p->errc = 1;
+ }
+
+ /* Save in ICC matrix order */
+ chadTag->data[0] = p->chadmx[0][0];
+ chadTag->data[1] = p->chadmx[0][1];
+ chadTag->data[2] = p->chadmx[0][2];
+ chadTag->data[3] = p->chadmx[1][0];
+ chadTag->data[4] = p->chadmx[1][1];
+ chadTag->data[5] = p->chadmx[1][2];
+ chadTag->data[6] = p->chadmx[2][0];
+ chadTag->data[7] = p->chadmx[2][1];
+ chadTag->data[8] = p->chadmx[2][2];
+
+ /* Set the media white point to D50 */
+ whitePointTag->data[0] = icmD50;
+ }
+ }
/* Check that the right tags etc. are present for a legal ICC profile */
if ((rv = check_icc_legal(p)) != 0) {
return rv;
}
- /* Compute the total size and tag element data offsets */
- if (p->header == NULL) {
- sprintf(p->err,"icc_write: No header defined");
- return p->errc = 1;
- }
+ for (i = 0; i < ALIGN_SIZE; i++)
+ pbuf[i] = 0;
size = sat_add(size, p->header->get_size(p->header));
/* Assume header is aligned */
@@ -12201,6 +12266,7 @@ static int icc_write(
/* Returns NULL if error - icc->errc will contain */
/* 2 on system error, */
/* 3 if unknown tag */
+/* 4 if duplicate tag */
/* NOTE: that we prevent tag duplication */
/* NOTE: to create a tag type icmSigUnknownType, set ttype to icmSigUnknownType, */
/* and set the actual tag type in icmSigUnknownType->uttype */
@@ -12250,7 +12316,7 @@ static icmBase *icc_add_tag(
for (j = 0; j < p->count; j++) {
if (p->data[j].sig == sig) {
sprintf(p->err,"icc_add_tag: Already have tag '%s' in profile",tag2str(p->data[j].sig));
- p->errc = 1;
+ p->errc = 4;
return NULL;
}
}
@@ -12458,6 +12524,7 @@ static icmBase *icc_read_tag_ix(
if (typetable[j].ttype == icMaxEnumType) {
if (!alow_unk) {
+ sprintf(p->err,"icc_read_tag_ix: found unknown tag");
p->errc = 2;
return NULL;
}
@@ -12631,7 +12698,7 @@ static int icc_unread_tag(
/* Delete the tag, and free the underlying tag type, */
/* if this was the last reference to it. */
-/* Note this finds the first tag with a matching signature */
+/* Note this finds the first tag with a matching signature. */
/* Returns non-zero on error: */
/* tag not found - icc->errc will contain 2 */
static int icc_delete_tag_ix(
@@ -13108,11 +13175,11 @@ static struct {
ICC V2 Lab encoding should be used in all PCS encodings in
a icSigLut16Type or icSigNamedColor2Type tag, and can be used
- for Lab encoding in device spaces for these tags.
+ for device space Lab encoding for these tags.
ICC V4 Lab encoding should be used in all PCS encodings in
- all other situations, and can be used for Lab encoding in
- device spaces for all other situtaions.
+ all other situations, and can be used for device space Lab encoding
+ for all other situtaions.
[ Since the ICC spec. doesn't cover device spaces labeled as Lab,
these are ripe for mis-matches between different implementations.]
@@ -14426,40 +14493,59 @@ extern ICCLIB_API void icm1960UCS21964WUV(icmXYZNumber *w, double *out, double *
}
/* - - - - - - - - - - - - - - - - - - - - - - - - */
-/* NOTE :- that these values are for the 1931 standard observer */
+/* NOTE :- that these values are for the 1931 standard observer. */
+/* Since they are an arbitrary 4 decimal place accuracy, we round */
+/* them to be exactly the same as ICC header encoded values, */
+/* to avoid any slight discrepancy with PCS white from profiles. */
/* available D50 Illuminant */
icmXYZNumber icmD50 = { /* Profile illuminant - D50 */
- 0.9642, 1.0000, 0.8249
+ RND_S15FIXED16(0.9642),
+ RND_S15FIXED16(1.0000),
+ RND_S15FIXED16(0.8249)
};
icmXYZNumber icmD50_100 = { /* Profile illuminant - D50, scaled to 100 */
- 96.42, 100.00, 82.49
+ RND_S15FIXED16(0.9642) * 100.0,
+ RND_S15FIXED16(1.0000) * 100.0,
+ RND_S15FIXED16(0.8249) * 100.0
};
double icmD50_ary3[3] = { /* Profile illuminant - D50 */
- 0.9642, 1.0000, 0.8249
+ RND_S15FIXED16(0.9642),
+ RND_S15FIXED16(1.0000),
+ RND_S15FIXED16(0.8249)
};
double icmD50_100_ary3[3] = { /* Profile illuminant - D50, scaled to 100 */
- 96.42, 100.00, 82.49
+ RND_S15FIXED16(0.9642) * 100.0,
+ RND_S15FIXED16(1.0000) * 100.0,
+ RND_S15FIXED16(0.8249) * 100.0
};
/* available D65 Illuminant */
icmXYZNumber icmD65 = { /* Profile illuminant - D65 */
- 0.9505, 1.0000, 1.0890
+ RND_S15FIXED16(0.9505),
+ RND_S15FIXED16(1.0000),
+ RND_S15FIXED16(1.0890)
};
icmXYZNumber icmD65_100 = { /* Profile illuminant - D65, scaled to 100 */
- 95.05, 100.00, 108.90
+ RND_S15FIXED16(0.9505) * 100.0,
+ RND_S15FIXED16(1.0000) * 100.0,
+ RND_S15FIXED16(1.0890) * 100.0
};
double icmD65_ary3[3] = { /* Profile illuminant - D65 */
- 0.9505, 1.0000, 1.0890
+ RND_S15FIXED16(0.9505),
+ RND_S15FIXED16(1.0000),
+ RND_S15FIXED16(1.0890)
};
double icmD65_100_ary3[3] = { /* Profile illuminant - D65, scaled to 100 */
- 95.05, 100.00, 108.90
+ RND_S15FIXED16(0.9505) * 100.0,
+ RND_S15FIXED16(1.0000) * 100.0,
+ RND_S15FIXED16(1.0890) * 100.0
};
@@ -14468,6 +14554,20 @@ icmXYZNumber icmBlack = {
0.0000, 0.0000, 0.0000
};
+/* The Standard ("wrong Von-Kries") chromatic transform matrix */
+double icmWrongVonKries[3][3] = {
+ { 1.0000, 0.0000, 0.0000 },
+ { 0.0000, 1.0000, 0.0000 },
+ { 0.0000, 0.0000, 1.0000 }
+};
+
+/* The Bradford chromatic transform matrix */
+double icmBradford[3][3] = {
+ { RND_S15FIXED16( 0.8951), RND_S15FIXED16( 0.2664), RND_S15FIXED16(-0.1614) },
+ { RND_S15FIXED16(-0.7502), RND_S15FIXED16( 1.7135), RND_S15FIXED16( 0.0367) },
+ { RND_S15FIXED16( 0.0389), RND_S15FIXED16(-0.0685), RND_S15FIXED16( 1.0296) }
+};
+
/* Return the normal Delta E given two Lab values */
double icmLabDE(double *Lab0, double *Lab1) {
double rv = 0.0, tt;
@@ -14659,6 +14759,7 @@ double icmCIE2Ksq(double *Lab0, double *Lab1) {
L = 0.5 * (Lab0[0] + Lab1[0]);
C = 0.5 * (C1 + C2);
+
if (C1 < 1e-9 || C2 < 1e-9) {
h = h1 + h2;
} else {
@@ -14671,22 +14772,25 @@ double icmCIE2Ksq(double *Lab0, double *Lab1) {
}
h *= 0.5;
}
+
T = 1.0 - 0.17 * cos(DEG2RAD(h-30.0)) + 0.24 * cos(DEG2RAD(2.0 * h))
+ 0.32 * cos(DEG2RAD(3.0 * h + 6.0)) - 0.2 * cos(DEG2RAD(4.0 * h - 63.0));
- hh = (h - 275.0)/25.0;
- ddeg = 30.0 * exp(-hh * hh);
- C7 = pow(C,7.0);
- RC = 2.0 * sqrt(C7/(C7 + 6103515625.0));
L50sq = (L - 50.0) * (L - 50.0);
+
SL = 1.0 + (0.015 * L50sq)/sqrt(20.0 + L50sq);
SC = 1.0 + 0.045 * C;
SH = 1.0 + 0.015 * C * T;
- RT = -sin(DEG2RAD(2 * ddeg)) * RC;
dLsq = dL/SL;
dCsq = dC/SC;
dHsq = dH/SH;
+ hh = (h - 275.0)/25.0;
+ ddeg = 30.0 * exp(-hh * hh);
+ C7 = pow(C, 7.0);
+ RC = 2.0 * sqrt(C7/(C7 + 6103515625.0));
+ RT = -sin(DEG2RAD(2 * ddeg)) * RC;
+
RCH = RT * dCsq * dHsq;
dLsq *= dLsq;
@@ -14719,35 +14823,23 @@ ICCLIB_API double icmXYZCIE2K(icmXYZNumber *w, double *in0, double *in1) {
/* - - - - - - - - - - - - - - - - - - - - - - - - */
-/* Chromatic adaptation transform utility */
+/* Independent chromatic adaptation transform utility. */
/* Return a 3x3 chromatic adaptation matrix */
/* Use icmMulBy3x3(dst, mat, src) */
/* NOTE that to transform primaries they */
/* must be mat[XYZ][RGB] format! */
void icmChromAdaptMatrix(
- int flags, /* Use Bradford, Transform given matrix flags */
+ int flags, /* Use Bradford flag, Transform given matrix flag */
icmXYZNumber d_wp, /* Destination white point */
icmXYZNumber s_wp, /* Source white point */
double mat[3][3] /* Destination matrix */
) {
double dst[3], src[3]; /* Source & destination white points */
double vkmat[3][3]; /* Von Kries matrix */
-#ifdef NEVER
- static double bradford[3][3] = { /* Linear cone space matrix */
- { 1.0, 0.0, 0.0 },
- { 0.0, 1.0, 0.0 },
- { 0.0, 0.0, 1.0 }
- };
-#endif /* NEVER */
- static double bradford[3][3] = { /* Bradford cone space matrix */
- { 0.8951, 0.2664, -0.1614 },
- { -0.7502, 1.7135, 0.0367 },
- { 0.0389, -0.0685, 1.0296 }
- };
static int inited = 0; /* Compute inverse bradford once */
static double ibradford[3][3]; /* Inverse Bradford */
- /* Set initial matrix to unity */
+ /* Set initial matrix to unity if creating from scratch */
if (!(flags & ICM_CAM_MULMATRIX)) {
icmSetUnity3x3(mat);
}
@@ -14756,8 +14848,8 @@ void icmChromAdaptMatrix(
icmXYZ2Ary(dst, d_wp);
if (flags & ICM_CAM_BRADFORD) {
- icmMulBy3x3(src, bradford, src);
- icmMulBy3x3(dst, bradford, dst);
+ icmMulBy3x3(src, icmBradford, src);
+ icmMulBy3x3(dst, icmBradford, dst);
}
/* Setup the Von Kries white point adaptation matrix */
@@ -14770,7 +14862,7 @@ void icmChromAdaptMatrix(
/* Transform to Bradford space if requested */
if (flags & ICM_CAM_BRADFORD) {
- icmMul3x3(mat, bradford);
+ icmMul3x3(mat, icmBradford);
}
/* Apply chromatic adaptation */
@@ -14779,7 +14871,7 @@ void icmChromAdaptMatrix(
/* Transform from Bradford space */
if (flags & ICM_CAM_BRADFORD) {
if (inited == 0) {
- icmInverse3x3(ibradford, bradford);
+ icmInverse3x3(ibradford, icmBradford);
inited = 1;
}
icmMul3x3(mat, ibradford);
@@ -14788,68 +14880,237 @@ void icmChromAdaptMatrix(
/* We're done */
}
+/* Setup the wpchtmx appropriately for creating profile */
+static void icc_setup_wpchtmx(icc *p) {
+ int useBradford = 1; /* Default use Bradford */
+
+ if (!p->autoWpchtmx)
+ return; /* Reading profile has set wpchtmx[][] */
+
+ /* If we should use ICC standard Wrong Von Kries for white point chromatic adapation */
+ if (p->header->deviceClass == icSigOutputClass
+ && p->useLinWpchtmx) {
+ useBradford = 0;
+ }
+
+ if (useBradford) {
+ icmCpy3x3(p->wpchtmx, icmBradford);
+ icmInverse3x3(p->iwpchtmx, p->wpchtmx);
+ } else {
+ icmCpy3x3(p->wpchtmx, icmWrongVonKries);
+ icmCpy3x3(p->iwpchtmx, icmWrongVonKries);
+ }
+
+ /* This is set for this profile class now */
+ p->wpchtmx_class = p->header->deviceClass;
+}
+
+/* icc Chromatic adaptation transform utility using */
+/* the current Absolute to Media Relative Transformation Space wpchtmx. */
+/* Return a 3x3 chromatic adaptation matrix */
+/* Use icmMulBy3x3(dst, mat, src) */
+/* NOTE that to transform primaries they */
+/* must be mat[XYZ][RGB] format! */
+/* NOTE that this resets the chadValid flag (i.e. we assume that if */
+/* this method gets called, that we are discarding any 'chad' tag */
+/* and creating our own chromatic adapation) */
+static void icc_chromAdaptMatrix(
+ icc *p,
+ int flags, /* Transform given matrix flag */
+ icmXYZNumber d_wp, /* Destination white point */
+ icmXYZNumber s_wp, /* Source white point */
+ double mat[3][3] /* Destination matrix */
+) {
+ double dst[3], src[3]; /* Source & destination white points */
+ double vkmat[3][3]; /* Von Kries matrix */
+
+ if (p->header->deviceClass == icMaxEnumClass) {
+ fprintf(stderr,"icc_chromAdaptMatrix called with no deviceClass!\n");
+ }
+
+ /* See if the profile type has changed, re-evaluate wpchtmx */
+ if (p->wpchtmx_class != p->header->deviceClass) {
+ icc_setup_wpchtmx(p);
+ }
+
+ /* Set initial matrix to unity if creating from scratch */
+ if (!(flags & ICM_CAM_MULMATRIX)) {
+ icmSetUnity3x3(mat);
+ }
+
+ /* Take a copy of src/dst */
+ icmXYZ2Ary(src, s_wp);
+ icmXYZ2Ary(dst, d_wp);
+
+ /* Transform src/dst to cone space */
+ icmMulBy3x3(src, p->wpchtmx, src);
+ icmMulBy3x3(dst, p->wpchtmx, dst);
+
+ /* Transform incoming matrix cone space */
+ icmMul3x3(mat, p->wpchtmx);
+
+ /* Setup the Von Kries white point adaptation matrix */
+ vkmat[0][0] = dst[0]/src[0];
+ vkmat[1][1] = dst[1]/src[1];
+ vkmat[2][2] = dst[2]/src[2];
+ vkmat[0][1] = vkmat[0][2] = 0.0;
+ vkmat[1][0] = vkmat[1][2] = 0.0;
+ vkmat[2][0] = vkmat[2][1] = 0.0;
+
+ /* Apply chromatic adaptation */
+ icmMul3x3(mat, vkmat);
+
+ /* Transform from con space */
+ icmMul3x3(mat, p->iwpchtmx);
+
+ p->chadValid = 0; /* Don't use this now */
+
+ /* We're done */
+}
+
/* - - - - - - - - - - - - - - - - - - - - - - - - */
-/* RGB primaries device to RGB->XYZ transform matrix. */
+/* RGB XYZ primaries device to RGB->XYZ transform matrix. */
/* We assume that the device is perfectly additive, but that */
/* there may be a scale factor applied to the channels to */
/* match the white point at RGB = 1. */
-
+/* Use icmMulBy3x3(dst, mat, src) */
/* Return non-zero if matrix would be singular */
-int icmRGBprim2matrix(
- icmXYZNumber white, /* White point */
- icmXYZNumber red, /* Red colorant */
- icmXYZNumber green, /* Green colorant */
- icmXYZNumber blue, /* Blue colorant */
- double mat[3][3] /* Destination matrix[RGB][XYZ] */
+int icmRGBXYZprim2matrix(
+ double red[3], /* Red colorant */
+ double green[3], /* Green colorant */
+ double blue[3], /* Blue colorant */
+ double white[3], /* White point */
+ double mat[3][3] /* Destination matrix[RGB][XYZ] */
) {
double tmat[3][3];
double t[3];
/* Assemble the colorants into a matrix */
- tmat[0][0] = red.X;
- tmat[0][1] = green.X;
- tmat[0][2] = blue.X;
- tmat[1][0] = red.Y;
- tmat[1][1] = green.Y;
- tmat[1][2] = blue.Y;
- tmat[2][0] = red.Z;
- tmat[2][1] = green.Z;
- tmat[2][2] = blue.Z;
+ tmat[0][0] = red[0];
+ tmat[0][1] = green[0];
+ tmat[0][2] = blue[0];
+ tmat[1][0] = red[1];
+ tmat[1][1] = green[1];
+ tmat[1][2] = blue[1];
+ tmat[2][0] = red[2];
+ tmat[2][1] = green[2];
+ tmat[2][2] = blue[2];
/* Compute the inverse */
if (icmInverse3x3(mat, tmat))
return 1;
/* Compute scale vector that maps colorants to white point */
- t[0] = mat[0][0] * white.X
- + mat[0][1] * white.Y
- + mat[0][2] * white.Z;
- t[1] = mat[1][0] * white.X
- + mat[1][1] * white.Y
- + mat[1][2] * white.Z;
- t[2] = mat[2][0] * white.X
- + mat[2][1] * white.Y
- + mat[2][2] * white.Z;
+ t[0] = mat[0][0] * white[0]
+ + mat[0][1] * white[1]
+ + mat[0][2] * white[2];
+ t[1] = mat[1][0] * white[0]
+ + mat[1][1] * white[1]
+ + mat[1][2] * white[2];
+ t[2] = mat[2][0] * white[0]
+ + mat[2][1] * white[1]
+ + mat[2][2] * white[2];
/* Now formulate the transform matrix */
- mat[0][0] = red.X * t[0];
- mat[1][0] = green.X * t[1];
- mat[2][0] = blue.X * t[2];
- mat[0][1] = red.Y * t[0];
- mat[1][1] = green.Y * t[1];
- mat[2][1] = blue.Y * t[2];
- mat[0][2] = red.Z * t[0];
- mat[1][2] = green.Z * t[1];
- mat[2][2] = blue.Z * t[2];
+ mat[0][0] = red[0] * t[0];
+ mat[0][1] = green[0] * t[1];
+ mat[0][2] = blue[0] * t[2];
+ mat[1][0] = red[1] * t[0];
+ mat[1][1] = green[1] * t[1];
+ mat[1][2] = blue[1] * t[2];
+ mat[2][0] = red[2] * t[0];
+ mat[2][1] = green[2] * t[1];
+ mat[2][2] = blue[2] * t[2];
return 0;
}
+/* RGB Yxy primaries to device to RGB->XYZ transform matrix */
+/* Return non-zero if matrix would be singular */
+/* Use icmMulBy3x3(dst, mat, src) */
+int icmRGBYxyprim2matrix(
+ double red[3], /* Red colorant */
+ double green[3], /* Green colorant */
+ double blue[3], /* Blue colorant */
+ double white[3], /* White point */
+ double mat[3][3], /* Return matrix[RGB][XYZ] */
+ double wXYZ[3] /* Return white XYZ */
+) {
+ double r[3], g[3], b[3];
+
+ icmYxy2XYZ(r, red);
+ icmYxy2XYZ(g, green);
+ icmYxy2XYZ(b, blue);
+ icmYxy2XYZ(wXYZ, white);
+
+ return icmRGBXYZprim2matrix(r, g, b, wXYZ, mat);
+}
+
/* - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Pre-round a 3x3 matrix to ensure that the product of */
+/* the matrix and the input value is the same as */
+/* the quantized matrix product. This is used to improve accuracy */
+/* of 'chad' tag in computing absolute white point. */
+void quantize3x3S15Fixed16(
+ double targ[3], /* Target of product */
+ double mat[3][3], /* matrix[][] to be quantized */
+ double in[3] /* Input of product - must not be 0.0! */
+) {
+ int i, j;
+ double sum[3]; /* == target */
+ double tmp[3]; /* Uncorrected sum */
+
+ printf("In = %.8f %.8f %.8f\n",in[0], in[1], in[2]);
+ printf("Target = %.8f %.8f %.8f\n",targ[0], targ[1], targ[2]);
+
+ for (j = 0; j < 3; j++)
+ sum[j] = targ[j];
+
+ /* Pre-quantize the matrix, and then ensure that the */
+ /* sum of the product of the quantized values is the quantized */
+ /* sum by assigning the rounding error to the largest component. */
+ for (i = 0; i < 3; i++) {
+ int bix = 0;
+ double bval = -1e9;
+
+ /* locate the largest and quantize each matrix component */
+ for (j = 0; j < 3; j++) {
+ if (fabs(mat[i][j]) > bval) { /* Locate largest */
+ bix = j;
+ bval = fabs(mat[i][j]);
+ }
+ mat[i][j] = round_S15Fixed16Number(mat[i][j]);
+ }
+
+ /* Check the product of the uncorrected quantized values */
+ tmp[i] = 0.0;
+ for (j = 0; j < 3; j++)
+ tmp[i] += mat[i][j] * in[j];
+
+ /* Compute the value the largest has to be */
+ /* to ensure that sum of the quantized mat[][] times in[] is */
+ /* equal to the quantized sum. */
+ for (j = 0; j < 3; j++) {
+ if (j == bix)
+ continue;
+ sum[i] -= mat[i][j] * in[j];
+ }
+ mat[i][bix] = round_S15Fixed16Number(sum[i]/in[i]);
+
+ /* Check the product of the corrected quantized values */
+ sum[i] = 0.0;
+ for (j = 0; j < 3; j++)
+ sum[i] += mat[i][j] * in[j];
+ }
+ printf("Q Sum = %.8f %.8f %.8f\n",tmp[0], tmp[1], tmp[2]);
+ printf("Q cor Sum = %.8f %.8f %.8f\n",sum[0], sum[1], sum[2]);
+}
+
/* Pre-round RGB device primary values to ensure that */
/* the sum of the quantized primaries is the same as */
/* the quantized sum. */
+/* [Note matrix is transposed compared to quantize3x3S15Fixed16() ] */
void quantizeRGBprimsS15Fixed16(
double mat[3][3] /* matrix[RGB][XYZ] */
) {
@@ -14897,7 +15158,7 @@ void quantizeRGBprimsS15Fixed16(
// for (j = 0; j < 3; j++)
// sum[i] += mat[j][i];
}
-// printf("Q Sum = %f %f %f\n",sum[0], sum[1], sum[2]);
+// printf("Q cor Sum = %f %f %f\n",sum[0], sum[1], sum[2]);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -15062,19 +15323,20 @@ void icmRec601_YPbPr_2_RGBd(double out[3], double in[3]) {
/* Convert Rec709 1150/60/2:1 RGB' into YPbPr, or "full range YCbCr" */
/* where input 0..1, output 0..1, -0.5 .. 0.5, -0.5 .. 0.5 */
+/* (This is for digital Rec709 and is very close to analog Rec709 60Hz.) */
/* [From the Rec709 specification] */
void icmRec709_RGBd_2_YPbPr(double out[3], double in[3]) {
double tt[3];
tt[0] = 0.2126 * in[0] + 0.7152 * in[1] + 0.0722 * in[2];
- tt[1] = 0.5389 * -0.2126 * in[0]
- + 0.5389 * -0.7152 * in[1]
- + 0.5389 * (1.0-0.0722) * in[2];
+ tt[1] = 1.0/1.8556 * -0.2126 * in[0]
+ + 1.0/1.8556 * -0.7152 * in[1]
+ + 1.0/1.8556 * (1.0-0.0722) * in[2];
- tt[2] = 0.6350 * (1.0-0.2126) * in[0]
- + 0.6350 * -0.7152 * in[1]
- + 0.6350 * -0.0722 * in[2];
+ tt[2] = 1.0/1.5748 * (1.0-0.2126) * in[0]
+ + 1.0/1.5748 * -0.7152 * in[1]
+ + 1.0/1.5748 * -0.0722 * in[2];
out[0] = tt[0];
out[1] = tt[1];
@@ -15083,13 +15345,14 @@ void icmRec709_RGBd_2_YPbPr(double out[3], double in[3]) {
/* Convert Rec709 1150/60/2:1 YPbPr to RGB' (== "full range YCbCr") */
/* where input 0..1, -0.5 .. 0.5, -0.5 .. 0.5, output 0.0 .. 1 */
+/* (This is for digital Rec709 and is very close to analog Rec709 60Hz.) */
/* [Inverse of above] */
void icmRec709_YPbPr_2_RGBd(double out[3], double in[3]) {
double tt[3];
- tt[0] = 1.000000000 * in[0] + 0.000000000 * in[1] + 1.574803150 * in[2];
- tt[1] = 1.000000000 * in[0] + -0.187327487 * in[1] + -0.468125209 * in[2];
- tt[2] = 1.000000000 * in[0] + 1.855631843 * in[1] + 0.000000000 * in[2];
+ tt[0] = 1.000000000 * in[0] + 0.000000000 * in[1] + 1.574800000 * in[2];
+ tt[1] = 1.000000000 * in[0] + -0.187324273 * in[1] + -0.468124273 * in[2];
+ tt[2] = 1.000000000 * in[0] + 1.855600000 * in[1] + 0.000000000 * in[2];
out[0] = tt[0];
out[1] = tt[1];
@@ -15098,19 +15361,20 @@ void icmRec709_YPbPr_2_RGBd(double out[3], double in[3]) {
/* Convert Rec709 1250/50/2:1 RGB' into YPbPr, or "full range YCbCr" */
/* where input 0..1, output 0..1, -0.5 .. 0.5, -0.5 .. 0.5 */
+/* (This is for analog Rec709 50Hz) */
/* [From the Rec709 specification] */
void icmRec709_50_RGBd_2_YPbPr(double out[3], double in[3]) {
double tt[3];
tt[0] = 0.299 * in[0] + 0.587 * in[1] + 0.114 * in[2];
- tt[1] = 0.564 * -0.299 * in[0]
- + 0.564 * -0.587 * in[1]
+ tt[1] = 0.564 * -0.299 * in[0]
+ + 0.564 * -0.587 * in[1]
+ 0.564 * (1.0-0.114) * in[2];
tt[2] = 0.713 * (1.0-0.299) * in[0]
- + 0.713 * -0.587 * in[1]
- + 0.713 * -0.114 * in[2];
+ + 0.713 * -0.587 * in[1]
+ + 0.713 * -0.114 * in[2];
out[0] = tt[0];
out[1] = tt[1];
@@ -15119,6 +15383,7 @@ void icmRec709_50_RGBd_2_YPbPr(double out[3], double in[3]) {
/* Convert Rec709 1250/50/2:1 YPbPr to RGB' (== "full range YCbCr") */
/* where input 0..1, -0.5 .. 0.5, -0.5 .. 0.5, output 0.0 .. 1 */
+/* (This is for analog Rec709 50Hz) */
/* [Inverse of above] */
void icmRec709_50_YPbPr_2_RGBd(double out[3], double in[3]) {
double tt[3];
@@ -15141,13 +15406,13 @@ void icmRec2020_NCL_RGBd_2_YPbPr(double out[3], double in[3]) {
tt[0] = 0.2627 * in[0] + 0.6780 * in[1] + 0.0593 * in[2];
- tt[1] = 1/1.8814 * -0.2627 * in[0]
- + 1/1.8814 * -0.6780 * in[1]
+ tt[1] = 1/1.8814 * -0.2627 * in[0]
+ + 1/1.8814 * -0.6780 * in[1]
+ 1/1.8814 * (1.0-0.0593) * in[2];
tt[2] = 1/1.4746 * (1.0-0.2627) * in[0]
- + 1/1.4746 * -0.6780 * in[1]
- + 1/1.4746 * -0.0593 * in[2];
+ + 1/1.4746 * -0.6780 * in[1]
+ + 1/1.4746 * -0.0593 * in[2];
out[0] = tt[0];
out[1] = tt[1];
@@ -15913,12 +16178,44 @@ struct _icmLuBase *lup
lup->blackisassumed = 0; /* The black is from the tag */
}
- /* Create absolute <-> relative conversion matricies */
- icmChromAdaptMatrix(ICM_CAM_BRADFORD, lup->whitePoint, icmD50, lup->toAbs);
- icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, lup->whitePoint, lup->fromAbs);
- DBLLL(("toAbs and fromAbs created from wp %f %f %f and D50 %f %f %f\n", lup->whitePoint.X, lup->whitePoint.Y, lup->whitePoint.Z, icmD50.X, icmD50.Y, icmD50.Z));
- DBLLL(("toAbs = %f %f %f\n %f %f %f\n %f %f %f\n", lup->toAbs[0][0], lup->toAbs[0][1], lup->toAbs[0][2], lup->toAbs[1][0], lup->toAbs[1][1], lup->toAbs[1][2], lup->toAbs[2][0], lup->toAbs[2][1], lup->toAbs[2][2]));
- DBLLL(("fromAbs = %f %f %f\n %f %f %f\n %f %f %f\n", lup->fromAbs[0][0], lup->fromAbs[0][1], lup->fromAbs[0][2], lup->fromAbs[1][0], lup->fromAbs[1][1], lup->fromAbs[1][2], lup->fromAbs[2][0], lup->fromAbs[2][1], lup->fromAbs[2][2]));
+ /* If this is a Display profile, check if there is a 'chad' tag, and setup the */
+ /* white point and toAbs/fromAbs matricies from that, so as to implement an */
+ /* effective Absolute Colorimetric intent for such profiles. */
+ if (p->header->deviceClass == icSigDisplayClass
+ && p->chadValid) {
+ double wp[3];
+
+ icmCpy3x3(lup->fromAbs, p->chadmx);
+ icmInverse3x3(lup->toAbs, lup->fromAbs);
+
+ /* Compute absolute white point. We deliberately ignore what's in the white point tag */
+ /* and assume D50, since dealing with a non-D50 white point tag is contrary to ICCV4 */
+ /* and full of ambiguity (i.e. is it a separate "media" white different to the */
+ /* display white and not D50, or has the profile creator mistakenly put the display */
+ /* white in the white point tag ?) */
+ icmMulBy3x3(wp, lup->toAbs, icmD50_ary3);
+ icmAry2XYZ(lup->whitePoint, wp);
+
+ DBLLL(("toAbs and fromAbs created from 'chad' tag\n"));
+ DBLLL(("computed wp %.8f %.8f %.8f\n", lup->whitePoint.X,
+ lup->whitePoint.Y, lup->whitePoint.Z));
+
+ } else {
+ /* Create absolute <-> relative conversion matricies */
+ p->chromAdaptMatrix(p, ICM_CAM_NONE, lup->whitePoint, icmD50, lup->toAbs);
+ p->chromAdaptMatrix(p, ICM_CAM_NONE, icmD50, lup->whitePoint, lup->fromAbs);
+ DBLLL(("toAbs and fromAbs created from wp %f %f %f and D50 %f %f %f\n", lup->whitePoint.X,
+ lup->whitePoint.Y, lup->whitePoint.Z, icmD50.X, icmD50.Y, icmD50.Z));
+ }
+
+ DBLLL(("toAbs = %f %f %f\n %f %f %f\n %f %f %f\n",
+ lup->toAbs[0][0], lup->toAbs[0][1], lup->toAbs[0][2],
+ lup->toAbs[1][0], lup->toAbs[1][1], lup->toAbs[1][2],
+ lup->toAbs[2][0], lup->toAbs[2][1], lup->toAbs[2][2]));
+ DBLLL(("fromAbs = %f %f %f\n %f %f %f\n %f %f %f\n",
+ lup->fromAbs[0][0], lup->fromAbs[0][1], lup->fromAbs[0][2],
+ lup->fromAbs[1][0], lup->fromAbs[1][1], lup->fromAbs[1][2],
+ lup->fromAbs[2][0], lup->fromAbs[2][1], lup->fromAbs[2][2]));
return 0;
}
@@ -16925,6 +17222,8 @@ static int icmLuLut_in_abs(icmLuLut *p, double *out, double *in) {
/* If Bwd Lut, take care of Absolute color space and effective input space */
if ((p->function == icmBwd || p->function == icmGamut || p->function == icmPreview)
+ && (p->e_inSpace == icSigLabData
+ || p->e_inSpace == icSigXYZData)
&& (p->intent == icAbsoluteColorimetric
|| p->intent == icmAbsolutePerceptual
|| p->intent == icmAbsoluteSaturation)) {
@@ -17022,6 +17321,8 @@ static int icmLuLut_out_abs(icmLuLut *p, double *out, double *in) {
/* If Fwd Lut, take care of Absolute color space */
/* and convert from native to effective out PCS */
if ((p->function == icmFwd || p->function == icmPreview)
+ && (p->outSpace == icSigLabData
+ || p->outSpace == icSigXYZData)
&& (p->intent == icAbsoluteColorimetric
|| p->intent == icmAbsolutePerceptual
|| p->intent == icmAbsoluteSaturation)) {
@@ -17253,6 +17554,8 @@ static int icmLuLut_inv_out_abs(icmLuLut *p, double *out, double *in) {
/* and convert from effective to native inverse output PCS */
/* OutSpace must be PCS: XYZ or Lab */
if ((p->function == icmFwd || p->function == icmPreview)
+ && (p->e_outSpace == icSigLabData
+ || p->e_outSpace == icSigXYZData)
&& (p->intent == icAbsoluteColorimetric
|| p->intent == icmAbsolutePerceptual
|| p->intent == icmAbsoluteSaturation)) {
@@ -17385,6 +17688,8 @@ static int icmLuLut_inv_in_abs(icmLuLut *p, double *out, double *in) {
/* If Bwd Lut, take care of Absolute color space, and */
/* convert from native to effective input space */
if ((p->function == icmBwd || p->function == icmGamut || p->function == icmPreview)
+ && (p->inSpace == icSigLabData
+ || p->inSpace == icSigXYZData)
&& (p->intent == icAbsoluteColorimetric
|| p->intent == icmAbsolutePerceptual
|| p->intent == icmAbsoluteSaturation)) {
@@ -17667,9 +17972,6 @@ icc_new_icmLuLut(
return NULL;
}
-// ~~~999
-#ifndef NEVER
- /* Standard code */
/* Determine appropriate clut lookup algorithm */
{
int use_sx; /* -1 = undecided, 0 = N-linear, 1 = Simplex lookup */
@@ -17795,64 +18097,6 @@ icc_new_icmLuLut(
p->lut->tune_value = icmLut_tune_value_nl;
}
}
-#else /* Development code */
- /* Determine appropriate clut lookup algorithm, */
- /* and set optimised simplex tables */
- {
- int lualg = 0; /* 0 = simplex, 1 = multi-linear, 2 = optimised simlpex */
- icColorSpaceSignature ins, outs; /* In and out Lut color spaces */
- int inn, outn; /* in and out number of Lut components */
-
- p->lutspaces((icmLuBase *)p, &ins, &inn, &outs, &outn, NULL);
-
- /* Determine which type of Lut lookup algorithm is likely to give */
- /* minimal interpolation errors. */
- /* To use the optimised simplex, we should ideally determine */
- /* the simplex cell orientation that makes the simplex split diagonal */
- /* always point towards the white or black points. */
- switch(ins) {
-
- case icSigXYZData: /* This seems to be appropriate ? */
- case icSigRgbData:
- case icSigGrayData:
- case icSigCmykData:
- case icSigCmyData:
- case icSigMch6Data:
- lualg = 0; /* Simplex interpolation is appropriate */
- break;
-
- case icSigLabData:
-// ~~~~9999 this isn't right! need to lookup Lab 50,0,0 through input curves then */
-/* quantize to clut nodes to figure threshold for axis flips */
- p->lut->finfo[0].fth = 0.5; p->lut->finfo[0].bthff = 0; p->lut->finfo[0].athff = 1;
- p->lut->finfo[1].fth = 0.5; p->lut->finfo[1].bthff = 1; p->lut->finfo[1].athff = 0;
- p->lut->finfo[2].fth = 0.5; p->lut->finfo[2].bthff = 1; p->lut->finfo[2].athff = 0;
- lualg = 2;
- break;
-
- /* !!! Should switch to optimised simplex for these too !!! */
- case icSigLuvData:
- case icSigYCbCrData:
- case icSigYxyData:
- case icSigHlsData:
- case icSigHsvData:
- lualg = 1; /* Multi-linear is best as a default for these ? */
- break;
-
- default:
- lualg = 0; /* Simplex is best if we known nothing. */
- break;
- }
-
- if (lualg == 2) {
- p->lookup_clut = icmLut_lookup_clut_osx;
- } else if (lualg == 1) {
- p->lookup_clut = p->lut->lookup_clut_nl;
- } else {
- p->lookup_clut = p->lut->lookup_clut_sx;
- }
- }
-#endif
return (icmLuBase *)p;
}
@@ -18591,6 +18835,7 @@ icmAlloc *al /* Memory allocator */
p->get_tac = icm_get_tac;
p->get_luobj = icc_get_luobj;
p->new_clutluobj = icc_new_icmLuLut;
+ p->chromAdaptMatrix = icc_chromAdaptMatrix;
#if defined(__IBMC__) && defined(_M_IX86)
_control87(EM_UNDERFLOW, EM_UNDERFLOW);
@@ -18609,10 +18854,10 @@ icmAlloc *al /* Memory allocator */
p->header->renderingIntent = icMaxEnumIntent; /* Rendering intent - must be set ! */
/* Values that should be set before writing */
- p->header->manufacturer = -1; /* Dev manufacturer - should be set ! */
- p->header->model = -1; /* Dev model number - should be set ! */
- p->header->attributes.l = 0; /* ICC Device attributes - should set ! */
- p->header->flags = 0; /* Embedding flags - should be set ! */
+ p->header->manufacturer = icmSigUnknownType;/* Dev manufacturer - should be set ! */
+ p->header->model = icmSigUnknownType; /* Dev model number - should be set ! */
+ p->header->attributes.l = 0; /* ICC Device attributes - should set ! */
+ p->header->flags = 0; /* Embedding flags - should be set ! */
/* Values that may be set before writing */
p->header->attributes.h = 0; /* Dev Device attributes - may be set ! */
@@ -18624,13 +18869,53 @@ icmAlloc *al /* Memory allocator */
p->header->minv = 2;
p->header->bfv = 0;
setcur_DateTimeNumber(&p->header->date);/* Creation Date */
+#ifdef NT
p->header->platform = icSigMicrosoft; /* Primary Platform */
+#endif
+#ifdef __APPLE__
+ p->header->platform = icSigMacintosh;
+#endif
+#if defined(UNIX) && !defined(__APPLE__)
+ p->header->platform = icmSig_nix;
+#endif
p->header->illuminant = icmD50; /* Profile illuminant - D50 */
/* Values that will be created automatically */
for (i = 0; i < 16; i++)
p->header->id[i] = 0;
+ p->autoWpchtmx = 1; /* Auto on create */
+
+ /* Should we use ICC standard Wrong Von Kries for */
+ /* white point chromatic adapation for output class ? */
+ if (getenv("ARGYLL_CREATE_WRONG_VON_KRIES_OUTPUT_CLASS_REL_WP") != NULL)
+ p->useLinWpchtmx = 1; /* Use Wrong Von Kries */
+ else
+ p->useLinWpchtmx = 0; /* Use Bradford by default */
+ p->wpchtmx_class = icMaxEnumClass; /* Not set yet */
+
+ /* Default to saving ArgyllCMS private 'arts' tag (if appropriate type of */
+ /* profile) to make white point chromatic adapation explicit. */
+ p->useArts = 1;
+
+ /* Should we create a V4 style Display profile with D50 media white point */
+ /* tag and 'chad' tag ? */
+ if (getenv("ARGYLL_CREATE_DISPLAY_PROFILE_WITH_CHAD") != NULL)
+ p->useChad = 1; /* Mark Media WP as D50 and put absolute to relative */
+ /* transform matrix in 'chad' tag. */
+ else
+ p->useChad = 0; /* No by default - use Bradford and store real Media WP */
+
+ /* Set a default wpchtmx in case the profile being read or written */
+ /* doesn't use a white point (i.e., it's a device link) */
+ if (!p->useLinWpchtmx) {
+ icmCpy3x3(p->wpchtmx, icmBradford);
+ icmInverse3x3(p->iwpchtmx, p->wpchtmx);
+ } else {
+ icmCpy3x3(p->wpchtmx, icmWrongVonKries);
+ icmCpy3x3(p->iwpchtmx, icmWrongVonKries);
+ }
+
return p;
}
@@ -18677,7 +18962,7 @@ char *icmPdv(int di, double *p) {
for (e = 0; e < di; e++) {
if (e > 0)
*bp++ = ' ';
- sprintf(bp, "%f", p[e]); bp += strlen(bp);
+ sprintf(bp, "%.8f", p[e]); bp += strlen(bp);
}
return buf[ix];
}
@@ -18700,7 +18985,7 @@ char *icmPfv(int di, float *p) {
for (e = 0; e < di; e++) {
if (e > 0)
*bp++ = ' ';
- sprintf(bp, "%f", p[e]); bp += strlen(bp);
+ sprintf(bp, "%.8f", p[e]); bp += strlen(bp);
}
return buf[ix];
}