From 0b70145d998a7bbda560610037526dff203dad12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B6bl?= Date: Mon, 15 Aug 2022 13:14:02 +0200 Subject: [PATCH] cgifsave: add support for interlaced GIF write (#2984) * cgifsave: add support for interlaced GIF write note: cgif >= v0.3.0 is required * switch to warning instead of error * add test * fix test --- libvips/foreign/cgifsave.c | 27 +++++++++++++++++++++++++++ meson.build | 3 +++ test/test-suite/test_foreign.py | 10 ++++++++++ 3 files changed, 40 insertions(+) diff --git a/libvips/foreign/cgifsave.c b/libvips/foreign/cgifsave.c index 4abb0b3a..202ee7d3 100644 --- a/libvips/foreign/cgifsave.c +++ b/libvips/foreign/cgifsave.c @@ -83,6 +83,7 @@ typedef struct _VipsForeignSaveCgif { int bitdepth; double interframe_maxerror; gboolean reoptimise; + gboolean interlace; double interpalette_maxerror; VipsTarget *target; @@ -601,6 +602,16 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif ) frame_config.numLocalPaletteEntries = n_colours; } + /* Write an interlaced GIF, if requested. + */ + if( cgif->interlace ) { +#ifdef HAVE_CGIF_FRAME_ATTR_INTERLACED + frame_config.attrFlags |= CGIF_FRAME_ATTR_INTERLACED; +#else /*!HAVE_CGIF_FRAME_ATTR_INTERLACED*/ + g_warning( "%s: cgif >= v0.3.0 required for interlaced GIF write", class->nickname ); +#endif /*HAVE_CGIF_FRAME_ATTR_INTERLACED*/ + } + /* Write frame to cgif. */ frame_config.pImageData = cgif->index; @@ -893,6 +904,14 @@ vips_foreign_save_cgif_class_init( VipsForeignSaveCgifClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveCgif, interpalette_maxerror ), 0, 256, 3.0 ); + + VIPS_ARG_BOOL( class, "interlace", 16, + _( "Interlaced" ), + _( "Generate an interlaced (progressive) GIF" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveCgif, interlace ), + FALSE ); + } static void @@ -903,6 +922,7 @@ vips_foreign_save_cgif_init( VipsForeignSaveCgif *gif ) gif->bitdepth = 8; gif->interframe_maxerror = 0.0; gif->reoptimise = FALSE; + gif->interlace = FALSE; gif->interpalette_maxerror = 3.0; gif->mode = VIPS_FOREIGN_SAVE_CGIF_MODE_GLOBAL; } @@ -1087,6 +1107,7 @@ vips_foreign_save_cgif_buffer_init( VipsForeignSaveCgifBuffer *buffer ) * * @bitdepth: %gint, number of bits per pixel * * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency * * @reoptimise: %gboolean, reoptimise colour palettes + * * @interlace: %gboolean, write an interlaced (progressive) GIF * * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette * reusage * @@ -1110,6 +1131,10 @@ vips_foreign_save_cgif_buffer_init( VipsForeignSaveCgifBuffer *buffer ) * @interpalette_maxerror to set the threshold below which one of the previously * generated palettes will be reused. * + * If @interlace is TRUE, the GIF file will be interlaced (progressive GIF). + * These files may be better for display over a slow network + * conection, but need more memory to encode. + * * See also: vips_image_new_from_file(). * * Returns: 0 on success, -1 on error. @@ -1141,6 +1166,7 @@ vips_gifsave( VipsImage *in, const char *filename, ... ) * * @bitdepth: %gint, number of bits per pixel * * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency * * @reoptimise: %gboolean, reoptimise colour palettes + * * @interlace: %gboolean, write an interlaced (progressive) GIF * * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette * reusage * @@ -1195,6 +1221,7 @@ vips_gifsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) * * @bitdepth: %gint, number of bits per pixel * * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency * * @reoptimise: %gboolean, reoptimise colour palettes + * * @interlace: %gboolean, write an interlaced (progressive) GIF * * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette * reusage * diff --git a/meson.build b/meson.build index 2cb70e6d..4daf6270 100644 --- a/meson.build +++ b/meson.build @@ -259,6 +259,9 @@ if quantisation_package.found() if cc.compiles('#include \nint i = CGIF_ATTR_NO_LOOP;', name: 'Has CGIF_ATTR_NO_LOOP', dependencies: cgif_dep) cfg_var.set('HAVE_CGIF_ATTR_NO_LOOP', '1') endif + if cc.compiles('#include \nint i = CGIF_FRAME_ATTR_INTERLACED;', name: 'Has CGIF_FRAME_ATTR_INTERLACED', dependencies: cgif_dep) + cfg_var.set('HAVE_CGIF_FRAME_ATTR_INTERLACED', '1') + endif endif endif diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index d438a754..4c83b49b 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -1478,6 +1478,16 @@ class TestForeign: # FIXME ... this requires cgif0.3 or later for fixed loop support # assert x1.get("loop") == x2.get("loop") + # Interlaced write + x1 = pyvips.Image.new_from_file(GIF_FILE, n=-1) + b1 = x1.gifsave_buffer(interlace=False) + b2 = x1.gifsave_buffer(interlace=True) + # Interlaced GIFs are usually larger in file size + # FIXME ... cgif v0.3 or later required for interlaced write. + # If interlaced write is not supported b2 and b1 are expected to be + # of the same file size. + assert len(b2) >= len(b1) + # Reducing dither will typically reduce file size (and quality) little_dither = self.colour.gifsave_buffer(dither=0.1, effort=1) large_dither = self.colour.gifsave_buffer(dither=0.9, effort=1)