From 75da1472e889ec9064e6a60c788da9fc638204db Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 29 Oct 2018 18:32:20 +0000 Subject: [PATCH 1/3] added anim write compiles, but untested --- configure.ac | 42 +-- libvips/foreign/vips2webp.c | 566 +++++++++++++++++++++--------------- 2 files changed, 344 insertions(+), 264 deletions(-) diff --git a/configure.ac b/configure.ac index 842622f0..b5c77226 100644 --- a/configure.ac +++ b/configure.ac @@ -1029,42 +1029,20 @@ if test x"$with_cfitsio" != x"no"; then ) fi -# libwebp -# some platforms, like ubuntu 12.04, are missing the .pc files for libwebp, so -# we fall back to FIND_LIBWEBP +# libwebp ... target 0.5+ to reduce complication +# webp has the stuff for handling metadata in two separate libraries -- we +# insit on having all of them AC_ARG_WITH([libwebp], AS_HELP_STRING([--without-libwebp], [build without libwebp (default: test)])) if test x"$with_libwebp" != x"no"; then - PKG_CHECK_MODULES(LIBWEBP, libwebp >= 0.1.3, - [AC_DEFINE(HAVE_LIBWEBP,1,[define if you have libwebp installed.]) + PKG_CHECK_MODULES(LIBWEBP, libwebp >= 0.5 libwebpmux >= 0.5 libwebpdemux >= 0.5, + [AC_DEFINE(HAVE_LIBWEBP,1,[define if you have libwebp/libwebpmux/libwebpdemux installed.]) with_libwebp=yes - PACKAGES_USED="$PACKAGES_USED libwebp" + PACKAGES_USED="$PACKAGES_USED libwebp libwebpmux libwebpdemux" ], - [FIND_LIBWEBP( - [with_libwebp="yes (found by search)" - ], - [AC_MSG_WARN([libwebp not found; disabling WEBP support]) - with_libwebp=no - ] - ) - ] - ) -fi - -# webp has the stuff for pulling out ICC profile etc in a separate library -# -# we can build with libwebpmux back to 0.3, but it's not until libwebp 0.5 that -# we can read that metadata back successfully ... insist on 0.5 so that tests -# can work smoothly -if test x"$with_libwebp" != x"no"; then - PKG_CHECK_MODULES(LIBWEBPMUX, libwebpmux >= 0.5.0, - [AC_DEFINE(HAVE_LIBWEBPMUX,1,[define if you have libwebpmux installed.]) - with_libwebpmux=yes - PACKAGES_USED="$PACKAGES_USED libwebpmux" - ], - [AC_MSG_WARN([libwebpmux not found; disabling webp metadata support]) - with_libwebpmux=no + [AC_MSG_WARN([libwebp, mux, demux not found; disabling WEBP support]) + with_libwebp=no ] ) fi @@ -1419,9 +1397,7 @@ SVG import with librsvg-2.0: $with_rsvg zlib: $with_zlib file import with cfitsio: $with_cfitsio file import/export with libwebp: $with_libwebp - (requires libwebp-0.1.3 or later) -support webp metadata: $with_libwebpmux - (requires libwebpmux-0.5 or later) + (requires libwebp, libwebpmux, libwebpdemux 0.5.0 or later) text rendering with pangoft2: $with_pangoft2 file import/export with libpng: $with_png (requires libpng-1.2.9 or later) diff --git a/libvips/foreign/vips2webp.c b/libvips/foreign/vips2webp.c index 106d17f4..9f3ef07c 100644 --- a/libvips/foreign/vips2webp.c +++ b/libvips/foreign/vips2webp.c @@ -8,6 +8,8 @@ * - used advanced encoding API, expose controls * 8/11/16 * - add metadata write + * 29/10/18 + * - target libwebp 0.5+ and remove some ifdefs */ /* @@ -58,10 +60,35 @@ #include "pforeign.h" #include +#include +#include typedef int (*webp_import)( WebPPicture *picture, const uint8_t *rgb, int stride ); +typedef struct { + VipsImage *image; + + int Q; + gboolean lossless; + VipsForeignWebpPreset preset; + gboolean smart_subsample; + gboolean near_lossless; + int alpha_q; + gboolean strip; + + WebPConfig config; + + /* Output is written here. We can only support memory write, since we + * handle metadata. + */ + WebPMemoryWriter memory_writer; + + /* Write animated webp here. + */ + WebPAnimEncoder *enc; +} VipsWebPWrite; + static WebPPreset get_preset( VipsForeignWebpPreset preset ) { @@ -88,61 +115,78 @@ get_preset( VipsForeignWebpPreset preset ) return( -1 ); } -typedef struct { - uint8_t *mem; - - /* We want to be able to detect >4gb even on machines that have size_t - * as uint32. - */ - guint64 size; - guint64 max_size; -} VipsWebPWriter; - static void -vips_webp_writer_init( VipsWebPWriter *writer ) +vips_webp_write_unset( VipsWebPWrite *write ) { - writer->mem = NULL; - writer->size = 0; - writer->max_size = 0; + WebPMemoryWriterClear( &write->memory_writer ); + VIPS_FREEF( WebPAnimEncoderDelete, write->enc ); +} + +static int +vips_webp_write_init( VipsWebPWrite *write, VipsImage *image, + int Q, gboolean lossless, VipsForeignWebpPreset preset, + gboolean smart_subsample, gboolean near_lossless, + int alpha_q, gboolean strip ) +{ + write->image = image; + write->Q = Q; + write->lossless = lossless; + write->preset = preset; + write->smart_subsample = smart_subsample; + write->near_lossless = near_lossless; + write->alpha_q = alpha_q; + write->strip = strip; + WebPMemoryWriterInit( &write->memory_writer ); + write->enc = NULL; + + if( !WebPConfigInit( &write->config ) ) { + vips_webp_write_unset( write ); + vips_error( "vips2webp", + "%s", _( "config version error" ) ); + return( -1 ); + } + + /* These presets are only for lossy compression. There seems to be + * separate API for lossless or near-lossless, see + * WebPConfigLosslessPreset(). + */ + if( !(lossless || near_lossless) && + !WebPConfigPreset( &write->config, get_preset( preset ), Q ) ) { + vips_webp_write_unset( write ); + vips_error( "vips2webp", "%s", _( "config version error" ) ); + return( -1 ); + } + + write->config.lossless = lossless || near_lossless; + write->config.alpha_quality = alpha_q; + + if( near_lossless ) + write->config.near_lossless = Q; + if( smart_subsample ) + write->config.preprocessing |= 4; + + if( !WebPValidateConfig( &write->config ) ) { + vips_webp_write_unset( write ); + vips_error( "vips2webp", "%s", _( "invalid configuration" ) ); + return( -1 ); + } + + return( 0 ); } static gboolean -vips_webp_writer_append( VipsWebPWriter *writer, +vips_webp_write_append( VipsWebPWrite *write, const uint8_t *data, guint64 data_size ) { - guint64 next_size; + WebPPicture pic; - next_size = writer->size + data_size; - - if( next_size > writer->max_size ) { - uint8_t *new_mem; - const guint64 next_max_size = - VIPS_MAX( 8192, VIPS_MAX( next_size, - writer->max_size * 2 ) ); - - /* We should let it creep up to 4gb rather than just - * blocking when max goes over, but no one will make a >2gb - * webp image. - */ - if( next_max_size > UINT_MAX ) { - vips_error( "webp", - "%s", _( "output webp image too large" ) ); - return( 0 ); - } - - if( !(new_mem = (uint8_t *) - g_try_realloc( writer->mem, next_max_size )) ) { - vips_error( "webp", "%s", _( "out of memory" ) ); - return( 0 ); - } - - writer->mem = new_mem; - writer->max_size = next_max_size; - } - - if( data_size > 0 ) { - memcpy( writer->mem + writer->size, data, data_size ); - writer->size += data_size; + /* Yuk! Sadly libwebp does not have a proper interface to this type. + */ + pic.custom_ptr = (void *) &write->memory_writer; + if( WebPMemoryWrite( data, data_size, &pic ) ) { + vips_error( "webp", + "%s", _( "output webp image too large" ) ); + return( 0 ); } return( 1 ); @@ -154,9 +198,8 @@ vips_webp_writer_append( VipsWebPWriter *writer, * Only attach metadata if we have something to read it back, otherwise * lots of our tests start failing. */ -#ifdef HAVE_LIBWEBPMUX static gboolean -vips_webp_writer_appendle( VipsWebPWriter *writer, uint32_t val, int n ) +vips_webp_write_appendle( VipsWebPWrite *write, uint32_t val, int n ) { unsigned char buf[4]; int i; @@ -168,131 +211,86 @@ vips_webp_writer_appendle( VipsWebPWriter *writer, uint32_t val, int n ) val >>= 8; } - return( vips_webp_writer_append( writer, buf, n ) ); + return( vips_webp_write_append( write, buf, n ) ); } static gboolean -vips_webp_writer_appendle32( VipsWebPWriter *writer, uint32_t val ) +vips_webp_write_appendle32( VipsWebPWrite *write, uint32_t val ) { - return( vips_webp_writer_appendle( writer, val, 4 ) ); + return( vips_webp_write_appendle( write, val, 4 ) ); } static gboolean -vips_webp_writer_appendle24( VipsWebPWriter *writer, uint32_t val ) +vips_webp_write_appendle24( VipsWebPWrite *write, uint32_t val ) { - return( vips_webp_writer_appendle( writer, val, 3 ) ); + return( vips_webp_write_appendle( write, val, 3 ) ); } static gboolean -vips_webp_writer_appendcc( VipsWebPWriter *writer, const char buf[4] ) +vips_webp_write_appendcc( VipsWebPWrite *write, const char buf[4] ) { - return( vips_webp_writer_append( writer, (const uint8_t *) buf, 4 ) ); + return( vips_webp_write_append( write, (const uint8_t *) buf, 4 ) ); } static gboolean -vips_webp_writer_appendc( VipsWebPWriter *writer, +vips_webp_write_appendc( VipsWebPWrite *write, const char fourcc[4], const uint8_t *data, guint64 data_size ) { const int zero = 0; gboolean need_padding = (data_size & 1) != 0; - if( !vips_webp_writer_appendcc( writer, fourcc ) || - !vips_webp_writer_appendle32( writer, data_size ) || - !vips_webp_writer_append( writer, data, data_size ) ) + if( !vips_webp_write_appendcc( write, fourcc ) || + !vips_webp_write_appendle32( write, data_size ) || + !vips_webp_write_append( write, data, data_size ) ) return( 0 ); if( need_padding && - !vips_webp_writer_append( writer, (const uint8_t *) &zero, 1 ) ) + !vips_webp_write_append( write, (const uint8_t *) &zero, 1 ) ) return( 0 ); return( 1 ); } -#endif /*HAVE_LIBWEBPMUX*/ - -static void -vips_webp_writer_unset( VipsWebPWriter *writer ) -{ - VIPS_FREE( writer->mem ); -} static gboolean -memory_write( const uint8_t *data, size_t data_size, - const WebPPicture *picture ) +vips_webp_pic_init( VipsWebPWrite *write, WebPPicture *pic ) { - VipsWebPWriter * const writer = - (VipsWebPWriter *) picture->custom_ptr; + if( !WebPPictureInit( pic ) ) { + vips_error( "vips2webp", "%s", _( "picture version error" ) ); + return( FALSE ); + } + pic->writer = WebPMemoryWrite; + pic->custom_ptr = (void *) &write->memory_writer; - if( !writer ) - return( 0 ); + /* Smart subsampling needs use_argb because it is applied during + * RGB to YUV conversion. + */ + pic->use_argb = write->lossless || + write->near_lossless || + write->smart_subsample; - return( vips_webp_writer_append( writer, data, data_size ) ); + return( TRUE ); } +/* Write a VipsImage into an unintialised pic. + */ static int -write_webp( WebPPicture *pic, VipsImage *in, - int Q, gboolean lossless, VipsForeignWebpPreset preset, - gboolean smart_subsample, gboolean near_lossless, - int alpha_q ) +write_webp_image( VipsWebPWrite *write, VipsImage *image, WebPPicture *pic ) { VipsImage *memory; - WebPConfig config; webp_import import; - if( !WebPConfigInit( &config ) ) { - vips_error( "vips2webp", - "%s", _( "config version error" ) ); + if( !vips_webp_pic_init( write, pic ) ) + return( -1 ); + + if( !(memory = vips_image_copy_memory( image )) ) { + WebPPictureFree( pic ); return( -1 ); } - /* These presets are only for lossy compression. There seems to be - * separate API for lossless or near-lossless, see - * WebPConfigLosslessPreset(). - */ - if( !(lossless || near_lossless) && - !WebPConfigPreset( &config, get_preset( preset ), Q ) ) { - vips_error( "vips2webp", "%s", _( "config version error" ) ); - return( -1 ); - } - -#if WEBP_ENCODER_ABI_VERSION >= 0x0100 - config.lossless = lossless || near_lossless; - config.alpha_quality = alpha_q; - /* Smart subsampling needs use_argb because - * it is applied during RGB to YUV conversion. - */ - pic->use_argb = lossless || near_lossless || smart_subsample; -#else - if( lossless || - near_lossless ) - g_warning( "%s", _( "lossless unsupported" ) ); - if( alpha_q != 100 ) - g_warning( "%s", _( "alpha_q unsupported" ) ); -#endif - -#if WEBP_ENCODER_ABI_VERSION >= 0x0209 - if( near_lossless ) - config.near_lossless = Q; - if( smart_subsample ) - config.preprocessing |= 4; -#else - if( near_lossless ) - g_warning( "%s", _( "near_lossless unsupported" ) ); - if( smart_subsample ) - g_warning( "%s", _( "smart_subsample unsupported" ) ); -#endif - - if( !WebPValidateConfig( &config ) ) { - vips_error( "vips2webp", "%s", _( "invalid configuration" ) ); - return( -1 ); - } - - if( !(memory = vips_image_copy_memory( in )) ) - return( -1 ); - pic->width = memory->Xsize; pic->height = memory->Ysize; - if( in->Bands == 4 ) + if( memory->Bands == 4 ) import = WebPPictureImportRGBA; else import = WebPPictureImportRGB; @@ -300,24 +298,138 @@ write_webp( WebPPicture *pic, VipsImage *in, if( !import( pic, VIPS_IMAGE_ADDR( memory, 0, 0 ), VIPS_IMAGE_SIZEOF_LINE( memory ) ) ) { VIPS_UNREF( memory ); + WebPPictureFree( pic ); vips_error( "vips2webp", "%s", _( "picture memory error" ) ); return( -1 ); } - if( !WebPEncode( &config, pic ) ) { - VIPS_UNREF( memory ); - vips_error( "vips2webp", "%s", _( "unable to encode" ) ); - return( -1 ); - } - VIPS_UNREF( memory ); return( 0 ); } -#ifdef HAVE_LIBWEBPMUX +/* Write a single image into write->memory_writer. + */ static int -vips_webp_add_chunk( VipsWebPWriter *writer, VipsImage *image, +write_webp_single( VipsWebPWrite *write, VipsImage *image ) +{ + WebPPicture pic; + + if( write_webp_image( write, image, &pic ) ) { + WebPPictureFree( &pic ); + return( -1 ); + } + + if( !WebPEncode( &write->config, &pic ) ) { + WebPPictureFree( &pic ); + vips_error( "vips2webp", "%s", _( "unable to encode" ) ); + return( -1 ); + } + + WebPPictureFree( &pic ); + + return( 0 ); +} + +/* Write a set of animated frames into write->memory_writer. + */ +static int +write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) +{ + int delay; + WebPAnimEncoderOptions anim_config; + WebPData webp_data; + int top; + int timestamp_ms; + + /* FIXME get delay + */ + delay = 16; + + if( !WebPAnimEncoderOptionsInit( &anim_config ) ) { + vips_error( "vips2webp", + "%s", _( "config version error" ) ); + return( -1 ); + } + + write->enc = WebPAnimEncoderNew( image->Xsize, page_height, + &anim_config ); + if( !write->enc ) { + vips_error( "vips2webp", + "%s", _( "unable to init animation" ) ); + return( -1 ); + } + + timestamp_ms = 0; + for( top = 0; top < image->Ysize; top += page_height ) { + VipsImage *x; + WebPPicture pic; + + if( vips_crop( image, &x, + 0, top, image->Xsize, page_height, NULL ) ) + return( -1 ); + + if( write_webp_image( write, x, &pic ) ) { + VIPS_UNREF( x ); + return( -1 ); + } + + VIPS_UNREF( x ); + + if( !WebPAnimEncoderAdd( write->enc, + &pic, timestamp_ms, &write->config ) ) { + WebPPictureFree( &pic ); + vips_error( "vips2webp", + "%s", _( "anim add error" ) ); + return( -1 ); + } + + WebPPictureFree( &pic ); + + timestamp_ms += delay; + } + + /* Add a last fake frame to signal the last duration. + */ + if( !WebPAnimEncoderAdd( write->enc, NULL, timestamp_ms, NULL ) || + WebPAnimEncoderAssemble( write->enc, &webp_data ) ) { + vips_error( "vips2webp", + "%s", _( "anim build error" ) ); + return( -1 ); + } + + /* Terrible. This will only work if the output buffer is currently + * empty. + */ + if( write->memory_writer.mem != NULL ) { + vips_error( "vips2webp", "%s", _( "internal error" ) ); + return( -1 ); + } + write->memory_writer.mem = (uint8_t *) webp_data.bytes; + write->memory_writer.size = webp_data.size; + + return( 0 ); +} + +static int +write_webp( VipsWebPWrite *write, VipsImage *image ) +{ + int page_height; + + page_height = 0; + if( vips_image_get_typeof( image, VIPS_META_PAGE_HEIGHT ) && + vips_image_get_int( image, VIPS_META_PAGE_HEIGHT, + &page_height ) ) + ; + + if( page_height > 0 ) + return( write_webp_anim( write, image, page_height ) ); + else + return( write_webp_single( write, image ) ); +} + +static int +vips_webp_add_chunk( VipsWebPWrite *write, VipsImage *image, const char *vips, const char webp[4] ) { if( vips_image_get_typeof( image, vips ) ) { @@ -328,14 +440,14 @@ vips_webp_add_chunk( VipsWebPWriter *writer, VipsImage *image, */ (void) vips_image_get_blob( image, vips, &data, &length ); - if( !vips_webp_writer_appendc( writer, webp, data, length ) ) + if( !vips_webp_write_appendc( write, webp, data, length ) ) return( -1 ); } return( 0 ); } -/* Turn @writer into a VP8X image with metadata from @image. +/* Turn @write into a VP8X image with metadata from @image. * * Based (partly) on cwep.c * @@ -368,18 +480,26 @@ vips_webp_add_chunk( VipsWebPWriter *writer, VipsImage *image, * https://developers.google.com/speed/webp/docs/riff_container */ static int -vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image ) +vips_webp_add_metadata( VipsWebPWrite *write, VipsImage *image ) { - /* The image in @writer may be VP8X already. + /* The chunk of data we've written so far. This is awful: we have to + * rewrite the entire image, pasting back bits from the first time + * around. */ - gboolean is_vp8x = !memcmp( writer->mem + 12, "VP8X", 4 ); - gboolean is_lossless = !memcmp( writer->mem + 12, "VP8L", 4 ); + WebPMemoryWriter old_memory_writer = write->memory_writer; + uint8_t *old = old_memory_writer.mem; + size_t old_size = old_memory_writer.size; + + /* The image in @write may be VP8X already. + */ + gboolean is_vp8x = !memcmp( old + 12, "VP8X", 4 ); + gboolean is_lossless = !memcmp( old + 12, "VP8L", 4 ); guint64 metadata_size; uint32_t flags; int i; + guint64 new_size; - VipsWebPWriter new; /* Rebuild the EXIF block, if any, ready for writing. */ @@ -393,7 +513,7 @@ vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image ) /* If there are any flags there already, we add to them. */ - flags = is_vp8x ? writer->mem[20] : 0; + flags = is_vp8x ? old[20] : 0; for( i = 0; i < vips__n_webp_names; i++ ) { const char *vips = vips__webp_names[i].vips; @@ -425,27 +545,32 @@ vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image ) /* If it's not already vp8x, we'll need to add a vp8x header, and * that'll add 18 bytes. -8 since size includes the RIFF header. */ - new_size = writer->size - 8 + (is_vp8x ? 0 : 18) + metadata_size; + new_size = old_size - 8 + (is_vp8x ? 0 : 18) + metadata_size; - vips_webp_writer_init( &new ); + write->memory_writer.mem = NULL; + write->memory_writer.size = 0; + write->memory_writer.max_size = 0; - if( !vips_webp_writer_appendcc( &new, "RIFF" ) || - !vips_webp_writer_appendle32( &new, new_size ) || - !vips_webp_writer_appendcc( &new, "WEBP" ) || - !vips_webp_writer_appendcc( &new, "VP8X" ) || - !vips_webp_writer_appendle32( &new, 10 ) ) { - vips_webp_writer_unset( &new ); + if( !vips_webp_write_appendcc( write, "RIFF" ) || + !vips_webp_write_appendle32( write, new_size ) || + !vips_webp_write_appendcc( write, "WEBP" ) || + !vips_webp_write_appendcc( write, "VP8X" ) || + !vips_webp_write_appendle32( write, 10 ) ) { + WebPMemoryWriterClear( &old_memory_writer ); return( -1 ); } + /* FIXME loop count has to go in somewhere too. + */ + if( is_vp8x ) { /* Copy the existing VP8X body and update the flag bits. */ - if( !vips_webp_writer_append( &new, writer->mem + 20, 10 ) ) { - vips_webp_writer_unset( &new ); + if( !vips_webp_write_append( write, old + 20, 10 ) ) { + WebPMemoryWriterClear( &old_memory_writer ); return( -1 ); } - new.mem[20] = flags; + write->memory_writer.mem[20] = flags; } else { /* We have to make a new vp8x header. @@ -458,152 +583,131 @@ vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image ) * another 4 to skip the length, then bit 8 - 3 == 5 */ if( is_lossless && - (writer->mem[12 + 8 + 3] & (1 << 5)) ) + (old[12 + 8 + 3] & (1 << 5)) ) flags |= 0x010; /* 10 is the length of the VPX8X header chunk. */ - if( !vips_webp_writer_appendle32( &new, flags ) || - !vips_webp_writer_appendle24( &new, image->Xsize - 1 ) || - !vips_webp_writer_appendle24( &new, image->Ysize- 1 ) ) { - vips_webp_writer_unset( &new ); + if( !vips_webp_write_appendle32( write, flags ) || + !vips_webp_write_appendle24( write, + image->Xsize - 1 ) || + !vips_webp_write_appendle24( write, + image->Ysize - 1 ) ) { + WebPMemoryWriterClear( &old_memory_writer ); return( -1 ); } } /* Extra chunks have to be in this order. */ - if( vips_webp_add_chunk( &new, image, VIPS_META_ICC_NAME, "ICCP" ) ) { - vips_webp_writer_unset( &new ); + if( vips_webp_add_chunk( write, image, VIPS_META_ICC_NAME, "ICCP" ) ) { + WebPMemoryWriterClear( &old_memory_writer ); return( -1 ); } /* The image chunk must come here. */ if( is_vp8x ) { - if( !vips_webp_writer_append( &new, - writer->mem + 30, writer->size - 30 ) ) { - vips_webp_writer_unset( &new ); + if( !vips_webp_write_append( write, + old + 30, old_size - 30 ) ) { + WebPMemoryWriterClear( &old_memory_writer ); return( -1 ); } } else { - if( !vips_webp_writer_append( &new, - writer->mem + 12, writer->size - 12 ) ) { - vips_webp_writer_unset( &new ); + if( !vips_webp_write_append( write, + old + 12, old_size - 12 ) ) { + WebPMemoryWriterClear( &old_memory_writer ); return( -1 ); } } - if( vips_webp_add_chunk( &new, image, VIPS_META_EXIF_NAME, "EXIF" ) ) { - vips_webp_writer_unset( &new ); - return( -1 ); - } + WebPMemoryWriterClear( &old_memory_writer ); - if( vips_webp_add_chunk( &new, image, VIPS_META_XMP_NAME, "XMP " ) ) { - vips_webp_writer_unset( &new ); + if( vips_webp_add_chunk( write, image, VIPS_META_EXIF_NAME, "EXIF" ) ) return( -1 ); - } - vips_webp_writer_unset( writer ); - *writer = new; + if( vips_webp_add_chunk( write, image, VIPS_META_XMP_NAME, "XMP " ) ) + return( -1 ); return( 0 ); } -#endif /*HAVE_LIBWEBPMUX*/ int -vips__webp_write_file( VipsImage *in, const char *filename, +vips__webp_write_file( VipsImage *image, const char *filename, int Q, gboolean lossless, VipsForeignWebpPreset preset, gboolean smart_subsample, gboolean near_lossless, int alpha_q, gboolean strip ) { - WebPPicture pic; - VipsWebPWriter writer; + VipsWebPWrite write; FILE *fp; - if( !WebPPictureInit( &pic ) ) { - vips_error( "vips2webp", - "%s", _( "picture version error" ) ); + if( vips_webp_write_init( &write, image, + Q, lossless, preset, smart_subsample, near_lossless, + alpha_q, strip ) ) + return( -1 ); + + if( write_webp( &write, image ) ) { + vips_webp_write_unset( &write ); return( -1 ); } - vips_webp_writer_init( &writer ); - pic.writer = memory_write; - pic.custom_ptr = &writer; - - if( write_webp( &pic, in, Q, lossless, preset, smart_subsample, - near_lossless, alpha_q ) ) { - WebPPictureFree( &pic ); - vips_webp_writer_unset( &writer ); - return( -1 ); - } - - WebPPictureFree( &pic ); - -#ifdef HAVE_LIBWEBPMUX if( !strip && - vips_webp_add_metadata( &writer, in ) ) { - vips_webp_writer_unset( &writer ); + vips_webp_add_metadata( &write, image ) ) { + vips_webp_write_unset( &write ); return( -1 ); } -#endif /*HAVE_LIBWEBPMUX*/ if( !(fp = vips__file_open_write( filename, FALSE )) ) { - vips_webp_writer_unset( &writer ); + vips_webp_write_unset( &write ); return( -1 ); } - if( vips__file_write( writer.mem, writer.size, 1, fp ) ) { + if( vips__file_write( + write.memory_writer.mem, write.memory_writer.size, 1, fp ) ) { fclose( fp ); - vips_webp_writer_unset( &writer ); + vips_webp_write_unset( &write ); return( -1 ); } fclose( fp ); - vips_webp_writer_unset( &writer ); + + vips_webp_write_unset( &write ); return( 0 ); } int -vips__webp_write_buffer( VipsImage *in, void **obuf, size_t *olen, +vips__webp_write_buffer( VipsImage *image, void **obuf, size_t *olen, int Q, gboolean lossless, VipsForeignWebpPreset preset, gboolean smart_subsample, gboolean near_lossless, int alpha_q, gboolean strip ) { - WebPPicture pic; - VipsWebPWriter writer; + VipsWebPWrite write; - if( !WebPPictureInit( &pic ) ) { - vips_error( "vips2webp", - "%s", _( "picture version error" ) ); + if( vips_webp_write_init( &write, image, + Q, lossless, preset, smart_subsample, near_lossless, + alpha_q, strip ) ) + return( -1 ); + + if( write_webp( &write, image ) ) { + vips_webp_write_unset( &write ); return( -1 ); } - vips_webp_writer_init( &writer ); - pic.writer = memory_write; - pic.custom_ptr = &writer; - - if( write_webp( &pic, in, Q, lossless, preset, smart_subsample, - near_lossless, alpha_q ) ) { - WebPPictureFree( &pic ); - vips_webp_writer_unset( &writer ); - return( -1 ); - } - - WebPPictureFree( &pic ); - -#ifdef HAVE_LIBWEBPMUX if( !strip && - vips_webp_add_metadata( &writer, in ) ) { - vips_webp_writer_unset( &writer ); + vips_webp_add_metadata( &write, image ) ) { + vips_webp_write_unset( &write ); return( -1 ); } -#endif /*HAVE_LIBWEBPMUX*/ - *obuf = writer.mem; - *olen = writer.size; + *obuf = write.memory_writer.mem; + *olen = write.memory_writer.size; + write.memory_writer.mem = NULL; + write.memory_writer.size = 0; + write.memory_writer.max_size = 0; + + vips_webp_write_unset( &write ); return( 0 ); } From f2ab0ab374fedf5525455434742f9a53a2bf002f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 30 Oct 2018 15:22:22 +0000 Subject: [PATCH 2/3] fix some typos ... it works! need to add support for delay and loop still --- ChangeLog | 1 + libvips/foreign/vips2webp.c | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index e66c6036..4a162933 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 21/9/18 started 8.8.0 - much faster smartcrop [lovell] - add low/high to smartcrop [jcupitt] +- add animated webp write [jcupitt] 23/9/18 started 8.7.1 - update function list in docs [janko-m] diff --git a/libvips/foreign/vips2webp.c b/libvips/foreign/vips2webp.c index 9f3ef07c..4c300b18 100644 --- a/libvips/foreign/vips2webp.c +++ b/libvips/foreign/vips2webp.c @@ -183,13 +183,13 @@ vips_webp_write_append( VipsWebPWrite *write, /* Yuk! Sadly libwebp does not have a proper interface to this type. */ pic.custom_ptr = (void *) &write->memory_writer; - if( WebPMemoryWrite( data, data_size, &pic ) ) { + if( !WebPMemoryWrite( data, data_size, &pic ) ) { vips_error( "webp", "%s", _( "output webp image too large" ) ); - return( 0 ); + return( FALSE ); } - return( 1 ); + return( TRUE ); } /* We don't actually use libwebpmux here, but we shouldn't attach metadata we @@ -391,8 +391,13 @@ write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) /* Add a last fake frame to signal the last duration. */ - if( !WebPAnimEncoderAdd( write->enc, NULL, timestamp_ms, NULL ) || - WebPAnimEncoderAssemble( write->enc, &webp_data ) ) { + if( !WebPAnimEncoderAdd( write->enc, NULL, timestamp_ms, NULL ) ) { + vips_error( "vips2webp", + "%s", _( "anim build error" ) ); + return( -1 ); + } + + if( !WebPAnimEncoderAssemble( write->enc, &webp_data ) ) { vips_error( "vips2webp", "%s", _( "anim build error" ) ); return( -1 ); @@ -547,9 +552,9 @@ vips_webp_add_metadata( VipsWebPWrite *write, VipsImage *image ) */ new_size = old_size - 8 + (is_vp8x ? 0 : 18) + metadata_size; - write->memory_writer.mem = NULL; - write->memory_writer.size = 0; - write->memory_writer.max_size = 0; + /* We've taken a copy of the pointer already. + */ + WebPMemoryWriterInit( &write->memory_writer ); if( !vips_webp_write_appendcc( write, "RIFF" ) || !vips_webp_write_appendle32( write, new_size ) || From bb1bcfbbf0bd0a18a040c2a1431c1605c819ae88 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 30 Oct 2018 15:42:11 +0000 Subject: [PATCH 3/3] add delay support --- libvips/foreign/vips2webp.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libvips/foreign/vips2webp.c b/libvips/foreign/vips2webp.c index 4c300b18..37a239b3 100644 --- a/libvips/foreign/vips2webp.c +++ b/libvips/foreign/vips2webp.c @@ -10,6 +10,7 @@ * - add metadata write * 29/10/18 * - target libwebp 0.5+ and remove some ifdefs + * - add animated webp write */ /* @@ -342,9 +343,13 @@ write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) int top; int timestamp_ms; - /* FIXME get delay + /* 100ms is the webp default. */ - delay = 16; + delay = 100; + if( vips_image_get_typeof( image, "gif-delay" ) && + vips_image_get_int( image, "gif-delay", &delay ) ) + ; + delay = VIPS_CLIP( 0, delay, 100000 ); if( !WebPAnimEncoderOptionsInit( &anim_config ) ) { vips_error( "vips2webp", @@ -427,7 +432,9 @@ write_webp( VipsWebPWrite *write, VipsImage *image ) &page_height ) ) ; - if( page_height > 0 ) + if( page_height > 0 && + page_height < image->Ysize && + image->Ysize % page_height == 0 ) return( write_webp_anim( write, image, page_height ) ); else return( write_webp_single( write, image ) );