feat: support frame delays
This commit is contained in:
parent
ea2367e24c
commit
ae50136845
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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" ) );
|
||||
|
@ -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 )) )
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user