add 1/2/4 bit PNG save

- new @bitdepth param
- deprecate @colours param
This commit is contained in:
John Cupitt 2020-06-25 10:40:43 +01:00
parent 31184ddb57
commit 4bf27acccb
5 changed files with 139 additions and 80 deletions

View File

@ -31,8 +31,9 @@
- deprecate heifload autorotate -- it's now always on - deprecate heifload autorotate -- it's now always on
- revised resize improves accuracy [kleisauke] - revised resize improves accuracy [kleisauke]
- add --vips-config flag to show configuration info - 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] - 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 - ppmsave also uses "bitdepth" now, for consistency
24/4/20 started 8.9.3 24/4/20 started 8.9.3

View File

@ -184,7 +184,8 @@ extern const char *vips__png_suffs[];
int vips__png_write_target( VipsImage *in, VipsTarget *target, int vips__png_write_target( VipsImage *in, VipsTarget *target,
int compress, int interlace, const char *profile, int compress, int interlace, const char *profile,
VipsForeignPngFilter filter, gboolean strip, 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. /* Map WEBP metadata names to vips names.
*/ */

View File

@ -6,6 +6,8 @@
* - compression should be 0-9, not 1-10 * - compression should be 0-9, not 1-10
* 20/6/18 [felixbuenemann] * 20/6/18 [felixbuenemann]
* - support png8 palette write with palette, colours, Q, dither * - 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; char *profile;
VipsForeignPngFilter filter; VipsForeignPngFilter filter;
gboolean palette; gboolean palette;
int colours;
int Q; int Q;
double dither; double dither;
int bitdepth;
/* Set by subclasses.
*/
VipsTarget *target;
/* Deprecated.
*/
int colours;
} VipsForeignSavePng; } VipsForeignSavePng;
typedef VipsForeignSaveClass VipsForeignSavePngClass; typedef VipsForeignSaveClass VipsForeignSavePngClass;
@ -73,6 +83,55 @@ typedef VipsForeignSaveClass VipsForeignSavePngClass;
G_DEFINE_ABSTRACT_TYPE( VipsForeignSavePng, vips_foreign_save_png, G_DEFINE_ABSTRACT_TYPE( VipsForeignSavePng, vips_foreign_save_png,
VIPS_TYPE_FOREIGN_SAVE ); 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. /* Save a bit of typing.
*/ */
#define UC VIPS_FORMAT_UCHAR #define UC VIPS_FORMAT_UCHAR
@ -99,11 +158,13 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class )
VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignClass *foreign_class = (VipsForeignClass *) class;
VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class;
gobject_class->dispose = vips_foreign_save_png_dispose;
gobject_class->set_property = vips_object_set_property; gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property; gobject_class->get_property = vips_object_get_property;
object_class->nickname = "pngsave_base"; object_class->nickname = "pngsave_base";
object_class->description = _( "save png" ); object_class->description = _( "save png" );
object_class->build = vips_foreign_save_png_build;
foreign_class->suffs = vips__png_suffs; foreign_class->suffs = vips__png_suffs;
@ -146,13 +207,6 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class )
G_STRUCT_OFFSET( VipsForeignSavePng, palette ), G_STRUCT_OFFSET( VipsForeignSavePng, palette ),
FALSE ); 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, VIPS_ARG_INT( class, "Q", 15,
_( "Quality" ), _( "Quality" ),
_( "Quantisation quality" ), _( "Quantisation quality" ),
@ -167,6 +221,20 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class )
G_STRUCT_OFFSET( VipsForeignSavePng, dither ), G_STRUCT_OFFSET( VipsForeignSavePng, dither ),
0.0, 1.0, 1.0 ); 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 static void
@ -174,7 +242,6 @@ vips_foreign_save_png_init( VipsForeignSavePng *png )
{ {
png->compression = 6; png->compression = 6;
png->filter = VIPS_FOREIGN_PNG_FILTER_ALL; png->filter = VIPS_FOREIGN_PNG_FILTER_ALL;
png->colours = 256;
png->Q = 100; png->Q = 100;
png->dither = 1.0; png->dither = 1.0;
} }
@ -193,19 +260,16 @@ G_DEFINE_TYPE( VipsForeignSavePngTarget, vips_foreign_save_png_target,
static int static int
vips_foreign_save_png_target_build( VipsObject *object ) vips_foreign_save_png_target_build( VipsObject *object )
{ {
VipsForeignSave *save = (VipsForeignSave *) object;
VipsForeignSavePng *png = (VipsForeignSavePng *) object; VipsForeignSavePng *png = (VipsForeignSavePng *) object;
VipsForeignSavePngTarget *target = (VipsForeignSavePngTarget *) 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 )-> if( VIPS_OBJECT_CLASS( vips_foreign_save_png_target_parent_class )->
build( object ) ) build( object ) )
return( -1 ); 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 ); return( 0 );
} }
@ -250,27 +314,16 @@ G_DEFINE_TYPE( VipsForeignSavePngFile, vips_foreign_save_png_file,
static int static int
vips_foreign_save_png_file_build( VipsObject *object ) vips_foreign_save_png_file_build( VipsObject *object )
{ {
VipsForeignSave *save = (VipsForeignSave *) object;
VipsForeignSavePng *png = (VipsForeignSavePng *) 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 )-> if( VIPS_OBJECT_CLASS( vips_foreign_save_png_file_parent_class )->
build( object ) ) build( object ) )
return( -1 ); 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 ); return( 0 );
} }
@ -314,34 +367,22 @@ G_DEFINE_TYPE( VipsForeignSavePngBuffer, vips_foreign_save_png_buffer,
static int static int
vips_foreign_save_png_buffer_build( VipsObject *object ) vips_foreign_save_png_buffer_build( VipsObject *object )
{ {
VipsForeignSave *save = (VipsForeignSave *) object;
VipsForeignSavePng *png = (VipsForeignSavePng *) object; VipsForeignSavePng *png = (VipsForeignSavePng *) object;
VipsForeignSavePngBuffer *buffer = (VipsForeignSavePngBuffer *) object; VipsForeignSavePngBuffer *buffer = (VipsForeignSavePngBuffer *) object;
VipsTarget *target;
VipsBlob *blob; VipsBlob *blob;
if( !(png->target = vips_target_new_to_memory()) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_save_png_buffer_parent_class )-> if( VIPS_OBJECT_CLASS( vips_foreign_save_png_buffer_parent_class )->
build( object ) ) build( object ) )
return( -1 ); return( -1 );
if( !(target = vips_target_new_to_memory()) ) g_object_get( png->target, "blob", &blob, NULL );
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_set( buffer, "buffer", blob, NULL ); g_object_set( buffer, "buffer", blob, NULL );
vips_area_unref( VIPS_AREA( blob ) ); vips_area_unref( VIPS_AREA( blob ) );
VIPS_UNREF( target );
return( 0 ); return( 0 );
} }
@ -386,9 +427,9 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer )
* * @profile: %gchararray, ICC profile to embed * * @profile: %gchararray, ICC profile to embed
* * @filter: #VipsForeignPngFilter row filter flag(s) * * @filter: #VipsForeignPngFilter row filter flag(s)
* * @palette: %gboolean, enable quantisation to 8bpp palette * * @palette: %gboolean, enable quantisation to 8bpp palette
* * @colours: %gint, max number of palette colours for quantisation * * @Q: %gint, quality for 8bpp quantisation
* * @Q: %gint, quality for 8bpp quantisation (does not exceed @colours)
* * @dither: %gdouble, amount of dithering for 8bpp quantization * * @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. * 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 * 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. * 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 * Set @palette to %TRUE to enable palette mode for RGB or RGBA images. A
* image with alpha transparency support. If @colours is given, it limits the * palette will be computed with enough space for @bitdepth (1, 2, 4 or 8)
* maximum number of palette entries. Similar to JPEG the quality can also be * bits. Use @Q to set the optimisation effort, and @dither to set the degree of
* be changed with the @Q parameter which further reduces the palette size and * Floyd-Steinberg dithering.
* @dither controls the amount of Floyd-Steinberg dithering.
* This feature requires libvips to be compiled with libimagequant. * 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 * XMP metadata is written to the XMP chunk. PNG comments are written to
* separate text chunks. * separate text chunks.
* *
@ -455,9 +498,9 @@ vips_pngsave( VipsImage *in, const char *filename, ... )
* * @profile: %gchararray, ICC profile to embed * * @profile: %gchararray, ICC profile to embed
* * @filter: #VipsForeignPngFilter row filter flag(s) * * @filter: #VipsForeignPngFilter row filter flag(s)
* * @palette: %gboolean, enable quantisation to 8bpp palette * * @palette: %gboolean, enable quantisation to 8bpp palette
* * @colours: %gint, max number of palette colours for quantisation * * @Q: %gint, quality for 8bpp quantisation
* * @Q: %gint, quality for 8bpp quantisation (does not exceed @colours)
* * @dither: %gdouble, amount of dithering for 8bpp quantization * * @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. * 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 * * @profile: ICC profile to embed
* * @filter: libpng row filter flag(s) * * @filter: libpng row filter flag(s)
* * @palette: enable quantisation to 8bpp palette * * @palette: enable quantisation to 8bpp palette
* * @colours: max number of palette colours for quantisation * * @Q: quality for 8bpp quantisation
* * @Q: quality for 8bpp quantisation (does not exceed @colours)
* * @dither: amount of dithering for 8bpp quantization * * @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. * As vips_pngsave(), but save to a target.
* *

