Skip to content

Commit

Permalink
fix low bitdepth PNG save of high bitdepth images
Browse files Browse the repository at this point in the history
SOme combinations of high bitdepth images with low bitdepth PNG save could
produce incorrect images, for example saving a 16-bit fourier image as 1-bit.
  • Loading branch information
jcupitt committed Aug 14, 2022
1 parent 94d3f93 commit 1989203
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 34 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- add "unlimited" to jpegload
- better 0 detection in unpremultiply
- fix low bitdepth spng save [jeffska]
- fix PNG low bitdepth save of high bitdepth images

21/11/21 started 8.13
- configure fails for requested but unmet dependencies [remicollet]
Expand Down
34 changes: 20 additions & 14 deletions libvips/foreign/pngsave.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,22 @@ vips_foreign_save_png_build( VipsObject *object )
in = save->ready;
g_object_ref( in );

/* save->ready will have been converted to uint16 for high-bitdepth
* formats (eg. float) ... we need to check Type to see if we want
* to save as 8 or 16-bits. Eg. imagine a float image tagged as sRGB.
/* If no output bitdepth has been specified, use input Type to pick.
*/
if( in->Type == VIPS_INTERPRETATION_sRGB ||
in->Type == VIPS_INTERPRETATION_B_W ) {
if( !vips_object_argument_isset( object, "bitdepth" ) )
png->bitdepth =
in->Type == VIPS_INTERPRETATION_RGB16 ||
in->Type == VIPS_INTERPRETATION_GREY16 ? 16 : 8;

/* Deprecated "colours" arg just sets bitdepth large enough to hold
* that many colours.
*/
if( vips_object_argument_isset( object, "colours" ) )
png->bitdepth = ceil( log2( png->colours ) );

/* Cast in down to 8 bit if we can.
*/
if( png->bitdepth <= 8 ) {
VipsImage *x;

if( vips_cast( in, &x, VIPS_FORMAT_UCHAR, NULL ) ) {
Expand All @@ -128,22 +138,18 @@ vips_foreign_save_png_build( VipsObject *object )
in = x;
}

/* Deprecated "colours" arg just sets bitdepth large enough to hold
* that many colours.
*/
if( vips_object_argument_isset( object, "colours" ) )
png->bitdepth = ceil( log2( png->colours ) );

if( !vips_object_argument_isset( object, "bitdepth" ) )
png->bitdepth = in->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16;

/* If this is a RGB or RGBA image and a low bit depth has been
* requested, enable palettization.
*/
if( in->Bands > 2 &&
png->bitdepth < 8 )
png->palette = TRUE;

/* Disable palettization for >8 bit save.
*/
if( png->bitdepth >= 8 )

This comment has been minimized.

Copy link
@lovell

lovell Sep 7, 2022

Member

@jcupitt Should this be testing against > 8 to match the comment?

png->palette = FALSE;

if( vips__png_write_target( in, png->target,
png->compression, png->interlace, png->profile, png->filter,
save->strip, png->palette, png->Q, png->dither,
Expand Down
42 changes: 22 additions & 20 deletions libvips/foreign/spngsave.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
*/

/*
*/
#define DEBUG_VERBOSE
#define DEBUG
*/

This comment has been minimized.

Copy link
@kleisauke

kleisauke Aug 14, 2022

Member

This is probably an oops.

This comment has been minimized.

Copy link
@jcupitt

jcupitt Aug 14, 2022

Author Member

Oh drat. Thanks!


#ifdef HAVE_CONFIG_H
#include <config.h>
Expand Down Expand Up @@ -485,7 +485,7 @@ vips_foreign_save_spng_write( VipsForeignSaveSpng *spng, VipsImage *in )
spng_set_option( spng->ctx,
SPNG_FILTER_CHOICE, spng->filter );

/* Set resolution. libpng uses pixels per meter.
/* Set resolution. png uses pixels per meter.
*/
phys.unit_specifier = 1;
phys.ppu_x = VIPS_RINT( in->Xres * 1000.0 );
Expand Down Expand Up @@ -580,12 +580,22 @@ vips_foreign_save_spng_build( VipsObject *object )
in = save->ready;
g_object_ref( in );

/* in will have been converted to uint16 for high-bitdepth
* formats (eg. float) ... we need to check Type to see if we want
* to save as 8 or 16-bits. Eg. imagine a float image tagged as sRGB.
/* If no output bitdepth has been specified, use input Type to pick.
*/
if( !vips_object_argument_isset( object, "bitdepth" ) )
spng->bitdepth =
in->Type == VIPS_INTERPRETATION_RGB16 ||
in->Type == VIPS_INTERPRETATION_GREY16 ? 16 : 8;

/* Deprecated "colours" arg just sets bitdepth large enough to hold
* that many colours.
*/
if( vips_object_argument_isset( object, "colours" ) )
spng->bitdepth = ceil( log2( spng->colours ) );

/* Cast in down to 8 bit if we can.
*/
if( in->Type == VIPS_INTERPRETATION_sRGB ||
in->Type == VIPS_INTERPRETATION_B_W ) {
if( spng->bitdepth <= 8 ) {
VipsImage *x;

if( vips_cast( in, &x, VIPS_FORMAT_UCHAR, NULL ) ) {
Expand All @@ -596,26 +606,18 @@ vips_foreign_save_spng_build( VipsObject *object )
in = x;
}

/* If no output bitdepth has been specified, use input Type to pick.
* We only go for 16 bits for the types where we know there's a
* 0-65535 range.
*/
if( !vips_object_argument_isset( object, "bitdepth" ) )
spng->bitdepth = in->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16;

/* Deprecated "colours" arg just sets bitdepth large enough to hold
* that many colours.
*/
if( vips_object_argument_isset( object, "colours" ) )
spng->bitdepth = ceil( log2( spng->colours ) );

/* If this is a RGB or RGBA image and a low bit depth has been
* requested, enable palettisation.
*/
if( in->Bands > 2 &&
spng->bitdepth < 8 )
spng->palette = TRUE;

/* Disable palettization for >8 bit save.
*/
if( spng->bitdepth >= 8 )
spng->palette = FALSE;

if( vips_foreign_save_spng_write( spng, in ) ) {
g_object_unref( in );
return( -1 );
Expand Down

0 comments on commit 1989203

Please sign in to comment.