add 1/2/4 bit PNG save
- new @bitdepth param - deprecate @colours param
This commit is contained in:
parent
31184ddb57
commit
4bf27acccb
@ -31,8 +31,9 @@
|
||||
- deprecate heifload autorotate -- it's now always on
|
||||
- revised resize improves accuracy [kleisauke]
|
||||
- add --vips-config flag to show configuration info
|
||||
- add "bitdepth" param to tiff load and save, deprecate "squash" [MathemanFlo]
|
||||
- add "bitdepth" param to tiff save, deprecate "squash" [MathemanFlo]
|
||||
- tiff load and save now supports 2 and 4 bit data [MathemanFlo]
|
||||
- pngsave @bitdepth parameter lets you write 1, 2 and 4 bit PNGs
|
||||
- ppmsave also uses "bitdepth" now, for consistency
|
||||
|
||||
24/4/20 started 8.9.3
|
||||
|
@ -184,7 +184,8 @@ extern const char *vips__png_suffs[];
|
||||
int vips__png_write_target( VipsImage *in, VipsTarget *target,
|
||||
int compress, int interlace, const char *profile,
|
||||
VipsForeignPngFilter filter, gboolean strip,
|
||||
gboolean palette, int colours, int Q, double dither );
|
||||
gboolean palette, int Q, double dither,
|
||||
int bitdepth );
|
||||
|
||||
/* Map WEBP metadata names to vips names.
|
||||
*/
|
||||
|
@ -6,6 +6,8 @@
|
||||
* - compression should be 0-9, not 1-10
|
||||
* 20/6/18 [felixbuenemann]
|
||||
* - support png8 palette write with palette, colours, Q, dither
|
||||
* 24/6/20
|
||||
* - add @bitdepth, deprecate @colours
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -63,9 +65,17 @@ typedef struct _VipsForeignSavePng {
|
||||
char *profile;
|
||||
VipsForeignPngFilter filter;
|
||||
gboolean palette;
|
||||
int colours;
|
||||
int Q;
|
||||
double dither;
|
||||
int bitdepth;
|
||||
|
||||
/* Set by subclasses.
|
||||
*/
|
||||
VipsTarget *target;
|
||||
|
||||
/* Deprecated.
|
||||
*/
|
||||
int colours;
|
||||
} VipsForeignSavePng;
|
||||
|
||||
typedef VipsForeignSaveClass VipsForeignSavePngClass;
|
||||
@ -73,6 +83,55 @@ typedef VipsForeignSaveClass VipsForeignSavePngClass;
|
||||
G_DEFINE_ABSTRACT_TYPE( VipsForeignSavePng, vips_foreign_save_png,
|
||||
VIPS_TYPE_FOREIGN_SAVE );
|
||||
|
||||
static void
|
||||
vips_foreign_save_png_dispose( GObject *gobject )
|
||||
{
|
||||
VipsForeignSavePng *png = (VipsForeignSavePng *) gobject;
|
||||
|
||||
if( png->target )
|
||||
vips_target_finish( png->target );
|
||||
VIPS_UNREF( png->target );
|
||||
|
||||
G_OBJECT_CLASS( vips_foreign_save_png_parent_class )->
|
||||
dispose( gobject );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_foreign_save_png_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSave *save = (VipsForeignSave *) object;
|
||||
VipsForeignSavePng *png = (VipsForeignSavePng *) object;
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_png_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
/* 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 =
|
||||
save->ready->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( save->ready->Bands > 2 &&
|
||||
png->bitdepth < 8 )
|
||||
png->palette = TRUE;
|
||||
|
||||
if( vips__png_write_target( save->ready, png->target,
|
||||
png->compression, png->interlace, png->profile, png->filter,
|
||||
save->strip, png->palette, png->Q, png->dither,
|
||||
png->bitdepth ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Save a bit of typing.
|
||||
*/
|
||||
#define UC VIPS_FORMAT_UCHAR
|
||||
@ -99,11 +158,13 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class )
|
||||
VipsForeignClass *foreign_class = (VipsForeignClass *) class;
|
||||
VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class;
|
||||
|
||||
gobject_class->dispose = vips_foreign_save_png_dispose;
|
||||
gobject_class->set_property = vips_object_set_property;
|
||||
gobject_class->get_property = vips_object_get_property;
|
||||
|
||||
object_class->nickname = "pngsave_base";
|
||||
object_class->description = _( "save png" );
|
||||
object_class->build = vips_foreign_save_png_build;
|
||||
|
||||
foreign_class->suffs = vips__png_suffs;
|
||||
|
||||
@ -146,13 +207,6 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class )
|
||||
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", 15,
|
||||
_( "Quality" ),
|
||||
_( "Quantisation quality" ),
|
||||
@ -167,6 +221,20 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class )
|
||||
G_STRUCT_OFFSET( VipsForeignSavePng, dither ),
|
||||
0.0, 1.0, 1.0 );
|
||||
|
||||
VIPS_ARG_INT( class, "bitdepth", 17,
|
||||
_( "Bit depth" ),
|
||||
_( "Write as a 1, 2, 4 or 8 bit image" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSavePng, bitdepth ),
|
||||
0, 8, 0 );
|
||||
|
||||
VIPS_ARG_INT( class, "colours", 14,
|
||||
_( "Colours" ),
|
||||
_( "Max number of palette colours" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
|
||||
G_STRUCT_OFFSET( VipsForeignSavePng, colours ),
|
||||
2, 256, 256 );
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
@ -174,7 +242,6 @@ vips_foreign_save_png_init( VipsForeignSavePng *png )
|
||||
{
|
||||
png->compression = 6;
|
||||
png->filter = VIPS_FOREIGN_PNG_FILTER_ALL;
|
||||
png->colours = 256;
|
||||
png->Q = 100;
|
||||
png->dither = 1.0;
|
||||
}
|
||||
@ -193,19 +260,16 @@ G_DEFINE_TYPE( VipsForeignSavePngTarget, vips_foreign_save_png_target,
|
||||
static int
|
||||
vips_foreign_save_png_target_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSave *save = (VipsForeignSave *) object;
|
||||
VipsForeignSavePng *png = (VipsForeignSavePng *) object;
|
||||
VipsForeignSavePngTarget *target = (VipsForeignSavePngTarget *) object;
|
||||
|
||||
png->target = target->target;
|
||||
g_object_ref( png->target );
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_png_target_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
if( vips__png_write_target( save->ready, target->target,
|
||||
png->compression, png->interlace, png->profile, png->filter,
|
||||
save->strip, png->palette, png->colours, png->Q, png->dither ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
@ -250,27 +314,16 @@ G_DEFINE_TYPE( VipsForeignSavePngFile, vips_foreign_save_png_file,
|
||||
static int
|
||||
vips_foreign_save_png_file_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSave *save = (VipsForeignSave *) object;
|
||||
VipsForeignSavePng *png = (VipsForeignSavePng *) object;
|
||||
VipsForeignSavePngFile *png_file = (VipsForeignSavePngFile *) object;
|
||||
VipsForeignSavePngFile *file = (VipsForeignSavePngFile *) object;
|
||||
|
||||
VipsTarget *target;
|
||||
if( !(png->target = vips_target_new_to_file( file->filename )) )
|
||||
return( -1 );
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_png_file_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
if( !(target = vips_target_new_to_file( png_file->filename )) )
|
||||
return( -1 );
|
||||
if( vips__png_write_target( save->ready, target,
|
||||
png->compression, png->interlace,
|
||||
png->profile, png->filter, save->strip, png->palette,
|
||||
png->colours, png->Q, png->dither ) ) {
|
||||
VIPS_UNREF( target );
|
||||
return( -1 );
|
||||
}
|
||||
VIPS_UNREF( target );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
@ -314,34 +367,22 @@ G_DEFINE_TYPE( VipsForeignSavePngBuffer, vips_foreign_save_png_buffer,
|
||||
static int
|
||||
vips_foreign_save_png_buffer_build( VipsObject *object )
|
||||
{
|
||||
VipsForeignSave *save = (VipsForeignSave *) object;
|
||||
VipsForeignSavePng *png = (VipsForeignSavePng *) object;
|
||||
VipsForeignSavePngBuffer *buffer = (VipsForeignSavePngBuffer *) object;
|
||||
|
||||
VipsTarget *target;
|
||||
VipsBlob *blob;
|
||||
|
||||
if( !(png->target = vips_target_new_to_memory()) )
|
||||
return( -1 );
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_png_buffer_parent_class )->
|
||||
build( object ) )
|
||||
return( -1 );
|
||||
|
||||
if( !(target = vips_target_new_to_memory()) )
|
||||
return( -1 );
|
||||
|
||||
if( vips__png_write_target( save->ready, target,
|
||||
png->compression, png->interlace, png->profile, png->filter,
|
||||
save->strip, png->palette, png->colours, png->Q,
|
||||
png->dither ) ) {
|
||||
VIPS_UNREF( target );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
g_object_get( target, "blob", &blob, NULL );
|
||||
g_object_get( png->target, "blob", &blob, NULL );
|
||||
g_object_set( buffer, "buffer", blob, NULL );
|
||||
vips_area_unref( VIPS_AREA( blob ) );
|
||||
|
||||
VIPS_UNREF( target );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
@ -386,9 +427,9 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer )
|
||||
* * @profile: %gchararray, ICC profile to embed
|
||||
* * @filter: #VipsForeignPngFilter row filter flag(s)
|
||||
* * @palette: %gboolean, enable quantisation to 8bpp palette
|
||||
* * @colours: %gint, max number of palette colours for quantisation
|
||||
* * @Q: %gint, quality for 8bpp quantisation (does not exceed @colours)
|
||||
* * @Q: %gint, quality for 8bpp quantisation
|
||||
* * @dither: %gdouble, amount of dithering for 8bpp quantization
|
||||
* * @bitdepth: %int, set write bit depth to 1, 2, 4 or 8
|
||||
*
|
||||
* Write a VIPS image to a file as PNG.
|
||||
*
|
||||
@ -414,13 +455,15 @@ 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.
|
||||
*
|
||||
* 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.
|
||||
* Set @palette to %TRUE to enable palette mode for RGB or RGBA images. A
|
||||
* palette will be computed with enough space for @bitdepth (1, 2, 4 or 8)
|
||||
* bits. Use @Q to set the optimisation effort, and @dither to set the degree of
|
||||
* Floyd-Steinberg dithering.
|
||||
* This feature requires libvips to be compiled with libimagequant.
|
||||
*
|
||||
* You can also set @bitdepth for mono and mono + alpha images, and the image
|
||||
* will be quantized.
|
||||
*
|
||||
* XMP metadata is written to the XMP chunk. PNG comments are written to
|
||||
* separate text chunks.
|
||||
*
|
||||
@ -455,9 +498,9 @@ vips_pngsave( VipsImage *in, const char *filename, ... )
|
||||
* * @profile: %gchararray, ICC profile to embed
|
||||
* * @filter: #VipsForeignPngFilter row filter flag(s)
|
||||
* * @palette: %gboolean, enable quantisation to 8bpp palette
|
||||
* * @colours: %gint, max number of palette colours for quantisation
|
||||
* * @Q: %gint, quality for 8bpp quantisation (does not exceed @colours)
|
||||
* * @Q: %gint, quality for 8bpp quantisation
|
||||
* * @dither: %gdouble, amount of dithering for 8bpp quantization
|
||||
* * @bitdepth: %int, set write bit depth to 1, 2, 4 or 8
|
||||
*
|
||||
* As vips_pngsave(), but save to a memory buffer.
|
||||
*
|
||||
@ -510,9 +553,9 @@ vips_pngsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
|
||||
* * @profile: ICC profile to embed
|
||||
* * @filter: libpng row filter flag(s)
|
||||
* * @palette: enable quantisation to 8bpp palette
|
||||
* * @colours: max number of palette colours for quantisation
|
||||
* * @Q: quality for 8bpp quantisation (does not exceed @colours)
|
||||
* * @Q: quality for 8bpp quantisation
|
||||
* * @dither: amount of dithering for 8bpp quantization
|
||||
* * @bitdepth: %int, set write bit depth to 1, 2, 4 or 8
|
||||
*
|
||||
* As vips_pngsave(), but save to a target.
|
||||
*
|
||||
|
@ -355,7 +355,7 @@ static int
|
||||
png2vips_header( Read *read, VipsImage *out )
|
||||
{
|
||||
png_uint_32 width, height;
|
||||
int bit_depth, color_type;
|
||||
int bitdepth, color_type;
|
||||
int interlace_type;
|
||||
|
||||
png_uint_32 res_x, res_y;
|
||||
@ -385,7 +385,7 @@ png2vips_header( Read *read, VipsImage *out )
|
||||
return( -1 );
|
||||
|
||||
png_get_IHDR( read->pPng, read->pInfo,
|
||||
&width, &height, &bit_depth, &color_type,
|
||||
&width, &height, &bitdepth, &color_type,
|
||||
&interlace_type, NULL, NULL );
|
||||
|
||||
/* png_get_channels() gives us 1 band for palette images ... so look
|
||||
@ -413,7 +413,7 @@ png2vips_header( Read *read, VipsImage *out )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( bit_depth > 8 ) {
|
||||
if( bitdepth > 8 ) {
|
||||
if( bands < 3 )
|
||||
interpretation = VIPS_INTERPRETATION_GREY16;
|
||||
else
|
||||
@ -448,13 +448,13 @@ png2vips_header( Read *read, VipsImage *out )
|
||||
/* Expand <8 bit images to full bytes.
|
||||
*/
|
||||
if( color_type == PNG_COLOR_TYPE_GRAY &&
|
||||
bit_depth < 8 )
|
||||
bitdepth < 8 )
|
||||
png_set_expand_gray_1_2_4_to_8( read->pPng );
|
||||
|
||||
/* If we're an INTEL byte order machine and this is 16bits, we need
|
||||
* to swap bytes.
|
||||
*/
|
||||
if( bit_depth > 8 &&
|
||||
if( bitdepth > 8 &&
|
||||
!vips_amiMSBfirst() )
|
||||
png_set_swap( read->pPng );
|
||||
|
||||
@ -480,8 +480,7 @@ png2vips_header( Read *read, VipsImage *out )
|
||||
*/
|
||||
vips_image_init_fields( out,
|
||||
width, height, bands,
|
||||
bit_depth > 8 ?
|
||||
VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR,
|
||||
bitdepth > 8 ? VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR,
|
||||
VIPS_CODING_NONE, interpretation,
|
||||
Xres, Yres );
|
||||
|
||||
@ -556,7 +555,7 @@ png2vips_header( Read *read, VipsImage *out )
|
||||
/* Attach original palette bit depth, if any, as metadata.
|
||||
*/
|
||||
if( color_type == PNG_COLOR_TYPE_PALETTE )
|
||||
vips_image_set_int( out, "palette-bit-depth", bit_depth );
|
||||
vips_image_set_int( out, "palette-bit-depth", bitdepth );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
@ -924,11 +923,11 @@ static int
|
||||
write_vips( Write *write,
|
||||
int compress, int interlace, const char *profile,
|
||||
VipsForeignPngFilter filter, gboolean strip,
|
||||
gboolean palette, int colours, int Q, double dither )
|
||||
gboolean palette, int Q, double dither,
|
||||
int bitdepth )
|
||||
{
|
||||
VipsImage *in = write->in;
|
||||
|
||||
int bit_depth;
|
||||
int color_type;
|
||||
int interlace_type;
|
||||
int i, nb_passes;
|
||||
@ -970,8 +969,6 @@ write_vips( Write *write,
|
||||
*/
|
||||
png_set_filter( write->pPng, 0, filter );
|
||||
|
||||
bit_depth = in->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16;
|
||||
|
||||
switch( in->Bands ) {
|
||||
case 1: color_type = PNG_COLOR_TYPE_GRAY; break;
|
||||
case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
|
||||
@ -987,12 +984,8 @@ write_vips( Write *write,
|
||||
#ifdef HAVE_IMAGEQUANT
|
||||
/* Enable image quantisation to paletted 8bpp PNG if colours is set.
|
||||
*/
|
||||
if( palette ) {
|
||||
g_assert( colours >= 2 &&
|
||||
colours <= 256 );
|
||||
bit_depth = 8;
|
||||
if( palette )
|
||||
color_type = PNG_COLOR_TYPE_PALETTE;
|
||||
}
|
||||
#else
|
||||
if( palette )
|
||||
g_warning( "%s",
|
||||
@ -1002,7 +995,7 @@ write_vips( Write *write,
|
||||
interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
|
||||
|
||||
png_set_IHDR( write->pPng, write->pInfo,
|
||||
in->Xsize, in->Ysize, bit_depth, color_type, interlace_type,
|
||||
in->Xsize, in->Ysize, bitdepth, color_type, interlace_type,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT );
|
||||
|
||||
/* Set resolution. libpng uses pixels per meter.
|
||||
@ -1107,7 +1100,7 @@ write_vips( Write *write,
|
||||
int trans_count;
|
||||
|
||||
if( vips__quantise_image( in, &im_index, &im_palette,
|
||||
colours, Q, dither ) )
|
||||
1 << bitdepth, Q, dither ) )
|
||||
return( -1 );
|
||||
|
||||
palette_count = im_palette->Xsize;
|
||||
@ -1167,10 +1160,14 @@ write_vips( Write *write,
|
||||
/* If we're an intel byte order CPU and this is a 16bit image, we need
|
||||
* to swap bytes.
|
||||
*/
|
||||
if( bit_depth > 8 &&
|
||||
if( bitdepth > 8 &&
|
||||
!vips_amiMSBfirst() )
|
||||
png_set_swap( write->pPng );
|
||||
|
||||
/* If bitdepth is 1/2/4, pack pixels into bytes.
|
||||
*/
|
||||
png_set_packing( write->pPng );
|
||||
|
||||
if( interlace )
|
||||
nb_passes = png_set_interlace_handling( write->pPng );
|
||||
else
|
||||
@ -1196,7 +1193,8 @@ int
|
||||
vips__png_write_target( VipsImage *in, VipsTarget *target,
|
||||
int compression, int interlace,
|
||||
const char *profile, VipsForeignPngFilter filter, gboolean strip,
|
||||
gboolean palette, int colours, int Q, double dither )
|
||||
gboolean palette, int Q, double dither,
|
||||
int bitdepth )
|
||||
{
|
||||
Write *write;
|
||||
|
||||
@ -1205,7 +1203,7 @@ vips__png_write_target( VipsImage *in, VipsTarget *target,
|
||||
|
||||
if( write_vips( write,
|
||||
compression, interlace, profile, filter, strip, palette,
|
||||
colours, Q, dither ) ) {
|
||||
Q, dither, bitdepth ) ) {
|
||||
write_finish( write );
|
||||
vips_error( "vips2png",
|
||||
"%s", _( "unable to write to target" ) );
|
||||
|
@ -313,6 +313,22 @@ class TestForeign:
|
||||
self.save_load_file(".png", "[interlace]", self.colour, 0)
|
||||
self.save_load_file(".png", "[interlace]", self.mono, 0)
|
||||
|
||||
# size of a regular mono PNG
|
||||
len_mono = len(self.mono.write_to_buffer(".png"))
|
||||
|
||||
# 4-bit should be smaller
|
||||
len_mono4 = len(self.mono.write_to_buffer(".png", bitdepth=4))
|
||||
assert( len_mono4 < len_mono )
|
||||
|
||||
len_mono2 = len(self.mono.write_to_buffer(".png", bitdepth=2))
|
||||
assert( len_mono2 < len_mono4 )
|
||||
|
||||
len_mono1 = len(self.mono.write_to_buffer(".png", bitdepth=1))
|
||||
assert( len_mono1 < len_mono2 )
|
||||
|
||||
# we can't test palette save since we can't be sure libimagequant is
|
||||
# available
|
||||
|
||||
@skip_if_no("tiffload")
|
||||
def test_tiff(self):
|
||||
def tiff_valid(im):
|
||||
|
Loading…
Reference in New Issue
Block a user