diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index cbd2f8f4..0e5188ee 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -7,6 +7,7 @@ libforeign_la_SOURCES = \ quantise.c \ exif.c \ gifload.c \ + gifload2.c \ cairo.c \ pdfload.c \ pdfload_pdfium.c \ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index f6c81e3b..b64536fa 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1832,6 +1832,9 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_load_gif_get_type( void ); extern GType vips_foreign_load_gif_file_get_type( void ); extern GType vips_foreign_load_gif_buffer_get_type( void ); + extern GType vips_foreign_load_gif2_get_type( void ); + extern GType vips_foreign_load_gif2_file_get_type( void ); + extern GType vips_foreign_load_gif2_buffer_get_type( void ); vips_foreign_load_csv_get_type(); vips_foreign_save_csv_get_type(); @@ -1881,6 +1884,9 @@ vips_foreign_operation_init( void ) vips_foreign_load_gif_get_type(); vips_foreign_load_gif_file_get_type(); vips_foreign_load_gif_buffer_get_type(); + vips_foreign_load_gif2_get_type(); + vips_foreign_load_gif2_file_get_type(); + vips_foreign_load_gif2_buffer_get_type(); #endif /*HAVE_GIFLIB*/ #ifdef HAVE_GSF diff --git a/libvips/foreign/gifload2.c b/libvips/foreign/gifload2.c new file mode 100644 index 00000000..3cdaec66 --- /dev/null +++ b/libvips/foreign/gifload2.c @@ -0,0 +1,1283 @@ +/* load a GIF with giflib + * + * 10/2/16 + * - from svgload.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 + */ + +/* + + This file is part of VIPS. + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef HAVE_GIFLIB + +#include + +/* giflib 5 is rather different :-( functions have error returns and there's + * no LastError(). + * + * 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. + */ +#ifndef HAVE_GIFLIB_5 +#define DISPOSAL_UNSPECIFIED 0 +#define DISPOSE_DO_NOT 1 +#define DISPOSE_BACKGROUND 2 +#define DISPOSE_PREVIOUS 3 +#endif + +typedef struct _VipsForeignLoadGif2 { + VipsForeignLoad parent_object; + + /* Load this page (frame number). + */ + int page; + + /* Load this many pages. + */ + int n; + + GifFileType *file; + + /* The current read position, in pages. + */ + int current_page; + + /* Set for EOF detected. + */ + gboolean eof; + + /* As we scan the file, the index of the transparent pixel for this + * frame. + */ + int transparency; + + /* Decompress lines of the gif file to here. + */ + GifPixelType *line; + + /* 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; + + /* 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. + */ + gboolean has_delay; + int delay; + + /* Number of times to loop the animation. + */ + int loop; + + /* The GIF comment, if any. + */ + char *comment; + + /* The current dispose method. + */ + int dispose; + + /* The FILE* we read from. + */ + FILE *fp; + +} VipsForeignLoadGif2; + +typedef VipsForeignLoadClass VipsForeignLoadGif2Class; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadGif2, vips_foreign_load_gif2, + 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 * +vips_foreign_load_gif2_errstr( int error_code ) +{ +#ifdef HAVE_GIFLIB_5 + return( GifErrorString( error_code ) ); +#else /*!HAVE_GIFLIB_5*/ + 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: + return( _( "Data is not a GIF file" ) ); + + case D_GIF_ERR_NO_SCRN_DSCR: + return( _( "No screen descriptor detected" ) ); + + case D_GIF_ERR_NO_IMAG_DSCR: + return( _( "No image descriptor detected" ) ); + + case D_GIF_ERR_NO_COLOR_MAP: + return( _( "Neither global nor local color map" ) ); + + case D_GIF_ERR_WRONG_RECORD: + return( _( "Wrong record type detected" ) ); + + case D_GIF_ERR_DATA_TOO_BIG: + return( _( "Number of pixels bigger than width * height" ) ); + + case D_GIF_ERR_NOT_ENOUGH_MEM: + return( _( "Failed to allocate required memory" ) ); + + case D_GIF_ERR_CLOSE_FAILED: + return( _( "Failed to close given file" ) ); + + 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: + return( _( "Unknown error" ) ); + } +#endif /*HAVE_GIFLIB_5*/ +} + +static void +vips_foreign_load_gif2_error_vips( VipsForeignLoadGif2 *gif, int error ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); + + const char *message; + + if( (message = vips_foreign_load_gif2_errstr( error )) ) + vips_error( class->nickname, "%s", message ); +} + +static void +vips_foreign_load_gif2_error( VipsForeignLoadGif2 *gif ) +{ + int error; + + error = 0; + +#ifdef HAVE_GIFLIB_5 + if( gif->file ) + error = gif->file->Error; +#else + error = GifLastError(); +#endif + + if( error ) + vips_foreign_load_gif2_error_vips( gif, error ); +} + +static void +vips_foreign_load_gif2_close( VipsForeignLoadGif2 *gif ) +{ +#ifdef HAVE_GIFLIB_5 + if( gif->file ) { + int error; + + if( DGifCloseFile( gif->file, &error ) == GIF_ERROR ) + vips_foreign_load_gif2_error_vips( gif, error ); + gif->file = NULL; + } +#else + if( gif->file ) { + if( DGifCloseFile( gif->file ) == GIF_ERROR ) + vips_foreign_load_gif2_error_vips( gif, GifLastError() ); + gif->file = NULL; + } +#endif + + VIPS_FREEF( fclose, gif->fp ); +} + +/* Our input function for file open. We can't use DGifOpenFileName(), since + * that just calls open() and won't work with unicode on win32. We can't use + * DGifOpenFileHandle() since that's an fd from open() and you can't pass those + * acoss DLL boundaries on Windows. + */ +static int +vips_giflib_file_read( GifFileType *file, GifByteType *buffer, int n ) +{ + FILE *fp = (FILE *) file->UserData; + + return( (int) fread( (void *) buffer, 1, n, fp ) ); +} + +static int +vips_foreign_load_gif2_open( VipsForeignLoadGif2 *gif, const char *filename ) +{ + g_assert( !gif->file ); + g_assert( !gif->fp ); + + if( !(gif->fp = vips__file_open_read( filename, NULL, FALSE )) ) + return( -1 ); + +#ifdef HAVE_GIFLIB_5 +{ + int error; + + if( !(gif->file = + DGifOpen( gif->fp, vips_giflib_file_read, &error )) ) { + vips_foreign_load_gif2_error_vips( gif, error ); + return( -1 ); + } +} +#else + if( !(gif->file = DGifOpen( gif->fp, vips_giflib_file_read )) ) { + vips_foreign_load_gif2_error_vips( gif, GifLastError() ); + return( -1 ); + } +#endif + + return( 0 ); +} + +static int +vips_foreign_load_gif2_open_buffer( VipsForeignLoadGif2 *gif, InputFunc read_fn ) +{ + g_assert( !gif->file ); + +#ifdef HAVE_GIFLIB_5 +{ + int error; + + if( !(gif->file = DGifOpen( gif, read_fn, &error )) ) { + vips_foreign_load_gif2_error_vips( gif, error ); + return( -1 ); + } +} +#else + if( !(gif->file = DGifOpen( gif, read_fn )) ) { + vips_foreign_load_gif2_error_vips( gif, GifLastError() ); + return( -1 ); + } +#endif + + return( 0 ); +} + +static void +vips_foreign_load_gif2_dispose( GObject *gobject ) +{ + VipsForeignLoadGif2 *gif = (VipsForeignLoadGif2 *) gobject; + + vips_foreign_load_gif2_close( gif ); + + VIPS_FREE( gif->comment ); + + G_OBJECT_CLASS( vips_foreign_load_gif2_parent_class )-> + dispose( gobject ); +} + +static VipsForeignFlags +vips_foreign_load_gif2_get_flags_filename( const char *filename ) +{ + /* We can render any part of the image on demand. + */ + return( VIPS_FOREIGN_PARTIAL ); +} + +static VipsForeignFlags +vips_foreign_load_gif2_get_flags( VipsForeignLoad *load ) +{ + return( VIPS_FOREIGN_PARTIAL ); +} + +static gboolean +vips_foreign_load_gif2_is_a_buffer( const void *buf, size_t len ) +{ + const guchar *str = (const guchar *) buf; + + if( len >= 4 && + str[0] == 'G' && + str[1] == 'I' && + str[2] == 'F' && + str[3] == '8' ) + return( 1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_gif2_is_a( const char *filename ) +{ + unsigned char buf[4]; + + if( vips__get_bytes( filename, buf, 4 ) == 4 && + vips_foreign_load_gif2_is_a_buffer( buf, 4 ) ) + return( 1 ); + + return( 0 ); +} + +static void +vips_foreign_load_gif2_render_line( VipsForeignLoadGif2 *gif, + int width, VipsPel * restrict q, VipsPel * restrict p ) +{ + ColorMapObject *map = gif->file->Image.ColorMap ? + gif->file->Image.ColorMap : gif->file->SColorMap; + + int x; + + for( x = 0; x < width; x++ ) { + VipsPel v = p[x]; + + if( map && + v < map->ColorCount && + v != gif->transparency ) { + q[0] = map->Colors[v].Red; + q[1] = map->Colors[v].Green; + q[2] = map->Colors[v].Blue; + q[3] = 255; + } + else if( v != gif->transparency ) { + /* If there's no map, just save the index. + */ + q[0] = v; + q[1] = v; + q[2] = v; + q[3] = 255; + } + else if( gif->dispose == DISPOSE_DO_NOT ) { + /* Transparent pixels let the previous frame show + * through, ie., do nothing. + */ + } + else { + /* All other modes are just transparent. + */ + q[0] = 0; + q[1] = 0; + q[2] = 0; + q[3] = 0; + } + + q += 4; + } +} + +/* Render the current gif frame into an RGBA buffer. GIFs can accumulate, + * depending on the current dispose mode. + */ +static int +vips_foreign_load_gif2_render( VipsForeignLoadGif2 *gif, + VipsImage *previous, VipsImage *out ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); + GifFileType *file = gif->file; + ColorMapObject *map = file->Image.ColorMap ? + file->Image.ColorMap : file->SColorMap; + + /* Check that the frame lies within our image. + */ + if( file->Image.Left < 0 || + file->Image.Left + file->Image.Width > out->Xsize || + file->Image.Top < 0 || + file->Image.Top + file->Image.Height > out->Ysize ) { + vips_error( class->nickname, + "%s", _( "frame is outside image area" ) ); + return( -1 ); + } + + /* Check if we have a non-greyscale colourmap for this frame. + */ + 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 ) { + VIPS_DEBUG_MSG( "gifload: not mono\n" ); + gif->has_colour = TRUE; + break; + } + } + + /* We need a line buffer to decompress to. + */ + if( !gif->line ) + if( !(gif->line = VIPS_ARRAY( gif, + gif->file->SWidth, GifPixelType )) ) + return( -1 ); + + if( file->Image.Interlace ) { + int i; + + VIPS_DEBUG_MSG( "gifload: 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] ) { + VipsPel *q = VIPS_IMAGE_ADDR( out, + file->Image.Left, file->Image.Top + y ); + + if( DGifGetLine( gif->file, gif->line, + file->Image.Width ) == GIF_ERROR ) { + vips_foreign_load_gif2_error( gif ); + return( -1 ); + } + + vips_foreign_load_gif2_render_line( gif, + file->Image.Width, q, gif->line ); + } + } + } + else { + int y; + + VIPS_DEBUG_MSG( "gifload: 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++ ) { + VipsPel *q = VIPS_IMAGE_ADDR( out, + file->Image.Left, file->Image.Top + y ); + + if( DGifGetLine( gif->file, gif->line, + file->Image.Width ) == GIF_ERROR ) { + vips_foreign_load_gif2_error( gif ); + return( -1 ); + } + + vips_foreign_load_gif2_render_line( gif, + file->Image.Width, q, gif->line ); + } + } + + return( 0 ); +} + +static int +vips_foreign_load_gif2_extension_next( VipsForeignLoadGif2 *gif, + GifByteType **extension ) +{ + if( DGifGetExtensionNext( gif->file, extension ) == GIF_ERROR ) { + vips_foreign_load_gif2_error( gif ); + return( -1 ); + } + + if( *extension ) + VIPS_DEBUG_MSG( "gifload: EXTENSION_NEXT\n" ); + + return( 0 ); +} + +static int +vips_foreign_load_gif2_graphics_ext( VipsForeignLoadGif2 *gif, + GifByteType *extension ) +{ + VIPS_DEBUG_MSG( "gifload: type: graphics\n" ); + + if( extension[0] == 4 ) { + /* Bytes are flags, delay low, delay high, + * transparency. Flag bit 1 means transparency + * is being set. + */ + gif->transparency = -1; + if( extension[1] & 0x1 ) { + gif->transparency = extension[4]; + gif->has_transparency = TRUE; + VIPS_DEBUG_MSG( "gifload: transparency %d\n", + gif->transparency ); + } + + /* Set the current dispose mode. This is read during frame load + * to set the meaning of background and transparent pixels. + */ + gif->dispose = (extension[1] >> 2) & 0x7; + VIPS_DEBUG_MSG( "gifload: dispose %d\n", gif->dispose ); + + if( !gif->has_delay ) { + gif->has_delay = TRUE; + gif->delay = extension[2] | (extension[3] << 8); + } + } + + while( extension != NULL ) + if( vips_foreign_load_gif2_extension_next( gif, &extension ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_gif2_application_ext( VipsForeignLoadGif2 *gif, + GifByteType *extension ) +{ + gboolean have_netscape; + + VIPS_DEBUG_MSG( "gifload: type: application\n" ); + + /* The 11-byte NETSCAPE extension. + */ + have_netscape = FALSE; + if( extension[0] == 11 && + vips_isprefix( "NETSCAPE2.0", (const char *) (extension + 1) ) ) + have_netscape = TRUE; + + while( extension != NULL ) { + if( vips_foreign_load_gif2_extension_next( gif, &extension ) ) + return( -1 ); + + if( have_netscape && + extension && + extension[0] == 3 && + extension[1] == 1 ) { + gif->loop = extension[2] | (extension[3] << 8); + VIPS_DEBUG_MSG( "gifload: seen loop %d\n", + gif->loop ); + } + } + + return( 0 ); +} + +static int +vips_foreign_load_gif2_comment_ext( VipsForeignLoadGif2 *gif, + GifByteType *extension ) +{ + VIPS_DEBUG_MSG( "gifload: type: comment\n" ); + + if( !gif->comment ) { + /* Up to 257 with a NULL terminator. + */ + char comment[257]; + + vips_strncpy( comment, (char *) (extension + 1), 256 ); + comment[extension[0]] = '\0'; + gif->comment = g_strdup( comment ); + } + + while( extension != NULL ) + if( vips_foreign_load_gif2_extension_next( gif, &extension ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_gif2_extension( VipsForeignLoadGif2 *gif ) +{ + GifByteType *extension; + int ext_code; + + VIPS_DEBUG_MSG( "gifload: EXTENSION_RECORD_TYPE\n" ); + + if( DGifGetExtension( gif->file, &ext_code, &extension ) == + GIF_ERROR ) { + vips_foreign_load_gif2_error( gif ); + return( -1 ); + } + + if( extension ) + switch( ext_code ) { + case GRAPHICS_EXT_FUNC_CODE: + if( vips_foreign_load_gif2_graphics_ext( gif, + extension ) ) + return( -1 ); + break; + + case APPLICATION_EXT_FUNC_CODE: + if( vips_foreign_load_gif2_application_ext( gif, + extension ) ) + return( -1 ); + break; + + case COMMENT_EXT_FUNC_CODE: + if( vips_foreign_load_gif2_comment_ext( gif, extension ) ) + return( -1 ); + break; + + default: + /* Step over any NEXT blocks for unknown extensions. + */ + while( extension != NULL ) + if( vips_foreign_load_gif2_extension_next( gif, + &extension ) ) + return( -1 ); + break; + } + + return( 0 ); +} + +/* Write the next page, if there is one, to @page. Set EOF if we hit the end of + * the file. @page must be a memory image of the right size. @previous is the + * previous frame, if any. + */ +static int +vips_foreign_load_gif2_page( VipsForeignLoadGif2 *gif, + VipsImage *previous, VipsImage *out ) +{ + GifRecordType record; + int n_pages; + + n_pages = 0; + + do { + if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { + vips_foreign_load_gif2_error( gif ); + return( -1 ); + } + + switch( record ) { + case IMAGE_DESC_RECORD_TYPE: + VIPS_DEBUG_MSG( "gifload: IMAGE_DESC_RECORD_TYPE\n" ); + + if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { + vips_foreign_load_gif2_error( gif ); + return( -1 ); + } + + if( vips_foreign_load_gif2_render( gif, previous, out ) ) + return( -1 ); + + n_pages += 1; + + VIPS_DEBUG_MSG( "gifload: page %d\n", + gif->current_page + n_pages ); + + break; + + case EXTENSION_RECORD_TYPE: + if( vips_foreign_load_gif2_extension( gif ) ) + return( -1 ); + break; + + case TERMINATE_RECORD_TYPE: + VIPS_DEBUG_MSG( "gifload: TERMINATE_RECORD_TYPE\n" ); + gif->eof = TRUE; + break; + + case SCREEN_DESC_RECORD_TYPE: + VIPS_DEBUG_MSG( "gifload: SCREEN_DESC_RECORD_TYPE\n" ); + break; + + case UNDEFINED_RECORD_TYPE: + VIPS_DEBUG_MSG( "gifload: UNDEFINED_RECORD_TYPE\n" ); + break; + + default: + break; + } + } while( n_pages < 1 && + !gif->eof ); + + gif->current_page += n_pages; + + return( 0 ); +} + +static VipsImage * +vips_foreign_load_gif2_new_page( VipsForeignLoadGif2 *gif ) +{ + VipsImage *out; + + out = vips_image_new_memory(); + + vips_image_init_fields( out, + gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + + /* We will have the whole GIF frame in memory, so we can render any + * area. + */ + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_ANY, NULL ); + + /* Turn out into a memory image which we then render the GIF frames + * into. + */ + if( vips_image_write_prepare( out ) ) { + g_object_unref( out ); + return( NULL ); + } + + /* Some GIFs may not clear the background, so we must start + * transparent. + */ + memset( VIPS_IMAGE_ADDR( out, 0, 0 ), + 0, + VIPS_IMAGE_SIZEOF_IMAGE( out ) ); + + return( out ); +} + +static void * +unref_object( void *data, void *a, void *b ) +{ + g_object_unref( G_OBJECT( data ) ); + + return( NULL ); +} + +static void +unref_array( GSList *list ) +{ + /* g_slist_free_full() was added in 2.28 and we have to work with + * 2.6 :( + */ + vips_slist_map2( list, unref_object, NULL, NULL ); + g_slist_free( list ); +} + +/* We render each frame to a separate memory image held in a linked + * list, then assemble to out. We don't know the number of frames in advance, + * so we can't just allocate a large area. + */ +static int +vips_foreign_load_gif2_pages( VipsForeignLoadGif2 *gif, VipsImage **out ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); + + GSList *frames; + VipsImage *frame; + VipsImage *previous; + VipsImage **t; + int n_frames; + int i; + + frames = NULL; + previous = NULL; + + /* Accumulate any start stuff up to the first frame we need. + */ + if( !(frame = vips_foreign_load_gif2_new_page( gif )) ) + return( -1 ); + do { + if( vips_foreign_load_gif2_page( gif, NULL, frame ) ) { + g_object_unref( frame ); + return( -1 ); + } + } while( !gif->eof && + gif->current_page <= gif->page ); + + if( gif->eof ) { + vips_error( class->nickname, + "%s", _( "too few frames in GIF file" ) ); + g_object_unref( frame ); + return( -1 ); + } + + frames = g_slist_append( frames, frame ); + previous = frame; + + while( gif->n == -1 || + gif->current_page < gif->page + gif->n ) { + /* We might need a frame for this read to render to. + */ + if( !(frame = vips_foreign_load_gif2_new_page( gif )) ) { + unref_array( frames ); + return( -1 ); + } + + if( gif->dispose == DISPOSE_BACKGROUND ) + /* BACKGROUND means the bg shows through, ie. (in web + * terms) everything is transparent. + */ + memset( VIPS_IMAGE_ADDR( frame, 0, 0 ), + 0, + VIPS_IMAGE_SIZEOF_IMAGE( frame ) ); + else + memcpy( VIPS_IMAGE_ADDR( frame, 0, 0 ), + VIPS_IMAGE_ADDR( previous, 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( frame ) ); + + if( vips_foreign_load_gif2_page( gif, previous, frame ) ) { + g_object_unref( frame ); + unref_array( frames ); + return( -1 ); + } + + if( gif->eof ) { + /* Nope, didn't need the new frame. + */ + g_object_unref( frame ); + break; + } + else { + frames = g_slist_append( frames, frame ); + + /* These two dispose modes set new background frames. + */ + if( gif->dispose == DISPOSAL_UNSPECIFIED || + gif->dispose == DISPOSE_DO_NOT ) + previous = frame; + } + } + + n_frames = g_slist_length( frames ); + + if( gif->eof && + gif->n != -1 && + n_frames < gif->n ) { + unref_array( frames ); + vips_error( class->nickname, + "%s", _( "too few frames in GIF file" ) ); + return( -1 ); + } + + /* We've rendered to a set of memory images ... we can shut down the GIF + * reader now. + */ + vips_foreign_load_gif2_close( gif ); + + if( !(t = VIPS_ARRAY( gif, n_frames, VipsImage * )) ) { + unref_array( frames ); + return( -1 ); + } + + for( i = 0; i < n_frames; i++ ) + t[i] = (VipsImage *) g_slist_nth_data( frames, i ); + + if( vips_arrayjoin( t, out, n_frames, + "across", 1, + NULL ) ) { + unref_array( frames ); + return( -1 ); + } + + unref_array( frames ); + + if( n_frames > 1 ) { + vips_image_set_int( *out, VIPS_META_PAGE_HEIGHT, t[0]->Ysize ); + + /* n-pages is supposed to be the number of pages in the file, + * but we'd need to scan the entire image to find that :( so + * just set it to the number of pages we've read so far. + */ + vips_image_set_int( *out, VIPS_META_N_PAGES, + gif->current_page ); + } + vips_image_set_int( *out, "gif-delay", gif->delay ); + vips_image_set_int( *out, "gif-loop", gif->loop ); + if( gif->comment ) + vips_image_set_string( *out, "gif-comment", gif->comment ); + + return( 0 ); +} + +static int +vips_foreign_load_gif2_load( VipsForeignLoad *load ) +{ + VipsForeignLoadGif2 *gif = (VipsForeignLoadGif2 *) load; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( load ), 4 ); + + VipsImage *im; + + if( vips_foreign_load_gif2_pages( gif, &t[0] ) ) + return( -1 ); + im = t[0]; + + /* Depending on what we found, transform and write to load->real. + */ + if( gif->has_colour && + gif->has_transparency ) { + /* Nothing to do. + */ + } + else if( gif->has_colour ) { + /* RGB. + */ + if( vips_extract_band( im, &t[1], 0, + "n", 3, + NULL ) ) + return( -1 ); + im = t[1]; + } + else if( gif->has_transparency ) { + /* GA. Take BA so we have neighboring channels. + */ + if( vips_extract_band( im, &t[1], 2, + "n", 2, + NULL ) ) + return( -1 ); + im = t[1]; + im->Type = VIPS_INTERPRETATION_B_W; + } + else { + /* G. + */ + if( vips_extract_band( im, &t[1], 0, NULL ) ) + return( -1 ); + im = t[1]; + im->Type = VIPS_INTERPRETATION_B_W; + } + + if( vips_image_write( im, load->out ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_gif2_class_init( VipsForeignLoadGif2Class *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->dispose = vips_foreign_load_gif2_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "gifload_base"; + object_class->description = _( "load GIF with giflib" ); + + load_class->get_flags_filename = + vips_foreign_load_gif2_get_flags_filename; + load_class->get_flags = vips_foreign_load_gif2_get_flags; + + VIPS_ARG_INT( class, "page", 20, + _( "Page" ), + _( "Load this page from the file" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadGif2, page ), + 0, 100000, 0 ); + + VIPS_ARG_INT( class, "n", 21, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadGif2, n ), + -1, 100000, 1 ); + +} + +static void +vips_foreign_load_gif2_init( VipsForeignLoadGif2 *gif ) +{ + gif->n = 1; + gif->transparency = -1; + gif->delay = 4; + gif->loop = 0; + gif->comment = NULL; + gif->dispose = 0; +} + +typedef struct _VipsForeignLoadGif2File { + VipsForeignLoadGif2 parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadGif2File; + +typedef VipsForeignLoadGif2Class VipsForeignLoadGif2FileClass; + +G_DEFINE_TYPE( VipsForeignLoadGif2File, vips_foreign_load_gif2_file, + vips_foreign_load_gif2_get_type() ); + +static int +vips_foreign_load_gif2_file_header( VipsForeignLoad *load ) +{ + VipsForeignLoadGif2 *gif = (VipsForeignLoadGif2 *) load; + VipsForeignLoadGif2File *file = (VipsForeignLoadGif2File *) load; + + if( vips_foreign_load_gif2_open( gif, file->filename ) ) + return( -1 ); + + VIPS_SETSTR( load->out->filename, file->filename ); + + return( vips_foreign_load_gif2_load( load ) ); +} + +static const char *vips_foreign_gif_suffs[] = { + ".gif", + NULL +}; + +static void +vips_foreign_load_gif2_file_class_init( + VipsForeignLoadGif2FileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "gifload2"; + object_class->description = _( "load GIF with giflib" ); + + foreign_class->suffs = vips_foreign_gif_suffs; + + load_class->is_a = vips_foreign_load_gif2_is_a; + load_class->header = vips_foreign_load_gif2_file_header; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadGif2File, filename ), + NULL ); + +} + +static void +vips_foreign_load_gif2_file_init( VipsForeignLoadGif2File *file ) +{ +} + +typedef struct _VipsForeignLoadGif2Buffer { + VipsForeignLoadGif2 parent_object; + + /* Load from a buffer. + */ + VipsArea *buf; + + /* Current read point, bytes left in buffer. + */ + VipsPel *p; + size_t bytes_to_go; + +} VipsForeignLoadGif2Buffer; + +typedef VipsForeignLoadGif2Class VipsForeignLoadGif2BufferClass; + +G_DEFINE_TYPE( VipsForeignLoadGif2Buffer, vips_foreign_load_gif2_buffer, + vips_foreign_load_gif2_get_type() ); + +/* Callback from the gif loader. + * + * Read up to len bytes into buffer, return number of bytes read, 0 for EOF. + */ +static int +vips_giflib_buffer_read( GifFileType *file, GifByteType *buf, int n ) +{ + VipsForeignLoadGif2Buffer *buffer = + (VipsForeignLoadGif2Buffer *) file->UserData; + size_t will_read = VIPS_MIN( n, buffer->bytes_to_go ); + + memcpy( buf, buffer->p, will_read ); + buffer->p += will_read; + buffer->bytes_to_go -= will_read; + + return( will_read ); +} + +static int +vips_foreign_load_gif2_buffer_header( VipsForeignLoad *load ) +{ + VipsForeignLoadGif2 *gif = (VipsForeignLoadGif2 *) load; + VipsForeignLoadGif2Buffer *buffer = (VipsForeignLoadGif2Buffer *) load; + + /* Init the read point. + */ + buffer->p = buffer->buf->data; + buffer->bytes_to_go = buffer->buf->length; + + if( vips_foreign_load_gif2_open_buffer( gif, vips_giflib_buffer_read ) ) + return( -1 ); + + return( vips_foreign_load_gif2_load( load ) ); +} + +static void +vips_foreign_load_gif2_buffer_class_init( + VipsForeignLoadGif2BufferClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "gifload2_buffer"; + object_class->description = _( "load GIF with giflib" ); + + load_class->is_a_buffer = vips_foreign_load_gif2_is_a_buffer; + load_class->header = vips_foreign_load_gif2_buffer_header; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadGif2Buffer, buf ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_load_gif2_buffer_init( VipsForeignLoadGif2Buffer *buffer ) +{ +} + +#endif /*HAVE_GIFLIB*/ + +/** + * vips_gifload: + * @filename: file to load + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @page: %gint, page (frame) to read + * * @n: %gint, load this many pages + * + * Read a GIF file into a VIPS image. + * + * 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 + * rendered in a vertical column, with each individual page aligned to the + * left. Set to -1 to mean "until the end of the document". Use vips_grid() + * to change page layout. + * + * The whole GIF is rendered into memory on header access. The output image + * will be 1, 2, 3 or 4 bands depending on what the reader finds in the file. + * + * See also: vips_image_new_from_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_gifload2( const char *filename, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "gifload2", ap, filename, out ); + va_end( ap ); + + return( result ); +} + +/** + * vips_gifload_buffer: + * @buf: (array length=len) (element-type guint8): memory area to load + * @len: (type gsize): size of memory area + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @page: %gint, page (frame) to read + * * @n: %gint, load this many pages + * + * Read a GIF-formatted memory block into a VIPS image. Exactly as + * vips_gifload(), but read from a memory buffer. + * + * You must not free the buffer while @out is active. The + * #VipsObject::postclose signal on @out is a good place to free. + * + * See also: vips_gifload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_gifload2_buffer( void *buf, size_t len, VipsImage **out, ... ) +{ + va_list ap; + VipsBlob *blob; + int result; + + /* We don't take a copy of the data or free it. + */ + blob = vips_blob_new( NULL, buf, len ); + + va_start( ap, out ); + result = vips_call_split( "gifload2_buffer", ap, blob, out ); + va_end( ap ); + + vips_area_unref( VIPS_AREA( blob ) ); + + return( result ); +} + diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 4711d448..0ae9f6a1 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -560,6 +560,11 @@ int vips_gifload( const char *filename, VipsImage **out, ... ) int vips_gifload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_gifload2( const char *filename, VipsImage **out, ... ) + __attribute__((sentinel)); +int vips_gifload2_buffer( void *buf, size_t len, VipsImage **out, ... ) + __attribute__((sentinel)); + int vips_niftiload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_niftisave( VipsImage *in, const char *filename, ... )