From 198920398509465ab00e7c6f5584fd345bda20e7 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 14 Aug 2022 12:14:55 +0100 Subject: [PATCH] fix low bitdepth PNG save of high bitdepth images 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. --- ChangeLog | 1 + libvips/foreign/pngsave.c | 34 +++++++++++++++++------------- libvips/foreign/spngsave.c | 42 ++++++++++++++++++++------------------ 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3c753524..87add23f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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] diff --git a/libvips/foreign/pngsave.c b/libvips/foreign/pngsave.c index 78cf506b..2b9711f5 100644 --- a/libvips/foreign/pngsave.c +++ b/libvips/foreign/pngsave.c @@ -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 ) ) { @@ -128,15 +138,6 @@ 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. */ @@ -144,6 +145,11 @@ vips_foreign_save_png_build( VipsObject *object ) png->bitdepth < 8 ) png->palette = TRUE; + /* Disable palettization for >8 bit save. + */ + if( png->bitdepth >= 8 ) + 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, diff --git a/libvips/foreign/spngsave.c b/libvips/foreign/spngsave.c index 594871ce..d408fe29 100644 --- a/libvips/foreign/spngsave.c +++ b/libvips/foreign/spngsave.c @@ -42,9 +42,9 @@ */ /* + */ #define DEBUG_VERBOSE #define DEBUG - */ #ifdef HAVE_CONFIG_H #include @@ -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 ); @@ -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( in->Type == VIPS_INTERPRETATION_sRGB || - in->Type == VIPS_INTERPRETATION_B_W ) { + 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( spng->bitdepth <= 8 ) { VipsImage *x; if( vips_cast( in, &x, VIPS_FORMAT_UCHAR, NULL ) ) { @@ -596,19 +606,6 @@ 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. */ @@ -616,6 +613,11 @@ vips_foreign_save_spng_build( VipsObject *object ) 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 );