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.
This commit is contained in:
parent
b5531cf1ed
commit
d9d2f7b89a
@ -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.
|
||||
*/
|
||||
|
@ -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.
|
||||
|
@ -124,6 +124,10 @@
|
||||
#error "PNG library too old."
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IMAGEQUANT
|
||||
#include <libimagequant.h>
|
||||
#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" ) );
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user