From ae501368459836e0dc378ca856e6c38509ed1e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Szabo?= Date: Fri, 21 Jun 2019 15:06:22 +0200 Subject: [PATCH] feat: support frame delays --- libvips/foreign/gifload.c | 39 ++++++++++++++------------------ libvips/foreign/magicksave.c | 11 ++++++--- libvips/foreign/vips2webp.c | 39 ++++++++++++++++++++++++++++++-- libvips/foreign/webp2vips.c | 44 ++++++++++++++++++++---------------- libvips/foreign/webpsave.c | 4 ++-- 5 files changed, 89 insertions(+), 48 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index b43624a5..8b12f6a1 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -132,12 +132,9 @@ typedef struct _VipsForeignLoadGif { gboolean has_transparency; gboolean has_colour; - /* Delay in 1/100ths of a second. We only track a single delay - * value for the whole file, and we report the first delay we see. Some - * GIFs have a long delay on the final frame. + /* Delays between frames (in miliseconds). */ - gboolean has_delay; - int delay; + int *delays; /* Number of times to loop the animation. */ @@ -324,7 +321,8 @@ vips_foreign_load_gif_dispose( GObject *gobject ) VIPS_UNREF( gif->frame ); VIPS_UNREF( gif->previous ); VIPS_FREE( gif->comment ); - VIPS_FREE( gif->line ) + VIPS_FREE( gif->line ); + VIPS_FREE( gif->delays ); G_OBJECT_CLASS( vips_foreign_load_gif_parent_class )-> dispose( gobject ); @@ -521,11 +519,10 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) gif->has_transparency = TRUE; } - if( !gif->has_delay ) { - VIPS_DEBUG_MSG( "gifload: has delay\n" ); - gif->has_delay = TRUE; - gif->delay = extension[2] | (extension[3] << 8); - } + if( gif->n_pages % 64 == 0 ) { + gif->delays = (int *) g_realloc( gif->delays, (gif->n_pages + 64) * sizeof(int) ); + } + gif->delays[gif->n_pages] = (extension[2] | (extension[3] << 8)) * 10; while( extension != NULL ) if( vips_foreign_load_gif_ext_next( gif, @@ -575,20 +572,18 @@ vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image ) vips_image_set_int( image, VIPS_META_PAGE_HEIGHT, gif->file->SHeight ); vips_image_set_int( image, VIPS_META_N_PAGES, gif->n_pages ); - vips_image_set_int( image, "gif-delay", gif->delay ); vips_image_set_int( image, "gif-loop", gif->loop ); + + if ( gif->delays ) { + vips_image_set_int( image, "gif-delay", VIPS_RINT( gif->delays[0] / 10.0 ) ); + vips_image_set_array_int( image, "delay", gif->delays, gif->n_pages ); + } else { + vips_image_set_int( image, "gif-delay", 4 ); + } + if( gif->comment ) vips_image_set_string( image, "gif-comment", gif->comment ); -{ - int array[10]; - int i; - - for( i = 0; i < 10; i++ ) - array[i] = i; - vips_image_set_array_int( image, "delay", array, 10 ); -} - return( 0 ); } @@ -1126,7 +1121,7 @@ vips_foreign_load_gif_init( VipsForeignLoadGif *gif ) { gif->n = 1; gif->transparency = -1; - gif->delay = 4; + gif->delays = NULL; gif->loop = 0; gif->comment = NULL; gif->dispose = 0; diff --git a/libvips/foreign/magicksave.c b/libvips/foreign/magicksave.c index fc32f8dc..d23b138d 100644 --- a/libvips/foreign/magicksave.c +++ b/libvips/foreign/magicksave.c @@ -109,6 +109,8 @@ vips_foreign_save_magick_next_image( VipsForeignSaveMagick *magick ) Image *image; int number; + int *numbers; + int numbers_length; const char *str; g_assert( !magick->current_image ); @@ -139,9 +141,12 @@ vips_foreign_save_magick_next_image( VipsForeignSaveMagick *magick ) im->Xsize, magick->page_height, magick->exception ) ) return( -1 ); - if( vips_image_get_typeof( im, "gif-delay" ) && - !vips_image_get_int( im, "gif-delay", &number ) ) - image->delay = (size_t) number; + if( vips_image_get_typeof( im, "delay" ) && + !vips_image_get_array_int( im, "delay", &numbers, &numbers_length ) ) { + int page_index = magick->position.top / magick->page_height; + if( page_index < numbers_length ) + image->delay = (size_t) VIPS_RINT( numbers[page_index] / 10.0 ); + } /* ImageMagick uses iterations like this (at least in gif save): * 0 - set 0 loops (infinite) diff --git a/libvips/foreign/vips2webp.c b/libvips/foreign/vips2webp.c index b45a1d4b..b352abba 100644 --- a/libvips/foreign/vips2webp.c +++ b/libvips/foreign/vips2webp.c @@ -289,6 +289,29 @@ get_int( VipsImage *image, const char *field, int default_value ) return( default_value ); } +/* Get an image field as an array of ints. + */ +static int* +get_array_int( VipsImage *image, const char *field, int* n ) +{ + int *value; + + if( vips_image_get_typeof( image, field ) && + !vips_image_get_array_int( image, field, &value, n ) ) + return( value ); + + return( NULL ); +} + +/* Returns a delay on a given index or the default delay. + */ +static int +extract_delay( int index, int *delays, int delays_length, int default_delay ) +{ + if( delays == NULL || index > delays_length ) return( default_delay ); + return( delays[index] ); +} + /* Write a set of animated frames into write->memory_writer. */ static int @@ -297,7 +320,9 @@ write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) /* 100ms is the webp default. gif-delay is in centiseconds (the GIF * standard). */ - const int delay = 10 * get_int( image, "gif-delay", 10 ); + const int default_delay = 10 * get_int( image, "gif-delay", 10 ); + int delays_length; + const int *delays = get_array_int( image, "delay", &delays_length ); WebPAnimEncoderOptions anim_config; WebPData webp_data; @@ -348,9 +373,19 @@ write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) WebPPictureFree( &pic ); - timestamp_ms += delay; + int page_index = top / page_height; + timestamp_ms += extract_delay( page_index, delays, delays_length, default_delay ); } + /* Closes encoder and add last frame delay. + */ + if( !WebPAnimEncoderAdd( write->enc, + NULL, timestamp_ms, NULL ) ) { + vips_error( "vips2webp", + "%s", _( "anim close error" ) ); + return( -1 ); + } + if( !WebPAnimEncoderAssemble( write->enc, &webp_data ) ) { vips_error( "vips2webp", "%s", _( "anim build error" ) ); diff --git a/libvips/foreign/webp2vips.c b/libvips/foreign/webp2vips.c index 695e87c1..0e79c5fc 100644 --- a/libvips/foreign/webp2vips.c +++ b/libvips/foreign/webp2vips.c @@ -115,9 +115,9 @@ typedef struct { */ int frame_count; - /* Delay between frames. We don't let this change between frames. + /* Delays between frames (in miliseconds). */ - int delay; + int *delays; /* If we are opening a file object, the fd. */ @@ -376,7 +376,7 @@ read_new( const char *filename, const void *data, size_t length, read->page = page; read->n = n; read->scale = scale; - read->delay = 100; + read->delays = NULL; read->fd = 0; read->demux = NULL; read->frame = NULL; @@ -470,21 +470,32 @@ read_header( Read *read, VipsImage *out ) vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, read->frame_height ); - /* We must get the first frame to get the delay. Frames number - * from 1. - */ - if( WebPDemuxGetFrame( read->demux, 1, &iter ) ) { - read->delay = iter.duration; + if ( read->frame_count > 1) { + read->delays = (int *) g_malloc( read->frame_count * sizeof(int) ); + + for( int i = 0; i < read->frame_count; i++ ) { + if( WebPDemuxGetFrame( read->demux, i + 1, &iter ) ) { + read->delays[i] = iter.duration; + } else { + read->delays[i] = 0; + } + } #ifdef DEBUG - printf( "webp2vips: duration = %d\n", read->delay ); + for( int i = 0; i < read->frame_count; i++ ) { + printf( "webp2vips: frame = %d; duration = %d\n", i + 1, read->delays[i] ); + } #endif /*DEBUG*/ - /* webp uses ms for delays, gif uses centiseconds. - */ - vips_image_set_int( out, "gif-delay", - VIPS_RINT( read->delay / 10.0 ) ); - } + vips_image_set_array_int( out, "delay", read->delays, read->frame_count ); + g_free( read->delays ); + + /* webp uses ms for delays, gif uses centiseconds. + */ + vips_image_set_int( out, "gif-delay", + VIPS_RINT( read->delays[0] / 10.0 ) ); + } + WebPDemuxReleaseIterator( &iter ); if( read->n == -1 ) @@ -688,11 +699,6 @@ read_next_frame( Read *read ) printf( "don't blend\n" ); #endif /*DEBUG*/ - if( read->frame_count > 1 && - read->iter.duration != read->delay ) - g_warning( "webp2vips: " - "not all frames have equal duration" ); - if( !(frame = read_frame( read, area.width, area.height, read->iter.fragment.bytes, read->iter.fragment.size )) ) diff --git a/libvips/foreign/webpsave.c b/libvips/foreign/webpsave.c index b9466421..b8340d8e 100644 --- a/libvips/foreign/webpsave.c +++ b/libvips/foreign/webpsave.c @@ -466,8 +466,8 @@ vips_foreign_save_webp_mime_init( VipsForeignSaveWebpMime *mime ) * frames between frames. Setting 0 means no keyframes. By default, keyframes * are disabled. * - * Use the metadata items `gif-loop` and `gif-delay` to set the number of - * loops for the animation and the frame delay. + * Use the metadata items `gif-loop` and `delay` to set the number of + * loops for the animation and the frame delays. * * The writer will attach ICC, EXIF and XMP metadata, unless @strip is set to * %TRUE.