mirror of
https://git.code.sf.net/p/libpng/code.git
synced 2025-07-10 18:04:09 +02:00
Simplified API sRGB gamma handling correction
If a gamma encoded file that has a gamma not matching that of sRGB is passed to the simplified API the previous code simply interpreted it as a power law encoding. However old Mac files had a power law correction of 1.45 built in to the encoding, even though the display devices were consistent with sRGB. Assuming a power law encoding results in substantial differences in the interpretation of low 8-bit values; below 10. For example an Apple '5' which is equivalent to an sRGB '17' ends up as the value '8'. This patch provides some measure of correction for this by making the gamma correction done within the simplified API assume that any encoded data is encoded relative to an sRGB-like transfer function; the data is corrected back to the PNG-nominal 2.2 value then decoded to linear (if required) using the sRGB transfer function. This reduces the errors reported by pngstest for such files (colormapped ones) but still leaves the issue with files where the standard libpng code does the gamma decoding. To cope with the latter cases the patch also includes a new pngstest-errors which allows the result, however this is still a work-in-progress; a better solution is possible. Signed-off-by: John Bowler <jbowler@acm.org>
This commit is contained in:
parent
d555056b24
commit
8c025bcf3f
@ -32,7 +32,7 @@ static png_uint_16 gpc_error[16/*in*/][16/*out*/][4/*a*/] =
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }
|
||||
}, { /* input: sRGB-rgb+alpha */
|
||||
{ 0, 4, 13, 0 }, { 0, 15, 13, 0 }, { 0, 19, 1, 0 }, { 0, 0, 0, 0 },
|
||||
{ 0, 14, 13, 0 }, { 0, 15, 13, 0 }, { 0, 19, 1, 0 }, { 0, 0, 0, 0 },
|
||||
{ 0, 832, 764, 0 }, { 0, 832, 764, 0 }, { 0, 897, 788, 0 }, { 0, 897, 788, 0 },
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }
|
||||
@ -52,7 +52,7 @@ static png_uint_16 gpc_error[16/*in*/][16/*out*/][4/*a*/] =
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }
|
||||
}, { /* input: linear-rgb+alpha */
|
||||
{ 0, 126, 143, 0 }, { 0, 9, 7, 0 }, { 0, 74, 9, 0 }, { 0, 17, 9, 0 },
|
||||
{ 0, 126, 143, 0 }, { 0, 11, 7, 0 }, { 0, 74, 9, 0 }, { 0, 17, 9, 0 },
|
||||
{ 0, 4, 4, 0 }, { 0, 5, 4, 0 }, { 0, 0, 0, 0 }, { 0, 1, 0, 0 },
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }
|
||||
@ -73,7 +73,7 @@ static png_uint_16 gpc_error[16/*in*/][16/*out*/][4/*a*/] =
|
||||
{ 0, 0, 460, 0 }, { 0, 0, 460, 0 }, { 0, 0, 263, 0 }, { 0, 0, 263, 0 }
|
||||
}, { /* input: color-mapped-sRGB-rgb+alpha */
|
||||
{ 0, 6, 8, 0 }, { 0, 7, 8, 0 }, { 0, 75, 8, 0 }, { 0, 9, 8, 0 },
|
||||
{ 0, 585, 427, 0 }, { 0, 585, 427, 0 }, { 0, 717, 462, 0 }, { 0, 717, 462, 0 },
|
||||
{ 0, 585, 427, 0 }, { 0, 585, 427, 0 }, { 0, 717, 584, 0 }, { 0, 717, 584, 0 },
|
||||
{ 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 },
|
||||
{ 0, 13323, 460, 0 }, { 0, 427, 460, 0 }, { 0, 16480, 263, 0 }, { 0, 243, 263, 0 }
|
||||
}, { /* input: color-mapped-linear-gray */
|
||||
@ -115,7 +115,7 @@ static png_uint_16 gpc_error_via_linear[16][4/*out*/][4] =
|
||||
}, { /* input: linear-rgb */
|
||||
{ 0, 0, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 1, 0 }
|
||||
}, { /* input: linear-rgb+alpha */
|
||||
{ 0, 1, 1, 0 }, { 0, 8, 1, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }
|
||||
{ 0, 1, 1, 0 }, { 0, 9, 1, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }
|
||||
}, { /* input: color-mapped-sRGB-gray */
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }
|
||||
}, { /* input: color-mapped-sRGB-gray+alpha */
|
||||
@ -123,7 +123,7 @@ static png_uint_16 gpc_error_via_linear[16][4/*out*/][4] =
|
||||
}, { /* input: color-mapped-sRGB-rgb */
|
||||
{ 0, 0, 13, 0 }, { 0, 0, 13, 0 }, { 0, 0, 14, 0 }, { 0, 0, 14, 0 }
|
||||
}, { /* input: color-mapped-sRGB-rgb+alpha */
|
||||
{ 0, 4, 8, 0 }, { 0, 9, 8, 0 }, { 0, 9, 3, 0 }, { 0, 32, 3, 0 }
|
||||
{ 0, 4, 8, 0 }, { 0, 9, 8, 0 }, { 0, 9, 9, 0 }, { 0, 32, 9, 0 }
|
||||
}, { /* input: color-mapped-linear-gray */
|
||||
{ 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }
|
||||
}, { /* input: color-mapped-linear-gray+alpha */
|
||||
@ -138,7 +138,7 @@ static png_uint_16 gpc_error_to_colormap[8/*i*/][8/*o*/][4] =
|
||||
{
|
||||
{ /* input: sRGB-gray */
|
||||
{ 0, 0, 9, 0 }, { 0, 0, 9, 0 }, { 0, 0, 9, 0 }, { 0, 0, 9, 0 },
|
||||
{ 0, 0, 560, 0 }, { 0, 0, 560, 0 }, { 0, 0, 560, 0 }, { 0, 0, 560, 0 }
|
||||
{ 0, 0, 742, 0 }, { 0, 0, 742, 0 }, { 0, 0, 742, 0 }, { 0, 0, 742, 0 }
|
||||
}, { /* input: sRGB-gray+alpha */
|
||||
{ 0, 19, 2, 0 }, { 0, 255, 2, 25 }, { 0, 88, 2, 0 }, { 0, 255, 2, 25 },
|
||||
{ 0, 1012, 745, 0 }, { 0, 16026, 745, 6425 }, { 0, 1012, 745, 0 }, { 0, 16026, 745, 6425 }
|
||||
@ -147,19 +147,19 @@ static png_uint_16 gpc_error_to_colormap[8/*i*/][8/*o*/][4] =
|
||||
{ 0, 0, 962, 0 }, { 0, 0, 962, 0 }, { 0, 0, 13677, 0 }, { 0, 0, 13677, 0 }
|
||||
}, { /* input: sRGB-rgb+alpha */
|
||||
{ 0, 63, 77, 0 }, { 0, 255, 19, 25 }, { 0, 220, 25, 0 }, { 0, 255, 25, 67 },
|
||||
{ 0, 17534, 18491, 0 }, { 0, 15614, 2824, 6425 }, { 0, 14019, 13677, 0 }, { 0, 48573, 13677, 17219 }
|
||||
{ 0, 17534, 18491, 0 }, { 0, 15998, 2824, 6425 }, { 0, 14103, 13677, 0 }, { 0, 50115, 13677, 17219 }
|
||||
}, { /* input: linear-gray */
|
||||
{ 0, 0, 73, 0 }, { 0, 0, 73, 0 }, { 0, 0, 73, 0 }, { 0, 0, 73, 0 },
|
||||
{ 0, 0, 18817, 0 }, { 0, 0, 18817, 0 }, { 0, 0, 18817, 0 }, { 0, 0, 18817, 0 }
|
||||
}, { /* input: linear-gray+alpha */
|
||||
{ 0, 74, 74, 0 }, { 0, 255, 74, 25 }, { 0, 97, 74, 0 }, { 0, 255, 74, 25 },
|
||||
{ 0, 18919, 18907, 0 }, { 0, 24549, 18907, 6552 }, { 0, 18919, 18907, 0 }, { 0, 24549, 18907, 6552 }
|
||||
{ 0, 18919, 18907, 0 }, { 0, 24549, 18907, 6553 }, { 0, 18919, 18907, 0 }, { 0, 24549, 18907, 6553 }
|
||||
}, { /* input: linear-rgb */
|
||||
{ 0, 0, 73, 0 }, { 0, 0, 73, 0 }, { 0, 0, 98, 0 }, { 0, 0, 98, 0 },
|
||||
{ 0, 0, 18664, 0 }, { 0, 0, 18664, 0 }, { 0, 0, 24998, 0 }, { 0, 0, 24998, 0 }
|
||||
}, { /* input: linear-rgb+alpha */
|
||||
{ 0, 181, 196, 0 }, { 0, 255, 61, 25 }, { 206, 187, 98, 0 }, { 0, 255, 98, 67 },
|
||||
{ 0, 18141, 18137, 0 }, { 0, 17494, 17504, 6553 }, { 0, 24979, 24992, 0 }, { 0, 46509, 24992, 17347 }
|
||||
{ 0, 18141, 18137, 0 }, { 0, 17494, 17504, 6553 }, { 0, 24979, 24992, 0 }, { 0, 49172, 24992, 17347 }
|
||||
}
|
||||
};
|
||||
/* END MACHINE GENERATED */
|
||||
|
132
pngread.c
132
pngread.c
@ -756,7 +756,7 @@ png_read_destroy(png_structrp png_ptr)
|
||||
if (png_ptr->zstream.state != NULL)
|
||||
{
|
||||
int ret = inflateEnd(&png_ptr->zstream);
|
||||
|
||||
|
||||
if (ret != Z_OK)
|
||||
{
|
||||
png_zstream_error(png_ptr, ret);
|
||||
@ -1081,7 +1081,7 @@ typedef struct
|
||||
png_voidp first_row;
|
||||
ptrdiff_t row_bytes; /* step between rows */
|
||||
int file_encoding; /* E_ values above */
|
||||
png_fixed_point gamma_to_linear; /* For P_FILE, reciprocal of gamma */
|
||||
png_fixed_point file_to_sRGB; /* Cached correction factor */
|
||||
int colormap_processing; /* PNG_CMAP_ values above */
|
||||
} png_image_read_control;
|
||||
|
||||
@ -1445,20 +1445,15 @@ png_image_skip_unused_chunks(png_structrp png_ptr)
|
||||
static void
|
||||
set_file_encoding(png_image_read_control *display)
|
||||
{
|
||||
/* First test for an encoding close to linear: */
|
||||
if (png_need_gamma_correction(display->image->opaque->png_ptr,
|
||||
0/*PNG gamma*/, 0/*not sRGB*/))
|
||||
{
|
||||
png_fixed_point g = display->image->opaque->png_ptr->colorspace.gamma;
|
||||
|
||||
/* Now look for one close to sRGB: */
|
||||
if (png_gamma_not_sRGB(g))
|
||||
{
|
||||
display->file_encoding = P_FILE;
|
||||
/* Record the reciprocal of 'g', the colorspace gamma. If this
|
||||
* overflows just store FP_1.
|
||||
*/
|
||||
if (!png_muldiv(&display->gamma_to_linear, PNG_FP_1, PNG_FP_1, g))
|
||||
display->gamma_to_linear = PNG_FP_1;
|
||||
}
|
||||
|
||||
else
|
||||
display->file_encoding = P_sRGB;
|
||||
@ -1468,6 +1463,96 @@ set_file_encoding(png_image_read_control *display)
|
||||
display->file_encoding = P_LINEAR8;
|
||||
}
|
||||
|
||||
/* For colormap entries we end up doing the gamma correction here and the
|
||||
* following routines are provided to separate out the code. In all cases the
|
||||
* input value is in the range 0..255 and is encoded P_FILE with the gamma value
|
||||
* stored in the png_struct colorspace.
|
||||
*/
|
||||
static void
|
||||
init_correct(png_const_structrp png_ptr, png_fixed_point *correct)
|
||||
{
|
||||
/* Record the convertion necessary to get from the encoding values to
|
||||
* sRGB. If this overflows just store FP_1.
|
||||
*
|
||||
* NOTE: this code used to store, and use, a convertion factor to
|
||||
* linear then use the sRGB encoding tables to get back to sRGB, but
|
||||
* this smashes the low values; the ones which fall in the linear part
|
||||
* of the sRGB transfer function.
|
||||
*
|
||||
* The new version of this code assumes an encoding which is neither
|
||||
* linear nor sRGB is a power law transform of the sRGB curve, not
|
||||
* linear values. This is somewhat at odds with a precise reading of
|
||||
* the PNG spec, but given that we are trying to produce sRGB values
|
||||
* here it is most likely to be correct.
|
||||
*/
|
||||
affirm(png_ptr->colorspace.gamma > 0);
|
||||
|
||||
if (!png_muldiv(correct, PNG_GAMMA_sRGB_INVERSE, PNG_FP_1,
|
||||
png_ptr->colorspace.gamma))
|
||||
*correct = PNG_FP_1;
|
||||
}
|
||||
|
||||
static png_uint_32
|
||||
convert_to_sRGB(png_image_read_control *display, png_uint_32 value)
|
||||
{
|
||||
/* Converts an 8-bit value from P_FILE to P_sRGB */
|
||||
png_const_structrp png_ptr = display->image->opaque->png_ptr;
|
||||
|
||||
affirm(value <= 255U);
|
||||
|
||||
if (display->file_to_sRGB == 0)
|
||||
init_correct(png_ptr, &display->file_to_sRGB);
|
||||
|
||||
/* Now simply apply this correction factor and scale back to 8 bits. */
|
||||
if (display->file_to_sRGB != PNG_FP_1)
|
||||
value = PNG_DIV257(
|
||||
png_gamma_16bit_correct(png_ptr, value*257U, display->file_to_sRGB));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static png_uint_32
|
||||
convert_to_linear(png_image_read_control *display, png_uint_32 value)
|
||||
{
|
||||
/* Converts an 8-bit value from P_FILE to 16-bit P_LINEAR */
|
||||
png_const_structrp png_ptr = display->image->opaque->png_ptr;
|
||||
|
||||
affirm(value <= 255U);
|
||||
|
||||
if (display->file_to_sRGB == 0)
|
||||
init_correct(png_ptr, &display->file_to_sRGB);
|
||||
|
||||
/* Use this correct to get a 16-bit sRGB value: */
|
||||
value *= 257U;
|
||||
|
||||
if (display->file_to_sRGB != PNG_FP_1)
|
||||
value = png_gamma_16bit_correct(png_ptr, value, display->file_to_sRGB);
|
||||
|
||||
/* Now convert this back to linear, using the correct transfer function. */
|
||||
if (value <= 2650U /* 65535 * 0.04045 */)
|
||||
{
|
||||
/* We want to divide a 12-bit number by 12.92, do this by scaling to 32
|
||||
* bits then dividing by 2^24, with rounding:
|
||||
*/
|
||||
value = (value * 1298546U + 649273U) >> 24;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
/* Calculate for v in the range 0.04045..1.0 calculate:
|
||||
*
|
||||
* ((v + 0.055)/1.055)^2.4
|
||||
*
|
||||
* the gamma correction function needs a 16-bit value:
|
||||
*/
|
||||
value *= 62119U;
|
||||
value += 223904831U+32768U; /* cannot overflow; test with 65535 */
|
||||
value = png_gamma_16bit_correct(png_ptr, value >> 16, 240000);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
decode_gamma(png_image_read_control *display, png_uint_32 value, int encoding)
|
||||
{
|
||||
@ -1483,8 +1568,7 @@ decode_gamma(png_image_read_control *display, png_uint_32 value, int encoding)
|
||||
switch (encoding)
|
||||
{
|
||||
case P_FILE:
|
||||
value = png_gamma_16bit_correct(display->image->opaque->png_ptr,
|
||||
value*257, display->gamma_to_linear);
|
||||
value = convert_to_linear(display, value);
|
||||
break;
|
||||
|
||||
case P_sRGB:
|
||||
@ -1567,31 +1651,26 @@ png_create_colormap_entry(png_image_read_control *display,
|
||||
if (display->file_encoding == P_NOTSET)
|
||||
set_file_encoding(display);
|
||||
|
||||
/* Note that the cached value may be P_FILE too, but if it is then the
|
||||
* gamma_to_linear member has been set.
|
||||
*/
|
||||
/* Note that the cached value may be P_FILE too. */
|
||||
encoding = display->file_encoding;
|
||||
}
|
||||
|
||||
if (encoding == P_FILE)
|
||||
{
|
||||
png_fixed_point g = display->gamma_to_linear;
|
||||
|
||||
red = png_gamma_16bit_correct(png_ptr, red*257, g);
|
||||
green = png_gamma_16bit_correct(png_ptr, green*257, g);
|
||||
blue = png_gamma_16bit_correct(png_ptr, blue*257, g);
|
||||
|
||||
if (convert_to_Y != 0 || output_encoding == P_LINEAR)
|
||||
{
|
||||
red = convert_to_linear(display, red);
|
||||
green = convert_to_linear(display, green);
|
||||
blue = convert_to_linear(display, blue);
|
||||
alpha *= 257;
|
||||
encoding = P_LINEAR;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
red = PNG_sRGB_FROM_LINEAR(png_ptr, red * 255);
|
||||
green = PNG_sRGB_FROM_LINEAR(png_ptr, green * 255);
|
||||
blue = PNG_sRGB_FROM_LINEAR(png_ptr, blue * 255);
|
||||
red = convert_to_sRGB(display, red);
|
||||
green = convert_to_sRGB(display, green);
|
||||
blue = convert_to_sRGB(display, blue);
|
||||
encoding = P_sRGB;
|
||||
}
|
||||
}
|
||||
@ -1960,7 +2039,12 @@ png_image_read_colormap(png_voidp argument)
|
||||
else
|
||||
png_ptr->colorspace.gamma = PNG_GAMMA_sRGB_INVERSE;
|
||||
|
||||
png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA;
|
||||
/* Make sure libpng doesn't ignore the setting: */
|
||||
if (png_ptr->colorspace.flags & PNG_COLORSPACE_INVALID)
|
||||
png_ptr->colorspace.flags = PNG_COLORSPACE_HAVE_GAMMA;
|
||||
|
||||
else
|
||||
png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA;
|
||||
}
|
||||
|
||||
/* Decide what to do based on the PNG color type of the input data. The
|
||||
|
@ -4926,10 +4926,7 @@ png_do_background_alpha_GA(png_transformp *transform, png_transform_controlp tc)
|
||||
|
||||
case 65535U: /* opaque */
|
||||
if (copy)
|
||||
{
|
||||
memcpy(dp, sp, 4U);
|
||||
UNTESTED
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user