View File

@ -355,7 +355,7 @@ static int
png2vips_header( Read *read, VipsImage *out ) png2vips_header( Read *read, VipsImage *out )
{ {
png_uint_32 width, height; png_uint_32 width, height;
int bit_depth, color_type; int bitdepth, color_type;
int interlace_type; int interlace_type;
png_uint_32 res_x, res_y; png_uint_32 res_x, res_y;
@ -385,7 +385,7 @@ png2vips_header( Read *read, VipsImage *out )
return( -1 ); return( -1 );
png_get_IHDR( read->pPng, read->pInfo, png_get_IHDR( read->pPng, read->pInfo,
&width, &height, &bit_depth, &color_type, &width, &height, &bitdepth, &color_type,
&interlace_type, NULL, NULL ); &interlace_type, NULL, NULL );
/* png_get_channels() gives us 1 band for palette images ... so look /* png_get_channels() gives us 1 band for palette images ... so look
@ -413,7 +413,7 @@ png2vips_header( Read *read, VipsImage *out )
return( -1 ); return( -1 );
} }
if( bit_depth > 8 ) { if( bitdepth > 8 ) {
if( bands < 3 ) if( bands < 3 )
interpretation = VIPS_INTERPRETATION_GREY16; interpretation = VIPS_INTERPRETATION_GREY16;
else else
@ -448,13 +448,13 @@ png2vips_header( Read *read, VipsImage *out )
/* Expand <8 bit images to full bytes. /* Expand <8 bit images to full bytes.
*/ */
if( color_type == PNG_COLOR_TYPE_GRAY && if( color_type == PNG_COLOR_TYPE_GRAY &&
bit_depth < 8 ) bitdepth < 8 )
png_set_expand_gray_1_2_4_to_8( read->pPng ); 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 /* If we're an INTEL byte order machine and this is 16bits, we need
* to swap bytes. * to swap bytes.
*/ */
if( bit_depth > 8 && if( bitdepth > 8 &&
!vips_amiMSBfirst() ) !vips_amiMSBfirst() )
png_set_swap( read->pPng ); png_set_swap( read->pPng );
@ -480,8 +480,7 @@ png2vips_header( Read *read, VipsImage *out )
*/ */
vips_image_init_fields( out, vips_image_init_fields( out,
width, height, bands, width, height, bands,
bit_depth > 8 ? bitdepth > 8 ? VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR,
VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, interpretation, VIPS_CODING_NONE, interpretation,
Xres, Yres ); Xres, Yres );
@ -556,7 +555,7 @@ png2vips_header( Read *read, VipsImage *out )
/* Attach original palette bit depth, if any, as metadata. /* Attach original palette bit depth, if any, as metadata.
*/ */
if( color_type == PNG_COLOR_TYPE_PALETTE ) 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 ); return( 0 );
} }
@ -924,11 +923,11 @@ static int
write_vips( Write *write, write_vips( Write *write,
int compress, int interlace, const char *profile, int compress, int interlace, const char *profile,
VipsForeignPngFilter filter, gboolean strip, VipsForeignPngFilter filter, gboolean strip,
gboolean palette, int colours, int Q, double dither ) gboolean palette, int Q, double dither,
int bitdepth )
{ {
VipsImage *in = write->in; VipsImage *in = write->in;
int bit_depth;
int color_type; int color_type;
int interlace_type; int interlace_type;
int i, nb_passes; int i, nb_passes;
@ -970,8 +969,6 @@ write_vips( Write *write,
*/ */
png_set_filter( write->pPng, 0, filter ); png_set_filter( write->pPng, 0, filter );
bit_depth = in->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16;
switch( in->Bands ) { switch( in->Bands ) {
case 1: color_type = PNG_COLOR_TYPE_GRAY; break; case 1: color_type = PNG_COLOR_TYPE_GRAY; break;
case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
@ -987,12 +984,8 @@ write_vips( Write *write,
#ifdef HAVE_IMAGEQUANT #ifdef HAVE_IMAGEQUANT
/* Enable image quantisation to paletted 8bpp PNG if colours is set. /* Enable image quantisation to paletted 8bpp PNG if colours is set.
*/ */
if( palette ) { if( palette )
g_assert( colours >= 2 &&
colours <= 256 );
bit_depth = 8;
color_type = PNG_COLOR_TYPE_PALETTE; color_type = PNG_COLOR_TYPE_PALETTE;
}
#else #else
if( palette ) if( palette )
g_warning( "%s", g_warning( "%s",
@ -1002,7 +995,7 @@ write_vips( Write *write,
interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
png_set_IHDR( write->pPng, write->pInfo, 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 ); PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT );
/* Set resolution. libpng uses pixels per meter. /* Set resolution. libpng uses pixels per meter.
@ -1107,7 +1100,7 @@ write_vips( Write *write,
int trans_count; int trans_count;
if( vips__quantise_image( in, &im_index, &im_palette, if( vips__quantise_image( in, &im_index, &im_palette,
colours, Q, dither ) ) 1 << bitdepth, Q, dither ) )
return( -1 ); return( -1 );
palette_count = im_palette->Xsize; 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 /* If we're an intel byte order CPU and this is a 16bit image, we need
* to swap bytes. * to swap bytes.
*/ */
if( bit_depth > 8 && if( bitdepth > 8 &&
!vips_amiMSBfirst() ) !vips_amiMSBfirst() )
png_set_swap( write->pPng ); png_set_swap( write->pPng );
/* If bitdepth is 1/2/4, pack pixels into bytes.
*/
png_set_packing( write->pPng );
if( interlace ) if( interlace )
nb_passes = png_set_interlace_handling( write->pPng ); nb_passes = png_set_interlace_handling( write->pPng );
else else
@ -1196,7 +1193,8 @@ int
vips__png_write_target( VipsImage *in, VipsTarget *target, vips__png_write_target( VipsImage *in, VipsTarget *target,
int compression, int interlace, int compression, int interlace,
const char *profile, VipsForeignPngFilter filter, gboolean strip, 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; Write *write;
@ -1205,7 +1203,7 @@ vips__png_write_target( VipsImage *in, VipsTarget *target,
if( write_vips( write, if( write_vips( write,
compression, interlace, profile, filter, strip, palette, compression, interlace, profile, filter, strip, palette,
colours, Q, dither ) ) { Q, dither, bitdepth ) ) {
write_finish( write ); write_finish( write );
vips_error( "vips2png", vips_error( "vips2png",
"%s", _( "unable to write to target" ) ); "%s", _( "unable to write to target" ) );

View File

@ -313,6 +313,22 @@ class TestForeign:
self.save_load_file(".png", "[interlace]", self.colour, 0) self.save_load_file(".png", "[interlace]", self.colour, 0)
self.save_load_file(".png", "[interlace]", self.mono, 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") @skip_if_no("tiffload")
def test_tiff(self): def test_tiff(self):
def tiff_valid(im): def tiff_valid(im):