From b5531cf1ed4c92402164d76bc3cb1158d47a7f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sun, 10 Jun 2018 20:02:04 +0200 Subject: [PATCH 1/5] Add libimagequant detection to configure --- configure.ac | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 76b4f054..13b8c609 100644 --- a/configure.ac +++ b/configure.ac @@ -1142,6 +1142,24 @@ if test x"$with_png" != "xno"; then ) fi +# look for libimagequant with pkg-config (only if libpng is enabled) +AC_ARG_WITH([imagequant], + AS_HELP_STRING([--without-imagequant], [build without imagequant (default: test)])) + +if test x"$with_imagequant" != "xno" && test x"$with_png" != "xno"; then + PKG_CHECK_MODULES(IMAGEQUANT, imagequant, + [AC_DEFINE(HAVE_IMAGEQUANT,1,[define if you have imagequant installed.]) + with_imagequant=yes + PACKAGES_USED="$PACKAGES_USED imagequant" + ], + [AC_MSG_WARN([libimagequant not found; disabling 8bpp PNG support]) + with_imagequant=no + ] + ) +else + with_imagequant=no +fi + # look for libjpeg with pkg-config ... fall back to our tester AC_ARG_WITH([jpeg], AS_HELP_STRING([--without-jpeg], [build without libjpeg (default: test)])) @@ -1260,14 +1278,14 @@ fi # Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS # sort includes to get longer, more specific dirs first # helps, for example, selecting graphicsmagick over imagemagick -VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $JPEG_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $LIBWEBPMUX_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $PDFIUM_INCLUDES $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS +VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $JPEG_CFLAGS $PNG_CFLAGS $IMAGEQUANT_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $LIBWEBPMUX_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $PDFIUM_INCLUDES $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS do echo $i done | sort -ru` VIPS_CFLAGS=`echo $VIPS_CFLAGS` VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS" VIPS_INCLUDES="$ZLIB_INCLUDES $PNG_INCLUDES $TIFF_INCLUDES $JPEG_INCLUDES" -VIPS_LIBS="$ZLIB_LIBS $MAGICK_LIBS $PNG_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" +VIPS_LIBS="$ZLIB_LIBS $MAGICK_LIBS $PNG_LIBS $IMAGEQUANT_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" AC_SUBST(VIPS_LIBDIR) @@ -1375,6 +1393,7 @@ support webp metadata: $with_libwebpmux text rendering with pangoft2: $with_pangoft2 file import/export with libpng: $with_png (requires libpng-1.2.9 or later) +support 8bpp PNG quantization: $with_imagequant file import/export with libtiff: $with_tiff file import/export with giflib: $with_giflib file import/export with libjpeg: $with_jpeg From d9d2f7b89af5480f02ae7ea98b0a1efdb078bb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Mon, 18 Jun 2018 02:22:46 +0200 Subject: [PATCH 2/5] Add 8bpp PNG quantization support This adds support for saving 8-Bit one band palette based PNG images with palette based alpha channel (often called PNG8+Alpha). The image is first converted to sRGBA and then quantized using libimagequant controlled by the colors, Q and dither params. --- libvips/foreign/pforeign.h | 5 +- libvips/foreign/pngsave.c | 42 +++++++- libvips/foreign/vipspng.c | 190 +++++++++++++++++++++++++++++++++++-- test/test_formats.sh | 3 +- 4 files changed, 229 insertions(+), 11 deletions(-) diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index bc521f47..9bb72022 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -197,10 +197,11 @@ int vips__png_header_buffer( const void *buffer, size_t length, VipsImage *out ) int vips__png_write( VipsImage *in, const char *filename, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter, gboolean strip ); + VipsForeignPngFilter filter, gboolean strip, int colors, int Q, double dither ); int vips__png_write_buf( VipsImage *in, void **obuf, size_t *olen, int compression, int interlace, - const char *profile, VipsForeignPngFilter filter, gboolean strip ); + const char *profile, VipsForeignPngFilter filter, gboolean strip, + int colors, int Q, double dither ); /* Map WEBP metadata names to vips names. */ diff --git a/libvips/foreign/pngsave.c b/libvips/foreign/pngsave.c index a6e2cf13..32cbb6ce 100644 --- a/libvips/foreign/pngsave.c +++ b/libvips/foreign/pngsave.c @@ -60,6 +60,9 @@ typedef struct _VipsForeignSavePng { gboolean interlace; char *profile; VipsForeignPngFilter filter; + int colors; + int Q; + double dither; } VipsForeignSavePng; typedef VipsForeignSaveClass VipsForeignSavePngClass; @@ -133,6 +136,27 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class ) VIPS_TYPE_FOREIGN_PNG_FILTER, VIPS_FOREIGN_PNG_FILTER_ALL ); + VIPS_ARG_INT( class, "colors", 13, + _( "Colors" ), + _( "Number of palette entries" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePng, colors ), + 2, 256, 256 ); + + VIPS_ARG_INT( class, "Q", 14, + _( "Q" ), + _( "Q factor" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePng, Q ), + 0, 100, 75 ); + + VIPS_ARG_DOUBLE( class, "dither", 15, + _( "Dithering" ), + _( "Amount of dithering" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePng, dither ), + 0.0, 1.0, 1.0 ); + } static void @@ -140,6 +164,9 @@ vips_foreign_save_png_init( VipsForeignSavePng *png ) { png->compression = 6; png->filter = VIPS_FOREIGN_PNG_FILTER_ALL; + png->colors = 0; + png->Q = 75; + png->dither = 1.0; } typedef struct _VipsForeignSavePngFile { @@ -166,7 +193,8 @@ vips_foreign_save_png_file_build( VipsObject *object ) if( vips__png_write( save->ready, png_file->filename, png->compression, png->interlace, - png->profile, png->filter, save->strip ) ) + png->profile, png->filter, save->strip, png->colors, png->Q, + png->dither ) ) return( -1 ); return( 0 ); @@ -225,7 +253,7 @@ vips_foreign_save_png_buffer_build( VipsObject *object ) if( vips__png_write_buf( save->ready, &obuf, &olen, png->compression, png->interlace, png->profile, png->filter, - save->strip ) ) + save->strip, png->colors, png->Q, png->dither ) ) return( -1 ); /* vips__png_write_buf() makes a buffer that needs g_free(), not @@ -278,6 +306,9 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer ) * * @interlace: interlace image * * @profile: ICC profile to embed * * @filter: #VipsForeignPngFilter row filter flag(s) + * * @colors: enable 8bpp quantization with max n colors + * * @Q: quality for 8bpp quantization (does not exceed @colors) + * * @dither: amount of dithering for 8bpp quantization * * Write a VIPS image to a file as PNG. * @@ -304,6 +335,13 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer ) * alpha before saving. Images with more than one byte per band element are * saved as 16-bit PNG, others are saved as 8-bit PNG. * + * If @colors is given, it limits the maximum number of colors in the image + * and the source image will be quantized down to an 8-Bit one band indexed + * image with palette based alpha transparency. Similar to JPEG the quality + * can be controlled with the @Q parameter and the amount of Floyd-Steinberg + * dithering is set with @dither. + * This feature requires libvips to be compiled with libimagequant. + * * See also: vips_image_new_from_file(). * * Returns: 0 on success, -1 on error. diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index e4913c97..5b810ecd 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -124,6 +124,10 @@ #error "PNG library too old." #endif +#ifdef HAVE_IMAGEQUANT +#include +#endif + static void user_error_function( png_structp png_ptr, png_const_charp error_msg ) { @@ -877,12 +881,110 @@ write_png_block( VipsRegion *region, VipsRect *area, void *a ) return( 0 ); } +#ifdef HAVE_IMAGEQUANT +static int +quantize_image( VipsImage *in, VipsImage *out, VipsImage *palette_out, + int colors, int Q, double dither ) +{ + /* Ensure input is sRGB. */ + if( in->Type != VIPS_INTERPRETATION_sRGB) { + VipsImage *srgb; + if( vips_colourspace( in, &srgb, VIPS_INTERPRETATION_sRGB, + NULL ) ) + return( -1 ); + in = srgb; + VIPS_UNREF( srgb ); + } + /* Add alpha channel if missing. */ + if( in->Bands == 3 ) { + VipsImage *srgba; + if( vips_bandjoin_const1( in, &srgba, 255, NULL ) ) + return( -1 ); + in = srgba; + VIPS_UNREF( srgba ); + } + VipsImage *memory; + if( !(memory = vips_image_copy_memory( in )) ) + return( -1 ); + in = memory; + + liq_attr *attr = liq_attr_create(); + liq_set_max_colors( attr, colors ); + liq_set_quality( attr, 0, Q ); + + liq_image *input_image = liq_image_create_rgba( attr, + VIPS_IMAGE_ADDR( in, 0, 0 ), in->Xsize, in->Ysize, 0 ); + + liq_result *quantization_result; + if ( liq_image_quantize( input_image, attr, &quantization_result ) ) { + liq_result_destroy( quantization_result ); + liq_image_destroy( input_image ); + liq_attr_destroy( attr ); + VIPS_UNREF( memory ); + return( -1 ); + } + + liq_set_dithering_level( quantization_result, (float) dither ); + + vips_image_init_fields( out, in->Xsize, in->Ysize, 1, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + + if( vips_image_write_prepare( out ) ) { + liq_result_destroy( quantization_result ); + liq_image_destroy( input_image ); + liq_attr_destroy( attr ); + VIPS_UNREF( memory ); + return( -1 ); + } + + if( liq_write_remapped_image( quantization_result, input_image, + VIPS_IMAGE_ADDR( out, 0, 0 ), VIPS_IMAGE_N_PELS( out ) ) ) { + liq_result_destroy( quantization_result ); + liq_image_destroy( input_image ); + liq_attr_destroy( attr ); + VIPS_UNREF( memory ); + return( -1 ); + } + + const liq_palette *palette = liq_get_palette( quantization_result ); + + vips_image_init_fields( palette_out, palette->count, 1, 4, + VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, + 1.0, 1.0 ); + + if( vips_image_write_prepare( palette_out ) ) { + liq_result_destroy( quantization_result ); + liq_image_destroy( input_image ); + liq_attr_destroy( attr ); + VIPS_UNREF( memory ); + return( -1 ); + } + + int i; + for( i = 0; i < palette->count; i++ ) { + unsigned char *p = VIPS_IMAGE_ADDR( palette_out, i, 0 ); + p[0] = palette->entries[i].r; + p[1] = palette->entries[i].g; + p[2] = palette->entries[i].b; + p[3] = palette->entries[i].a; + } + + liq_result_destroy( quantization_result ); + liq_image_destroy( input_image ); + liq_attr_destroy( attr ); + VIPS_UNREF( memory ); + + return( 0 ); +} +#endif + /* Write a VIPS image to PNG. */ static int write_vips( Write *write, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter, gboolean strip ) + VipsForeignPngFilter filter, gboolean strip, int colors, int Q, + double dither ) { VipsImage *in = write->in; @@ -942,6 +1044,19 @@ write_vips( Write *write, return( -1 ); } +#ifdef HAVE_IMAGEQUANT + /* Enable image quantization to paletted 8bpp PNG if colors is set. + */ + if( colors ) { + g_assert( colors >= 2 && colors <= 256 ); + bit_depth = 8; + color_type = PNG_COLOR_TYPE_PALETTE; + } +#else + if( colors ) + g_warning( "%s", _( "ignoring colors" ) ); +#endif + interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; png_set_IHDR( write->pPng, write->pInfo, @@ -994,7 +1109,66 @@ write_vips( Write *write, PNG_COMPRESSION_TYPE_BASE, data, length ); } - png_write_info( write->pPng, write->pInfo ); +#ifdef HAVE_IMAGEQUANT + if( colors ) { + VipsImage *quantized = vips_image_new_memory(); + VipsImage *palette = vips_image_new_memory(); + if( quantize_image( in, quantized, palette, colors, Q, + dither ) ) { + vips_error( "vips2png", + "%s", _( "quantization failed" ) ); + VIPS_UNREF( quantized ); + VIPS_UNREF( palette ); + return( -1 ); + } + + int palette_count = palette->Xsize; + g_assert( palette_count <= PNG_MAX_PALETTE_LENGTH); + + png_color *png_palette = (png_color *) png_malloc( write->pPng, + palette_count * sizeof( png_color ) ); + png_byte *png_trans = (png_byte *) png_malloc( write->pPng, + palette_count * sizeof( png_byte ) ); + int trans_count = 0; + + for( i = 0; i < palette_count; i++ ) { + png_byte *p = (png_byte *) VIPS_IMAGE_ADDR( palette, i, + 0 ); + png_color *col = &png_palette[i]; + col->red = p[0]; + col->green = p[1]; + col->blue = p[2]; + png_trans[i] = p[3]; + if( p[3] != 255 ) + trans_count = i + 1; +#ifdef DEBUG + printf( "write_vips: palette[%d] %d %d %d %d\n", + i + 1, p[0], p[1], p[2], p[3] ); +#endif /*DEBUG*/ + } + +#ifdef DEBUG + printf( "write_vips: attaching %d color palette\n", + palette_count ); +#endif /*DEBUG*/ + png_set_PLTE( write->pPng, write->pInfo, png_palette, + palette_count ); + if( trans_count ) { +#ifdef DEBUG + printf( "write_vips: attaching %d alpha values\n", + trans_count ); +#endif /*DEBUG*/ + png_set_tRNS( write->pPng, write->pInfo, png_trans, + trans_count, NULL ); + } + VIPS_UNREF( palette ); + + VIPS_UNREF( write->memory ); + write->memory = quantized; + in = write->memory; + } +#endif + png_write_info( write->pPng, write->pInfo ); /* If we're an intel byte order CPU and this is a 16bit image, we need * to swap bytes. @@ -1027,7 +1201,8 @@ write_vips( Write *write, int vips__png_write( VipsImage *in, const char *filename, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter, gboolean strip ) + VipsForeignPngFilter filter, gboolean strip, + int colors, int Q, double dither ) { Write *write; @@ -1047,7 +1222,8 @@ vips__png_write( VipsImage *in, const char *filename, /* Convert it! */ if( write_vips( write, - compress, interlace, profile, filter, strip ) ) { + compress, interlace, profile, filter, strip, colors, Q, + dither ) ) { vips_error( "vips2png", _( "unable to write \"%s\"" ), filename ); @@ -1074,7 +1250,8 @@ user_write_data( png_structp png_ptr, png_bytep data, png_size_t length ) int vips__png_write_buf( VipsImage *in, void **obuf, size_t *olen, int compression, int interlace, - const char *profile, VipsForeignPngFilter filter, gboolean strip ) + const char *profile, VipsForeignPngFilter filter, gboolean strip, + int colors, int Q, double dither ) { Write *write; @@ -1086,7 +1263,8 @@ vips__png_write_buf( VipsImage *in, /* Convert it! */ if( write_vips( write, - compression, interlace, profile, filter, strip ) ) { + compression, interlace, profile, filter, strip, colors, Q, + dither ) ) { vips_error( "vips2png", "%s", _( "unable to write to buffer" ) ); diff --git a/test/test_formats.sh b/test/test_formats.sh index abaa5241..5ff76dc5 100755 --- a/test/test_formats.sh +++ b/test/test_formats.sh @@ -196,7 +196,8 @@ if test_supported tiffload; then fi if test_supported pngload; then test_format $image png 0 - test_format $image png 0 [compression=9,interlace=1] + test_format $image png 0 [compression=9,interlace=1] + test_format $image png 90 [colors=256,Q=100,dither=0,compression=9,interlace=1] fi if test_supported jpegload; then test_format $image jpg 90 From c63d7f7373c7a10a16c0c092bb360ad10c88c796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Tue, 19 Jun 2018 09:19:43 +0200 Subject: [PATCH 3/5] Change default PNG quantization quality to 100 and improve parameter description. --- libvips/foreign/pngsave.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/pngsave.c b/libvips/foreign/pngsave.c index 32cbb6ce..78ea929a 100644 --- a/libvips/foreign/pngsave.c +++ b/libvips/foreign/pngsave.c @@ -144,11 +144,11 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class ) 2, 256, 256 ); VIPS_ARG_INT( class, "Q", 14, - _( "Q" ), - _( "Q factor" ), + _( "Quality" ), + _( "Quantization quality" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSavePng, Q ), - 0, 100, 75 ); + 0, 100, 100 ); VIPS_ARG_DOUBLE( class, "dither", 15, _( "Dithering" ), @@ -165,7 +165,7 @@ vips_foreign_save_png_init( VipsForeignSavePng *png ) png->compression = 6; png->filter = VIPS_FOREIGN_PNG_FILTER_ALL; png->colors = 0; - png->Q = 75; + png->Q = 100; png->dither = 1.0; } From 1e68b3a486935f1c87630406f49d0c1f767fcd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Tue, 19 Jun 2018 21:32:44 +0200 Subject: [PATCH 4/5] Rename pngsave colors/colours, quantize/quantise --- configure.ac | 2 +- libvips/foreign/pforeign.h | 4 +-- libvips/foreign/pngsave.c | 26 ++++++++--------- libvips/foreign/vipspng.c | 58 +++++++++++++++++++------------------- test/test_formats.sh | 2 +- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/configure.ac b/configure.ac index 13b8c609..f707c791 100644 --- a/configure.ac +++ b/configure.ac @@ -1393,7 +1393,7 @@ support webp metadata: $with_libwebpmux text rendering with pangoft2: $with_pangoft2 file import/export with libpng: $with_png (requires libpng-1.2.9 or later) -support 8bpp PNG quantization: $with_imagequant +support 8bpp PNG quantisation: $with_imagequant file import/export with libtiff: $with_tiff file import/export with giflib: $with_giflib file import/export with libjpeg: $with_jpeg diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 9bb72022..48e7b938 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -197,11 +197,11 @@ int vips__png_header_buffer( const void *buffer, size_t length, VipsImage *out ) int vips__png_write( VipsImage *in, const char *filename, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter, gboolean strip, int colors, int Q, double dither ); + VipsForeignPngFilter filter, gboolean strip, int colours, int Q, double dither ); int vips__png_write_buf( VipsImage *in, void **obuf, size_t *olen, int compression, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - int colors, int Q, double dither ); + int colours, int Q, double dither ); /* Map WEBP metadata names to vips names. */ diff --git a/libvips/foreign/pngsave.c b/libvips/foreign/pngsave.c index 78ea929a..2b7568b0 100644 --- a/libvips/foreign/pngsave.c +++ b/libvips/foreign/pngsave.c @@ -60,7 +60,7 @@ typedef struct _VipsForeignSavePng { gboolean interlace; char *profile; VipsForeignPngFilter filter; - int colors; + int colours; int Q; double dither; } VipsForeignSavePng; @@ -136,16 +136,16 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class ) VIPS_TYPE_FOREIGN_PNG_FILTER, VIPS_FOREIGN_PNG_FILTER_ALL ); - VIPS_ARG_INT( class, "colors", 13, - _( "Colors" ), - _( "Number of palette entries" ), + VIPS_ARG_INT( class, "colours", 13, + _( "Colours" ), + _( "Max number of palette colours" ), VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSavePng, colors ), + G_STRUCT_OFFSET( VipsForeignSavePng, colours ), 2, 256, 256 ); VIPS_ARG_INT( class, "Q", 14, _( "Quality" ), - _( "Quantization quality" ), + _( "Quantisation quality" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSavePng, Q ), 0, 100, 100 ); @@ -164,7 +164,7 @@ vips_foreign_save_png_init( VipsForeignSavePng *png ) { png->compression = 6; png->filter = VIPS_FOREIGN_PNG_FILTER_ALL; - png->colors = 0; + png->colours = 0; png->Q = 100; png->dither = 1.0; } @@ -193,7 +193,7 @@ vips_foreign_save_png_file_build( VipsObject *object ) if( vips__png_write( save->ready, png_file->filename, png->compression, png->interlace, - png->profile, png->filter, save->strip, png->colors, png->Q, + png->profile, png->filter, save->strip, png->colours, png->Q, png->dither ) ) return( -1 ); @@ -253,7 +253,7 @@ vips_foreign_save_png_buffer_build( VipsObject *object ) if( vips__png_write_buf( save->ready, &obuf, &olen, png->compression, png->interlace, png->profile, png->filter, - save->strip, png->colors, png->Q, png->dither ) ) + save->strip, png->colours, png->Q, png->dither ) ) return( -1 ); /* vips__png_write_buf() makes a buffer that needs g_free(), not @@ -306,8 +306,8 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer ) * * @interlace: interlace image * * @profile: ICC profile to embed * * @filter: #VipsForeignPngFilter row filter flag(s) - * * @colors: enable 8bpp quantization with max n colors - * * @Q: quality for 8bpp quantization (does not exceed @colors) + * * @colours: enable 8bpp quantisation with max n colours + * * @Q: quality for 8bpp quantisation (does not exceed @colours) * * @dither: amount of dithering for 8bpp quantization * * Write a VIPS image to a file as PNG. @@ -335,8 +335,8 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer ) * alpha before saving. Images with more than one byte per band element are * saved as 16-bit PNG, others are saved as 8-bit PNG. * - * If @colors is given, it limits the maximum number of colors in the image - * and the source image will be quantized down to an 8-Bit one band indexed + * If @colours is given, it limits the maximum number of colours in the image + * and the source image will be quantised down to an 8-Bit one band indexed * image with palette based alpha transparency. Similar to JPEG the quality * can be controlled with the @Q parameter and the amount of Floyd-Steinberg * dithering is set with @dither. diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index 5b810ecd..64fc9e99 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -883,8 +883,8 @@ write_png_block( VipsRegion *region, VipsRect *area, void *a ) #ifdef HAVE_IMAGEQUANT static int -quantize_image( VipsImage *in, VipsImage *out, VipsImage *palette_out, - int colors, int Q, double dither ) +quantise_image( VipsImage *in, VipsImage *out, VipsImage *palette_out, + int colours, int Q, double dither ) { /* Ensure input is sRGB. */ if( in->Type != VIPS_INTERPRETATION_sRGB) { @@ -909,51 +909,51 @@ quantize_image( VipsImage *in, VipsImage *out, VipsImage *palette_out, in = memory; liq_attr *attr = liq_attr_create(); - liq_set_max_colors( attr, colors ); + liq_set_max_colors( attr, colours ); liq_set_quality( attr, 0, Q ); liq_image *input_image = liq_image_create_rgba( attr, VIPS_IMAGE_ADDR( in, 0, 0 ), in->Xsize, in->Ysize, 0 ); - liq_result *quantization_result; - if ( liq_image_quantize( input_image, attr, &quantization_result ) ) { - liq_result_destroy( quantization_result ); + liq_result *quantisation_result; + if ( liq_image_quantize( input_image, attr, &quantisation_result ) ) { + liq_result_destroy( quantisation_result ); liq_image_destroy( input_image ); liq_attr_destroy( attr ); VIPS_UNREF( memory ); return( -1 ); } - liq_set_dithering_level( quantization_result, (float) dither ); + liq_set_dithering_level( quantisation_result, (float) dither ); vips_image_init_fields( out, in->Xsize, in->Ysize, 1, VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); if( vips_image_write_prepare( out ) ) { - liq_result_destroy( quantization_result ); + liq_result_destroy( quantisation_result ); liq_image_destroy( input_image ); liq_attr_destroy( attr ); VIPS_UNREF( memory ); return( -1 ); } - if( liq_write_remapped_image( quantization_result, input_image, + if( liq_write_remapped_image( quantisation_result, input_image, VIPS_IMAGE_ADDR( out, 0, 0 ), VIPS_IMAGE_N_PELS( out ) ) ) { - liq_result_destroy( quantization_result ); + liq_result_destroy( quantisation_result ); liq_image_destroy( input_image ); liq_attr_destroy( attr ); VIPS_UNREF( memory ); return( -1 ); } - const liq_palette *palette = liq_get_palette( quantization_result ); + const liq_palette *palette = liq_get_palette( quantisation_result ); vips_image_init_fields( palette_out, palette->count, 1, 4, VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); if( vips_image_write_prepare( palette_out ) ) { - liq_result_destroy( quantization_result ); + liq_result_destroy( quantisation_result ); liq_image_destroy( input_image ); liq_attr_destroy( attr ); VIPS_UNREF( memory ); @@ -969,7 +969,7 @@ quantize_image( VipsImage *in, VipsImage *out, VipsImage *palette_out, p[3] = palette->entries[i].a; } - liq_result_destroy( quantization_result ); + liq_result_destroy( quantisation_result ); liq_image_destroy( input_image ); liq_attr_destroy( attr ); VIPS_UNREF( memory ); @@ -983,7 +983,7 @@ quantize_image( VipsImage *in, VipsImage *out, VipsImage *palette_out, static int write_vips( Write *write, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter, gboolean strip, int colors, int Q, + VipsForeignPngFilter filter, gboolean strip, int colours, int Q, double dither ) { VipsImage *in = write->in; @@ -1045,16 +1045,16 @@ write_vips( Write *write, } #ifdef HAVE_IMAGEQUANT - /* Enable image quantization to paletted 8bpp PNG if colors is set. + /* Enable image quantisation to paletted 8bpp PNG if colours is set. */ - if( colors ) { - g_assert( colors >= 2 && colors <= 256 ); + if( colours ) { + g_assert( colours >= 2 && colours <= 256 ); bit_depth = 8; color_type = PNG_COLOR_TYPE_PALETTE; } #else - if( colors ) - g_warning( "%s", _( "ignoring colors" ) ); + if( colours ) + g_warning( "%s", _( "ignoring colours" ) ); #endif interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; @@ -1110,14 +1110,14 @@ write_vips( Write *write, } #ifdef HAVE_IMAGEQUANT - if( colors ) { - VipsImage *quantized = vips_image_new_memory(); + if( colours ) { + VipsImage *quantised = vips_image_new_memory(); VipsImage *palette = vips_image_new_memory(); - if( quantize_image( in, quantized, palette, colors, Q, + if( quantise_image( in, quantised, palette, colours, Q, dither ) ) { vips_error( "vips2png", - "%s", _( "quantization failed" ) ); - VIPS_UNREF( quantized ); + "%s", _( "quantisation failed" ) ); + VIPS_UNREF( quantised ); VIPS_UNREF( palette ); return( -1 ); } @@ -1164,7 +1164,7 @@ write_vips( Write *write, VIPS_UNREF( palette ); VIPS_UNREF( write->memory ); - write->memory = quantized; + write->memory = quantised; in = write->memory; } #endif @@ -1202,7 +1202,7 @@ int vips__png_write( VipsImage *in, const char *filename, int compress, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - int colors, int Q, double dither ) + int colours, int Q, double dither ) { Write *write; @@ -1222,7 +1222,7 @@ vips__png_write( VipsImage *in, const char *filename, /* Convert it! */ if( write_vips( write, - compress, interlace, profile, filter, strip, colors, Q, + compress, interlace, profile, filter, strip, colours, Q, dither ) ) { vips_error( "vips2png", _( "unable to write \"%s\"" ), filename ); @@ -1251,7 +1251,7 @@ int vips__png_write_buf( VipsImage *in, void **obuf, size_t *olen, int compression, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - int colors, int Q, double dither ) + int colours, int Q, double dither ) { Write *write; @@ -1263,7 +1263,7 @@ vips__png_write_buf( VipsImage *in, /* Convert it! */ if( write_vips( write, - compression, interlace, profile, filter, strip, colors, Q, + compression, interlace, profile, filter, strip, colours, Q, dither ) ) { vips_error( "vips2png", "%s", _( "unable to write to buffer" ) ); diff --git a/test/test_formats.sh b/test/test_formats.sh index 5ff76dc5..f66f76cd 100755 --- a/test/test_formats.sh +++ b/test/test_formats.sh @@ -197,7 +197,7 @@ fi if test_supported pngload; then test_format $image png 0 test_format $image png 0 [compression=9,interlace=1] - test_format $image png 90 [colors=256,Q=100,dither=0,compression=9,interlace=1] + test_format $image png 90 [colours=256,Q=100,dither=0,compression=9,interlace=1] fi if test_supported jpegload; then test_format $image jpg 90 From 1abc5f901a9009456c762cde549a714c0bdd6dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Tue, 19 Jun 2018 22:41:40 +0200 Subject: [PATCH 5/5] Add palette flag to trigger pngsave quantisation and tweak docstrings. --- libvips/foreign/pforeign.h | 5 ++-- libvips/foreign/pngsave.c | 35 +++++++++++++++--------- libvips/foreign/vipspng.c | 55 +++++++++++++++++++------------------- test/test_formats.sh | 2 +- 4 files changed, 54 insertions(+), 43 deletions(-) diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 48e7b938..50e632e7 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -197,11 +197,12 @@ int vips__png_header_buffer( const void *buffer, size_t length, VipsImage *out ) int vips__png_write( VipsImage *in, const char *filename, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter, gboolean strip, int colours, int Q, double dither ); + VipsForeignPngFilter filter, gboolean strip, + gboolean palette, int colours, int Q, double dither ); int vips__png_write_buf( VipsImage *in, void **obuf, size_t *olen, int compression, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - int colours, int Q, double dither ); + gboolean palette, int colours, int Q, double dither ); /* Map WEBP metadata names to vips names. */ diff --git a/libvips/foreign/pngsave.c b/libvips/foreign/pngsave.c index 2b7568b0..2761a291 100644 --- a/libvips/foreign/pngsave.c +++ b/libvips/foreign/pngsave.c @@ -60,6 +60,7 @@ typedef struct _VipsForeignSavePng { gboolean interlace; char *profile; VipsForeignPngFilter filter; + gboolean palette; int colours; int Q; double dither; @@ -136,21 +137,28 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class ) VIPS_TYPE_FOREIGN_PNG_FILTER, VIPS_FOREIGN_PNG_FILTER_ALL ); - VIPS_ARG_INT( class, "colours", 13, + VIPS_ARG_BOOL( class, "palette", 13, + _( "Palette" ), + _( "Quantise to 8bpp palette" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePng, palette ), + FALSE ); + + VIPS_ARG_INT( class, "colours", 14, _( "Colours" ), _( "Max number of palette colours" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSavePng, colours ), 2, 256, 256 ); - VIPS_ARG_INT( class, "Q", 14, + VIPS_ARG_INT( class, "Q", 15, _( "Quality" ), _( "Quantisation quality" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSavePng, Q ), 0, 100, 100 ); - VIPS_ARG_DOUBLE( class, "dither", 15, + VIPS_ARG_DOUBLE( class, "dither", 16, _( "Dithering" ), _( "Amount of dithering" ), VIPS_ARGUMENT_OPTIONAL_INPUT, @@ -164,7 +172,7 @@ vips_foreign_save_png_init( VipsForeignSavePng *png ) { png->compression = 6; png->filter = VIPS_FOREIGN_PNG_FILTER_ALL; - png->colours = 0; + png->colours = 256; png->Q = 100; png->dither = 1.0; } @@ -193,8 +201,8 @@ vips_foreign_save_png_file_build( VipsObject *object ) if( vips__png_write( save->ready, png_file->filename, png->compression, png->interlace, - png->profile, png->filter, save->strip, png->colours, png->Q, - png->dither ) ) + png->profile, png->filter, save->strip, png->palette, + png->colours, png->Q, png->dither ) ) return( -1 ); return( 0 ); @@ -253,7 +261,7 @@ vips_foreign_save_png_buffer_build( VipsObject *object ) if( vips__png_write_buf( save->ready, &obuf, &olen, png->compression, png->interlace, png->profile, png->filter, - save->strip, png->colours, png->Q, png->dither ) ) + save->strip, png->palette, png->colours, png->Q, png->dither ) ) return( -1 ); /* vips__png_write_buf() makes a buffer that needs g_free(), not @@ -306,7 +314,8 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer ) * * @interlace: interlace image * * @profile: ICC profile to embed * * @filter: #VipsForeignPngFilter row filter flag(s) - * * @colours: enable 8bpp quantisation with max n colours + * * @palette: enable quantisation to 8bpp palette + * * @colours: max number of palette colours for quantisation * * @Q: quality for 8bpp quantisation (does not exceed @colours) * * @dither: amount of dithering for 8bpp quantization * @@ -335,11 +344,11 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer ) * alpha before saving. Images with more than one byte per band element are * saved as 16-bit PNG, others are saved as 8-bit PNG. * - * If @colours is given, it limits the maximum number of colours in the image - * and the source image will be quantised down to an 8-Bit one band indexed - * image with palette based alpha transparency. Similar to JPEG the quality - * can be controlled with the @Q parameter and the amount of Floyd-Steinberg - * dithering is set with @dither. + * Set @palette to %TRUE to enable quantisation to an 8-bit per pixel palette + * image with alpha transparency support. If @colours is given, it limits the + * maximum number of palette entries. Similar to JPEG the quality can also be + * be changed with the @Q parameter which further reduces the palette size and + * @dither controls the amount of Floyd-Steinberg dithering. * This feature requires libvips to be compiled with libimagequant. * * See also: vips_image_new_from_file(). diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index 64fc9e99..fd9da69e 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -896,7 +896,7 @@ quantise_image( VipsImage *in, VipsImage *out, VipsImage *palette_out, VIPS_UNREF( srgb ); } /* Add alpha channel if missing. */ - if( in->Bands == 3 ) { + if( !vips_image_hasalpha(in) ) { VipsImage *srgba; if( vips_bandjoin_const1( in, &srgba, 255, NULL ) ) return( -1 ); @@ -976,15 +976,15 @@ quantise_image( VipsImage *in, VipsImage *out, VipsImage *palette_out, return( 0 ); } -#endif +#endif /*HAVE_IMAGEQUANT*/ /* Write a VIPS image to PNG. */ static int write_vips( Write *write, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter, gboolean strip, int colours, int Q, - double dither ) + VipsForeignPngFilter filter, gboolean strip, + gboolean palette, int colours, int Q, double dither ) { VipsImage *in = write->in; @@ -1047,15 +1047,16 @@ write_vips( Write *write, #ifdef HAVE_IMAGEQUANT /* Enable image quantisation to paletted 8bpp PNG if colours is set. */ - if( colours ) { + if( palette ) { g_assert( colours >= 2 && colours <= 256 ); bit_depth = 8; color_type = PNG_COLOR_TYPE_PALETTE; } #else - if( colours ) - g_warning( "%s", _( "ignoring colours" ) ); -#endif + if( palette ) + g_warning( "%s", + _( "ignoring palette (no quantisation support)" ) ); +#endif /*HAVE_IMAGEQUANT*/ interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; @@ -1110,19 +1111,19 @@ write_vips( Write *write, } #ifdef HAVE_IMAGEQUANT - if( colours ) { - VipsImage *quantised = vips_image_new_memory(); - VipsImage *palette = vips_image_new_memory(); - if( quantise_image( in, quantised, palette, colours, Q, + if( palette ) { + VipsImage *im_quantised = vips_image_new_memory(); + VipsImage *im_palette = vips_image_new_memory(); + if( quantise_image( in, im_quantised, im_palette, colours, Q, dither ) ) { vips_error( "vips2png", "%s", _( "quantisation failed" ) ); - VIPS_UNREF( quantised ); - VIPS_UNREF( palette ); + VIPS_UNREF( im_quantised ); + VIPS_UNREF( im_palette ); return( -1 ); } - int palette_count = palette->Xsize; + int palette_count = im_palette->Xsize; g_assert( palette_count <= PNG_MAX_PALETTE_LENGTH); png_color *png_palette = (png_color *) png_malloc( write->pPng, @@ -1132,8 +1133,8 @@ write_vips( Write *write, int trans_count = 0; for( i = 0; i < palette_count; i++ ) { - png_byte *p = (png_byte *) VIPS_IMAGE_ADDR( palette, i, - 0 ); + png_byte *p = (png_byte *) VIPS_IMAGE_ADDR( im_palette, + i, 0 ); png_color *col = &png_palette[i]; col->red = p[0]; col->green = p[1]; @@ -1161,13 +1162,13 @@ write_vips( Write *write, png_set_tRNS( write->pPng, write->pInfo, png_trans, trans_count, NULL ); } - VIPS_UNREF( palette ); + VIPS_UNREF( im_palette ); VIPS_UNREF( write->memory ); - write->memory = quantised; + write->memory = im_quantised; in = write->memory; } -#endif +#endif /*HAVE_IMAGEQUANT*/ png_write_info( write->pPng, write->pInfo ); /* If we're an intel byte order CPU and this is a 16bit image, we need @@ -1202,7 +1203,7 @@ int vips__png_write( VipsImage *in, const char *filename, int compress, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - int colours, int Q, double dither ) + gboolean palette, int colours, int Q, double dither ) { Write *write; @@ -1222,8 +1223,8 @@ vips__png_write( VipsImage *in, const char *filename, /* Convert it! */ if( write_vips( write, - compress, interlace, profile, filter, strip, colours, Q, - dither ) ) { + compress, interlace, profile, filter, strip, palette, + colours, Q, dither ) ) { vips_error( "vips2png", _( "unable to write \"%s\"" ), filename ); @@ -1251,7 +1252,7 @@ int vips__png_write_buf( VipsImage *in, void **obuf, size_t *olen, int compression, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - int colours, int Q, double dither ) + gboolean palette, int colours, int Q, double dither ) { Write *write; @@ -1263,11 +1264,11 @@ vips__png_write_buf( VipsImage *in, /* Convert it! */ if( write_vips( write, - compression, interlace, profile, filter, strip, colours, Q, - dither ) ) { + compression, interlace, profile, filter, strip, palette, + colours, Q, dither ) ) { vips_error( "vips2png", "%s", _( "unable to write to buffer" ) ); - + return( -1 ); } diff --git a/test/test_formats.sh b/test/test_formats.sh index f66f76cd..60a4cccc 100755 --- a/test/test_formats.sh +++ b/test/test_formats.sh @@ -197,7 +197,7 @@ fi if test_supported pngload; then test_format $image png 0 test_format $image png 0 [compression=9,interlace=1] - test_format $image png 90 [colours=256,Q=100,dither=0,compression=9,interlace=1] + test_format $image png 90 [palette,colours=256,Q=100,dither=0,interlace=1] fi if test_supported jpegload; then test_format $image jpg 90