Comments on this document can be sent to the PNG specification maintainers at one of the following addresses:
png-list@dworkin.wustl.edu. png-group@w3.org png-info@uunet.uu.net
Distribution of this memo is unlimited.
At present, the latest version of this document is available on the World Wide Web from
ftp://swrinde.nde.swri.edu/pub/png/documents/.
Permission is granted to copy and distribute this document for any purpose and without charge, provided that the copyright notice and this notice are preserved, and that any substantive changes or deletions from the original are clearly marked.
<URL:ftp://swrinde.nde.swri.edu/pub/png/documents/>.
chunk name Multiple Ordering OK? constraints pCAL No Before IDAT
The "pCAL" chunk's contents are a text string (purpose) that names the equation, original sample limits "X0" and "X1", an equation type, a text string (unit), and a set of parameters for the equation:
n bytes: Purpose (Latin-1 text) 1 byte: null separator 4 bytes: X0 (signed integer) Lower limit of original sample range. This original sample value is mapped to the stored sample value 0. 4 bytes: X1 (signed integer) Upper limit of original sample range. This original sample value is mapped to the stored sample value (2^sample_depth - 1). Must not equal X0. 1 byte: Equation type (unsigned integer). 0: Linear mapping 1: Base-e exponential mapping 2: Arbitrary-base exponential mapping 3: Hypberbolic mapping 1 byte: N (unsigned integer), number of parameters. u bytes: Unit (Latin-1 string). Symbol or description of the unit, eg. K, Population Density, MPa, etc). A zero-length string can be used if the data is dimensionless. 1 byte: null separator p0 bytes: P0 (ASCII text). First parameter, a real number written as a text floating-point value [link to Floating-Point Values in PNG extensions document] 1 byte: null separator p1 bytes: P1 (ASCII text). Second parameter. etc.There is no null separator after the final parameter (or after the "Unit" field, if N=0). The number of parameters present must agree with the "N" field and must be correct for the specified "equation_type".
The "purpose" identifies the equation, which can permit applications or people to choose the appropriate one when more than one "pCAL" chunk is present (this could occur in a multiple-image file, but not in a PNG file). The "purpose" string must follow the format of a "tEXt" keyword, i.e. 1-79 printable Latin-1 characters. One way the "purpose" field could be used s to identify the system of units. A "purpose" string such as "SI" or "English" could be used in the "pCAL" chunk as well as in other chunk types, to permit a decoder to select an appropriate set of chunks based on the contents of their "purpose" fields.
The "pCAL" chunk defines two mappings:
The mapping algorithms are
original_sample = (stored_sample * (X1 - X0) + M/2) / M + X0using integer arithmetic, where (a/b) means (integer(floor(real(a)/real(b)))). Note that this is the same as the "/" operator in the C programming language when "a" and "b" are nonnegative, but not necessarily when "a" or "b" is negative.
if equation_type = 0 then physical_value = P0 + P1 * original_sample/(X1-X0) else if equation_type = 1 then physical_value = P0 + P1 * EXP(P2 * original_sample/(X1-X0)) else if equation_type = 2 then physical_value = P0 + P1 * P2^(original_sample/(X1-X0)) else if equation_type = 3 then physical_value = P0 + P1*SINH(P2*(original_sample - P3)/(X1-X0))using floating point arithmetic, in which
SINH(x) = 0.5 * ( EXP(x) - EXP(-x) )
stored_sample = ((original_sample - X0) * M + (X1 - X0) / 2) / (X1 - X0) (limited to the range [0..M])This mapping is lossless and reversible when (ABS(X1-X0) <= M) and the original sample is in the range [X0..X1]. If (ABS(X1-X0) > M) then there can be no lossless reversible mapping, but the functions provide the best integer approximations to floating-point affine transformations.
For color_types 2, 3, and 6, the mapping algorithms are applied independently to each of the color sample values. In the case of color type 3 (indexed color), the mapping refers to the RGB samples and not to the index values.
Linear data can be expressed with equation_type 0.
Pure logarithmic data can be expressed either with either equation_type 1 or equation_type 2:
X0 = 0 X0 = 0 X1 = M X1 = M Equation_type = 1 Equation_type = 2 N = 3 or with N = 3 P0 = 0 P0 = 0 P1 = min P1 = min P2 = LOGe(max/min) P2 = max/minEquation_types 1 and 2 are functionally equivalent; both are defined because authors may find one or the other more convenient.
Using equation type 3, floating point data in the range [-v0..v1] can be reduced (with loss) to a set of integer samples such that the resolution of the stored data is roughly proportional to its magnitude. For example, floating point data ranging from -10^31 to 10^31 (the usual range of floating point numbers on 32-bit machines) can be represented with
X0 = 0 X1 = 65535 Equation_type = 3 N = 4 P0 = 0.0 P1 = 1.0e-30 P2 = 280.0 P3 = 32767.0The resolution near zero is about 10^-33, while the resolution around +/-10^31 is about 10^28. Everywhere the resolution is about 0.4 percent of the magnitude.
Applications should use double precision arithmetic (or take other precautions) while performing the mappings for equation types 1, 2, and 3, to prevent overflow of intermediate results when the parameter "P1" is small and the exponential or power functions are large.
If present, the "pCAL" chunk must appear before the first "IDAT" chunk. Only one instance of the "pCAL" chunk is permitted in a PNG stream.
Boutell, T., et. al.,
PNG (Portable Network Graphics Format Version 1.0),
RFC 2083
<URL:ftp://ds.internic.net/rfc/rfc2083.txt>
,
also available at
<URL:ftp://swrinde.nde.swri.edu/pub/png/documents/>
.
This specification has also been published as a W3C Recommendation, which
is available at <URL:http://www.w3.org>
.
PNG Special-Purpose Public Chunks,
<URL:ftp://swrinde.nde.swri.edu/pub/png/documents/>
.
Applications must take care to avoid underflow and overflow of intermediate results when converting data from one form to another according to the "pCAL" mappings.
/* Sample code for the PNG pCAL chunk */ #include <math.h> /* Math.h supplies double precision exp(), pow(), and * sinh() functions. If your math.h doesn't supply sinh(), * use sinh(x)=(exp(x)+exp(-x))/2. */ unsigned short limit(long low, double x, long high) { if (low < high){ if(x < low)return low; else if (x > high) return high; else return x; } else { if(x < high)return high; else if( x > low) return low; else return x; } } int pcal_encode (unsigned short *stored_sample, long n, float *physical_value, unsigned int m, int equation_type, long x0, long x1, float *p) /* returns 0 (success) * -1 (error, x0==x1) * -2 (unknown equation type) * input: * n: number of samples * physical_value[0..n-1] * m: PNG sample depth * equation_type: from pCAL chunk * x0, x1: stored sample to original sample mapping * p[]: equation parameters, from pCAL chunk * output: * stored_samples[0..n-1] (caller must allocate space) */ { double d,dm; /* force double precision arithmetic */ long isample, osample, k; d=x1-x0; dm=m; if (x1 != x0) { if(equation_type == 0){ for (k=0; k<n; k++) { isample=.5+d*(physical_value[k] - p[0])/p[1]; osample=limit(x0, isample, x1); stored_sample[k]= floor(((osample-x0)*dm +floor(d/2))/d); } } else if(equation_type == 1){ for (k=0; k<n; k++) { isample= .5+d*(log(physical_value[k] - p[0])/p[1])/p[2]; osample=limit(x0, isample, x1); stored_sample[k]= floor(((osample-x0)*dm +floor(d/2))/d); } } else if(equation_type == 2){ double factor; factor=d/log(p[2]); for (k=0; k<n; k++) { isample=.5+log((physical_value[k] -p[0])/p[1])*factor; osample=limit(x0, isample, x1); stored_sample[k]= floor(((osample-x0)*dm +floor(d/2))/d); } } else if(equation_type == 3){ for (k=0; k<n; k++) { isample= .5+p[3]+d*asinh((physical_value[k] -p[0])/p[1])/p[2]; osample=limit(x0, isample, x1); stored_sample[k]= floor(((osample-x0)*dm +floor(d/2))/d); } } else return (-2); /* ERROR, unknown equation type */ } else return (-1); /* ERROR, x0 == x1 */ return (0); } int pcal_make_lut (float *physical_value, unsigned int m, int equation_type, long x0, long x1, float *p) /* returns 0 (success) * -1 (error, x0==x1) * -2 (unknown equation type) * input: * m: PNG sample depth * equation_type: from pCAL chunk * x0, x1: stored sample to original sample mapping * p[]: equation parameters, from pCAL chunk * output: * physical_value[0..m] (caller must allocate space) */ { double d, dm; /* force double precision arithmetic */ long sample, osample; d=x1-x0; dm=m; if (x1 != x0) { if(equation_type == 0){ for (sample=0; sample<=m; sample++){ osample=floor((sample*d+floor(dm/2))/dm) + x0; physical_value[sample] = p[0] + p[1]*osample/d; } } else if(equation_type == 1){ for (sample=0; sample<=m; sample++){ osample=floor((sample*d+floor(dm/2))/dm) + x0; physical_value[sample] = p[0] + p[1]*exp(p[2]*osample/d); } } else if(equation_type == 2){ for (sample=0; sample<=m; sample++){ osample=floor((sample*d+floor(dm/2))/dm) + x0; physical_value[sample] = p[0] + p[1]*pow(p[2],osample/d); } } else if(equation_type == 3){ for (sample=0; sample<=m; sample++){ osample=floor((sample*d+floor(dm/2))/dm) + x0; physical_value[sample] = p[0] + p[1]*sinh(p[2]*(osample-p[3])/d); } } else return (-2); /* ERROR, unknown equation type */ } else return (-1); /* ERROR, X0 == X1 */ return (0); }
We want to avoid any surprises due to differences in "C" compiler implementations. Also, if we were to depend upon division truncating toward zero, we'd have to account for a discontinuity at original_sample value zero. Zero would represent twice as large a range of physical values: