From a454225ef73100a2184815348e6738ac50258fad Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 9 Feb 2019 12:23:21 +0000 Subject: [PATCH 1/9] split off the old loader as gifload2 --- libvips/foreign/Makefile.am | 1 + libvips/foreign/foreign.c | 6 + libvips/foreign/gifload2.c | 1283 ++++++++++++++++++++++++++++++++ libvips/include/vips/foreign.h | 5 + 4 files changed, 1295 insertions(+) create mode 100644 libvips/foreign/gifload2.c 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, ... ) From 9fe013f72d76fd4284509b17263145d34d6d8dd0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 10 Feb 2019 05:26:07 +0000 Subject: [PATCH 2/9] move file stuff into subclass, add rewind --- libvips/foreign/gifload.c | 202 ++++++++++++++++++++++++-------------- 1 file changed, 126 insertions(+), 76 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 58037617..9582591c 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -93,10 +93,29 @@ #define DISPOSE_PREVIOUS 3 #endif +/* TODO: + * + * - initial very quick scan to discover + * - number of pages in file + * - has_transparency; + * - has_colour; + * can we avoid decompression of every pixel? + * + * - once we know this, we can set the header of the output image + * + * - second slow scan driven by _generate to render pages as required, with a + * single page held as a memory image + * + * Try the fast scan idea first -- can we get everything we need quickly? + * + * Need vmethods for open/close/rewind in base class, implement in file and + * buffer. + */ + typedef struct _VipsForeignLoadGif { VipsForeignLoad parent_object; - /* Load this page (frame number). + /* Load from this page (frame number). */ int page; @@ -150,13 +169,16 @@ typedef struct _VipsForeignLoadGif { */ int dispose; - /* The FILE* we read from. - */ - FILE *fp; - } VipsForeignLoadGif; -typedef VipsForeignLoadClass VipsForeignLoadGifClass; +typedef struct _VipsForeignLoadGifClass { + VipsForeignLoadClass parent_class; + + /* Reset the read position. Subclasses must implement this. Close goes + * into _dispose, open is not virtual. + */ + int (*rewind)( VipsForeignLoadGif *gif ); +} VipsForeignLoadGifClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadGif, vips_foreign_load_gif, VIPS_TYPE_FOREIGN_LOAD ); @@ -268,74 +290,6 @@ vips_foreign_load_gif_close( VipsForeignLoadGif *gif ) 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_gif_open( VipsForeignLoadGif *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_gif_error_vips( gif, error ); - return( -1 ); - } -} -#else - if( !(gif->file = DGifOpen( gif->fp, vips_giflib_file_read )) ) { - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - return( -1 ); - } -#endif - - return( 0 ); -} - -static int -vips_foreign_load_gif_open_buffer( VipsForeignLoadGif *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_gif_error_vips( gif, error ); - return( -1 ); - } -} -#else - if( !(gif->file = DGifOpen( gif, read_fn )) ) { - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - return( -1 ); - } -#endif - - return( 0 ); } static void @@ -946,6 +900,26 @@ vips_foreign_load_gif_pages( VipsForeignLoadGif *gif, VipsImage **out ) return( 0 ); } +/* Attempt to quickly scan a GIF and discover: + * - number of frames (pages) + * - mono or colour (check colourmap on each page) + * - has transparency (check bit in ext block on every page) + */ +static int +vips_foreign_load_gif_header( VipsForeignLoad *load ) +{ + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + + printf( "vips_foreign_load_gif_header:\n" ); + + if( class->rewind( gif ) ) + return( -1 ); + + return( 0 ); +} + static int vips_foreign_load_gif_load( VipsForeignLoad *load ) { @@ -955,6 +929,9 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) VipsImage *im; + if( vips_foreign_load_gif_header( load ) ) + return( -1 ); + if( vips_foreign_load_gif_pages( gif, &t[0] ) ) return( -1 ); im = t[0]; @@ -1052,6 +1029,10 @@ typedef struct _VipsForeignLoadGifFile { */ char *filename; + /* The FILE* we read from. + */ + FILE *fp; + } VipsForeignLoadGifFile; typedef VipsForeignLoadGifClass VipsForeignLoadGifFileClass; @@ -1059,20 +1040,70 @@ typedef VipsForeignLoadGifClass VipsForeignLoadGifFileClass; G_DEFINE_TYPE( VipsForeignLoadGifFile, vips_foreign_load_gif_file, vips_foreign_load_gif_get_type() ); +static void +vips_foreign_load_gif_file_dispose( GObject *gobject ) +{ + VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gobject; + + VIPS_FREEF( fclose, file->fp ); + + G_OBJECT_CLASS( vips_foreign_load_gif_file_parent_class )-> + dispose( gobject ); +} + +/* 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 + * across 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_gif_file_header( VipsForeignLoad *load ) { VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) load; - if( vips_foreign_load_gif_open( gif, file->filename ) ) + g_assert( !gif->file ); + g_assert( !file->fp ); + + if( !(file->fp = vips__file_open_read( file->filename, NULL, FALSE )) ) return( -1 ); +#ifdef HAVE_GIFLIB_5 +{ + int error; + + if( !(gif->file = + DGifOpen( file->fp, vips_giflib_file_read, &error )) ) { + vips_foreign_load_gif_error_vips( gif, error ); + return( -1 ); + } +} +#else + if( !(gif->file = DGifOpen( file->fp, vips_giflib_file_read )) ) { + vips_foreign_load_gif_error_vips( gif, GifLastError() ); + return( -1 ); + } +#endif + VIPS_SETSTR( load->out->filename, file->filename ); return( vips_foreign_load_gif_load( load ) ); } +static int +vips_foreign_load_gif_file_rewind( VipsForeignLoadGif *gif ) +{ + return( 0 ); +} + static const char *vips_foreign_gif_suffs[] = { ".gif", NULL @@ -1086,7 +1117,9 @@ vips_foreign_load_gif_file_class_init( VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + VipsForeignLoadGifClass *gif_class = (VipsForeignLoadGifClass *) class; + gobject_class->dispose = vips_foreign_load_gif_file_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; @@ -1098,6 +1131,8 @@ vips_foreign_load_gif_file_class_init( load_class->is_a = vips_foreign_load_gif_is_a; load_class->header = vips_foreign_load_gif_file_header; + gif_class->rewind = vips_foreign_load_gif_file_rewind; + VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to load from" ), @@ -1155,13 +1190,28 @@ vips_foreign_load_gif_buffer_header( VipsForeignLoad *load ) VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) load; + g_assert( !gif->file ); + /* Init the read point. */ buffer->p = buffer->buf->data; buffer->bytes_to_go = buffer->buf->length; - if( vips_foreign_load_gif_open_buffer( gif, vips_giflib_buffer_read ) ) +#ifdef HAVE_GIFLIB_5 +{ + int error; + + if( !(gif->file = DGifOpen( gif, vips_giflib_buffer_read, &error )) ) { + vips_foreign_load_gif_error_vips( gif, error ); return( -1 ); + } +} +#else + if( !(gif->file = DGifOpen( gif, vips_giflib_buffer_read )) ) { + vips_foreign_load_gif_error_vips( gif, GifLastError() ); + return( -1 ); + } +#endif return( vips_foreign_load_gif_load( load ) ); } From 7037377f156ea461690e0472f6036b2889fc42fa Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 10 Feb 2019 22:09:25 +0000 Subject: [PATCH 3/9] add _header scan of whole gif --- libvips/foreign/gifload.c | 523 ++++++++++++++++++++++++-------------- 1 file changed, 329 insertions(+), 194 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 9582591c..ec725c06 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -94,12 +94,6 @@ #endif /* TODO: - * - * - initial very quick scan to discover - * - number of pages in file - * - has_transparency; - * - has_colour; - * can we avoid decompression of every pixel? * * - once we know this, we can set the header of the output image * @@ -112,6 +106,21 @@ * buffer. */ +#define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_gif_get_type()) +#define VIPS_FOREIGN_LOAD_GIF( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGif )) +#define VIPS_FOREIGN_LOAD_GIF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGifClass)) +#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 ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGifClass )) + typedef struct _VipsForeignLoadGif { VipsForeignLoad parent_object; @@ -130,6 +139,9 @@ typedef struct _VipsForeignLoadGif { int current_page; /* Set for EOF detected. + * + * FIXME ... do we need this? + * */ gboolean eof; @@ -169,15 +181,20 @@ typedef struct _VipsForeignLoadGif { */ int dispose; + /* Params for DGifOpen(). Set by subclasses, called by base class in + * _open(). + */ + void *userPtr; + InputFunc read_func; + } VipsForeignLoadGif; typedef struct _VipsForeignLoadGifClass { VipsForeignLoadClass parent_class; - /* Reset the read position. Subclasses must implement this. Close goes - * into _dispose, open is not virtual. + /* Close and reopen gif->file. */ - int (*rewind)( VipsForeignLoadGif *gif ); + int (*open)( VipsForeignLoadGif *gif ); } VipsForeignLoadGifClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadGif, vips_foreign_load_gif, @@ -430,13 +447,6 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, } } - /* 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; @@ -492,8 +502,7 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, } static int -vips_foreign_load_gif_extension_next( VipsForeignLoadGif *gif, - GifByteType **extension ) +vips_foreign_load_gif_next( VipsForeignLoadGif *gif, GifByteType **extension ) { if( DGifGetExtensionNext( gif->file, extension ) == GIF_ERROR ) { vips_foreign_load_gif_error( gif ); @@ -506,99 +515,6 @@ vips_foreign_load_gif_extension_next( VipsForeignLoadGif *gif, return( 0 ); } -static int -vips_foreign_load_gif_graphics_ext( VipsForeignLoadGif *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_gif_extension_next( gif, &extension ) ) - return( -1 ); - - return( 0 ); -} - -static int -vips_foreign_load_gif_application_ext( VipsForeignLoadGif *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_gif_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_gif_comment_ext( VipsForeignLoadGif *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_gif_extension_next( gif, &extension ) ) - return( -1 ); - - return( 0 ); -} - static int vips_foreign_load_gif_extension( VipsForeignLoadGif *gif ) { @@ -613,34 +529,26 @@ vips_foreign_load_gif_extension( VipsForeignLoadGif *gif ) return( -1 ); } - if( extension ) - switch( ext_code ) { - case GRAPHICS_EXT_FUNC_CODE: - if( vips_foreign_load_gif_graphics_ext( gif, - extension ) ) - return( -1 ); - break; + if( extension && + ext_code == GRAPHICS_EXT_FUNC_CODE && + 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]; - case APPLICATION_EXT_FUNC_CODE: - if( vips_foreign_load_gif_application_ext( gif, - extension ) ) - return( -1 ); - break; + /* 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; + } - case COMMENT_EXT_FUNC_CODE: - if( vips_foreign_load_gif_comment_ext( gif, extension ) ) - return( -1 ); - break; - - default: - /* Step over any NEXT blocks for unknown extensions. - */ - while( extension != NULL ) - if( vips_foreign_load_gif_extension_next( gif, - &extension ) ) - return( -1 ); - break; - } + while( extension != NULL ) + if( vips_foreign_load_gif_next( gif, &extension ) ) + return( -1 ); return( 0 ); } @@ -900,6 +808,157 @@ vips_foreign_load_gif_pages( VipsForeignLoadGif *gif, VipsImage **out ) return( 0 ); } +/* Quickly scan an image record. + */ +static int +vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif ) +{ + GifFileType *file = gif->file; + ColorMapObject *map = file->Image.ColorMap ? + file->Image.ColorMap : file->SColorMap; + + GifByteType *dummy; + + /* Test for 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 ) { + printf( "gifload: not mono\n" ); + gif->has_colour = TRUE; + break; + } + } + + /* Step over compressed image data. + */ + do { + if( DGifGetCodeNext( gif->file, &dummy ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } + } while( dummy != NULL ); + + return( 0 ); +} + +static int +vips_foreign_load_gif_scan_application_ext( VipsForeignLoadGif *gif, + GifByteType *extension ) +{ + gboolean have_netscape; + + printf( "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_gif_next( gif, &extension ) ) + return( -1 ); + + if( have_netscape && + extension && + extension[0] == 3 && + extension[1] == 1 ) { + gif->loop = extension[2] | (extension[3] << 8); + printf( "gifload: seen loop %d\n", + gif->loop ); + } + } + + return( 0 ); +} + +static int +vips_foreign_load_gif_scan_comment_ext( VipsForeignLoadGif *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_gif_next( gif, &extension ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) +{ + GifByteType *extension; + int ext_code; + + printf( "gifload: EXTENSION_RECORD_TYPE\n" ); + + 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] & 0x1 ) + gif->has_transparency = TRUE; + + if( !gif->has_delay ) { + gif->has_delay = TRUE; + gif->delay = extension[2] | (extension[3] << 8); + } + + while( extension != NULL ) + if( vips_foreign_load_gif_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_next( gif, + &extension ) ) + return( -1 ); + break; + } + + return( 0 ); +} + /* Attempt to quickly scan a GIF and discover: * - number of frames (pages) * - mono or colour (check colourmap on each page) @@ -912,17 +971,78 @@ vips_foreign_load_gif_header( VipsForeignLoad *load ) (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + GifRecordType record; + int n_pages; + printf( "vips_foreign_load_gif_header:\n" ); - if( class->rewind( gif ) ) + if( class->open( gif ) ) return( -1 ); + printf( "vips_foreign_load_gif_header: starting page scan\n" ); + + n_pages = 0; + + do { + if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } + + switch( record ) { + case IMAGE_DESC_RECORD_TYPE: + printf( "gifload: IMAGE_DESC_RECORD_TYPE\n" ); + + if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } + + /* Read in the image record. + */ + if( vips_foreign_load_gif_scan_image_record( gif ) ) + return( -1 ); + + n_pages += 1; + + break; + + case EXTENSION_RECORD_TYPE: + /* We will need to fetch the extensions to check for + * cmaps and transparency. + */ + if( vips_foreign_load_gif_scan_extension( gif ) ) + return( -1 ); + break; + + case TERMINATE_RECORD_TYPE: + printf( "gifload: TERMINATE_RECORD_TYPE\n" ); + gif->eof = TRUE; + break; + + case SCREEN_DESC_RECORD_TYPE: + printf( "gifload: SCREEN_DESC_RECORD_TYPE\n" ); + break; + + case UNDEFINED_RECORD_TYPE: + printf( "gifload: UNDEFINED_RECORD_TYPE\n" ); + break; + + default: + break; + } + } while( !gif->eof ); + + printf( "vips_foreign_load_gif_header: found %d pages\n", n_pages ); + return( 0 ); } static int vips_foreign_load_gif_load( VipsForeignLoad *load ) { + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( load ), 4 ); @@ -932,6 +1052,11 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) if( vips_foreign_load_gif_header( load ) ) return( -1 ); + if( class->open( gif ) ) + return( -1 ); + + printf( "vips_foreign_load_gif_load:\n" ); + if( vips_foreign_load_gif_pages( gif, &t[0] ) ) return( -1 ); im = t[0]; @@ -977,17 +1102,55 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) return( 0 ); } +static int +vips_foreign_load_gif_open( VipsForeignLoadGif *gif ) +{ + printf( "vips_foreign_load_gif_open:\n" ); + +#ifdef HAVE_GIFLIB_5 +{ + int error; + + if( !(gif->file = DGifOpen( gif->userPtr, gif->read_func, &error )) ) { + vips_foreign_load_gif_error_vips( gif, error ); + return( -1 ); + } +} +#else + if( !(gif->file = DGifOpen( gif->userPtr, gif->read_func )) ) { + vips_foreign_load_gif_error_vips( gif, GifLastError() ); + return( -1 ); + } +#endif + + gif->eof = FALSE; + + /* Allocate a line buffer now that we have the GIF width. + */ + if( !gif->line ) + if( !(gif->line = VIPS_ARRAY( gif, + gif->file->SWidth, GifPixelType )) ) + return( -1 ); + + return( 0 ); +} + static void vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + VipsForeignLoadGifClass *gif_class = (VipsForeignLoadGifClass *) class; gobject_class->dispose = vips_foreign_load_gif_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; + gif_class->open = vips_foreign_load_gif_open; + load_class->header = vips_foreign_load_gif_header; + load_class->load = vips_foreign_load_gif_load; + object_class->nickname = "gifload_base"; object_class->description = _( "load GIF with giflib" ); @@ -1065,43 +1228,29 @@ vips_giflib_file_read( GifFileType *file, GifByteType *buffer, int n ) } static int -vips_foreign_load_gif_file_header( VipsForeignLoad *load ) +vips_foreign_load_gif_file_open( VipsForeignLoadGif *gif ) { - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; - VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) load; + VipsForeignLoad *load = (VipsForeignLoad *) gif; + VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif; - g_assert( !gif->file ); - g_assert( !file->fp ); + printf( "vips_foreign_load_gif_file_open:\n" ); - if( !(file->fp = vips__file_open_read( file->filename, NULL, FALSE )) ) - return( -1 ); + if( !file->fp ) { + if( !(file->fp = + vips__file_open_read( file->filename, NULL, FALSE )) ) + return( -1 ); -#ifdef HAVE_GIFLIB_5 -{ - int error; - - if( !(gif->file = - DGifOpen( file->fp, vips_giflib_file_read, &error )) ) { - vips_foreign_load_gif_error_vips( gif, error ); - return( -1 ); + VIPS_SETSTR( load->out->filename, file->filename ); } -} -#else - if( !(gif->file = DGifOpen( file->fp, vips_giflib_file_read )) ) { - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - return( -1 ); - } -#endif + else + rewind( file->fp ); - VIPS_SETSTR( load->out->filename, file->filename ); + vips_foreign_load_gif_close( gif ); + gif->userPtr = file->fp; + gif->read_func = vips_giflib_file_read; - return( vips_foreign_load_gif_load( load ) ); -} - -static int -vips_foreign_load_gif_file_rewind( VipsForeignLoadGif *gif ) -{ - return( 0 ); + return( VIPS_FOREIGN_LOAD_GIF_CLASS( + vips_foreign_load_gif_file_parent_class )->open( gif ) ); } static const char *vips_foreign_gif_suffs[] = { @@ -1129,9 +1278,8 @@ vips_foreign_load_gif_file_class_init( foreign_class->suffs = vips_foreign_gif_suffs; load_class->is_a = vips_foreign_load_gif_is_a; - load_class->header = vips_foreign_load_gif_file_header; - gif_class->rewind = vips_foreign_load_gif_file_rewind; + gif_class->open = vips_foreign_load_gif_file_open; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), @@ -1185,35 +1333,20 @@ vips_giflib_buffer_read( GifFileType *file, GifByteType *buf, int n ) } static int -vips_foreign_load_gif_buffer_header( VipsForeignLoad *load ) +vips_foreign_load_gif_buffer_open( VipsForeignLoadGif *gif ) { - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; - VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) load; + VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) gif; - g_assert( !gif->file ); + printf( "vips_foreign_load_gif_buffer_open:\n" ); - /* Init the read point. - */ + vips_foreign_load_gif_close( gif ); buffer->p = buffer->buf->data; buffer->bytes_to_go = buffer->buf->length; + gif->userPtr = gif; + gif->read_func = vips_giflib_buffer_read;; -#ifdef HAVE_GIFLIB_5 -{ - int error; - - if( !(gif->file = DGifOpen( gif, vips_giflib_buffer_read, &error )) ) { - vips_foreign_load_gif_error_vips( gif, error ); - return( -1 ); - } -} -#else - if( !(gif->file = DGifOpen( gif, vips_giflib_buffer_read )) ) { - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - return( -1 ); - } -#endif - - return( vips_foreign_load_gif_load( load ) ); + return( VIPS_FOREIGN_LOAD_GIF_CLASS( + vips_foreign_load_gif_file_parent_class )->open( gif ) ); } static void @@ -1223,6 +1356,7 @@ vips_foreign_load_gif_buffer_class_init( GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + VipsForeignLoadGifClass *gif_class = (VipsForeignLoadGifClass *) class; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; @@ -1231,7 +1365,8 @@ vips_foreign_load_gif_buffer_class_init( object_class->description = _( "load GIF with giflib" ); load_class->is_a_buffer = vips_foreign_load_gif_is_a_buffer; - load_class->header = vips_foreign_load_gif_buffer_header; + + gif_class->open = vips_foreign_load_gif_buffer_open; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), From e12b44e7904478e6a611e4618fed95fc85877b80 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 11 Feb 2019 08:38:43 +0000 Subject: [PATCH 4/9] fast header method works need to rework _load next --- libvips/foreign/gifload.c | 586 +++++++++++++++++++++----------------- 1 file changed, 318 insertions(+), 268 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index ec725c06..379dda6f 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -134,26 +134,6 @@ typedef struct _VipsForeignLoadGif { GifFileType *file; - /* The current read position, in pages. - */ - int current_page; - - /* Set for EOF detected. - * - * FIXME ... do we need this? - * - */ - 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 @@ -177,10 +157,35 @@ typedef struct _VipsForeignLoadGif { */ char *comment; + /* A memory image the sized of one frame ... we accumulate to this as + * we scan the image, and copy lines to the output on generate. + */ + VipsImage *frame; + + /* The position of @frame, in pages. + */ + int current_page; + + /* Decompress lines of the gif file to here. + */ + GifPixelType *line; + /* The current dispose method. */ int dispose; + /* Set for EOF detected. + * + * FIXME ... do we need this? + * + */ + gboolean eof; + + /* As we scan the file, the index of the transparent pixel for this + * frame. + */ + int transparency; + /* Params for DGifOpen(). Set by subclasses, called by base class in * _open(). */ @@ -317,6 +322,7 @@ vips_foreign_load_gif_dispose( GObject *gobject ) vips_foreign_load_gif_close( gif ); VIPS_FREE( gif->comment ); + VIPS_UNREF( gif->frame ); G_OBJECT_CLASS( vips_foreign_load_gif_parent_class )-> dispose( gobject ); @@ -363,6 +369,297 @@ vips_foreign_load_gif_is_a( const char *filename ) return( 0 ); } +static int +vips_foreign_load_gif_ext_next( VipsForeignLoadGif *gif, + GifByteType **extension ) +{ + if( DGifGetExtensionNext( gif->file, extension ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } + + if( *extension ) + VIPS_DEBUG_MSG( "gifload: EXTENSION_NEXT\n" ); + + 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 ); + } + + if( *extension ) + VIPS_DEBUG_MSG( "gifload: CODE_NEXT\n" ); + + return( 0 ); +} + +/* Quickly scan an image record. + */ +static int +vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif ) +{ + GifFileType *file = gif->file; + ColorMapObject *map = file->Image.ColorMap ? + file->Image.ColorMap : file->SColorMap; + + GifByteType *extension; + + /* Test for 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 ) { + printf( "gifload: not mono\n" ); + 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; + + printf( "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_gif_ext_next( gif, &extension ) ) + return( -1 ); + + if( have_netscape && + extension && + extension[0] == 3 && + extension[1] == 1 ) { + gif->loop = extension[2] | (extension[3] << 8); + printf( "gifload: seen loop %d\n", + gif->loop ); + } + } + + return( 0 ); +} + +static int +vips_foreign_load_gif_scan_comment_ext( VipsForeignLoadGif *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_gif_ext_next( gif, &extension ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) +{ + GifByteType *extension; + int ext_code; + + printf( "gifload: EXTENSION_RECORD_TYPE\n" ); + + 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] & 0x1 ) + gif->has_transparency = TRUE; + + if( !gif->has_delay ) { + gif->has_delay = TRUE; + gif->delay = extension[2] | (extension[3] << 8); + } + + 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 ); +} + +/* Attempt to quickly scan a GIF and discover: + * - number of frames (pages) + * - mono or colour (check colourmap on each page) + * - has transparency (check bit in ext block on every page) + */ +static int +vips_foreign_load_gif_header( VipsForeignLoad *load ) +{ + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + + GifRecordType record; + int n_pages; + + printf( "vips_foreign_load_gif_header:\n" ); + + if( class->open( gif ) ) + return( -1 ); + + printf( "vips_foreign_load_gif_header: starting page scan\n" ); + + n_pages = 0; + + do { + if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } + + switch( record ) { + case IMAGE_DESC_RECORD_TYPE: + printf( "gifload: IMAGE_DESC_RECORD_TYPE\n" ); + + if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } + + /* Read in the image record. + */ + if( vips_foreign_load_gif_scan_image_record( gif ) ) + return( -1 ); + + n_pages += 1; + + break; + + case EXTENSION_RECORD_TYPE: + /* We will need to fetch the extensions to check for + * cmaps and transparency. + */ + if( vips_foreign_load_gif_scan_extension( gif ) ) + return( -1 ); + break; + + case TERMINATE_RECORD_TYPE: + printf( "gifload: TERMINATE_RECORD_TYPE\n" ); + gif->eof = TRUE; + break; + + case SCREEN_DESC_RECORD_TYPE: + printf( "gifload: SCREEN_DESC_RECORD_TYPE\n" ); + break; + + case UNDEFINED_RECORD_TYPE: + printf( "gifload: UNDEFINED_RECORD_TYPE\n" ); + break; + + default: + break; + } + } while( !gif->eof ); + + printf( "vips_foreign_load_gif_header: found %d pages\n", n_pages ); + + /* Make the memory image we accumulate pixels in. We always accumulate + * to RGBA, then trim down to whatever the output image needs on + * _generate. + */ + gif->frame = vips_image_new_memory(); + vips_image_init_fields( gif->frame, + gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + vips_image_pipelinev( gif->frame, VIPS_DEMAND_STYLE_ANY, NULL ); + if( vips_image_write_prepare( gif->frame ) ) + return( -1 ); + + /* The real output image is long, and made on demand in _generate. + */ + vips_image_init_fields( load->out, + gif->file->SWidth, gif->file->SHeight * n_pages, + (gif->has_colour ? 3 : 1) + (gif->has_transparency ? 1 : 0), + VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, + gif->has_colour ? + VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W, + 1.0, 1.0 ); + vips_image_pipelinev( load->out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + + if( n_pages > 1 ) { + vips_image_set_int( load->out, + VIPS_META_PAGE_HEIGHT, gif->file->SHeight ); + vips_image_set_int( load->out, VIPS_META_N_PAGES, n_pages ); + } + vips_image_set_int( load->out, "gif-delay", gif->delay ); + vips_image_set_int( load->out, "gif-loop", gif->loop ); + if( gif->comment ) + vips_image_set_string( load->out, "gif-comment", gif->comment ); + + return( 0 ); +} + static void vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, int width, VipsPel * restrict q, VipsPel * restrict p ) @@ -501,20 +798,6 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, return( 0 ); } -static int -vips_foreign_load_gif_next( VipsForeignLoadGif *gif, GifByteType **extension ) -{ - if( DGifGetExtensionNext( gif->file, extension ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - if( *extension ) - VIPS_DEBUG_MSG( "gifload: EXTENSION_NEXT\n" ); - - return( 0 ); -} - static int vips_foreign_load_gif_extension( VipsForeignLoadGif *gif ) { @@ -547,7 +830,7 @@ vips_foreign_load_gif_extension( VipsForeignLoadGif *gif ) } while( extension != NULL ) - if( vips_foreign_load_gif_next( gif, &extension ) ) + if( vips_foreign_load_gif_ext_next( gif, &extension ) ) return( -1 ); return( 0 ); @@ -808,236 +1091,6 @@ vips_foreign_load_gif_pages( VipsForeignLoadGif *gif, VipsImage **out ) return( 0 ); } -/* Quickly scan an image record. - */ -static int -vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif ) -{ - GifFileType *file = gif->file; - ColorMapObject *map = file->Image.ColorMap ? - file->Image.ColorMap : file->SColorMap; - - GifByteType *dummy; - - /* Test for 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 ) { - printf( "gifload: not mono\n" ); - gif->has_colour = TRUE; - break; - } - } - - /* Step over compressed image data. - */ - do { - if( DGifGetCodeNext( gif->file, &dummy ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - } while( dummy != NULL ); - - return( 0 ); -} - -static int -vips_foreign_load_gif_scan_application_ext( VipsForeignLoadGif *gif, - GifByteType *extension ) -{ - gboolean have_netscape; - - printf( "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_gif_next( gif, &extension ) ) - return( -1 ); - - if( have_netscape && - extension && - extension[0] == 3 && - extension[1] == 1 ) { - gif->loop = extension[2] | (extension[3] << 8); - printf( "gifload: seen loop %d\n", - gif->loop ); - } - } - - return( 0 ); -} - -static int -vips_foreign_load_gif_scan_comment_ext( VipsForeignLoadGif *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_gif_next( gif, &extension ) ) - return( -1 ); - - return( 0 ); -} - -static int -vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) -{ - GifByteType *extension; - int ext_code; - - printf( "gifload: EXTENSION_RECORD_TYPE\n" ); - - 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] & 0x1 ) - gif->has_transparency = TRUE; - - if( !gif->has_delay ) { - gif->has_delay = TRUE; - gif->delay = extension[2] | (extension[3] << 8); - } - - while( extension != NULL ) - if( vips_foreign_load_gif_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_next( gif, - &extension ) ) - return( -1 ); - break; - } - - return( 0 ); -} - -/* Attempt to quickly scan a GIF and discover: - * - number of frames (pages) - * - mono or colour (check colourmap on each page) - * - has transparency (check bit in ext block on every page) - */ -static int -vips_foreign_load_gif_header( VipsForeignLoad *load ) -{ - VipsForeignLoadGifClass *class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; - - GifRecordType record; - int n_pages; - - printf( "vips_foreign_load_gif_header:\n" ); - - if( class->open( gif ) ) - return( -1 ); - - printf( "vips_foreign_load_gif_header: starting page scan\n" ); - - n_pages = 0; - - do { - if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - switch( record ) { - case IMAGE_DESC_RECORD_TYPE: - printf( "gifload: IMAGE_DESC_RECORD_TYPE\n" ); - - if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - /* Read in the image record. - */ - if( vips_foreign_load_gif_scan_image_record( gif ) ) - return( -1 ); - - n_pages += 1; - - break; - - case EXTENSION_RECORD_TYPE: - /* We will need to fetch the extensions to check for - * cmaps and transparency. - */ - if( vips_foreign_load_gif_scan_extension( gif ) ) - return( -1 ); - break; - - case TERMINATE_RECORD_TYPE: - printf( "gifload: TERMINATE_RECORD_TYPE\n" ); - gif->eof = TRUE; - break; - - case SCREEN_DESC_RECORD_TYPE: - printf( "gifload: SCREEN_DESC_RECORD_TYPE\n" ); - break; - - case UNDEFINED_RECORD_TYPE: - printf( "gifload: UNDEFINED_RECORD_TYPE\n" ); - break; - - default: - break; - } - } while( !gif->eof ); - - printf( "vips_foreign_load_gif_header: found %d pages\n", n_pages ); - - return( 0 ); -} - static int vips_foreign_load_gif_load( VipsForeignLoad *load ) { @@ -1049,9 +1102,6 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) VipsImage *im; - if( vips_foreign_load_gif_header( load ) ) - return( -1 ); - if( class->open( gif ) ) return( -1 ); From 617d910379e8ae51f9f38c71dabefa700965076f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 13 Feb 2019 18:04:30 +0000 Subject: [PATCH 5/9] almost loading --- libvips/foreign/gifload.c | 454 +++++++++++--------------------------- 1 file changed, 129 insertions(+), 325 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 379dda6f..59847b4f 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -50,8 +50,8 @@ */ /* -#define VIPS_DEBUG */ +#define VIPS_DEBUG #ifdef HAVE_CONFIG_H #include @@ -93,19 +93,6 @@ #define DISPOSE_PREVIOUS 3 #endif -/* TODO: - * - * - once we know this, we can set the header of the output image - * - * - second slow scan driven by _generate to render pages as required, with a - * single page held as a memory image - * - * Try the fast scan idea first -- can we get everything we need quickly? - * - * Need vmethods for open/close/rewind in base class, implement in file and - * buffer. - */ - #define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_gif_get_type()) #define VIPS_FOREIGN_LOAD_GIF( obj ) \ (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ @@ -157,6 +144,10 @@ typedef struct _VipsForeignLoadGif { */ char *comment; + /* The number of pages (frame) in the image. + */ + int n_pages; + /* A memory image the sized of one frame ... we accumulate to this as * we scan the image, and copy lines to the output on generate. */ @@ -404,12 +395,28 @@ vips_foreign_load_gif_code_next( VipsForeignLoadGif *gif, static int vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); GifFileType *file = gif->file; ColorMapObject *map = file->Image.ColorMap ? file->Image.ColorMap : file->SColorMap; GifByteType *extension; + /* Check that the frame looks sane. Perhaps giflib checks + * this for us. + */ + if( file->Image.Left < 0 || + file->Image.Width < 1 || + file->Image.Width > 10000 || + file->Image.Left + file->Image.Width > file->SWidth || + file->Image.Top < 0 || + file->Image.Height < 1 || + file->Image.Height > 10000 || + file->Image.Top + file->Image.Height > file->SHeight ) { + vips_error( class->nickname, "%s", _( "bad frame size" ) ); + return( -1 ); + } + /* Test for a non-greyscale colourmap for this frame. */ if( !gif->has_colour && @@ -508,10 +515,13 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) switch( ext_code ) { case GRAPHICS_EXT_FUNC_CODE: if( extension[0] == 4 && - extension[1] & 0x1 ) + extension[1] & 0x1 ) { + VIPS_DEBUG_MSG( "gifload: has transp.\n" ); 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); } @@ -548,29 +558,52 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) return( 0 ); } -/* Attempt to quickly scan a GIF and discover: - * - number of frames (pages) - * - mono or colour (check colourmap on each page) - * - has transparency (check bit in ext block on every page) +static int +vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image ) +{ + vips_image_init_fields( image, + gif->file->SWidth, gif->file->SHeight * gif->n, + (gif->has_colour ? 3 : 1) + (gif->has_transparency ? 1 : 0), + VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, + gif->has_colour ? + VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W, + 1.0, 1.0 ); + vips_image_pipelinev( image, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + + if( gif->n_pages > 1 ) { + 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->comment ) + vips_image_set_string( image, "gif-comment", gif->comment ); + + return( 0 ); +} + +/* Attempt to quickly scan a GIF and discover what we need for our header. We + * need to scan the whole file to get n_pages, transparency and colour. */ static int vips_foreign_load_gif_header( VipsForeignLoad *load ) { - VipsForeignLoadGifClass *class = + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadGifClass *gif_class = (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; GifRecordType record; - int n_pages; printf( "vips_foreign_load_gif_header:\n" ); - if( class->open( gif ) ) + if( gif_class->open( gif ) ) return( -1 ); printf( "vips_foreign_load_gif_header: starting page scan\n" ); - n_pages = 0; + gif->n_pages = 0; do { if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { @@ -592,7 +625,7 @@ vips_foreign_load_gif_header( VipsForeignLoad *load ) if( vips_foreign_load_gif_scan_image_record( gif ) ) return( -1 ); - n_pages += 1; + gif->n_pages += 1; break; @@ -622,40 +655,23 @@ vips_foreign_load_gif_header( VipsForeignLoad *load ) } } while( !gif->eof ); - printf( "vips_foreign_load_gif_header: found %d pages\n", n_pages ); + printf( "vips_foreign_load_gif_header: found %d pages\n", + gif->n_pages ); - /* Make the memory image we accumulate pixels in. We always accumulate - * to RGBA, then trim down to whatever the output image needs on - * _generate. - */ - gif->frame = vips_image_new_memory(); - vips_image_init_fields( gif->frame, - gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); - vips_image_pipelinev( gif->frame, VIPS_DEMAND_STYLE_ANY, NULL ); - if( vips_image_write_prepare( gif->frame ) ) - return( -1 ); + if( gif->n == -1 ) + gif->n = gif->n_pages - gif->page; - /* The real output image is long, and made on demand in _generate. - */ - vips_image_init_fields( load->out, - gif->file->SWidth, gif->file->SHeight * n_pages, - (gif->has_colour ? 3 : 1) + (gif->has_transparency ? 1 : 0), - VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, - gif->has_colour ? - VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W, - 1.0, 1.0 ); - vips_image_pipelinev( load->out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); - - if( n_pages > 1 ) { - vips_image_set_int( load->out, - VIPS_META_PAGE_HEIGHT, gif->file->SHeight ); - vips_image_set_int( load->out, VIPS_META_N_PAGES, n_pages ); + if( gif->page < 0 || + gif->n <= 0 || + gif->page + gif->n > gif->n_pages ) { + vips_error( class->nickname, "%s", _( "bad page number" ) ); + return( -1 ); } - vips_image_set_int( load->out, "gif-delay", gif->delay ); - vips_image_set_int( load->out, "gif-loop", gif->loop ); - if( gif->comment ) - vips_image_set_string( load->out, "gif-comment", gif->comment ); + + /* And set the output vips header from what we've learned. + */ + if( vips_foreign_load_gif_set_header( gif, load->out ) ) + return( -1 ); return( 0 ); } @@ -710,39 +726,9 @@ vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, * depending on the current dispose mode. */ static int -vips_foreign_load_gif_render( VipsForeignLoadGif *gif, - VipsImage *previous, VipsImage *out ) +vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) { - 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; - } - } if( file->Image.Interlace ) { int i; @@ -758,7 +744,7 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, for( y = InterlacedOffset[i]; y < file->Image.Height; y += InterlacedJumps[i] ) { - VipsPel *q = VIPS_IMAGE_ADDR( out, + VipsPel *q = VIPS_IMAGE_ADDR( gif->frame, file->Image.Left, file->Image.Top + y ); if( DGifGetLine( gif->file, gif->line, @@ -781,7 +767,7 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, file->Image.Left, file->Image.Top ); for( y = 0; y < file->Image.Height; y++ ) { - VipsPel *q = VIPS_IMAGE_ADDR( out, + VipsPel *q = VIPS_IMAGE_ADDR( gif->frame, file->Image.Left, file->Image.Top + y ); if( DGifGetLine( gif->file, gif->line, @@ -836,19 +822,15 @@ vips_foreign_load_gif_extension( VipsForeignLoadGif *gif ) 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. +/* Read the next page from the file into @frame. */ static int -vips_foreign_load_gif_page( VipsForeignLoadGif *gif, - VipsImage *previous, VipsImage *out ) +vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif ) { GifRecordType record; - int n_pages; - - n_pages = 0; + gboolean have_read_frame; + have_read_frame = FALSE; do { if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { vips_foreign_load_gif_error( gif ); @@ -864,13 +846,10 @@ vips_foreign_load_gif_page( VipsForeignLoadGif *gif, return( -1 ); } - if( vips_foreign_load_gif_render( gif, previous, out ) ) + if( vips_foreign_load_gif_render( gif ) ) return( -1 ); - n_pages += 1; - - VIPS_DEBUG_MSG( "gifload: page %d\n", - gif->current_page + n_pages ); + have_read_frame = TRUE; break; @@ -895,198 +874,40 @@ vips_foreign_load_gif_page( VipsForeignLoadGif *gif, default: break; } - } while( n_pages < 1 && + } while( !have_read_frame && !gif->eof ); - gif->current_page += n_pages; - return( 0 ); } -static VipsImage * -vips_foreign_load_gif_new_page( VipsForeignLoadGif *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_gif_pages( VipsForeignLoadGif *gif, VipsImage **out ) +vips_foreign_load_gif_generate( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); + VipsRect *r = &or->valid; + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) a; - 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. + /* The page for this generate, and the line number in page. */ - if( !(frame = vips_foreign_load_gif_new_page( gif )) ) - return( -1 ); - do { - if( vips_foreign_load_gif_page( gif, NULL, frame ) ) { - g_object_unref( frame ); + int page = r->top / gif->file->SHeight + gif->page; + int line = r->top % gif->file->SHeight; + +#ifdef DEBUG_VERBOSE + printf( "vips_foreign_load_gif_generate: line %d\n", r->top ); +#endif /*DEBUG_VERBOSE*/ + + g_assert( r->height == 1 ); + + while( gif->current_page < page ) { + if( vips_foreign_load_gif_next_page( gif ) ) 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 ); + gif->current_page += 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_gif_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_gif_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_gif_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 ); + memcpy( VIPS_REGION_ADDR( or, 0, r->top ), + VIPS_IMAGE_ADDR( gif->frame, 0, line ), + VIPS_IMAGE_SIZEOF_LINE( gif->frame ) ); return( 0 ); } @@ -1100,53 +921,35 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( load ), 4 ); - VipsImage *im; + printf( "vips_foreign_load_gif_load:\n" ); + /* Rewind. + */ if( class->open( gif ) ) return( -1 ); - printf( "vips_foreign_load_gif_load:\n" ); - - if( vips_foreign_load_gif_pages( gif, &t[0] ) ) - return( -1 ); - im = t[0]; - - /* Depending on what we found, transform and write to load->real. + /* Make the memory image we accumulate pixels in. We always accumulate + * to RGBA, then trim down to whatever the output image needs on + * _generate. */ - 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; - } + gif->frame = vips_image_new_memory(); + vips_image_init_fields( gif->frame, + gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + vips_image_pipelinev( gif->frame, VIPS_DEMAND_STYLE_ANY, NULL ); + if( vips_image_write_prepare( gif->frame ) ) + return( -1 ); - if( vips_image_write( im, load->out ) ) + /* Make the output pipeline. + */ + t[0] = vips_image_new(); + if( vips_foreign_load_gif_set_header( gif, t[0] ) ) + return( -1 ); + + if( vips_image_generate( t[0], + NULL, vips_foreign_load_gif_generate, NULL, gif, NULL ) || + vips_sequential( t[0], &t[1], NULL ) || + vips_image_write( t[1], load->real ) ) return( -1 ); return( 0 ); @@ -1174,13 +977,14 @@ vips_foreign_load_gif_open( VipsForeignLoadGif *gif ) #endif gif->eof = FALSE; + gif->current_page = 0; /* Allocate a line buffer now that we have the GIF width. */ - if( !gif->line ) - if( !(gif->line = VIPS_ARRAY( gif, - gif->file->SWidth, GifPixelType )) ) - return( -1 ); + VIPS_FREE( gif->line ) + if( !(gif->line = VIPS_ARRAY( gif, + gif->file->SWidth, GifPixelType )) ) + return( -1 ); return( 0 ); } From b02a50aaed825e39d012c62361470b79158a9d31 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 14 Feb 2019 10:13:17 +0000 Subject: [PATCH 6/9] two small fixes --- libvips/foreign/gifload.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 59847b4f..f6133fca 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -50,8 +50,8 @@ */ /* - */ #define VIPS_DEBUG + */ #ifdef HAVE_CONFIG_H #include @@ -322,15 +322,13 @@ vips_foreign_load_gif_dispose( GObject *gobject ) static VipsForeignFlags vips_foreign_load_gif_get_flags_filename( const char *filename ) { - /* We can render any part of the image on demand. - */ - return( VIPS_FOREIGN_PARTIAL ); + return( VIPS_FOREIGN_SEQUENTIAL ); } static VipsForeignFlags vips_foreign_load_gif_get_flags( VipsForeignLoad *load ) { - return( VIPS_FOREIGN_PARTIAL ); + return( VIPS_FOREIGN_SEQUENTIAL ); } static gboolean @@ -730,6 +728,8 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) { GifFileType *file = gif->file; + printf( "vips_foreign_load_gif_render:\n" ); + if( file->Image.Interlace ) { int i; @@ -830,6 +830,8 @@ vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif ) GifRecordType record; gboolean have_read_frame; + printf( "vips_foreign_load_gif_next_page:\n" ); + have_read_frame = FALSE; do { if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { @@ -893,12 +895,17 @@ vips_foreign_load_gif_generate( VipsRegion *or, int line = r->top % gif->file->SHeight; #ifdef DEBUG_VERBOSE - printf( "vips_foreign_load_gif_generate: line %d\n", r->top ); #endif /*DEBUG_VERBOSE*/ + printf( "vips_foreign_load_gif_generate: line %d\n", r->top ); g_assert( r->height == 1 ); + g_assert( line >= 0 && lines < gif->frame->Ysize ); + g_assert( page >= 0 && page < gif->n_pages ); - while( gif->current_page < page ) { + /* current_page == 0 means we've not loaded any pages yet. So we need + * to have loaded the page beyond the page we want. + */ + while( gif->current_page <= page ) { if( vips_foreign_load_gif_next_page( gif ) ) return( -1 ); From bfa1f22520269f099e56a66cf7840bb1b0823d94 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 14 Feb 2019 15:22:51 +0000 Subject: [PATCH 7/9] works! tuned a bit too --- ChangeLog | 1 + libvips/foreign/gifload.c | 138 +++++++++++++++++++++----------------- 2 files changed, 78 insertions(+), 61 deletions(-) diff --git a/ChangeLog b/ChangeLog index d5ff4c21..86cc1625 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,7 @@ - add @reduction_effort param to webpsave [lovell] - add @option_string param to thumbnail_buffer [kleisauke] - add XMP, IPCT, ICC, EXIF etc. support to magickload/magicksave +- much lower memuse for gifload 4/1/19 started 8.7.4 - magickload with magick6 API did not chain exceptions correctly causing a diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index f6133fca..7bb88cfe 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -20,6 +20,8 @@ * - add dispose handling * 13/8/18 * - init pages to 0 before load + * 14/2/19 + * - rework as a sequential loader ... smaller, less mem use */ /* @@ -51,6 +53,7 @@ /* #define VIPS_DEBUG +#define DEBUG_VERBOSE */ #ifdef HAVE_CONFIG_H @@ -312,8 +315,9 @@ vips_foreign_load_gif_dispose( GObject *gobject ) vips_foreign_load_gif_close( gif ); - VIPS_FREE( gif->comment ); VIPS_UNREF( gif->frame ); + VIPS_FREE( gif->comment ); + VIPS_FREE( gif->line ) G_OBJECT_CLASS( vips_foreign_load_gif_parent_class )-> dispose( gobject ); @@ -424,7 +428,6 @@ vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif ) for( i = 0; i < map->ColorCount; i++ ) if( map->Colors[i].Red != map->Colors[i].Green || map->Colors[i].Green != map->Colors[i].Blue ) { - printf( "gifload: not mono\n" ); gif->has_colour = TRUE; break; } @@ -446,8 +449,6 @@ vips_foreign_load_gif_scan_application_ext( VipsForeignLoadGif *gif, { gboolean have_netscape; - printf( "gifload: type: application\n" ); - /* The 11-byte NETSCAPE extension. */ have_netscape = FALSE; @@ -462,11 +463,8 @@ vips_foreign_load_gif_scan_application_ext( VipsForeignLoadGif *gif, if( have_netscape && extension && extension[0] == 3 && - extension[1] == 1 ) { + extension[1] == 1 ) gif->loop = extension[2] | (extension[3] << 8); - printf( "gifload: seen loop %d\n", - gif->loop ); - } } return( 0 ); @@ -501,8 +499,6 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) GifByteType *extension; int ext_code; - printf( "gifload: EXTENSION_RECORD_TYPE\n" ); - if( DGifGetExtension( gif->file, &ext_code, &extension ) == GIF_ERROR ) { vips_foreign_load_gif_error( gif ); @@ -566,7 +562,7 @@ vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image ) gif->has_colour ? VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); - vips_image_pipelinev( image, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + vips_image_pipelinev( image, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); if( gif->n_pages > 1 ) { vips_image_set_int( image, @@ -594,13 +590,9 @@ vips_foreign_load_gif_header( VipsForeignLoad *load ) GifRecordType record; - printf( "vips_foreign_load_gif_header:\n" ); - if( gif_class->open( gif ) ) return( -1 ); - printf( "vips_foreign_load_gif_header: starting page scan\n" ); - gif->n_pages = 0; do { @@ -611,8 +603,6 @@ vips_foreign_load_gif_header( VipsForeignLoad *load ) switch( record ) { case IMAGE_DESC_RECORD_TYPE: - printf( "gifload: IMAGE_DESC_RECORD_TYPE\n" ); - if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { vips_foreign_load_gif_error( gif ); return( -1 ); @@ -636,16 +626,11 @@ vips_foreign_load_gif_header( VipsForeignLoad *load ) break; case TERMINATE_RECORD_TYPE: - printf( "gifload: TERMINATE_RECORD_TYPE\n" ); gif->eof = TRUE; break; case SCREEN_DESC_RECORD_TYPE: - printf( "gifload: SCREEN_DESC_RECORD_TYPE\n" ); - break; - case UNDEFINED_RECORD_TYPE: - printf( "gifload: UNDEFINED_RECORD_TYPE\n" ); break; default: @@ -653,9 +638,6 @@ vips_foreign_load_gif_header( VipsForeignLoad *load ) } } while( !gif->eof ); - printf( "vips_foreign_load_gif_header: found %d pages\n", - gif->n_pages ); - if( gif->n == -1 ) gif->n = gif->n_pages - gif->page; @@ -728,8 +710,6 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) { GifFileType *file = gif->file; - printf( "vips_foreign_load_gif_render:\n" ); - if( file->Image.Interlace ) { int i; @@ -830,8 +810,6 @@ vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif ) GifRecordType record; gboolean have_read_frame; - printf( "vips_foreign_load_gif_next_page:\n" ); - have_read_frame = FALSE; do { if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { @@ -889,33 +867,76 @@ vips_foreign_load_gif_generate( VipsRegion *or, VipsRect *r = &or->valid; VipsForeignLoadGif *gif = (VipsForeignLoadGif *) a; - /* The page for this generate, and the line number in page. - */ - int page = r->top / gif->file->SHeight + gif->page; - int line = r->top % gif->file->SHeight; + int y; -#ifdef DEBUG_VERBOSE -#endif /*DEBUG_VERBOSE*/ - printf( "vips_foreign_load_gif_generate: line %d\n", r->top ); + for( y = 0; y < r->height; y++ ) { + /* The page for this output line, and the line number in page. + */ + int page = (r->top + y) / gif->file->SHeight + gif->page; + int line = (r->top + y) % gif->file->SHeight; - g_assert( r->height == 1 ); - g_assert( line >= 0 && lines < gif->frame->Ysize ); - g_assert( page >= 0 && page < gif->n_pages ); + VipsPel *p, *q; + int x; - /* current_page == 0 means we've not loaded any pages yet. So we need - * to have loaded the page beyond the page we want. - */ - while( gif->current_page <= page ) { - if( vips_foreign_load_gif_next_page( gif ) ) - return( -1 ); + g_assert( line >= 0 && lines < gif->frame->Ysize ); + g_assert( page >= 0 && page < gif->n_pages ); - gif->current_page += 1; + /* current_page == 0 means we've not loaded any pages yet. So + * we need to have loaded the page beyond the page we want. + */ + while( gif->current_page <= page ) { + if( vips_foreign_load_gif_next_page( gif ) ) + return( -1 ); + + gif->current_page += 1; + } + + /* @frame is always RGBA, but or may be G, GA, RGB or RGBA. + * We have to pick out the values we want. + */ + p = VIPS_IMAGE_ADDR( gif->frame, 0, line ); + q = VIPS_REGION_ADDR( or, 0, r->top + y ); + switch( or->im->Bands ) { + case 1: + for( x = 0; x < gif->frame->Xsize; x++ ) { + q[0] = p[1]; + + 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( x = 0; x < gif->frame->Xsize; x++ ) { + q[0] = p[0]; + q[1] = p[1]; + q[2] = p[2]; + + q += 3; + p += 4; + } + break; + + case 4: + memcpy( q, p, VIPS_IMAGE_SIZEOF_LINE( gif->frame ) ); + break; + + default: + g_assert_not_reached(); + break; + } } - memcpy( VIPS_REGION_ADDR( or, 0, r->top ), - VIPS_IMAGE_ADDR( gif->frame, 0, line ), - VIPS_IMAGE_SIZEOF_LINE( gif->frame ) ); - return( 0 ); } @@ -928,8 +949,6 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( load ), 4 ); - printf( "vips_foreign_load_gif_load:\n" ); - /* Rewind. */ if( class->open( gif ) ) @@ -953,9 +972,13 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) if( vips_foreign_load_gif_set_header( gif, t[0] ) ) return( -1 ); + /* Strips 8 pixels high to avoid too many tiny regions. + */ if( vips_image_generate( t[0], NULL, vips_foreign_load_gif_generate, NULL, gif, NULL ) || - vips_sequential( t[0], &t[1], NULL ) || + vips_sequential( t[0], &t[1], + "tile_height", VIPS__FATSTRIP_HEIGHT, + NULL ) || vips_image_write( t[1], load->real ) ) return( -1 ); @@ -965,8 +988,6 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) static int vips_foreign_load_gif_open( VipsForeignLoadGif *gif ) { - printf( "vips_foreign_load_gif_open:\n" ); - #ifdef HAVE_GIFLIB_5 { int error; @@ -989,8 +1010,7 @@ vips_foreign_load_gif_open( VipsForeignLoadGif *gif ) /* Allocate a line buffer now that we have the GIF width. */ VIPS_FREE( gif->line ) - if( !(gif->line = VIPS_ARRAY( gif, - gif->file->SWidth, GifPixelType )) ) + if( !(gif->line = VIPS_ARRAY( NULL, gif->file->SWidth, GifPixelType )) ) return( -1 ); return( 0 ); @@ -1094,8 +1114,6 @@ vips_foreign_load_gif_file_open( VipsForeignLoadGif *gif ) VipsForeignLoad *load = (VipsForeignLoad *) gif; VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif; - printf( "vips_foreign_load_gif_file_open:\n" ); - if( !file->fp ) { if( !(file->fp = vips__file_open_read( file->filename, NULL, FALSE )) ) @@ -1198,8 +1216,6 @@ vips_foreign_load_gif_buffer_open( VipsForeignLoadGif *gif ) { VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) gif; - printf( "vips_foreign_load_gif_buffer_open:\n" ); - vips_foreign_load_gif_close( gif ); buffer->p = buffer->buf->data; buffer->bytes_to_go = buffer->buf->length; From 25f0f652d10767c2d042941e312cd21306bf49e5 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 14 Feb 2019 15:31:33 +0000 Subject: [PATCH 8/9] works! --- libvips/foreign/gifload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 7bb88cfe..339f68c8 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -21,7 +21,7 @@ * 13/8/18 * - init pages to 0 before load * 14/2/19 - * - rework as a sequential loader ... smaller, less mem use + * - rework as a sequential loader ... simpler, much lower mem use */ /* From 4ed0ce3e2e6f8f643297f2ed3b0911761db8822c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 14 Feb 2019 16:34:59 +0000 Subject: [PATCH 9/9] remove old gif loader --- libvips/foreign/Makefile.am | 1 - libvips/foreign/foreign.c | 6 ------ libvips/include/vips/foreign.h | 5 ----- 3 files changed, 12 deletions(-) diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index 0e5188ee..cbd2f8f4 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -7,7 +7,6 @@ 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 b64536fa..f6c81e3b 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1832,9 +1832,6 @@ 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(); @@ -1884,9 +1881,6 @@ 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/include/vips/foreign.h b/libvips/include/vips/foreign.h index 0ae9f6a1..4711d448 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -560,11 +560,6 @@ 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, ... )