"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "libvips/foreign/gifload.c" between
vips-8.10.6.tar.gz and vips-8.11.0.tar.gz

About: VIPS is a free image processing system (see also the GUI nip2).

gifload.c  (vips-8.10.6):gifload.c  (vips-8.11.0)
/* load a GIF with giflib /* load a GIF with libnsgif
* *
* 10/2/16 * 6/10/18
* - from svgload.c * - from gifload.c
* 25/4/16
* - add giflib5 support
* 26/7/16
* - transparency was wrong if there was no EXTENSION_RECORD
* - write 1, 2, 3, or 4 bands depending on file contents
* 17/8/16
* - support unicode on win
* 19/8/16
* - better transparency detection, thanks diegocsandrim
* 25/11/16
* - support @n, page-height
* 5/10/17
* - colormap can be missing thanks Kleis
* 21/11/17
* - add "gif-delay", "gif-loop", "gif-comment" metadata
* - add dispose handling
* 13/8/18
* - init pages to 0 before load
* 14/2/19
* - rework as a sequential loader ... simpler, much lower mem use
* 6/7/19 [deftomat]
* - support array of delays
* 24/7/19
* - close early on minimise
* - close early on error
* 23/8/18
* - allow GIF read errors during header scan
* - better feof() handling
* 27/8/19
* - check image and frame bounds, since giflib does not
* 1/9/19
* - improve early close again
* 30/1/19
* - rework on top of VipsSource
* - add gifload_source
* 5/2/20 alon-ne
* - fix DISPOSE_BACKGROUND and DISPOSE_PREVIOUS
* 2/7/20
* - clip out of bounds images against canvas
* - fix PREVIOUS handling, again
*/ */
/* /*
This file is part of VIPS. This file is part of VIPS.
VIPS is free software; you can redistribute it and/or modify VIPS is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
skipping to change at line 75 skipping to change at line 35
*/ */
/* /*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/ */
/* /*
#define DEBUG_VERBOSE #define VERBOSE
#define VIPS_DEBUG #define VIPS_DEBUG
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
#include <config.h> #include <config.h>
#endif /*HAVE_CONFIG_H*/ #endif /*HAVE_CONFIG_H*/
#include <vips/intl.h> #include <vips/intl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <ctype.h> #include <ctype.h>
#include <vips/vips.h> #include <vips/vips.h>
#include <vips/buf.h> #include <vips/buf.h>
#include <vips/internal.h> #include <vips/internal.h>
#include <vips/debug.h> #include <vips/debug.h>
#ifdef HAVE_GIFLIB /* TODO:
*
#include <gif_lib.h> * - libnsgif does not seem to support comment metadata
*
/* giflib 5 is rather different :-( functions have error returns and there's * - it always loads the entire source file into memory
* no LastError(). *
* Notes:
*
* - hard to detect mono images -- local_colour_table in libnsgif is only set
* when we decode a frame, so we can't tell just from init whether any
* frames have colour info
*
* - don't bother detecting alpha -- if we can't detect RGB, alpha won't help
* much
* *
* GIFLIB_MAJOR was introduced in 4.1.6. Use it to test for giflib 5.x.
*/ */
#ifdef GIFLIB_MAJOR
# if GIFLIB_MAJOR > 4
# define HAVE_GIFLIB_5
# endif
#endif
/* Added in giflib5. #ifdef HAVE_NSGIF
*/
#ifndef HAVE_GIFLIB_5 #include <libnsgif/libnsgif.h>
#define DISPOSAL_UNSPECIFIED 0
#define DISPOSE_DO_NOT 1
#define DISPOSE_BACKGROUND 2
#define DISPOSE_PREVIOUS 3
#endif
#define NO_TRANSPARENT_INDEX -1
#define TRANSPARENT_MASK 0x01
#define DISPOSE_MASK 0x07
#define DISPOSE_SHIFT 2
#define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_gif_get_type()) #define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_nsgif_get_type())
#define VIPS_FOREIGN_LOAD_GIF( obj ) \ #define VIPS_FOREIGN_LOAD_GIF( obj ) \
(G_TYPE_CHECK_INSTANCE_CAST( (obj), \ (G_TYPE_CHECK_INSTANCE_CAST( (obj), \
VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGif )) VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgif ))
#define VIPS_FOREIGN_LOAD_GIF_CLASS( klass ) \ #define VIPS_FOREIGN_LOAD_GIF_CLASS( klass ) \
(G_TYPE_CHECK_CLASS_CAST( (klass), \ (G_TYPE_CHECK_CLASS_CAST( (klass), \
VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGifClass)) VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgifClass))
#define VIPS_IS_FOREIGN_LOAD_GIF( obj ) \
(G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_GIF ))
#define VIPS_IS_FOREIGN_LOAD_GIF_CLASS( klass ) \
(G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_GIF ))
#define VIPS_FOREIGN_LOAD_GIF_GET_CLASS( obj ) \ #define VIPS_FOREIGN_LOAD_GIF_GET_CLASS( obj ) \
(G_TYPE_INSTANCE_GET_CLASS( (obj), \ (G_TYPE_INSTANCE_GET_CLASS( (obj), \
VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGifClass )) VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgifClass ))
typedef struct _VipsForeignLoadGif { typedef struct _VipsForeignLoadNsgif {
VipsForeignLoad parent_object; VipsForeignLoad parent_object;
/* Load from this page (frame number). /* Load this page (frame number).
*/ */
int page; int page;
/* Load this many pages. /* Load this many pages.
*/ */
int n; int n;
/* Load from this source (set by subclasses). /* Load from this source (set by subclasses).
*/ */
VipsSource *source; VipsSource *source;
GifFileType *file; /* The animation created by libnsgif.
/* We decompress the whole thing to a huge RGBA memory image, and
* as we render, watch for bands and transparency. At the end of
* loading, we copy 1 or 3 bands, with or without transparency to
* output.
*/
gboolean has_transparency;
gboolean has_colour;
/* Delays between frames (in milliseconds).
*/ */
int *delays; gif_animation *anim;
int delays_length;
/* Number of times to loop the animation. /* The data/size pair we pass to libnsgif.
*/ */
int loop; unsigned char *data;
size_t size;
/* The GIF comment, if any. /* The frame_count, after we have removed undisplayable frames.
*/ */
char *comment; int frame_count_displayable;
/* The number of pages (frame) in the image. /* Delays between frames (in milliseconds). Array of length
* @frame_count_displayable.
*/ */
int n_pages; int *delay;
/* A memory image the size of one frame ... we accumulate to this as /* A single centisecond value for compatibility.
* we scan the image, and copy lines to the output on generate.
*/ */
VipsImage *frame; int gif_delay;
/* A scratch buffer the size of the largest Image inside the GIF. We /* If the GIF contains any frames with transparent elements.
* decompress lines from the GIF to this.
*/ */
VipsImage *scratch; gboolean has_transparency;
/* A copy of the previous frame, in case we need a DISPOSE_PREVIOUS.
*/
VipsImage *previous;
/* The position of @frame, in pages.
*/
int current_page;
/* Decompress lines of the gif file to here. This is large enough to
* hold one line of the widest sub-image in the GIF.
*/
int max_image_width;
GifPixelType *line;
/* The current dispose method.
*/
int dispose;
/* Set for EOF detected.
*/
gboolean eof;
/* The current cmap unpacked to a simple LUT. Each uint32 is really an
* RGBA pixel ready to be blasted into @frame.
*/
guint32 cmap[256];
/* As we scan the file, the index of the transparent pixel for this
* frame.
*/
int transparent_index;
} VipsForeignLoadGif; } VipsForeignLoadNsgif;
typedef VipsForeignLoadClass VipsForeignLoadGifClass; typedef VipsForeignLoadClass VipsForeignLoadNsgifClass;
G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadGif, vips_foreign_load_gif, G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadNsgif, vips_foreign_load_nsgif,
VIPS_TYPE_FOREIGN_LOAD ); VIPS_TYPE_FOREIGN_LOAD );
/* From gif2rgb.c ... offsets and jumps for interlaced GIF images.
*/
static int
InterlacedOffset[] = { 0, 4, 2, 1 },
InterlacedJumps[] = { 8, 8, 4, 2 };
/* giflib4 was missing this.
*/
static const char * static const char *
vips_foreign_load_gif_errstr( int error_code ) vips_foreign_load_nsgif_errstr( gif_result result )
{ {
#ifdef HAVE_GIFLIB_5 switch( result ) {
return( GifErrorString( error_code ) ); case GIF_WORKING:
#else /*!HAVE_GIFLIB_5*/ return( _( "Working" ) );
switch( error_code ) {
case D_GIF_ERR_OPEN_FAILED:
return( _( "Failed to open given file" ) );
case D_GIF_ERR_READ_FAILED:
return( _( "Failed to read from given file" ) );
case D_GIF_ERR_NOT_GIF_FILE: case GIF_OK:
return( _( "Data is not a GIF file" ) ); return( _( "OK" ) );
case D_GIF_ERR_NO_SCRN_DSCR: case GIF_INSUFFICIENT_FRAME_DATA:
return( _( "No screen descriptor detected" ) ); return( _( "Insufficient data to complete frame" ) );
case D_GIF_ERR_NO_IMAG_DSCR: case GIF_FRAME_DATA_ERROR:
return( _( "No image descriptor detected" ) ); return( _( "GIF frame data error" ) );
case D_GIF_ERR_NO_COLOR_MAP: case GIF_INSUFFICIENT_DATA:
return( _( "Neither global nor local color map" ) ); return( _( "Insufficient data to do anything" ) );
case D_GIF_ERR_WRONG_RECORD: case GIF_DATA_ERROR:
return( _( "Wrong record type detected" ) ); return( _( "GIF header data error" ) );
case D_GIF_ERR_DATA_TOO_BIG: case GIF_INSUFFICIENT_MEMORY:
return( _( "Number of pixels bigger than width * height" ) ); return( _( "Insuficient memory to process" ) );
case D_GIF_ERR_NOT_ENOUGH_MEM: case GIF_FRAME_NO_DISPLAY:
return( _( "Failed to allocate required memory" ) ); return( _( "No display" ) );
case D_GIF_ERR_CLOSE_FAILED: case GIF_END_OF_FRAME:
return( _( "Failed to close given file" ) ); return( _( "At end of frame" ) );
case D_GIF_ERR_NOT_READABLE:
return( _( "Given file was not opened for read" ) );
case D_GIF_ERR_IMAGE_DEFECT:
return( _( "Image is defective, decoding aborted" ) );
case D_GIF_ERR_EOF_TOO_SOON:
return( _( "Image EOF detected, before image complete" ) );
default: default:
return( _( "Unknown error" ) ); return( _( "Unknown error" ) );
} }
#endif /*HAVE_GIFLIB_5*/
} }
static void static void
vips_foreign_load_gif_error_vips( VipsForeignLoadGif *gif, int error ) vips_foreign_load_nsgif_error( VipsForeignLoadNsgif *gif, gif_result result )
{ {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
const char *message; vips_error( class->nickname, "%s",
vips_foreign_load_nsgif_errstr( result ) );
if( (message = vips_foreign_load_gif_errstr( error )) )
vips_error( class->nickname, "%s", message );
} }
static void static void
vips_foreign_load_gif_error( VipsForeignLoadGif *gif ) vips_foreign_load_nsgif_dispose( GObject *gobject )
{ {
int error; VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) gobject;
error = 0;
#ifdef HAVE_GIFLIB_5 VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_dispose:\n" );
if( gif->file )
error = gif->file->Error;
#else
error = GifLastError();
#endif
if( error ) if( gif->anim ) {
vips_foreign_load_gif_error_vips( gif, error ); gif_finalise( gif->anim );
} VIPS_FREE( gif->anim );
/* Shut down giflib plus any underlying file resource.
*/
static int
vips_foreign_load_gif_close_giflib( VipsForeignLoadGif *gif )
{
VIPS_DEBUG_MSG( "vips_foreign_load_gif_close_giflib:\n" );
#ifdef HAVE_GIFLIB_5
if( gif->file ) {
int error;
if( DGifCloseFile( gif->file, &error ) == GIF_ERROR ) {
vips_foreign_load_gif_error_vips( gif, error );
gif->file = NULL;
return( -1 );
}
gif->file = NULL;
} }
#else
if( gif->file ) {
if( DGifCloseFile( gif->file ) == GIF_ERROR ) {
vips_foreign_load_gif_error_vips( gif, GifLastError() );
gif->file = NULL;
return( -1 );
}
gif->file = NULL;
}
#endif
if( gif->source )
vips_source_minimise( gif->source );
return( 0 );
}
/* Callback from the gif loader.
*
* Read up to len bytes into buffer, return number of bytes read. This is
* called by giflib exactly as fread, so it does not distinguish between EOF
* and read error.
*/
static int
vips_giflib_read( GifFileType *file, GifByteType *buf, int n )
{
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) file->UserData;
int to_read;
to_read = n;
while( to_read > 0 ) {
gint64 bytes_read;
bytes_read = vips_source_read( gif->source, buf, n );
if( bytes_read == 0 ) {
gif->eof = TRUE;
return( -1 );
}
if( bytes_read < 0 )
return( -1 );
if( bytes_read > INT_MAX )
return( -1 );
to_read -= bytes_read;
buf += bytes_read;
}
return( (int) n );
}
/* Open any underlying file resource, then giflib.
*/
static int
vips_foreign_load_gif_open_giflib( VipsForeignLoadGif *gif )
{
VIPS_DEBUG_MSG( "vips_foreign_load_gif_open_giflib:\n" );
g_assert( !gif->file );
/* Must always rewind before opening giflib again.
*/
vips_source_rewind( gif->source );
#ifdef HAVE_GIFLIB_5
{
int error;
if( !(gif->file = DGifOpen( gif, vips_giflib_read, &error )) ) {
vips_foreign_load_gif_error_vips( gif, error );
(void) vips_foreign_load_gif_close_giflib( gif );
return( -1 );
}
}
#else
if( !(gif->file = DGifOpen( gif, vips_giflib_read )) ) {
vips_foreign_load_gif_error_vips( gif, GifLastError() );
(void) vips_foreign_load_gif_close_giflib( gif );
return( -1 );
}
#endif
gif->eof = FALSE;
gif->current_page = 0;
gif->max_image_width = 0;
return( 0 );
}
static void
vips_foreign_load_gif_dispose( GObject *gobject )
{
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) gobject;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_dispose:\n" );
vips_foreign_load_gif_close_giflib( gif );
VIPS_UNREF( gif->source ); VIPS_UNREF( gif->source );
VIPS_UNREF( gif->frame ); VIPS_FREE( gif->delay );
VIPS_UNREF( gif->scratch );
VIPS_UNREF( gif->previous );
VIPS_FREE( gif->comment );
VIPS_FREE( gif->line );
VIPS_FREE( gif->delays );
G_OBJECT_CLASS( vips_foreign_load_gif_parent_class )-> G_OBJECT_CLASS( vips_foreign_load_nsgif_parent_class )->
dispose( gobject ); dispose( gobject );
} }
static VipsForeignFlags static VipsForeignFlags
vips_foreign_load_gif_get_flags_filename( const char *filename ) vips_foreign_load_nsgif_get_flags_filename( const char *filename )
{ {
return( VIPS_FOREIGN_SEQUENTIAL ); return( VIPS_FOREIGN_SEQUENTIAL );
} }
static VipsForeignFlags static VipsForeignFlags
vips_foreign_load_gif_get_flags( VipsForeignLoad *load ) vips_foreign_load_nsgif_get_flags( VipsForeignLoad *load )
{ {
return( VIPS_FOREIGN_SEQUENTIAL ); return( VIPS_FOREIGN_SEQUENTIAL );
} }
static gboolean static gboolean
vips_foreign_load_gif_is_a_source( VipsSource *source ) vips_foreign_load_nsgif_is_a_source( VipsSource *source )
{ {
const unsigned char *data; const unsigned char *data;
if( (data = vips_source_sniff( source, 4 )) && if( (data = vips_source_sniff( source, 4 )) &&
data[0] == 'G' && data[0] == 'G' &&
data[1] == 'I' && data[1] == 'I' &&
data[2] == 'F' && data[2] == 'F' &&
data[3] == '8' ) data[3] == '8' )
return( TRUE ); return( TRUE );
return( FALSE ); return( FALSE );
} }
/* Make sure delays is allocated and large enough. #ifdef VERBOSE
*/
static void static void
vips_foreign_load_gif_allocate_delays( VipsForeignLoadGif *gif ) print_frame( gif_frame *frame )
{ {
if( gif->n_pages >= gif->delays_length ) { printf( "frame:\n" );
int old = gif->delays_length; printf( " display = %d\n", frame->display );
int i; printf( " frame_delay = %d\n", frame->frame_delay );
printf( " virgin = %d\n", frame->virgin );
gif->delays_length = gif->delays_length + gif->n_pages + 64; printf( " opaque = %d\n", frame->opaque );
gif->delays = (int *) g_realloc( gif->delays, printf( " redraw_required = %d\n", frame->redraw_required );
gif->delays_length * sizeof( int ) ); printf( " disposal_method = %d\n", frame->disposal_method );
for( i = old; i < gif->delays_length; i++ ) printf( " transparency = %d\n", frame->transparency );
gif->delays[i] = 40; printf( " transparency_index = %d\n", frame->transparency_index );
} printf( " redraw_x = %d\n", frame->redraw_x );
printf( " redraw_y = %d\n", frame->redraw_y );
printf( " redraw_width = %d\n", frame->redraw_width );
printf( " redraw_height = %d\n", frame->redraw_height );
} }
static int static void
vips_foreign_load_gif_ext_next( VipsForeignLoadGif *gif, print_animation( gif_animation *anim )
GifByteType **extension )
{
if( DGifGetExtensionNext( gif->file, extension ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 );
}
#ifdef DEBUG_VERBOSE
if( *extension )
printf( "gifload: EXTENSION_NEXT\n" );
#endif /*DEBUG_VERBOSE*/
return( 0 );
}
static int
vips_foreign_load_gif_code_next( VipsForeignLoadGif *gif,
GifByteType **extension )
{
if( DGifGetCodeNext( gif->file, extension ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 );
}
#ifdef DEBUG_VERBOSE
if( *extension )
printf( "gifload: CODE_NEXT\n" );
#endif /*DEBUG_VERBOSE*/
return( 0 );
}
/* Quickly scan an image record.
*/
static int
vips_foreign_load_gif_scan_image( VipsForeignLoadGif *gif )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
GifFileType *file = gif->file;
ColorMapObject *map;
GifByteType *extension;
if( DGifGetImageDesc( file ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 );
}
VIPS_DEBUG_MSG( "vips_foreign_load_gif_scan_image: "
"frame of %dx%d pixels at %dx%d\n",
file->Image.Width, file->Image.Height,
file->Image.Left, file->Image.Top );
/* giflib does no checking of image dimensions, not even for 0.
*/
if( file->Image.Width <= 0 ||
file->Image.Width > VIPS_MAX_COORD ||
file->Image.Height <= 0 ||
file->Image.Height > VIPS_MAX_COORD ) {
vips_error( class->nickname,
"%s", _( "image size out of bounds" ) );
return( -1 );
}
/* We need to find the max scanline size inside the GIF
* so we can allocate the decompress buffer.
*/
gif->max_image_width = VIPS_MAX( gif->max_image_width,
file->Image.Width );
/* Test for a non-greyscale colourmap for this frame.
*/
map = file->Image.ColorMap ? file->Image.ColorMap : file->SColorMap;
if( !gif->has_colour &&
map ) {
int i;
for( i = 0; i < map->ColorCount; i++ )
if( map->Colors[i].Red != map->Colors[i].Green ||
map->Colors[i].Green != map->Colors[i].Blue ) {
gif->has_colour = TRUE;
break;
}
}
/* Step over compressed image data.
*/
do {
if( vips_foreign_load_gif_code_next( gif, &extension ) )
return( -1 );
} while( extension != NULL );
return( 0 );
}
static int
vips_foreign_load_gif_scan_application_ext( VipsForeignLoadGif *gif,
GifByteType *extension )
{
gboolean have_netscape;
/* The 11-byte NETSCAPE extension.
*/
have_netscape = FALSE;
if( extension[0] == 11 &&
(vips_isprefix( "NETSCAPE2.0",
(const char*) (extension + 1) ) ||
vips_isprefix( "ANIMEXTS1.0",
(const char*) (extension + 1) )) )
have_netscape = TRUE;
while( extension != NULL ) {
if( vips_foreign_load_gif_ext_next( gif, &extension ) )
return( -1 );
if( have_netscape &&
extension &&
extension[0] == 3 &&
extension[1] == 1 ) {
gif->loop = extension[2] | (extension[3] << 8);
if( gif->loop != 0 )
gif->loop += 1;
}
}
return( 0 );
}
static int
vips_foreign_load_gif_scan_comment_ext( VipsForeignLoadGif *gif,
GifByteType *extension )
{ {
VIPS_DEBUG_MSG( "gifload: type: comment\n" ); int i;
if( !gif->comment ) { printf( "animation:\n" );
/* Up to 257 with a NULL terminator. printf( " width = %d\n", anim->width );
*/ printf( " height = %d\n", anim->height );
char comment[257]; printf( " frame_count = %d\n", anim->frame_count );
printf( " frame_count_partial = %d\n", anim->frame_count_partial );
printf( " decoded_frame = %d\n", anim->decoded_frame );
printf( " frame_image = %p\n", anim->frame_image );
printf( " loop_count = %d\n", anim->loop_count );
printf( " frame_holders = %d\n", anim->frame_holders );
printf( " background_index = %d\n", anim->background_index );
printf( " colour_table_size = %d\n", anim->colour_table_size );
printf( " global_colours = %d\n", anim->global_colours );
printf( " global_colour_table = %p\n", anim->global_colour_table );
printf( " local_colour_table = %p\n", anim->local_colour_table );
vips_strncpy( comment, (char *) (extension + 1), 256 ); for( i = 0; i < anim->frame_holders; i++ ) {
comment[extension[0]] = '\0'; printf( "%d ", i );
gif->comment = g_strdup( comment ); print_frame( &anim->frames[i] );
} }
while( extension != NULL )
if( vips_foreign_load_gif_ext_next( gif, &extension ) )
return( -1 );
return( 0 );
}
static int
vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif )
{
GifByteType *extension;
int ext_code;
if( DGifGetExtension( gif->file, &ext_code, &extension ) ==
GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 );
}
if( extension )
switch( ext_code ) {
case GRAPHICS_EXT_FUNC_CODE:
if( extension[0] == 4 &&
extension[1] & TRANSPARENT_MASK ) {
VIPS_DEBUG_MSG( "gifload: has transp.\n" );
gif->has_transparency = TRUE;
}
/* giflib uses centiseconds, we use ms.
*/
gif->delays[gif->n_pages] =
(extension[2] | (extension[3] << 8)) * 10;
while( extension != NULL )
if( vips_foreign_load_gif_ext_next( gif,
&extension ) )
return( -1 );
break;
case APPLICATION_EXT_FUNC_CODE:
if( vips_foreign_load_gif_scan_application_ext( gif,
extension ) )
return( -1 );
break;
case COMMENT_EXT_FUNC_CODE:
if( vips_foreign_load_gif_scan_comment_ext( gif,
extension ) )
return( -1 );
break;
default:
/* Step over any NEXT blocks for unknown extensions.
*/
while( extension != NULL )
if( vips_foreign_load_gif_ext_next( gif,
&extension ) )
return( -1 );
break;
}
return( 0 );
} }
#endif /*VERBOSE*/
static int static int
vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image ) vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
VipsImage *image )
{ {
const gint64 total_height = (gint64) gif->file->SHeight * gif->n; VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_set_header:\n" );
if( total_height <= 0 ||
total_height > VIPS_MAX_COORD ) {
vips_error( "gifload", "%s", _( "image size out of bounds" ) );
return( -1 );
}
vips_image_init_fields( image, vips_image_init_fields( image,
gif->file->SWidth, total_height, gif->anim->width, gif->anim->height * gif->n,
(gif->has_colour ? 3 : 1) + (gif->has_transparency ? 1 : 0), gif->has_transparency ? 4 : 3,
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
gif->has_colour ? VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W,
1.0, 1.0 );
vips_image_pipelinev( image, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); vips_image_pipelinev( image, VIPS_DEMAND_STYLE_FATSTRIP, NULL );
if( vips_object_argument_isset( VIPS_OBJECT( gif ), "n" ) ) if( vips_object_argument_isset( VIPS_OBJECT( gif ), "n" ) )
vips_image_set_int( image, vips_image_set_int( image,
VIPS_META_PAGE_HEIGHT, gif->file->SHeight ); VIPS_META_PAGE_HEIGHT, gif->anim->height );
vips_image_set_int( image, VIPS_META_N_PAGES, gif->n_pages ); vips_image_set_int( image, VIPS_META_N_PAGES,
vips_image_set_int( image, "loop", gif->loop ); gif->frame_count_displayable );
vips_image_set_int( image, "loop", gif->anim->loop_count );
vips_image_set_array_int( image, "delay",
gif->delay, gif->frame_count_displayable );
if( gif->anim->global_colours &&
gif->anim->global_colour_table &&
gif->anim->background_index >= 0 &&
gif->anim->background_index < gif->anim->colour_table_size ) {
int index = gif->anim->background_index;
unsigned char *entry = (unsigned char *)
&gif->anim->global_colour_table[index];
double array[3];
array[0] = entry[0];
array[1] = entry[1];
array[2] = entry[2];
vips_image_set_array_double( image, "background", array, 3 );
}
VIPS_SETSTR( image->filename,
vips_connection_filename( VIPS_CONNECTION( gif->source ) ) );
/* DEPRECATED "gif-loop" /* DEPRECATED "gif-loop"
* *
* Not the correct behavior as loop=1 became gif-loop=0 * Not the correct behavior as loop=1 became gif-loop=0
* but we want to keep the old behavior untouched! * but we want to keep the old behavior untouched!
*/ */
vips_image_set_int( image, vips_image_set_int( image,
"gif-loop", gif->loop == 0 ? 0 : gif->loop - 1 ); "gif-loop", gif->anim->loop_count == 0 ?
0 : gif->anim->loop_count - 1 );
if( gif->delays ) {
/* The deprecated gif-delay field is in centiseconds.
*/
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 ) /* The deprecated gif-delay field is in centiseconds.
vips_image_set_string( image, "gif-comment", gif->comment ); */
vips_image_set_int( image, "gif-delay", gif->gif_delay );
return( 0 ); return( 0 );
} }
/* Attempt to quickly scan a GIF and discover what we need for our header. We /* Scan the GIF as quickly as we can and extract transparency, bands, pages,
* need to scan the whole file to get n_pages, transparency, colour etc. * etc.
* *
* Don't flag errors during header scan. Many GIFs do not follow spec. * Don't flag any errors unless we have to: we want to work for corrupt or
*/ * malformed GIFs.
static int *
vips_foreign_load_gif_scan( VipsForeignLoadGif *gif ) * Close as soon as we can to free up the fd.
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
GifRecordType record;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_scan:\n" );
gif->n_pages = 0;
do {
if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR )
continue;
switch( record ) {
case IMAGE_DESC_RECORD_TYPE:
(void) vips_foreign_load_gif_scan_image( gif );
gif->n_pages += 1;
vips_foreign_load_gif_allocate_delays( gif );
break;
case EXTENSION_RECORD_TYPE:
/* We need to fetch the extensions to check for
* cmaps and transparency.
*/
(void) vips_foreign_load_gif_scan_extension( gif );
break;
case TERMINATE_RECORD_TYPE:
gif->eof = TRUE;
break;
case SCREEN_DESC_RECORD_TYPE:
case UNDEFINED_RECORD_TYPE:
break;
default:
break;
}
} while( !gif->eof );
if( gif->n == -1 )
gif->n = gif->n_pages - gif->page;
if( gif->page < 0 ||
gif->n <= 0 ||
gif->page + gif->n > gif->n_pages ) {
vips_error( class->nickname, "%s", _( "bad page number" ) );
return( -1 );
}
if( gif->max_image_width <= 0 ||
gif->max_image_width > VIPS_MAX_COORD ) {
vips_error( class->nickname, "%s", _( "bad image size" ) );
return( -1 );
}
return( 0 );
}
/* Scan the GIF and set the libvips header. We always close after scan, even
* on an error.
*/ */
static int static int
vips_foreign_load_gif_header( VipsForeignLoad *load ) vips_foreign_load_nsgif_header( VipsForeignLoad *load )
{ {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadGif *gif = VIPS_FOREIGN_LOAD_GIF( load ); VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) load;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_header: %p\n", gif ); const void *data;
size_t size;
gif_result result;
int i;
if( vips_foreign_load_gif_open_giflib( gif ) ) VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_header:\n" );
return( -1 );
/* giflib does no checking of image dimensions, not even for 0. /* We map in the image, then minimise to close any underlying file
* object. This won't unmap.
*/ */
if( gif->file->SWidth <= 0 || if( !(data = vips_source_map( gif->source, &size )) )
gif->file->SWidth > VIPS_MAX_COORD ||
gif->file->SHeight <= 0 ||
gif->file->SHeight > VIPS_MAX_COORD ) {
vips_error( class->nickname,
"%s", _( "image size out of bounds" ) );
(void) vips_foreign_load_gif_close_giflib( gif );
return( -1 ); return( -1 );
} vips_source_minimise( gif->source );
/* Allocate a line buffer now that we have the GIF width.
*/
if( vips_foreign_load_gif_scan( gif ) ||
!(gif->line = VIPS_ARRAY( NULL,
gif->max_image_width, GifPixelType )) ||
vips_foreign_load_gif_set_header( gif, load->out ) ) {
(void) vips_foreign_load_gif_close_giflib( gif );
result = gif_initialise( gif->anim, size, (void *) data );
VIPS_DEBUG_MSG( "gif_initialise() = %d\n", result );
#ifdef VERBOSE
print_animation( gif->anim );
#endif /*VERBOSE*/
if( result != GIF_OK &&
result != GIF_WORKING &&
result != GIF_INSUFFICIENT_FRAME_DATA ) {
vips_foreign_load_nsgif_error( gif, result );
return( -1 ); return( -1 );
} }
else if( result == GIF_INSUFFICIENT_FRAME_DATA &&
(void) vips_foreign_load_gif_close_giflib( gif ); load->fail ) {
vips_error( class->nickname, "%s", _( "truncated GIF" ) );
return( 0 );
}
static void
vips_foreign_load_gif_build_cmap( VipsForeignLoadGif *gif )
{
ColorMapObject *map = gif->file->Image.ColorMap ?
gif->file->Image.ColorMap : gif->file->SColorMap;
int v;
for( v = 0; v < 256; v++ ) {
VipsPel *q = (VipsPel *) &gif->cmap[v];
if( map &&
v < map->ColorCount ) {
q[0] = map->Colors[v].Red;
q[1] = map->Colors[v].Green;
q[2] = map->Colors[v].Blue;
q[3] = 255;
}
else {
/* If there's no map, just save the index.
*/
q[0] = v;
q[1] = v;
q[2] = v;
q[3] = 255;
}
}
}
/* Paint line y from the image left/top/width/height into scratch, clipping as
* we go.
*/
static void
vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif,
int y,
int left, int top, int width, int height,
VipsPel *line )
{
VipsRect canvas;
VipsRect row;
VipsRect overlap;
/* Many GIFs have frames which lie outside the canvas. We have to
* clip.
*/
canvas.left = 0;
canvas.top = 0;
canvas.width = gif->file->SWidth;
canvas.height = gif->file->SHeight;
row.left = left;
row.top = top + y;
row.width = width;
row.height = height;
vips_rect_intersectrect( &canvas, &row, &overlap );
if( !vips_rect_isempty( &overlap ) ) {
VipsPel *dst = VIPS_IMAGE_ADDR( gif->scratch,
overlap.left, overlap.top );
guint32 * restrict idst = (guint32 *) dst;
VipsPel * restrict src = line + (overlap.left - row.left);
int x;
for( x = 0; x < overlap.width; x++ ) {
VipsPel v = src[x];
if( v != gif->transparent_index )
idst[x] = gif->cmap[v];
}
}
}
/* Render the current gif frame into an RGBA buffer. GIFs can accumulate,
* depending on the current dispose mode.
*/
static int
vips_foreign_load_gif_render( VipsForeignLoadGif *gif )
{
GifFileType *file = gif->file;
if( DGifGetImageDesc( file ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 ); return( -1 );
} }
/* Update the colour map for this frame. /* Many GIFs have dead frames at the end. Remove these from our count.
*/ */
vips_foreign_load_gif_build_cmap( gif ); for( i = gif->anim->frame_count - 1;
i >= 0 && !gif->anim->frames[i].display; i-- )
;
gif->frame_count_displayable = i + 1;
#ifdef VERBOSE
if( gif->frame_count_displayable != gif->anim->frame_count )
printf( "vips_foreign_load_nsgif_open: "
"removed %d undisplayable frames\n",
gif->anim->frame_count - gif->frame_count_displayable );
#endif /*VERBOSE*/
/* If this is PREVIOUS, then after we're done, we'll need to restore if( !gif->frame_count_displayable ) {
* the frame to what it was previously. Make a note of the current vips_error( class->nickname, "%s", _( "no frames in GIF" ) );
* state. return( -1 );
*/
if( gif->dispose == DISPOSE_PREVIOUS )
memcpy( VIPS_IMAGE_ADDR( gif->previous, 0, 0 ),
VIPS_IMAGE_ADDR( gif->frame, 0, 0 ),
VIPS_IMAGE_SIZEOF_IMAGE( gif->previous ) );
if( file->Image.Interlace ) {
int i;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_render: "
"interlaced frame of %d x %d pixels at %d x %d\n",
file->Image.Width, file->Image.Height,
file->Image.Left, file->Image.Top );
for( i = 0; i < 4; i++ ) {
int y;
for( y = InterlacedOffset[i]; y < file->Image.Height;
y += InterlacedJumps[i] ) {
if( DGifGetLine( gif->file,
gif->line, file->Image.Width ) ==
GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 );
}
vips_foreign_load_gif_render_line( gif, y,
file->Image.Left,
file->Image.Top,
file->Image.Width,
file->Image.Height,
gif->line );
}
}
}
else {
int y;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_render: "
"non-interlaced frame of %d x %d pixels at %d x %d\n",
file->Image.Width, file->Image.Height,
file->Image.Left, file->Image.Top );
for( y = 0; y < file->Image.Height; y++ ) {
if( DGifGetLine( gif->file,
gif->line, file->Image.Width ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 );
}
vips_foreign_load_gif_render_line( gif, y,
file->Image.Left,
file->Image.Top,
file->Image.Width,
file->Image.Height,
gif->line );
}
} }
/* Copy the result to frame, ready to be copied to our output. /* Check for any transparency.
*/ */
memcpy( VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), for( i = 0; i < gif->frame_count_displayable; i++ )
VIPS_IMAGE_ADDR( gif->scratch, 0, 0 ), if( gif->anim->frames[i].transparency ) {
VIPS_IMAGE_SIZEOF_IMAGE( gif->frame ) ); gif->has_transparency = TRUE;
break;
if( gif->dispose == DISPOSE_BACKGROUND ) {
/* BACKGROUND means we reset the area we just painted to
* transparent. We have to clip against the canvas.
*/
VipsRect canvas;
VipsRect image;
VipsRect overlap;
canvas.left = 0;
canvas.top = 0;
canvas.width = gif->file->SWidth;
canvas.height = gif->file->SHeight;
image.left = file->Image.Left,
image.top = file->Image.Top,
image.width = file->Image.Width,
image.height = file->Image.Height,
vips_rect_intersectrect( &canvas, &image, &overlap );
if( !vips_rect_isempty( &overlap ) ) {
guint32 *q = (guint32 *) VIPS_IMAGE_ADDR( gif->scratch,
overlap.left, overlap.top );
/* What we write for transparent pixels. We want RGB
* to be 255, and A to be 0.
*/
guint32 transparent = GUINT32_TO_BE( 0xffffff00 );
int x, y;
/* Generate the first line a pixel at a time,
* memcpy() for subsequent lines.
*/
for( x = 0; x < overlap.width; x++ )
q[x] = transparent;
for( y = 1; y < overlap.height; y++ )
memcpy( q + gif->scratch->Xsize * y,
q,
overlap.width * sizeof( guint32 ) );
} }
}
else if( gif->dispose == DISPOSE_PREVIOUS )
/* PREVIOUS means we restore the previous state of the scratch
* area.
*/
memcpy( VIPS_IMAGE_ADDR( gif->scratch, 0, 0 ),
VIPS_IMAGE_ADDR( gif->previous, 0, 0 ),
VIPS_IMAGE_SIZEOF_IMAGE( gif->scratch ) );
/* Reset values, as the Graphic Control Extension is optional if( gif->n == -1 )
*/ gif->n = gif->frame_count_displayable - gif->page;
gif->dispose = DISPOSAL_UNSPECIFIED;
gif->transparent_index = NO_TRANSPARENT_INDEX;
return( 0 );
}
#ifdef VIPS_DEBUG
static const char *
dispose2str( int dispose )
{
switch( dispose ) {
case DISPOSAL_UNSPECIFIED: return( "DISPOSAL_UNSPECIFIED" );
case DISPOSE_DO_NOT: return( "DISPOSE_DO_NOT" );
case DISPOSE_BACKGROUND: return( "DISPOSE_BACKGROUND" );
case DISPOSE_PREVIOUS: return( "DISPOSE_PREVIOUS" );
default: return( "<unknown>" );
}
}
#endif /*VIPS_DEBUG*/
static int
vips_foreign_load_gif_extension( VipsForeignLoadGif *gif )
{
GifByteType *extension;
int ext_code;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension:\n" );
if( DGifGetExtension( gif->file, &ext_code, &extension ) == if( gif->page < 0 ||
GIF_ERROR ) { gif->n <= 0 ||
vips_foreign_load_gif_error( gif ); gif->page + gif->n > gif->frame_count_displayable ) {
vips_error( class->nickname, "%s", _( "bad page number" ) );
return( -1 ); return( -1 );
} }
if( extension && /* In ms, frame_delay in cs.
ext_code == GRAPHICS_EXT_FUNC_CODE && */
extension[0] == 4 ) { VIPS_FREE( gif->delay );
int flags = extension[1]; if( !(gif->delay = VIPS_ARRAY( NULL,
gif->frame_count_displayable, int )) )
/* Bytes are flags, delay low, delay high, transparency. return( -1 );
* Flag bit 1 means transparency is being set. for( i = 0; i < gif->frame_count_displayable; i++ )
*/ gif->delay[i] = 10 * gif->anim->frames[i].frame_delay;
gif->transparent_index = (flags & TRANSPARENT_MASK) ?
extension[4] : NO_TRANSPARENT_INDEX;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension: "
"transparency = %d\n", gif->transparent_index );
/* Set the current dispose mode. This is read during frame load
* to set the meaning of background and transparent pixels.
*/
gif->dispose = (flags >> DISPOSE_SHIFT) & DISPOSE_MASK;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension: "
"dispose = %s\n", dispose2str( gif->dispose ) );
}
while( extension != NULL )
if( vips_foreign_load_gif_ext_next( gif, &extension ) )
return( -1 );
return( 0 );
}
/* Read the next page from the file into @frame.
*/
static int
vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif )
{
GifRecordType record;
gboolean have_read_frame;
have_read_frame = FALSE;
do {
if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 );
}
switch( record ) {
case IMAGE_DESC_RECORD_TYPE:
VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: "
"IMAGE_DESC_RECORD_TYPE\n" );
if( vips_foreign_load_gif_render( gif ) )
return( -1 );
have_read_frame = TRUE;
break;
case EXTENSION_RECORD_TYPE:
if( vips_foreign_load_gif_extension( gif ) )
return( -1 );
break;
case TERMINATE_RECORD_TYPE:
VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: "
"TERMINATE_RECORD_TYPE\n" );
gif->eof = TRUE;
break;
case SCREEN_DESC_RECORD_TYPE:
VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: "
"SCREEN_DESC_RECORD_TYPE\n" );
break;
case UNDEFINED_RECORD_TYPE: gif->gif_delay = gif->anim->frames[0].frame_delay;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: "
"UNDEFINED_RECORD_TYPE\n" );
break;
default: vips_foreign_load_nsgif_set_header( gif, load->out );
break;
}
} while( !have_read_frame &&
!gif->eof );
return( 0 ); return( 0 );
} }
static int static int
vips_foreign_load_gif_generate( VipsRegion *or, vips_foreign_load_nsgif_generate( VipsRegion *or,
void *seq, void *a, void *b, gboolean *stop ) void *seq, void *a, void *b, gboolean *stop )
{ {
VipsRect *r = &or->valid; VipsRect *r = &or->valid;
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) a; VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) a;
int y; int y;
#ifdef DEBUG_VERBOSE #ifdef VERBOSE
printf( "vips_foreign_load_gif_generate: %p " VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_generate: "
"left = %d, top = %d, width = %d, height = %d\n", "top = %d, height = %d\n", r->top, r->height );
gif, #endif /*VERBOSE*/
r->left, r->top, r->width, r->height );
#endif /*DEBUG_VERBOSE*/
for( y = 0; y < r->height; y++ ) { for( y = 0; y < r->height; y++ ) {
/* The page for this output line, and the line number in page. /* The page for this output line, and the line number in page.
*/ */
int page = (r->top + y) / gif->file->SHeight + gif->page; int page = (r->top + y) / gif->anim->height + gif->page;
int line = (r->top + y) % gif->file->SHeight; int line = (r->top + y) % gif->anim->height;
gif_result result;
VipsPel *p, *q; VipsPel *p, *q;
int x;
g_assert( line >= 0 && line < gif->frame->Ysize ); g_assert( line >= 0 && line < gif->anim->height );
g_assert( page >= 0 && page < gif->n_pages ); g_assert( page >= 0 && page < gif->frame_count_displayable );
/* current_page == 0 means we've not loaded any pages yet. So if( gif->anim->decoded_frame != page ) {
* we need to have loaded the page beyond the page we want. result = gif_decode_frame( gif->anim, page );
*/ VIPS_DEBUG_MSG( " gif_decode_frame(%d) = %d\n",
while( gif->current_page <= page ) { page, result );
if( vips_foreign_load_gif_next_page( gif ) ) if( result != GIF_OK ) {
vips_foreign_load_nsgif_error( gif, result );
return( -1 ); return( -1 );
}
gif->current_page += 1; #ifdef VERBOSE
print_animation( gif->anim );
#endif /*VERBOSE*/
} }
/* @frame is always RGBA, but or may be G, GA, RGB or RGBA. p = gif->anim->frame_image +
* We have to pick out the values we want. line * gif->anim->width * sizeof( int );
*/
p = VIPS_IMAGE_ADDR( gif->frame, 0, line );
q = VIPS_REGION_ADDR( or, 0, r->top + y ); q = VIPS_REGION_ADDR( or, 0, r->top + y );
switch( or->im->Bands ) { if( gif->has_transparency )
case 1: memcpy( q, p, VIPS_REGION_SIZEOF_LINE( or ) );
for( x = 0; x < gif->frame->Xsize; x++ ) { else {
q[0] = p[1]; int i;
q += 1;
p += 4;
}
break;
case 2:
for( x = 0; x < gif->frame->Xsize; x++ ) {
q[0] = p[1];
q[1] = p[3];
q += 2;
p += 4;
}
break;
case 3: for( i = 0; i < r->width; i++ ) {
for( x = 0; x < gif->frame->Xsize; x++ ) {
q[0] = p[0]; q[0] = p[0];
q[1] = p[1]; q[1] = p[1];
q[2] = p[2]; q[2] = p[2];
q += 3; q += 3;
p += 4; p += 4;
} }
break;
case 4:
memcpy( q, p, VIPS_IMAGE_SIZEOF_LINE( gif->frame ) );
break;
default:
g_assert_not_reached();
break;
} }
} }
return( 0 ); return( 0 );
} }
static void
vips_foreign_load_gif_minimise( VipsObject *object, VipsForeignLoadGif *gif )
{
vips_source_minimise( gif->source );
}
static VipsImage *
vips_foreign_load_gif_temp( VipsForeignLoadGif *gif )
{
VipsImage *temp;
temp = vips_image_new_memory();
vips_image_init_fields( temp,
gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
if( vips_image_write_prepare( temp ) ) {
VIPS_UNREF( temp );
return( NULL );
}
return( temp );
}
static int static int
vips_foreign_load_gif_load( VipsForeignLoad *load ) vips_foreign_load_nsgif_load( VipsForeignLoad *load )
{ {
VipsForeignLoadGif *gif = VIPS_FOREIGN_LOAD_GIF( load ); VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) load;
VipsImage **t = (VipsImage **) VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( load ), 4 ); vips_object_local_array( VIPS_OBJECT( load ), 4 );
VIPS_DEBUG_MSG( "vips_foreign_load_gif_load: %p\n", gif ); VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_load:\n" );
if( vips_foreign_load_gif_open_giflib( gif ) )
return( -1 );
/* Set of temp images we use during rendering.
*/
if( !(gif->frame = vips_foreign_load_gif_temp( gif )) ||
!(gif->scratch = vips_foreign_load_gif_temp( gif )) ||
!(gif->previous = vips_foreign_load_gif_temp( gif )) )
return( -1 );
/* Make the output pipeline. /* Make the output pipeline.
*/ */
t[0] = vips_image_new(); t[0] = vips_image_new();
if( vips_foreign_load_gif_set_header( gif, t[0] ) ) if( vips_foreign_load_nsgif_set_header( gif, t[0] ) )
return( -1 ); return( -1 );
/* Close input immediately at end of read.
*/
g_signal_connect( t[0], "minimise",
G_CALLBACK( vips_foreign_load_gif_minimise ), gif );
/* Strips 8 pixels high to avoid too many tiny regions. /* Strips 8 pixels high to avoid too many tiny regions.
*/ */
if( vips_image_generate( t[0], if( vips_image_generate( t[0],
NULL, vips_foreign_load_gif_generate, NULL, gif, NULL ) || NULL, vips_foreign_load_nsgif_generate, NULL, gif, NULL ) ||
vips_sequential( t[0], &t[1], vips_sequential( t[0], &t[1],
"tile_height", VIPS__FATSTRIP_HEIGHT, "tile_height", VIPS__FATSTRIP_HEIGHT,
NULL ) || NULL ) ||
vips_image_write( t[1], load->real ) ) vips_image_write( t[1], load->real ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }
static void static void
vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) vips_foreign_load_nsgif_class_init( VipsForeignLoadNsgifClass *class )
{ {
GObjectClass *gobject_class = G_OBJECT_CLASS( class ); GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class; VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignClass *foreign_class = (VipsForeignClass *) class;
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
gobject_class->dispose = vips_foreign_load_gif_dispose; gobject_class->dispose = vips_foreign_load_nsgif_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 = "gifload_base"; object_class->nickname = "gifload_base";
object_class->description = _( "load GIF with giflib" ); object_class->description = _( "load GIF with libnsgif" );
/* High priority, so that we handle vipsheader etc.
*/
foreign_class->priority = 50;
load_class->header = vips_foreign_load_gif_header;
load_class->load = vips_foreign_load_gif_load;
load_class->get_flags_filename = load_class->get_flags_filename =
vips_foreign_load_gif_get_flags_filename; vips_foreign_load_nsgif_get_flags_filename;
load_class->get_flags = vips_foreign_load_gif_get_flags; load_class->get_flags = vips_foreign_load_nsgif_get_flags;
load_class->header = vips_foreign_load_nsgif_header;
load_class->load = vips_foreign_load_nsgif_load;
VIPS_ARG_INT( class, "page", 20, VIPS_ARG_INT( class, "page", 10,
_( "Page" ), _( "Page" ),
_( "Load this page from the file" ), _( "Load this page from the file" ),
VIPS_ARGUMENT_OPTIONAL_INPUT, VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadGif, page ), G_STRUCT_OFFSET( VipsForeignLoadNsgif, page ),
0, 100000, 0 ); 0, 100000, 0 );
VIPS_ARG_INT( class, "n", 21, VIPS_ARG_INT( class, "n", 6,
_( "n" ), _( "n" ),
_( "Load this many pages" ), _( "Load this many pages" ),
VIPS_ARGUMENT_OPTIONAL_INPUT, VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadGif, n ), G_STRUCT_OFFSET( VipsForeignLoadNsgif, n ),
-1, 100000, 1 ); -1, 100000, 1 );
} }
static void *
vips_foreign_load_nsgif_bitmap_create( int width, int height )
{
/* Enforce max GIF dimensions of 16383 (0x7FFF). This should be enough
* for anyone, and will prevent the worst GIF bombs.
*/
if( width <= 0 ||
width > 16383 ||
height <= 0 ||
height > 16383 ) {
vips_error( "gifload",
"%s", _( "bad image dimensions") );
return( NULL );
}
return g_malloc0( (gsize) width * height * 4 );
}
static void static void
vips_foreign_load_gif_init( VipsForeignLoadGif *gif ) vips_foreign_load_nsgif_bitmap_set_opaque( void *bitmap, bool opaque )
{ {
gif->n = 1; (void) opaque; /* unused */
gif->transparent_index = NO_TRANSPARENT_INDEX; (void) bitmap; /* unused */
gif->delays = NULL; g_assert( bitmap );
gif->delays_length = 0; }
gif->loop = 1;
gif->comment = NULL; static bool
gif->dispose = DISPOSAL_UNSPECIFIED; vips_foreign_load_nsgif_bitmap_test_opaque( void *bitmap )
{
(void) bitmap; /* unused */
g_assert( bitmap );
return( false );
}
vips_foreign_load_gif_allocate_delays( gif ); static unsigned char *
vips_foreign_load_nsgif_bitmap_get_buffer( void *bitmap )
{
g_assert( bitmap );
return( bitmap );
}
static void
vips_foreign_load_nsgif_bitmap_destroy( void *bitmap )
{
g_assert( bitmap );
g_free( bitmap );
}
static void
vips_foreign_load_nsgif_bitmap_modified( void *bitmap )
{
(void) bitmap; /* unused */
g_assert( bitmap );
return;
}
static gif_bitmap_callback_vt vips_foreign_load_nsgif_bitmap_callbacks = {
vips_foreign_load_nsgif_bitmap_create,
vips_foreign_load_nsgif_bitmap_destroy,
vips_foreign_load_nsgif_bitmap_get_buffer,
vips_foreign_load_nsgif_bitmap_set_opaque,
vips_foreign_load_nsgif_bitmap_test_opaque,
vips_foreign_load_nsgif_bitmap_modified
};
static void
vips_foreign_load_nsgif_init( VipsForeignLoadNsgif *gif )
{
gif->anim = g_new0( gif_animation, 1 );
gif_create( gif->anim, &vips_foreign_load_nsgif_bitmap_callbacks );
gif->n = 1;
} }
typedef struct _VipsForeignLoadGifFile { typedef struct _VipsForeignLoadNsgifFile {
VipsForeignLoadGif parent_object; VipsForeignLoadNsgif parent_object;
/* Filename for load. /* Filename for load.
*/ */
char *filename; char *filename;
} VipsForeignLoadGifFile; } VipsForeignLoadNsgifFile;
typedef VipsForeignLoadGifClass VipsForeignLoadGifFileClass; typedef VipsForeignLoadNsgifClass VipsForeignLoadNsgifFileClass;
G_DEFINE_TYPE( VipsForeignLoadGifFile, vips_foreign_load_gif_file, G_DEFINE_TYPE( VipsForeignLoadNsgifFile, vips_foreign_load_nsgif_file,
vips_foreign_load_gif_get_type() ); vips_foreign_load_nsgif_get_type() );
static int static int
vips_foreign_load_gif_file_build( VipsObject *object ) vips_foreign_load_gif_file_build( VipsObject *object )
{ {
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object;
VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) object; VipsForeignLoadNsgifFile *file = (VipsForeignLoadNsgifFile *) object;
if( file->filename ) if( file->filename )
if( !(gif->source = if( !(gif->source =
vips_source_new_from_file( file->filename )) ) vips_source_new_from_file( file->filename )) )
return( -1 ); return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_file_parent_class )-> if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_file_parent_class )->
build( object ) ) build( object ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }
static const char *vips_foreign_gif_suffs[] = { static const char *vips_foreign_nsgif_suffs[] = {
".gif", ".gif",
NULL NULL
}; };
static gboolean static gboolean
vips_foreign_load_gif_file_is_a( const char *filename ) vips_foreign_load_nsgif_file_is_a( const char *filename )
{ {
VipsSource *source; VipsSource *source;
gboolean result; gboolean result;
if( !(source = vips_source_new_from_file( filename )) ) if( !(source = vips_source_new_from_file( filename )) )
return( FALSE ); return( FALSE );
result = vips_foreign_load_gif_is_a_source( source ); result = vips_foreign_load_nsgif_is_a_source( source );
VIPS_UNREF( source ); VIPS_UNREF( source );
return( result ); return( result );
} }
static void static void
vips_foreign_load_gif_file_class_init( vips_foreign_load_nsgif_file_class_init(
VipsForeignLoadGifFileClass *class ) VipsForeignLoadNsgifFileClass *class )
{ {
GObjectClass *gobject_class = G_OBJECT_CLASS( class ); GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class; VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignClass *foreign_class = (VipsForeignClass *) class;
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
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 = "gifload"; object_class->nickname = "gifload";
object_class->description = _( "load GIF with giflib" ); object_class->description = _( "load GIF with libnsgif" );
object_class->build = vips_foreign_load_gif_file_build; object_class->build = vips_foreign_load_gif_file_build;
foreign_class->suffs = vips_foreign_gif_suffs; foreign_class->suffs = vips_foreign_nsgif_suffs;
load_class->is_a = vips_foreign_load_gif_file_is_a; load_class->is_a = vips_foreign_load_nsgif_file_is_a;
VIPS_ARG_STRING( class, "filename", 1, VIPS_ARG_STRING( class, "filename", 1,
_( "Filename" ), _( "Filename" ),
_( "Filename to load from" ), _( "Filename to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT, VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadGifFile, filename ), G_STRUCT_OFFSET( VipsForeignLoadNsgifFile, filename ),
NULL ); NULL );
} }
static void static void
vips_foreign_load_gif_file_init( VipsForeignLoadGifFile *file ) vips_foreign_load_nsgif_file_init( VipsForeignLoadNsgifFile *file )
{ {
} }
typedef struct _VipsForeignLoadGifBuffer { typedef struct _VipsForeignLoadNsgifBuffer {
VipsForeignLoadGif parent_object; VipsForeignLoadNsgif parent_object;
/* Load from a buffer. /* Load from a buffer.
*/ */
VipsArea *blob; VipsArea *blob;
} VipsForeignLoadGifBuffer; } VipsForeignLoadNsgifBuffer;
typedef VipsForeignLoadGifClass VipsForeignLoadGifBufferClass; typedef VipsForeignLoadNsgifClass VipsForeignLoadNsgifBufferClass;
G_DEFINE_TYPE( VipsForeignLoadGifBuffer, vips_foreign_load_gif_buffer, G_DEFINE_TYPE( VipsForeignLoadNsgifBuffer, vips_foreign_load_nsgif_buffer,
vips_foreign_load_gif_get_type() ); vips_foreign_load_nsgif_get_type() );
static int static int
vips_foreign_load_gif_buffer_build( VipsObject *object ) vips_foreign_load_nsgif_buffer_build( VipsObject *object )
{ {
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object;
VipsForeignLoadGifBuffer *buffer = VipsForeignLoadNsgifBuffer *buffer =
(VipsForeignLoadGifBuffer *) object; (VipsForeignLoadNsgifBuffer *) object;
if( buffer->blob && if( buffer->blob &&
!(gif->source = vips_source_new_from_memory( !(gif->source = vips_source_new_from_memory(
VIPS_AREA( buffer->blob )->data, buffer->blob->data,
VIPS_AREA( buffer->blob )->length )) ) buffer->blob->length )) )
return( -1 ); return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_buffer_parent_class )-> if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_buffer_parent_class )->
build( object ) ) build( object ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }
static gboolean static gboolean
vips_foreign_load_gif_buffer_is_a_buffer( const void *buf, size_t len ) vips_foreign_load_nsgif_buffer_is_a_buffer( const void *buf, size_t len )
{ {
VipsSource *source; VipsSource *source;
gboolean result; gboolean result;
if( !(source = vips_source_new_from_memory( buf, len )) ) if( !(source = vips_source_new_from_memory( buf, len )) )
return( FALSE ); return( FALSE );
result = vips_foreign_load_gif_is_a_source( source ); result = vips_foreign_load_nsgif_is_a_source( source );
VIPS_UNREF( source ); VIPS_UNREF( source );
return( result ); return( result );
} }
static void static void
vips_foreign_load_gif_buffer_class_init( vips_foreign_load_nsgif_buffer_class_init(
VipsForeignLoadGifBufferClass *class ) VipsForeignLoadNsgifBufferClass *class )
{ {
GObjectClass *gobject_class = G_OBJECT_CLASS( class ); GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class; VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
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 = "gifload_buffer"; object_class->nickname = "gifload_buffer";
object_class->description = _( "load GIF with giflib" ); object_class->description = _( "load GIF with libnsgif" );
object_class->build = vips_foreign_load_gif_buffer_build; object_class->build = vips_foreign_load_nsgif_buffer_build;
load_class->is_a_buffer = vips_foreign_load_gif_buffer_is_a_buffer; load_class->is_a_buffer = vips_foreign_load_nsgif_buffer_is_a_buffer;
VIPS_ARG_BOXED( class, "buffer", 1, VIPS_ARG_BOXED( class, "buffer", 1,
_( "Buffer" ), _( "Buffer" ),
_( "Buffer to load from" ), _( "Buffer to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT, VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadGifBuffer, blob ), G_STRUCT_OFFSET( VipsForeignLoadNsgifBuffer, blob ),
VIPS_TYPE_BLOB ); VIPS_TYPE_BLOB );
} }
static void static void
vips_foreign_load_gif_buffer_init( VipsForeignLoadGifBuffer *buffer ) vips_foreign_load_nsgif_buffer_init( VipsForeignLoadNsgifBuffer *buffer )
{ {
} }
typedef struct _VipsForeignLoadGifSource { typedef struct _VipsForeignLoadNsgifSource {
VipsForeignLoadGif parent_object; VipsForeignLoadNsgif parent_object;
/* Load from a source. /* Load from a source.
*/ */
VipsSource *source; VipsSource *source;
} VipsForeignLoadGifSource; } VipsForeignLoadNsgifSource;
typedef VipsForeignLoadGifClass VipsForeignLoadGifSourceClass; typedef VipsForeignLoadClass VipsForeignLoadNsgifSourceClass;
G_DEFINE_TYPE( VipsForeignLoadGifSource, vips_foreign_load_gif_source, G_DEFINE_TYPE( VipsForeignLoadNsgifSource, vips_foreign_load_nsgif_source,
vips_foreign_load_gif_get_type() ); vips_foreign_load_nsgif_get_type() );
static int static int
vips_foreign_load_gif_source_build( VipsObject *object ) vips_foreign_load_nsgif_source_build( VipsObject *object )
{ {
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object;
VipsForeignLoadGifSource *source = VipsForeignLoadNsgifSource *source =
(VipsForeignLoadGifSource *) object; (VipsForeignLoadNsgifSource *) object;
if( source->source ) { if( source->source ) {
gif->source = source->source; gif->source = source->source;
g_object_ref( gif->source ); g_object_ref( gif->source );
} }
if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_source_parent_class )-> if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_source_parent_class )->
build( object ) ) build( object ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }
static void static void
vips_foreign_load_gif_source_class_init( vips_foreign_load_nsgif_source_class_init(
VipsForeignLoadGifSourceClass *class ) VipsForeignLoadNsgifSourceClass *class )
{ {
GObjectClass *gobject_class = G_OBJECT_CLASS( class ); GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *object_class = (VipsObjectClass *) class; VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
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 = "gifload_source"; object_class->nickname = "gifload_source";
object_class->description = _( "load GIF with giflib" ); object_class->description = _( "load gif from source" );
object_class->build = vips_foreign_load_gif_source_build; object_class->build = vips_foreign_load_nsgif_source_build;
load_class->is_a_source = vips_foreign_load_gif_is_a_source; load_class->is_a_source = vips_foreign_load_nsgif_is_a_source;
VIPS_ARG_OBJECT( class, "source", 1, VIPS_ARG_OBJECT( class, "source", 1,
_( "Source" ), _( "Source" ),
_( "Source to load from" ), _( "Source to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT, VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadGifSource, source ), G_STRUCT_OFFSET( VipsForeignLoadNsgifSource, source ),
VIPS_TYPE_SOURCE ); VIPS_TYPE_SOURCE );
} }
static void static void
vips_foreign_load_gif_source_init( VipsForeignLoadGifSource *source ) vips_foreign_load_nsgif_source_init( VipsForeignLoadNsgifSource *source )
{ {
} }
#endif /*HAVE_GIFLIB*/ #endif /*HAVE_NSGIF*/
/** /**
* vips_gifload: * vips_gifload:
* @filename: file to load * @filename: file to load
* @out: (out): output image * @out: (out): output image
* @...: %NULL-terminated list of optional named arguments * @...: %NULL-terminated list of optional named arguments
* *
* Optional arguments: * Optional arguments:
* *
* * @page: %gint, page (frame) to read * * @page: %gint, page (frame) to read
* * @n: %gint, load this many pages * * @n: %gint, load this many pages
* *
* Read a GIF file into a libvips image. * Read a GIF file into a libvips image.
* *
* Use @page to select a page to render, numbering from zero. * Use @page to select a page to render, numbering from zero.
* *
* Use @n to select the number of pages to render. The default is 1. Pages are * Use @n to select the number of pages to render. The default is 1. Pages are
* rendered in a vertical column. Set to -1 to mean "until the end of the * rendered in a vertical column. Set to -1 to mean "until the end of the
* document". Use vips_grid() to change page layout. * document". Use vips_grid() to change page layout.
* *
* The output image will be 1, 2, 3 or 4 bands for mono, mono plus * The output image is RGBA for GIFs containing transparent elements, RGB
* transparency, RGB, or RGB plus transparency. * otherwise.
* *
* See also: vips_image_new_from_file(). * See also: vips_image_new_from_file().
* *
* Returns: 0 on success, -1 on error. * Returns: 0 on success, -1 on error.
*/ */
int int
vips_gifload( const char *filename, VipsImage **out, ... ) vips_gifload( const char *filename, VipsImage **out, ... )
{ {
va_list ap; va_list ap;
int result; int result;
 End of changes. 168 change blocks. 
1157 lines changed or deleted 402 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)