diff --git a/README.md b/README.md index 8d362ff7..1e7cc567 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,13 @@ Debug build: make make install +# Built-in loaders + +libvips has a number of built-in loaders and savers. You can disable these if +you wish, for example: + + ./configure --prefix=/Users/john/vips --without-nsgif --without-ppm + # Dependencies libvips has to have `libglib2.0-dev` and `libexpat1-dev`. Other dependencies @@ -128,14 +135,14 @@ libraries automatically. See `./configure --help` for a set of flags to control library detection. Packages are generally found with `pkg-config`, so make sure that is working. -Libraries like giflib and nifti do not use `pkg-config` so libvips will also +Libraries like nifti do not use `pkg-config` so libvips will also look for them in the default path and in `$prefix`. If you have installed your own versions of these libraries in a different location, libvips will not see them. Use switches to libvips configure like: ./configure --prefix=/Users/john/vips \ - --with-giflib-includes=/opt/local/include \ - --with-giflib-libraries=/opt/local/lib + --with-nifti-includes=/opt/local/include \ + --with-nifti-libraries=/opt/local/lib or perhaps: @@ -151,11 +158,6 @@ The IJG JPEG library. Use the `-turbo` version if you can. If available, libvips adds support for EXIF metadata in JPEG files. -### giflib - -The standard gif loader. If this is not present, vips will try to load gifs -via imagemagick instead. - ### librsvg The usual SVG loader. If this is not present, vips will try to load SVGs diff --git a/configure.ac b/configure.ac index e1cb4002..3df902da 100644 --- a/configure.ac +++ b/configure.ac @@ -979,6 +979,16 @@ VIPS_LIBS="$VIPS_LIBS $MATIO_LIBS" # not external libraries, but have options to disable them, helps to # reduce attack surface +AC_ARG_WITH([nsgif], + AS_HELP_STRING([--without-nsgif], [build without nsgif load (default: with)])) + +if test x"$with_nsgif" != x"no"; then + AC_DEFINE(HAVE_NSGIF,1,[define to build nsgif load support.]) + with_nsgif=yes +fi + +AM_CONDITIONAL(ENABLE_NSGIF, [test x"$with_nsgif" = x"yes"]) + AC_ARG_WITH([ppm], AS_HELP_STRING([--without-ppm], [build without ppm (default: with)])) @@ -1102,19 +1112,6 @@ VIPS_CFLAGS="$VIPS_CFLAGS $TIFF_CFLAGS" VIPS_INCLUDES="$VIPS_INCLUDES $TIFF_INCLUDES" VIPS_LIBS="$VIPS_LIBS $TIFF_LIBS" -# giflib -FIND_GIFLIB( - [with_giflib="yes (found by search)" - ], - [AC_MSG_WARN([giflib not found; disabling direct GIF support]) - with_giflib=no - ] -) - -VIPS_CFLAGS="$VIPS_CFLAGS $GIFLIB_CFLAGS" -VIPS_INCLUDES="$VIPS_INCLUDES $GIFLIB_INCLUDES" -VIPS_LIBS="$VIPS_LIBS $GIFLIB_LIBS" - # Look for libspng first # 0.6.1 uses "libspng.pc", git master libspng uses "spng.pc" AC_ARG_WITH([libspng], @@ -1292,6 +1289,7 @@ gobject introspection: $found_introspection, \ RAD load/save: $with_radiance, \ Analyze7 load/save: $with_analyze, \ PPM load/save: $with_ppm, \ +GIF load: $with_nsgif, \ generate C++ docs: $with_doxygen, \ use fftw3 for FFT: $with_fftw, \ accelerate loops with orc: $with_orc, \ @@ -1305,7 +1303,6 @@ PNG load/save with libpng: $with_png, \ 8bpp PNG quantisation: $with_imagequant, \ TIFF load/save with libtiff: $with_tiff, \ image pyramid save: $with_gsf, \ -GIF load with giflib: $with_giflib, \ HEIC/AVIF load/save with libheif: $with_heif, \ WebP load/save with libwebp: $with_libwebp, \ PDF load with PDFium: $with_pdfium, \ @@ -1350,6 +1347,7 @@ AC_CONFIG_FILES([ libvips/convolution/Makefile libvips/deprecated/Makefile libvips/foreign/Makefile + libvips/foreign/libnsgif/Makefile libvips/freqfilt/Makefile libvips/histogram/Makefile libvips/draw/Makefile @@ -1390,6 +1388,7 @@ gobject introspection: $found_introspection RAD load/save: $with_radiance Analyze7 load/save: $with_analyze PPM load/save: $with_ppm +GIF load: $with_nsgif generate C++ docs: $with_doxygen ## Optional dependencies @@ -1412,7 +1411,6 @@ PNG load/save with libpng: $with_png TIFF load/save with libtiff: $with_tiff image pyramid save: $with_gsf (requires libgsf-1 1.14.26 or later) -GIF load with giflib: $with_giflib HEIC/AVIF load/save with libheif: $with_heif WebP load/save with libwebp: $with_libwebp (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) diff --git a/cplusplus/include/vips/Makefile.am b/cplusplus/include/vips/Makefile.am index 46c61dcd..d1c0e805 100644 --- a/cplusplus/include/vips/Makefile.am +++ b/cplusplus/include/vips/Makefile.am @@ -5,9 +5,10 @@ pkginclude_HEADERS = \ VConnection8.h \ vips8 -vips-operators.h: - echo "// headers for vips operations" > vips-operators.h; \ - echo -n "// " >> vips-operators.h; \ - date >> vips-operators.h; \ - echo "// this file is generated automatically, do not edit!" >> vips-operators.h; \ - ./../../gen-operators.py -g h >> vips-operators.h +vips-operators: + echo "// headers for vips operations" > vips-operators; \ + echo "// paste this file into VImage8.h, do not leave in repo" > vips-operators; \ + echo -n "// " >> vips-operators; \ + date >> vips-operators; \ + echo "// this file is generated automatically, do not edit!" >> vips-operators; \ + ./../../gen-operators.py -g h >> vips-operators diff --git a/libvips/Makefile.am b/libvips/Makefile.am index bc05c661..212dc491 100644 --- a/libvips/Makefile.am +++ b/libvips/Makefile.am @@ -9,6 +9,10 @@ else OPTIONAL_DIST_DIR += deprecated endif +if ENABLE_NSGIF +OPTIONAL_LIB += foreign/libnsgif/libnsgif.la +endif + SUBDIRS = \ include \ foreign \ diff --git a/libvips/colour/Makefile.am b/libvips/colour/Makefile.am index b8ced977..ef013ad7 100644 --- a/libvips/colour/Makefile.am +++ b/libvips/colour/Makefile.am @@ -1,7 +1,7 @@ -noinst_LTLIBRARIES = libcolour.la - SUBDIRS = profiles +noinst_LTLIBRARIES = libcolour.la + libcolour_la_SOURCES = \ profiles.c \ profiles.h \ diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index 759df2e5..3af243b4 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -1,3 +1,7 @@ +if ENABLE_NSGIF +SUBDIRS = libnsgif +endif + noinst_LTLIBRARIES = libforeign.la libforeign_la_SOURCES = \ @@ -8,7 +12,7 @@ libforeign_la_SOURCES = \ niftisave.c \ quantise.c \ exif.c \ - gifload.c \ + nsgifload.c \ cairo.c \ pdfload.c \ pdfiumload.c \ @@ -66,7 +70,7 @@ libforeign_la_SOURCES = \ jpegload.c \ jpegsave.c -EXTRA_DIST = +EXTRA_DIST = libnsgif AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 12ea483a..4ee11c28 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2187,22 +2187,25 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_load_nifti_source_get_type( void ); extern GType vips_foreign_save_nifti_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_gif_source_get_type( void ); + extern GType vips_foreign_load_nsgif_file_get_type( void ); + extern GType vips_foreign_load_nsgif_buffer_get_type( void ); + extern GType vips_foreign_load_nsgif_source_get_type( void ); vips_foreign_load_csv_file_get_type(); vips_foreign_load_csv_source_get_type(); vips_foreign_save_csv_file_get_type(); vips_foreign_save_csv_target_get_type(); + vips_foreign_load_matrix_file_get_type(); vips_foreign_load_matrix_source_get_type(); vips_foreign_save_matrix_file_get_type(); vips_foreign_save_matrix_target_get_type(); vips_foreign_print_matrix_get_type(); + vips_foreign_load_raw_get_type(); vips_foreign_save_raw_get_type(); vips_foreign_save_raw_fd_get_type(); + vips_foreign_load_vips_file_get_type(); vips_foreign_load_vips_source_get_type(); vips_foreign_save_vips_file_get_type(); @@ -2246,11 +2249,11 @@ vips_foreign_operation_init( void ) vips_foreign_load_svg_source_get_type(); #endif /*HAVE_RSVG*/ -#ifdef HAVE_GIFLIB - vips_foreign_load_gif_file_get_type(); - vips_foreign_load_gif_buffer_get_type(); - vips_foreign_load_gif_source_get_type(); -#endif /*HAVE_GIFLIB*/ +#ifdef HAVE_NSGIF + vips_foreign_load_nsgif_file_get_type(); + vips_foreign_load_nsgif_buffer_get_type(); + vips_foreign_load_nsgif_source_get_type(); +#endif /*HAVE_NSGIF*/ #ifdef HAVE_GSF vips_foreign_save_dz_file_get_type(); diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c deleted file mode 100644 index 4507393c..00000000 --- a/libvips/foreign/gifload.c +++ /dev/null @@ -1,1745 +0,0 @@ -/* 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 - * 14/2/19 - * - rework as a sequential loader ... simpler, much lower mem use - * 6/7/19 [deftomat] - * - support array of delays - * 24/7/19 - * - close early on minimise - * - close early on error - * 23/8/18 - * - allow GIF read errors during header scan - * - better feof() handling - * 27/8/19 - * - check image and frame bounds, since giflib does not - * 1/9/19 - * - improve early close again - * 30/1/19 - * - rework on top of VipsSource - * - add gifload_source - * 5/2/20 alon-ne - * - fix DISPOSE_BACKGROUND and DISPOSE_PREVIOUS - * 2/7/20 - * - clip out of bounds images against canvas - * - fix PREVIOUS handling, again - * 19/2/21 781545872 - * - read out background, if we can - */ - -/* - - 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 DEBUG_VERBOSE -#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 - -#define NO_TRANSPARENT_INDEX -1 -#define TRANSPARENT_MASK 0x01 -#define DISPOSE_MASK 0x07 -#define DISPOSE_SHIFT 2 - -#define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_gif_get_type()) -#define VIPS_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_FOREIGN_LOAD_GIF_GET_CLASS( obj ) \ - (G_TYPE_INSTANCE_GET_CLASS( (obj), \ - VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGifClass )) - -typedef struct _VipsForeignLoadGif { - VipsForeignLoad parent_object; - - /* Load from this page (frame number). - */ - int page; - - /* Load this many pages. - */ - int n; - - /* Load from this source (set by subclasses). - */ - VipsSource *source; - - GifFileType *file; - - /* We decompress the whole thing to a huge RGBA memory image, and - * as we render, watch for bands and transparency. At the end of - * loading, we copy 1 or 3 bands, with or without transparency to - * output. - */ - gboolean has_transparency; - gboolean has_colour; - - /* Delays between frames (in milliseconds). - */ - int *delays; - int delays_length; - - /* Number of times to loop the animation. - */ - int loop; - - /* The GIF comment, if any. - */ - char *comment; - - /* The number of pages (frame) in the image. - */ - int n_pages; - - /* A memory image the size of one frame ... we accumulate to this as - * we scan the image, and copy lines to the output on generate. - */ - VipsImage *frame; - - /* A scratch buffer the size of the largest Image inside the GIF. We - * decompress lines from the GIF to this. - */ - VipsImage *scratch; - - /* A copy of the previous frame, in case we need a DISPOSE_PREVIOUS. - */ - VipsImage *previous; - - /* The position of @frame, in pages. - */ - int current_page; - - /* Decompress lines of the gif file to here. This is large enough to - * hold one line of the widest sub-image in the GIF. - */ - int max_image_width; - GifPixelType *line; - - /* The current dispose method. - */ - int dispose; - - /* Set for EOF detected. - */ - gboolean eof; - - /* The current cmap unpacked to a simple LUT. Each uint32 is really an - * RGBA pixel ready to be blasted into @frame. - */ - guint32 cmap[256]; - - /* As we scan the file, the index of the transparent pixel for this - * frame. - */ - int transparent_index; - -} VipsForeignLoadGif; - -typedef VipsForeignLoadClass VipsForeignLoadGifClass; - -G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadGif, vips_foreign_load_gif, - 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_gif_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_gif_error_vips( VipsForeignLoadGif *gif, int error ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); - - const char *message; - - if( (message = vips_foreign_load_gif_errstr( error )) ) - vips_error( class->nickname, "%s", message ); -} - -static void -vips_foreign_load_gif_error( VipsForeignLoadGif *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_gif_error_vips( gif, error ); -} - -/* Shut down giflib plus any underlying file resource. - */ -static int -vips_foreign_load_gif_close_giflib( VipsForeignLoadGif *gif ) -{ - VIPS_DEBUG_MSG( "vips_foreign_load_gif_close_giflib:\n" ); - -#ifdef HAVE_GIFLIB_5 - if( gif->file ) { - int error; - - if( DGifCloseFile( gif->file, &error ) == GIF_ERROR ) { - vips_foreign_load_gif_error_vips( gif, error ); - gif->file = NULL; - - return( -1 ); - } - gif->file = NULL; - } -#else - if( gif->file ) { - if( DGifCloseFile( gif->file ) == GIF_ERROR ) { - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - gif->file = NULL; - - return( -1 ); - } - gif->file = NULL; - } -#endif - - if( gif->source ) - vips_source_minimise( gif->source ); - - return( 0 ); -} - -/* Callback from the gif loader. - * - * Read up to len bytes into buffer, return number of bytes read. This is - * called by giflib exactly as fread, so it does not distinguish between EOF - * and read error. - */ -static int -vips_giflib_read( GifFileType *file, GifByteType *buf, int n ) -{ - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) file->UserData; - - int to_read; - - to_read = n; - while( to_read > 0 ) { - gint64 bytes_read; - - bytes_read = vips_source_read( gif->source, buf, n ); - if( bytes_read == 0 ) { - gif->eof = TRUE; - return( -1 ); - } - if( bytes_read < 0 ) - return( -1 ); - if( bytes_read > INT_MAX ) - return( -1 ); - - to_read -= bytes_read; - buf += bytes_read; - } - - return( (int) n ); -} - -/* Open any underlying file resource, then giflib. - */ -static int -vips_foreign_load_gif_open_giflib( VipsForeignLoadGif *gif ) -{ - VIPS_DEBUG_MSG( "vips_foreign_load_gif_open_giflib:\n" ); - - g_assert( !gif->file ); - - /* Must always rewind before opening giflib again. - */ - vips_source_rewind( gif->source ); - -#ifdef HAVE_GIFLIB_5 -{ - int error; - - if( !(gif->file = DGifOpen( gif, vips_giflib_read, &error )) ) { - vips_foreign_load_gif_error_vips( gif, error ); - (void) vips_foreign_load_gif_close_giflib( gif ); - return( -1 ); - } -} -#else - if( !(gif->file = DGifOpen( gif, vips_giflib_read )) ) { - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - (void) vips_foreign_load_gif_close_giflib( gif ); - return( -1 ); - } -#endif - - gif->eof = FALSE; - gif->current_page = 0; - gif->max_image_width = 0; - - return( 0 ); -} - -static void -vips_foreign_load_gif_dispose( GObject *gobject ) -{ - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) gobject; - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_dispose:\n" ); - - vips_foreign_load_gif_close_giflib( gif ); - - VIPS_UNREF( gif->source ); - VIPS_UNREF( gif->frame ); - VIPS_UNREF( gif->scratch ); - VIPS_UNREF( gif->previous ); - VIPS_FREE( gif->comment ); - VIPS_FREE( gif->line ); - VIPS_FREE( gif->delays ); - - G_OBJECT_CLASS( vips_foreign_load_gif_parent_class )-> - dispose( gobject ); -} - -static VipsForeignFlags -vips_foreign_load_gif_get_flags_filename( const char *filename ) -{ - return( VIPS_FOREIGN_SEQUENTIAL ); -} - -static VipsForeignFlags -vips_foreign_load_gif_get_flags( VipsForeignLoad *load ) -{ - return( VIPS_FOREIGN_SEQUENTIAL ); -} - -static gboolean -vips_foreign_load_gif_is_a_source( VipsSource *source ) -{ - const unsigned char *data; - - if( (data = vips_source_sniff( source, 4 )) && - data[0] == 'G' && - data[1] == 'I' && - data[2] == 'F' && - data[3] == '8' ) - return( TRUE ); - - return( FALSE ); -} - -/* Make sure delays is allocated and large enough. - */ -static void -vips_foreign_load_gif_allocate_delays( VipsForeignLoadGif *gif ) -{ - if( gif->n_pages >= gif->delays_length ) { - int old = gif->delays_length; - int i; - - gif->delays_length = gif->delays_length + gif->n_pages + 64; - gif->delays = (int *) g_realloc( gif->delays, - gif->delays_length * sizeof( int ) ); - for( i = old; i < gif->delays_length; i++ ) - gif->delays[i] = 40; - } -} - -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 ); - } - -#ifdef DEBUG_VERBOSE - if( *extension ) - printf( "gifload: EXTENSION_NEXT\n" ); -#endif /*DEBUG_VERBOSE*/ - - return( 0 ); -} - -static int -vips_foreign_load_gif_code_next( VipsForeignLoadGif *gif, - GifByteType **extension ) -{ - if( DGifGetCodeNext( gif->file, extension ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - -#ifdef DEBUG_VERBOSE - if( *extension ) - printf( "gifload: CODE_NEXT\n" ); -#endif /*DEBUG_VERBOSE*/ - - return( 0 ); -} - -/* Quickly scan an image record. - */ -static int -vips_foreign_load_gif_scan_image( VipsForeignLoadGif *gif ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); - GifFileType *file = gif->file; - - ColorMapObject *map; - GifByteType *extension; - - if( DGifGetImageDesc( file ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_scan_image: " - "frame of %dx%d pixels at %dx%d\n", - file->Image.Width, file->Image.Height, - file->Image.Left, file->Image.Top ); - - /* giflib does no checking of image dimensions, not even for 0. - */ - if( file->Image.Width <= 0 || - file->Image.Width > VIPS_MAX_COORD || - file->Image.Height <= 0 || - file->Image.Height > VIPS_MAX_COORD ) { - vips_error( class->nickname, - "%s", _( "image size out of bounds" ) ); - return( -1 ); - } - - /* We need to find the max scanline size inside the GIF - * so we can allocate the decompress buffer. - */ - gif->max_image_width = VIPS_MAX( gif->max_image_width, - file->Image.Width ); - - /* Test for a non-greyscale colourmap for this frame. - */ - map = file->Image.ColorMap ? file->Image.ColorMap : file->SColorMap; - if( !gif->has_colour && - map ) { - int i; - - for( i = 0; i < map->ColorCount; i++ ) - if( map->Colors[i].Red != map->Colors[i].Green || - map->Colors[i].Green != map->Colors[i].Blue ) { - gif->has_colour = TRUE; - break; - } - } - - /* Step over compressed image data. - */ - do { - if( vips_foreign_load_gif_code_next( gif, &extension ) ) - return( -1 ); - } while( extension != NULL ); - - return( 0 ); -} - -static int -vips_foreign_load_gif_scan_application_ext( VipsForeignLoadGif *gif, - GifByteType *extension ) -{ - gboolean have_netscape; - - /* The 11-byte NETSCAPE extension. - */ - have_netscape = FALSE; - if( extension[0] == 11 && - (vips_isprefix( "NETSCAPE2.0", - (const char*) (extension + 1) ) || - vips_isprefix( "ANIMEXTS1.0", - (const char*) (extension + 1) )) ) - have_netscape = TRUE; - - while( extension != NULL ) { - if( vips_foreign_load_gif_ext_next( gif, &extension ) ) - return( -1 ); - - if( have_netscape && - extension && - extension[0] == 3 && - extension[1] == 1 ) { - gif->loop = extension[2] | (extension[3] << 8); - if( gif->loop != 0 ) - gif->loop += 1; - } - } - - return( 0 ); -} - -static int -vips_foreign_load_gif_scan_comment_ext( VipsForeignLoadGif *gif, - GifByteType *extension ) -{ - VIPS_DEBUG_MSG( "gifload: type: comment\n" ); - - 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; - - if( DGifGetExtension( gif->file, &ext_code, &extension ) == - GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - if( extension ) - switch( ext_code ) { - case GRAPHICS_EXT_FUNC_CODE: - if( extension[0] == 4 && - extension[1] & TRANSPARENT_MASK ) { - VIPS_DEBUG_MSG( "gifload: has transp.\n" ); - gif->has_transparency = TRUE; - } - - /* giflib uses centiseconds, we use ms. - */ - gif->delays[gif->n_pages] = - (extension[2] | (extension[3] << 8)) * 10; - - while( extension != NULL ) - if( vips_foreign_load_gif_ext_next( gif, - &extension ) ) - return( -1 ); - - break; - - case APPLICATION_EXT_FUNC_CODE: - if( vips_foreign_load_gif_scan_application_ext( gif, - extension ) ) - return( -1 ); - break; - - case COMMENT_EXT_FUNC_CODE: - if( vips_foreign_load_gif_scan_comment_ext( gif, - extension ) ) - return( -1 ); - break; - - default: - /* Step over any NEXT blocks for unknown extensions. - */ - while( extension != NULL ) - if( vips_foreign_load_gif_ext_next( gif, - &extension ) ) - return( -1 ); - break; - } - - return( 0 ); -} - -static int -vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image ) -{ - const gint64 total_height = (gint64) gif->file->SHeight * gif->n; - ColorMapObject *map = gif->file->SColorMap; - - if( total_height <= 0 || - total_height > VIPS_MAX_COORD ) { - vips_error( "gifload", "%s", _( "image size out of bounds" ) ); - return( -1 ); - } - - vips_image_init_fields( image, - gif->file->SWidth, total_height, - (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_FATSTRIP, NULL ); - - if( vips_object_argument_isset( VIPS_OBJECT( gif ), "n" ) ) - 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, "loop", gif->loop ); - - /* DEPRECATED "gif-loop" - * - * Not the correct behavior as loop=1 became gif-loop=0 - * but we want to keep the old behavior untouched! - */ - vips_image_set_int( image, - "gif-loop", gif->loop == 0 ? 0 : gif->loop - 1 ); - - if( gif->delays ) { - /* The deprecated gif-delay field is in centiseconds. - */ - vips_image_set_int( image, - "gif-delay", VIPS_RINT( gif->delays[0] / 10.0 ) ); - vips_image_set_array_int( image, - "delay", gif->delays, gif->n_pages ); - } - else - vips_image_set_int( image, "gif-delay", 4 ); - - if( gif->comment ) - vips_image_set_string( image, "gif-comment", gif->comment ); - - if( map && - gif->file->SBackGroundColor >= 0 && - gif->file->SBackGroundColor < map->ColorCount ) { - double array[3]; - - array[0] = map->Colors[gif->file->SBackGroundColor].Red; - array[1] = map->Colors[gif->file->SBackGroundColor].Green; - array[2] = map->Colors[gif->file->SBackGroundColor].Blue; - - vips_image_set_array_double( image, "background", array, 3 ); - } - - 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, colour etc. - * - * Don't flag errors during header scan. Many GIFs do not follow spec. - */ -static int -vips_foreign_load_gif_scan( VipsForeignLoadGif *gif ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); - - GifRecordType record; - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_scan:\n" ); - - gif->n_pages = 0; - - do { - if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) - continue; - - switch( record ) { - case IMAGE_DESC_RECORD_TYPE: - (void) vips_foreign_load_gif_scan_image( gif ); - gif->n_pages += 1; - vips_foreign_load_gif_allocate_delays( gif ); - break; - - case EXTENSION_RECORD_TYPE: - /* We need to fetch the extensions to check for - * cmaps and transparency. - */ - (void) vips_foreign_load_gif_scan_extension( gif ); - break; - - case TERMINATE_RECORD_TYPE: - gif->eof = TRUE; - break; - - case SCREEN_DESC_RECORD_TYPE: - case UNDEFINED_RECORD_TYPE: - break; - - default: - break; - } - } while( !gif->eof ); - - if( gif->n == -1 ) - gif->n = gif->n_pages - gif->page; - - if( gif->page < 0 || - gif->n <= 0 || - gif->page + gif->n > gif->n_pages ) { - vips_error( class->nickname, "%s", _( "bad page number" ) ); - return( -1 ); - } - - if( gif->max_image_width <= 0 || - gif->max_image_width > VIPS_MAX_COORD ) { - vips_error( class->nickname, "%s", _( "bad image size" ) ); - return( -1 ); - } - - return( 0 ); -} - -/* Scan the GIF and set the libvips header. We always close after scan, even - * on an error. - */ -static int -vips_foreign_load_gif_header( VipsForeignLoad *load ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadGif *gif = VIPS_FOREIGN_LOAD_GIF( load ); - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_header: %p\n", gif ); - - if( vips_foreign_load_gif_open_giflib( gif ) ) - return( -1 ); - - /* giflib does no checking of image dimensions, not even for 0. - */ - if( gif->file->SWidth <= 0 || - gif->file->SWidth > VIPS_MAX_COORD || - gif->file->SHeight <= 0 || - gif->file->SHeight > VIPS_MAX_COORD ) { - vips_error( class->nickname, - "%s", _( "image size out of bounds" ) ); - (void) vips_foreign_load_gif_close_giflib( gif ); - - return( -1 ); - } - - /* Allocate a line buffer now that we have the GIF width. - */ - if( vips_foreign_load_gif_scan( gif ) || - !(gif->line = VIPS_ARRAY( NULL, - gif->max_image_width, GifPixelType )) || - vips_foreign_load_gif_set_header( gif, load->out ) ) { - (void) vips_foreign_load_gif_close_giflib( gif ); - - return( -1 ); - } - - (void) vips_foreign_load_gif_close_giflib( gif ); - - return( 0 ); -} - -static void -vips_foreign_load_gif_build_cmap( VipsForeignLoadGif *gif ) -{ - ColorMapObject *map = gif->file->Image.ColorMap ? - gif->file->Image.ColorMap : gif->file->SColorMap; - - int v; - - for( v = 0; v < 256; v++ ) { - VipsPel *q = (VipsPel *) &gif->cmap[v]; - - if( map && - v < map->ColorCount ) { - q[0] = map->Colors[v].Red; - q[1] = map->Colors[v].Green; - q[2] = map->Colors[v].Blue; - q[3] = 255; - } - else { - /* If there's no map, just save the index. - */ - q[0] = v; - q[1] = v; - q[2] = v; - q[3] = 255; - } - } -} - -/* Paint line y from the image left/top/width/height into scratch, clipping as - * we go. - */ -static void -vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, - int y, - int left, int top, int width, int height, - VipsPel *line ) -{ - VipsRect canvas; - VipsRect row; - VipsRect overlap; - - /* Many GIFs have frames which lie outside the canvas. We have to - * clip. - */ - canvas.left = 0; - canvas.top = 0; - canvas.width = gif->file->SWidth; - canvas.height = gif->file->SHeight; - row.left = left; - row.top = top + y; - row.width = width; - row.height = height; - vips_rect_intersectrect( &canvas, &row, &overlap ); - - if( !vips_rect_isempty( &overlap ) ) { - VipsPel *dst = VIPS_IMAGE_ADDR( gif->scratch, - overlap.left, overlap.top ); - guint32 * restrict idst = (guint32 *) dst; - VipsPel * restrict src = line + (overlap.left - row.left); - - int x; - - for( x = 0; x < overlap.width; x++ ) { - VipsPel v = src[x]; - - if( v != gif->transparent_index ) - idst[x] = gif->cmap[v]; - } - } -} - -/* Render the current gif frame into an RGBA buffer. GIFs can accumulate, - * depending on the current dispose mode. - */ -static int -vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) -{ - GifFileType *file = gif->file; - - if( DGifGetImageDesc( file ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - /* Update the colour map for this frame. - */ - vips_foreign_load_gif_build_cmap( gif ); - - /* If this is PREVIOUS, then after we're done, we'll need to restore - * the frame to what it was previously. Make a note of the current - * state. - */ - if( gif->dispose == DISPOSE_PREVIOUS ) - memcpy( VIPS_IMAGE_ADDR( gif->previous, 0, 0 ), - VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), - VIPS_IMAGE_SIZEOF_IMAGE( gif->previous ) ); - - if( file->Image.Interlace ) { - int i; - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_render: " - "interlaced frame of %d x %d pixels at %d x %d\n", - file->Image.Width, file->Image.Height, - file->Image.Left, file->Image.Top ); - - for( i = 0; i < 4; i++ ) { - int y; - - for( y = InterlacedOffset[i]; y < file->Image.Height; - y += InterlacedJumps[i] ) { - if( DGifGetLine( gif->file, - gif->line, file->Image.Width ) == - GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - vips_foreign_load_gif_render_line( gif, y, - file->Image.Left, - file->Image.Top, - file->Image.Width, - file->Image.Height, - gif->line ); - } - } - } - else { - int y; - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_render: " - "non-interlaced frame of %d x %d pixels at %d x %d\n", - file->Image.Width, file->Image.Height, - file->Image.Left, file->Image.Top ); - - for( y = 0; y < file->Image.Height; y++ ) { - if( DGifGetLine( gif->file, - gif->line, file->Image.Width ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - vips_foreign_load_gif_render_line( gif, y, - file->Image.Left, - file->Image.Top, - file->Image.Width, - file->Image.Height, - gif->line ); - } - } - - /* Copy the result to frame, ready to be copied to our output. - */ - memcpy( VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), - VIPS_IMAGE_ADDR( gif->scratch, 0, 0 ), - VIPS_IMAGE_SIZEOF_IMAGE( gif->frame ) ); - - if( gif->dispose == DISPOSE_BACKGROUND ) { - /* BACKGROUND means we reset the area we just painted to - * transparent. We have to clip against the canvas. - */ - VipsRect canvas; - VipsRect image; - VipsRect overlap; - - canvas.left = 0; - canvas.top = 0; - canvas.width = gif->file->SWidth; - canvas.height = gif->file->SHeight; - image.left = file->Image.Left, - image.top = file->Image.Top, - image.width = file->Image.Width, - image.height = file->Image.Height, - vips_rect_intersectrect( &canvas, &image, &overlap ); - - if( !vips_rect_isempty( &overlap ) ) { - guint32 *q = (guint32 *) VIPS_IMAGE_ADDR( gif->scratch, - overlap.left, overlap.top ); - - /* What we write for transparent pixels. We want RGB - * to be 255, and A to be 0. - */ - guint32 transparent = GUINT32_TO_BE( 0xffffff00 ); - - int x, y; - - /* Generate the first line a pixel at a time, - * memcpy() for subsequent lines. - */ - for( x = 0; x < overlap.width; x++ ) - q[x] = transparent; - - for( y = 1; y < overlap.height; y++ ) - memcpy( q + gif->scratch->Xsize * y, - q, - overlap.width * sizeof( guint32 ) ); - } - } - else if( gif->dispose == DISPOSE_PREVIOUS ) - /* PREVIOUS means we restore the previous state of the scratch - * area. - */ - memcpy( VIPS_IMAGE_ADDR( gif->scratch, 0, 0 ), - VIPS_IMAGE_ADDR( gif->previous, 0, 0 ), - VIPS_IMAGE_SIZEOF_IMAGE( gif->scratch ) ); - - /* Reset values, as the Graphic Control Extension is optional - */ - gif->dispose = DISPOSAL_UNSPECIFIED; - gif->transparent_index = NO_TRANSPARENT_INDEX; - - return( 0 ); -} - -#ifdef VIPS_DEBUG -static const char * -dispose2str( int dispose ) -{ - switch( dispose ) { - case DISPOSAL_UNSPECIFIED: return( "DISPOSAL_UNSPECIFIED" ); - case DISPOSE_DO_NOT: return( "DISPOSE_DO_NOT" ); - case DISPOSE_BACKGROUND: return( "DISPOSE_BACKGROUND" ); - case DISPOSE_PREVIOUS: return( "DISPOSE_PREVIOUS" ); - default: return( "" ); - } -} -#endif /*VIPS_DEBUG*/ - -static int -vips_foreign_load_gif_extension( VipsForeignLoadGif *gif ) -{ - GifByteType *extension; - int ext_code; - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension:\n" ); - - if( DGifGetExtension( gif->file, &ext_code, &extension ) == - GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - if( extension && - ext_code == GRAPHICS_EXT_FUNC_CODE && - extension[0] == 4 ) { - int flags = extension[1]; - - /* Bytes are flags, delay low, delay high, transparency. - * Flag bit 1 means transparency is being set. - */ - gif->transparent_index = (flags & TRANSPARENT_MASK) ? - extension[4] : NO_TRANSPARENT_INDEX; - VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension: " - "transparency = %d\n", gif->transparent_index ); - - /* Set the current dispose mode. This is read during frame load - * to set the meaning of background and transparent pixels. - */ - gif->dispose = (flags >> DISPOSE_SHIFT) & DISPOSE_MASK; - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension: " - "dispose = %s\n", dispose2str( gif->dispose ) ); - } - - while( extension != NULL ) - if( vips_foreign_load_gif_ext_next( gif, &extension ) ) - return( -1 ); - - return( 0 ); -} - -/* Read the next page from the file into @frame. - */ -static int -vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif ) -{ - GifRecordType record; - gboolean have_read_frame; - - have_read_frame = FALSE; - do { - if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - switch( record ) { - case IMAGE_DESC_RECORD_TYPE: - VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: " - "IMAGE_DESC_RECORD_TYPE\n" ); - - if( vips_foreign_load_gif_render( gif ) ) - return( -1 ); - - have_read_frame = TRUE; - - break; - - case EXTENSION_RECORD_TYPE: - if( vips_foreign_load_gif_extension( gif ) ) - return( -1 ); - break; - - case TERMINATE_RECORD_TYPE: - VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: " - "TERMINATE_RECORD_TYPE\n" ); - gif->eof = TRUE; - break; - - case SCREEN_DESC_RECORD_TYPE: - VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: " - "SCREEN_DESC_RECORD_TYPE\n" ); - break; - - case UNDEFINED_RECORD_TYPE: - VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: " - "UNDEFINED_RECORD_TYPE\n" ); - break; - - default: - break; - } - } while( !have_read_frame && - !gif->eof ); - - return( 0 ); -} - -static int -vips_foreign_load_gif_generate( VipsRegion *or, - void *seq, void *a, void *b, gboolean *stop ) -{ - VipsRect *r = &or->valid; - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) a; - - int y; - -#ifdef DEBUG_VERBOSE - printf( "vips_foreign_load_gif_generate: %p " - "left = %d, top = %d, width = %d, height = %d\n", - gif, - r->left, r->top, r->width, r->height ); -#endif /*DEBUG_VERBOSE*/ - - 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; - - VipsPel *p, *q; - int x; - - g_assert( line >= 0 && line < gif->frame->Ysize ); - g_assert( page >= 0 && page < gif->n_pages ); - - /* 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; - } - } - - return( 0 ); -} - -static void -vips_foreign_load_gif_minimise( VipsObject *object, VipsForeignLoadGif *gif ) -{ - vips_source_minimise( gif->source ); -} - -static VipsImage * -vips_foreign_load_gif_temp( VipsForeignLoadGif *gif ) -{ - VipsImage *temp; - - temp = vips_image_new_memory(); - vips_image_init_fields( temp, - gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); - if( vips_image_write_prepare( temp ) ) { - VIPS_UNREF( temp ); - return( NULL ); - } - - return( temp ); -} - -static int -vips_foreign_load_gif_load( VipsForeignLoad *load ) -{ - VipsForeignLoadGif *gif = VIPS_FOREIGN_LOAD_GIF( load ); - VipsImage **t = (VipsImage **) - vips_object_local_array( VIPS_OBJECT( load ), 4 ); - - VIPS_DEBUG_MSG( "vips_foreign_load_gif_load: %p\n", gif ); - - if( vips_foreign_load_gif_open_giflib( gif ) ) - return( -1 ); - - /* Set of temp images we use during rendering. - */ - if( !(gif->frame = vips_foreign_load_gif_temp( gif )) || - !(gif->scratch = vips_foreign_load_gif_temp( gif )) || - !(gif->previous = vips_foreign_load_gif_temp( gif )) ) - return( -1 ); - - /* Make the output pipeline. - */ - t[0] = vips_image_new(); - if( vips_foreign_load_gif_set_header( gif, t[0] ) ) - return( -1 ); - - /* Close input immediately at end of read. - */ - g_signal_connect( t[0], "minimise", - G_CALLBACK( vips_foreign_load_gif_minimise ), gif ); - - /* Strips 8 pixels high to avoid too many tiny regions. - */ - if( vips_image_generate( t[0], - NULL, vips_foreign_load_gif_generate, NULL, gif, NULL ) || - vips_sequential( t[0], &t[1], - "tile_height", VIPS__FATSTRIP_HEIGHT, - NULL ) || - vips_image_write( t[1], load->real ) ) - 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; - - gobject_class->dispose = vips_foreign_load_gif_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->header = vips_foreign_load_gif_header; - load_class->load = vips_foreign_load_gif_load; - load_class->get_flags_filename = - vips_foreign_load_gif_get_flags_filename; - load_class->get_flags = vips_foreign_load_gif_get_flags; - - VIPS_ARG_INT( class, "page", 20, - _( "Page" ), - _( "Load this page from the file" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadGif, page ), - 0, 100000, 0 ); - - VIPS_ARG_INT( class, "n", 21, - _( "n" ), - _( "Load this many pages" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadGif, n ), - -1, 100000, 1 ); - -} - -static void -vips_foreign_load_gif_init( VipsForeignLoadGif *gif ) -{ - gif->n = 1; - gif->transparent_index = NO_TRANSPARENT_INDEX; - gif->delays = NULL; - gif->delays_length = 0; - gif->loop = 1; - gif->comment = NULL; - gif->dispose = DISPOSAL_UNSPECIFIED; - - vips_foreign_load_gif_allocate_delays( gif ); -} - -typedef struct _VipsForeignLoadGifFile { - VipsForeignLoadGif parent_object; - - /* Filename for load. - */ - char *filename; - -} VipsForeignLoadGifFile; - -typedef VipsForeignLoadGifClass VipsForeignLoadGifFileClass; - -G_DEFINE_TYPE( VipsForeignLoadGifFile, vips_foreign_load_gif_file, - vips_foreign_load_gif_get_type() ); - -static int -vips_foreign_load_gif_file_build( VipsObject *object ) -{ - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; - VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) object; - - if( file->filename ) - if( !(gif->source = - vips_source_new_from_file( file->filename )) ) - return( -1 ); - - if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_file_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static const char *vips_foreign_gif_suffs[] = { - ".gif", - NULL -}; - -static gboolean -vips_foreign_load_gif_file_is_a( const char *filename ) -{ - VipsSource *source; - gboolean result; - - if( !(source = vips_source_new_from_file( filename )) ) - return( FALSE ); - result = vips_foreign_load_gif_is_a_source( source ); - VIPS_UNREF( source ); - - return( result ); -} - -static void -vips_foreign_load_gif_file_class_init( - VipsForeignLoadGifFileClass *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 = "gifload"; - object_class->description = _( "load GIF with giflib" ); - object_class->build = vips_foreign_load_gif_file_build; - - foreign_class->suffs = vips_foreign_gif_suffs; - - load_class->is_a = vips_foreign_load_gif_file_is_a; - - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadGifFile, filename ), - NULL ); - -} - -static void -vips_foreign_load_gif_file_init( VipsForeignLoadGifFile *file ) -{ -} - -typedef struct _VipsForeignLoadGifBuffer { - VipsForeignLoadGif parent_object; - - /* Load from a buffer. - */ - VipsArea *blob; - -} VipsForeignLoadGifBuffer; - -typedef VipsForeignLoadGifClass VipsForeignLoadGifBufferClass; - -G_DEFINE_TYPE( VipsForeignLoadGifBuffer, vips_foreign_load_gif_buffer, - vips_foreign_load_gif_get_type() ); - -static int -vips_foreign_load_gif_buffer_build( VipsObject *object ) -{ - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; - VipsForeignLoadGifBuffer *buffer = - (VipsForeignLoadGifBuffer *) object; - - if( buffer->blob && - !(gif->source = vips_source_new_from_memory( - VIPS_AREA( buffer->blob )->data, - VIPS_AREA( buffer->blob )->length )) ) - return( -1 ); - - if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_buffer_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static gboolean -vips_foreign_load_gif_buffer_is_a_buffer( const void *buf, size_t len ) -{ - VipsSource *source; - gboolean result; - - if( !(source = vips_source_new_from_memory( buf, len )) ) - return( FALSE ); - result = vips_foreign_load_gif_is_a_source( source ); - VIPS_UNREF( source ); - - return( result ); -} - -static void -vips_foreign_load_gif_buffer_class_init( - VipsForeignLoadGifBufferClass *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 = "gifload_buffer"; - object_class->description = _( "load GIF with giflib" ); - object_class->build = vips_foreign_load_gif_buffer_build; - - load_class->is_a_buffer = vips_foreign_load_gif_buffer_is_a_buffer; - - VIPS_ARG_BOXED( class, "buffer", 1, - _( "Buffer" ), - _( "Buffer to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadGifBuffer, blob ), - VIPS_TYPE_BLOB ); - -} - -static void -vips_foreign_load_gif_buffer_init( VipsForeignLoadGifBuffer *buffer ) -{ -} - -typedef struct _VipsForeignLoadGifSource { - VipsForeignLoadGif parent_object; - - /* Load from a source. - */ - VipsSource *source; - -} VipsForeignLoadGifSource; - -typedef VipsForeignLoadGifClass VipsForeignLoadGifSourceClass; - -G_DEFINE_TYPE( VipsForeignLoadGifSource, vips_foreign_load_gif_source, - vips_foreign_load_gif_get_type() ); - -static int -vips_foreign_load_gif_source_build( VipsObject *object ) -{ - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; - VipsForeignLoadGifSource *source = - (VipsForeignLoadGifSource *) object; - - if( source->source ) { - gif->source = source->source; - g_object_ref( gif->source ); - } - - if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_source_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static void -vips_foreign_load_gif_source_class_init( - VipsForeignLoadGifSourceClass *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 = "gifload_source"; - object_class->description = _( "load GIF with giflib" ); - object_class->build = vips_foreign_load_gif_source_build; - - load_class->is_a_source = vips_foreign_load_gif_is_a_source; - - VIPS_ARG_OBJECT( class, "source", 1, - _( "Source" ), - _( "Source to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadGifSource, source ), - VIPS_TYPE_SOURCE ); - -} - -static void -vips_foreign_load_gif_source_init( VipsForeignLoadGifSource *source ) -{ -} - -#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 libvips 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. Set to -1 to mean "until the end of the - * document". Use vips_grid() to change page layout. - * - * The output image will be 1, 2, 3 or 4 bands for mono, mono plus - * transparency, RGB, or RGB plus transparency. - * - * See also: vips_image_new_from_file(). - * - * Returns: 0 on success, -1 on error. - */ -int -vips_gifload( const char *filename, VipsImage **out, ... ) -{ - va_list ap; - int result; - - va_start( ap, out ); - result = vips_call_split( "gifload", 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_gifload_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( "gifload_buffer", ap, blob, out ); - va_end( ap ); - - vips_area_unref( VIPS_AREA( blob ) ); - - return( result ); -} - -/** - * vips_gifload_source: - * @source: source to load - * @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 - * - * Exactly as vips_gifload(), but read from a source. - * - * See also: vips_gifload(). - * - * Returns: 0 on success, -1 on error. - */ -int -vips_gifload_source( VipsSource *source, VipsImage **out, ... ) -{ - va_list ap; - int result; - - va_start( ap, out ); - result = vips_call_split( "gifload_source", ap, source, out ); - va_end( ap ); - - return( result ); -} diff --git a/libvips/foreign/libnsgif/Makefile.am b/libvips/foreign/libnsgif/Makefile.am new file mode 100644 index 00000000..0ab13f5b --- /dev/null +++ b/libvips/foreign/libnsgif/Makefile.am @@ -0,0 +1,15 @@ +EXTRA_DIST = \ + README-ns \ + README.md \ + patches \ + update.sh \ + utils + +noinst_LTLIBRARIES = libnsgif.la + +libnsgif_la_SOURCES = \ + libnsgif.h \ + libnsgif.c \ + lzw.c \ + lzw.h + diff --git a/libvips/foreign/libnsgif/README-ns b/libvips/foreign/libnsgif/README-ns new file mode 100644 index 00000000..498ee466 --- /dev/null +++ b/libvips/foreign/libnsgif/README-ns @@ -0,0 +1,36 @@ +libnsgif - Decoding GIF files +============================= + +The functions provided by this library allow for efficient progressive +GIF decoding. Whilst the initialisation does not ensure that there is +sufficient image data to complete the entire frame, it does ensure +that the information provided is valid. Any subsequent attempts to +decode an initialised GIF are guaranteed to succeed, and any bytes of +the image not present are assumed to be totally transparent. + +To begin decoding a GIF, the 'gif' structure must be initialised with +the 'gif_data' and 'buffer_size' set to their initial values. The +'buffer_position' should initially be 0, and will be internally +updated as the decoding commences. The caller should then repeatedly +call gif_initialise() with the structure until the function returns 1, +or no more data is avaliable. + +Once the initialisation has begun, the decoder completes the variables +'frame_count' and 'frame_count_partial'. The former being the total +number of frames that have been successfully initialised, and the +latter being the number of frames that a partial amount of data is +available for. This assists the caller in managing the animation +whilst decoding is continuing. + +To decode a frame, the caller must use gif_decode_frame() which +updates the current 'frame_image' to reflect the desired frame. The +required 'disposal_method' is also updated to reflect how the frame +should be plotted. The caller must not assume that the current +'frame_image' will be valid between calls if initialisation is still +occuring, and should either always request that the frame is decoded +(no processing will occur if the 'decoded_frame' has not been +invalidated by initialisation) or perform the check itself. + +It should be noted that gif_finalise() should always be called, even +if no frames were initialised. Additionally, it is the responsibility +of the caller to free 'gif_data'. diff --git a/libvips/foreign/libnsgif/README.md b/libvips/foreign/libnsgif/README.md new file mode 100644 index 00000000..2ee2a0fc --- /dev/null +++ b/libvips/foreign/libnsgif/README.md @@ -0,0 +1,16 @@ +# libnsgif + +This is [libnsgif](https://www.netsurf-browser.org/projects/libnsgif/), +but within the libvips build system. + +# To update + +Run `./update.sh` to update this copy of libnsgif from the upstream repo. It +will also patch libnsgif.c to prevent it modifying the input. + +Last updated 28 Feb 2021. + +# To do + +No attempt made to run tests or build docs. Though the gif loader is tested as +part of the libvips test suite. diff --git a/libvips/foreign/libnsgif/libnsgif.c b/libvips/foreign/libnsgif/libnsgif.c new file mode 100644 index 00000000..6cce6b6b --- /dev/null +++ b/libvips/foreign/libnsgif/libnsgif.c @@ -0,0 +1,1224 @@ +/* + * Copyright 2004 Richard Wilson + * Copyright 2008 Sean Fox + * + * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/ + * Licenced under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + */ + +#include +#include +#include +#include +#include +#include +#include "libnsgif.h" +#include "utils/log.h" + +#include "lzw.h" + +/** + * + * \file + * \brief GIF image decoder + * + * The GIF format is thoroughly documented; a full description can be found at + * http://www.w3.org/Graphics/GIF/spec-gif89a.txt + * + * \todo Plain text and comment extensions should be implemented. + */ + + +/** Maximum colour table size */ +#define GIF_MAX_COLOURS 256 + +/** Internal flag that the colour table needs to be processed */ +#define GIF_PROCESS_COLOURS 0xaa000000 + +/** Internal flag that a frame is invalid/unprocessed */ +#define GIF_INVALID_FRAME -1 + +/** Transparent colour */ +#define GIF_TRANSPARENT_COLOUR 0x00 + +/* GIF Flags */ +#define GIF_FRAME_COMBINE 1 +#define GIF_FRAME_CLEAR 2 +#define GIF_FRAME_RESTORE 3 +#define GIF_FRAME_QUIRKS_RESTORE 4 + +#define GIF_IMAGE_SEPARATOR 0x2c +#define GIF_INTERLACE_MASK 0x40 +#define GIF_COLOUR_TABLE_MASK 0x80 +#define GIF_COLOUR_TABLE_SIZE_MASK 0x07 +#define GIF_EXTENSION_INTRODUCER 0x21 +#define GIF_EXTENSION_GRAPHIC_CONTROL 0xf9 +#define GIF_DISPOSAL_MASK 0x1c +#define GIF_TRANSPARENCY_MASK 0x01 +#define GIF_EXTENSION_COMMENT 0xfe +#define GIF_EXTENSION_PLAIN_TEXT 0x01 +#define GIF_EXTENSION_APPLICATION 0xff +#define GIF_BLOCK_TERMINATOR 0x00 +#define GIF_TRAILER 0x3b + +/** standard GIF header size */ +#define GIF_STANDARD_HEADER_SIZE 13 + + +/** + * Updates the sprite memory size + * + * \param gif The animation context + * \param width The width of the sprite + * \param height The height of the sprite + * \return GIF_INSUFFICIENT_MEMORY for a memory error GIF_OK for success + */ +static gif_result +gif_initialise_sprite(gif_animation *gif, + unsigned int width, + unsigned int height) +{ + unsigned int max_width; + unsigned int max_height; + struct bitmap *buffer; + + /* Check if we've changed */ + if ((width <= gif->width) && (height <= gif->height)) { + return GIF_OK; + } + + /* Get our maximum values */ + max_width = (width > gif->width) ? width : gif->width; + max_height = (height > gif->height) ? height : gif->height; + + /* Allocate some more memory */ + assert(gif->bitmap_callbacks.bitmap_create); + buffer = gif->bitmap_callbacks.bitmap_create(max_width, max_height); + if (buffer == NULL) { + return GIF_INSUFFICIENT_MEMORY; + } + + assert(gif->bitmap_callbacks.bitmap_destroy); + gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); + gif->frame_image = buffer; + gif->width = max_width; + gif->height = max_height; + + /* Invalidate our currently decoded image */ + gif->decoded_frame = GIF_INVALID_FRAME; + return GIF_OK; +} + + +/** + * Attempts to initialise the frame's extensions + * + * \param gif The animation context + * \param frame The frame number + * @return GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the + * frame GIF_OK for successful initialisation. + */ +static gif_result +gif_initialise_frame_extensions(gif_animation *gif, const int frame) +{ + unsigned char *gif_data, *gif_end; + int gif_bytes; + unsigned int block_size; + + /* Get our buffer position etc. */ + gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); + gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); + + /* Initialise the extensions */ + while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) { + ++gif_data; + if ((gif_bytes = (gif_end - gif_data)) < 1) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + + /* Switch on extension label */ + switch (gif_data[0]) { + case GIF_EXTENSION_GRAPHIC_CONTROL: + /* 6-byte Graphic Control Extension is: + * + * +0 CHAR Graphic Control Label + * +1 CHAR Block Size + * +2 CHAR __Packed Fields__ + * 3BITS Reserved + * 3BITS Disposal Method + * 1BIT User Input Flag + * 1BIT Transparent Color Flag + * +3 SHORT Delay Time + * +5 CHAR Transparent Color Index + */ + if (gif_bytes < 6) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + + gif->frames[frame].frame_delay = gif_data[3] | (gif_data[4] << 8); + if (gif_data[2] & GIF_TRANSPARENCY_MASK) { + gif->frames[frame].transparency = true; + gif->frames[frame].transparency_index = gif_data[5]; + } + gif->frames[frame].disposal_method = ((gif_data[2] & GIF_DISPOSAL_MASK) >> 2); + /* I have encountered documentation and GIFs in the + * wild that use 0x04 to restore the previous frame, + * rather than the officially documented 0x03. I + * believe some (older?) software may even actually + * export this way. We handle this as a type of + * "quirks" mode. + */ + if (gif->frames[frame].disposal_method == GIF_FRAME_QUIRKS_RESTORE) { + gif->frames[frame].disposal_method = GIF_FRAME_RESTORE; + } + gif_data += (2 + gif_data[1]); + break; + + case GIF_EXTENSION_APPLICATION: + /* 14-byte+ Application Extension is: + * + * +0 CHAR Application Extension Label + * +1 CHAR Block Size + * +2 8CHARS Application Identifier + * +10 3CHARS Appl. Authentication Code + * +13 1-256 Application Data (Data sub-blocks) + */ + if (gif_bytes < 17) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + if ((gif_data[1] == 0x0b) && + (strncmp((const char *) gif_data + 2, + "NETSCAPE2.0", 11) == 0) && + (gif_data[13] == 0x03) && + (gif_data[14] == 0x01)) { + gif->loop_count = gif_data[15] | (gif_data[16] << 8); + } + gif_data += (2 + gif_data[1]); + break; + + case GIF_EXTENSION_COMMENT: + /* Move the pointer to the first data sub-block Skip 1 + * byte for the extension label + */ + ++gif_data; + break; + + default: + /* Move the pointer to the first data sub-block Skip 2 + * bytes for the extension label and size fields Skip + * the extension size itself + */ + if (gif_bytes < 2) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + gif_data += (2 + gif_data[1]); + } + + /* Repeatedly skip blocks until we get a zero block or run out + * of data This data is ignored by this gif decoder + */ + gif_bytes = (gif_end - gif_data); + block_size = 0; + while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) { + block_size = gif_data[0] + 1; + if ((gif_bytes -= block_size) < 0) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + gif_data += block_size; + } + ++gif_data; + } + + /* Set buffer position and return */ + gif->buffer_position = (gif_data - gif->gif_data); + return GIF_OK; +} + + +/** + * Attempts to initialise the next frame + * + * \param gif The animation context + * \return error code + * - GIF_INSUFFICIENT_DATA for insufficient data to do anything + * - GIF_FRAME_DATA_ERROR for GIF frame data error + * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process + * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame + * - GIF_DATA_ERROR for GIF error (invalid frame header) + * - GIF_OK for successful decoding + * - GIF_WORKING for successful decoding if more frames are expected +*/ +static gif_result gif_initialise_frame(gif_animation *gif) +{ + int frame; + gif_frame *temp_buf; + + unsigned char *gif_data, *gif_end; + int gif_bytes; + unsigned int flags = 0; + unsigned int width, height, offset_x, offset_y; + unsigned int block_size, colour_table_size; + bool first_image = true; + gif_result return_value; + + /* Get the frame to decode and our data position */ + frame = gif->frame_count; + + /* Get our buffer position etc. */ + gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); + gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); + gif_bytes = (gif_end - gif_data); + + /* Check if we've finished */ + if ((gif_bytes > 0) && (gif_data[0] == GIF_TRAILER)) { + return GIF_OK; + } + + /* Check if there is enough data remaining. The shortest block of data + * is a 4-byte comment extension + 1-byte block terminator + 1-byte gif + * trailer + */ + if (gif_bytes < 6) { + return GIF_INSUFFICIENT_DATA; + } + + /* We could theoretically get some junk data that gives us millions of + * frames, so we ensure that we don't have a silly number + */ + if (frame > 4096) { + return GIF_FRAME_DATA_ERROR; + } + + /* Get some memory to store our pointers in etc. */ + if ((int)gif->frame_holders <= frame) { + /* Allocate more memory */ + temp_buf = (gif_frame *)realloc(gif->frames, (frame + 1) * sizeof(gif_frame)); + if (temp_buf == NULL) { + return GIF_INSUFFICIENT_MEMORY; + } + gif->frames = temp_buf; + gif->frame_holders = frame + 1; + } + + /* Store our frame pointer. We would do it when allocating except we + * start off with one frame allocated so we can always use realloc. + */ + gif->frames[frame].frame_pointer = gif->buffer_position; + gif->frames[frame].display = false; + gif->frames[frame].virgin = true; + gif->frames[frame].disposal_method = 0; + gif->frames[frame].transparency = false; + gif->frames[frame].frame_delay = 100; + gif->frames[frame].redraw_required = false; + + /* Invalidate any previous decoding we have of this frame */ + if (gif->decoded_frame == frame) { + gif->decoded_frame = GIF_INVALID_FRAME; + } + + /* We pretend to initialise the frames, but really we just skip over + * all the data contained within. This is all basically a cut down + * version of gif_decode_frame that doesn't have any of the LZW bits in + * it. + */ + + /* Initialise any extensions */ + gif->buffer_position = gif_data - gif->gif_data; + return_value = gif_initialise_frame_extensions(gif, frame); + if (return_value != GIF_OK) { + return return_value; + } + gif_data = (gif->gif_data + gif->buffer_position); + gif_bytes = (gif_end - gif_data); + + /* Check if we've finished */ + if ((gif_bytes = (gif_end - gif_data)) < 1) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + + if (gif_data[0] == GIF_TRAILER) { + gif->buffer_position = (gif_data - gif->gif_data); + gif->frame_count = frame + 1; + return GIF_OK; + } + + /* If we're not done, there should be an image descriptor */ + if (gif_data[0] != GIF_IMAGE_SEPARATOR) { + return GIF_FRAME_DATA_ERROR; + } + + /* Do some simple boundary checking */ + if (gif_bytes < 10) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + offset_x = gif_data[1] | (gif_data[2] << 8); + offset_y = gif_data[3] | (gif_data[4] << 8); + width = gif_data[5] | (gif_data[6] << 8); + height = gif_data[7] | (gif_data[8] << 8); + + /* Set up the redraw characteristics. We have to check for extending + * the area due to multi-image frames. + */ + if (!first_image) { + if (gif->frames[frame].redraw_x > offset_x) { + gif->frames[frame].redraw_width += (gif->frames[frame].redraw_x - offset_x); + gif->frames[frame].redraw_x = offset_x; + } + + if (gif->frames[frame].redraw_y > offset_y) { + gif->frames[frame].redraw_height += (gif->frames[frame].redraw_y - offset_y); + gif->frames[frame].redraw_y = offset_y; + } + + if ((offset_x + width) > (gif->frames[frame].redraw_x + gif->frames[frame].redraw_width)) { + gif->frames[frame].redraw_width = (offset_x + width) - gif->frames[frame].redraw_x; + } + + if ((offset_y + height) > (gif->frames[frame].redraw_y + gif->frames[frame].redraw_height)) { + gif->frames[frame].redraw_height = (offset_y + height) - gif->frames[frame].redraw_y; + } + } else { + first_image = false; + gif->frames[frame].redraw_x = offset_x; + gif->frames[frame].redraw_y = offset_y; + gif->frames[frame].redraw_width = width; + gif->frames[frame].redraw_height = height; + } + + /* if we are clearing the background then we need to redraw enough to + * cover the previous frame too + */ + gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) || + (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE)); + + /* Boundary checking - shouldn't ever happen except with junk data */ + if (gif_initialise_sprite(gif, (offset_x + width), (offset_y + height))) { + return GIF_INSUFFICIENT_MEMORY; + } + + /* Decode the flags */ + flags = gif_data[9]; + colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); + + /* Move our data onwards and remember we've got a bit of this frame */ + gif_data += 10; + gif_bytes = (gif_end - gif_data); + gif->frame_count_partial = frame + 1; + + /* Skip the local colour table */ + if (flags & GIF_COLOUR_TABLE_MASK) { + gif_data += 3 * colour_table_size; + if ((gif_bytes = (gif_end - gif_data)) < 0) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + } + + /* Ensure we have a correct code size */ + if (gif_bytes < 1) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + if (gif_data[0] > LZW_CODE_MAX) { + return GIF_DATA_ERROR; + } + + /* Move our pointer to the actual image data */ + gif_data++; + --gif_bytes; + + /* Repeatedly skip blocks until we get a zero block or run out of data + * These blocks of image data are processed later by gif_decode_frame() + */ + block_size = 0; + while (block_size != 1) { + if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA; + block_size = gif_data[0] + 1; + /* Check if the frame data runs off the end of the file */ + if ((int)(gif_bytes - block_size) < 0) { + /* jcupitt 15/9/19 + * + * There was code here to set a TRAILER tag. But this + * wrote to the input buffer, which will not work for + * libvips, where buffers can be mmaped read only files. + * + * Instead, just signal insufficient frame data. + */ + return GIF_INSUFFICIENT_FRAME_DATA; + } else { + gif_bytes -= block_size; + gif_data += block_size; + } + } + + /* Add the frame and set the display flag */ + gif->buffer_position = gif_data - gif->gif_data; + gif->frame_count = frame + 1; + gif->frames[frame].display = true; + + /* Check if we've finished */ + if (gif_bytes < 1) { + return GIF_INSUFFICIENT_FRAME_DATA; + } else { + if (gif_data[0] == GIF_TRAILER) { + return GIF_OK; + } + } + return GIF_WORKING; +} + + + + +/** + * Skips the frame's extensions (which have been previously initialised) + * + * \param gif The animation context + * \return GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the + * frame GIF_OK for successful decoding + */ +static gif_result gif_skip_frame_extensions(gif_animation *gif) +{ + unsigned char *gif_data, *gif_end; + int gif_bytes; + unsigned int block_size; + + /* Get our buffer position etc. */ + gif_data = (unsigned char *)(gif->gif_data + gif->buffer_position); + gif_end = (unsigned char *)(gif->gif_data + gif->buffer_size); + gif_bytes = (gif_end - gif_data); + + /* Skip the extensions */ + while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) { + ++gif_data; + if (gif_data >= gif_end) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + + /* Switch on extension label */ + switch(gif_data[0]) { + case GIF_EXTENSION_COMMENT: + /* Move the pointer to the first data sub-block + * 1 byte for the extension label + */ + ++gif_data; + break; + + default: + /* Move the pointer to the first data sub-block 2 bytes + * for the extension label and size fields Skip the + * extension size itself + */ + if (gif_data + 1 >= gif_end) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + gif_data += (2 + gif_data[1]); + } + + /* Repeatedly skip blocks until we get a zero block or run out + * of data This data is ignored by this gif decoder + */ + gif_bytes = (gif_end - gif_data); + block_size = 0; + while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) { + block_size = gif_data[0] + 1; + if ((gif_bytes -= block_size) < 0) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + gif_data += block_size; + } + ++gif_data; + } + + /* Set buffer position and return */ + gif->buffer_position = (gif_data - gif->gif_data); + return GIF_OK; +} + +static unsigned int gif_interlaced_line(int height, int y) { + if ((y << 3) < height) { + return (y << 3); + } + y -= ((height + 7) >> 3); + if ((y << 3) < (height - 4)) { + return (y << 3) + 4; + } + y -= ((height + 3) >> 3); + if ((y << 2) < (height - 2)) { + return (y << 2) + 2; + } + y -= ((height + 1) >> 2); + return (y << 1) + 1; +} + + +static gif_result gif_error_from_lzw(lzw_result l_res) +{ + static const gif_result g_res[] = { + [LZW_OK] = GIF_OK, + [LZW_OK_EOD] = GIF_END_OF_FRAME, + [LZW_NO_MEM] = GIF_INSUFFICIENT_MEMORY, + [LZW_NO_DATA] = GIF_INSUFFICIENT_FRAME_DATA, + [LZW_EOI_CODE] = GIF_FRAME_DATA_ERROR, + [LZW_BAD_ICODE] = GIF_FRAME_DATA_ERROR, + [LZW_BAD_CODE] = GIF_FRAME_DATA_ERROR, + }; + return g_res[l_res]; +} + +static void gif__record_previous_frame(gif_animation *gif) +{ + bool need_alloc = gif->prev_frame == NULL; + const uint32_t *frame_data; + uint32_t *prev_frame; + + if (gif->decoded_frame == GIF_INVALID_FRAME || + gif->decoded_frame == gif->prev_index) { + /* No frame to copy, or already have this frame recorded. */ + return; + } + + assert(gif->bitmap_callbacks.bitmap_get_buffer); + frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); + if (!frame_data) { + return; + } + + if (gif->prev_frame != NULL && + gif->width * gif->height > gif->prev_width * gif->prev_height) { + need_alloc = true; + } + + if (need_alloc) { + prev_frame = realloc(gif->prev_frame, + gif->width * gif->height * 4); + if (prev_frame == NULL) { + return; + } + } else { + prev_frame = gif->prev_frame; + } + + memcpy(prev_frame, frame_data, gif->width * gif->height * 4); + + gif->prev_frame = prev_frame; + gif->prev_width = gif->width; + gif->prev_height = gif->height; + gif->prev_index = gif->decoded_frame; +} + +static gif_result gif__recover_previous_frame(const gif_animation *gif) +{ + const uint32_t *prev_frame = gif->prev_frame; + unsigned height = gif->height < gif->prev_height ? gif->height : gif->prev_height; + unsigned width = gif->width < gif->prev_width ? gif->width : gif->prev_width; + uint32_t *frame_data; + + if (prev_frame == NULL) { + return GIF_FRAME_DATA_ERROR; + } + + assert(gif->bitmap_callbacks.bitmap_get_buffer); + frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); + if (!frame_data) { + return GIF_INSUFFICIENT_MEMORY; + } + + for (unsigned y = 0; y < height; y++) { + memcpy(frame_data, prev_frame, width * 4); + + frame_data += gif->width; + prev_frame += gif->prev_width; + } + + return GIF_OK; +} + +/** + * decode a gif frame + * + * \param gif gif animation context. + * \param frame The frame number to decode. + * \param clear_image flag for image data being cleared instead of plotted. + */ +static gif_result +gif_internal_decode_frame(gif_animation *gif, + unsigned int frame, + bool clear_image) +{ + gif_result err; + unsigned int index = 0; + unsigned char *gif_data, *gif_end; + int gif_bytes; + unsigned int width, height, offset_x, offset_y; + unsigned int flags, colour_table_size, interlace; + unsigned int *colour_table; + unsigned int *frame_data = 0; // Set to 0 for no warnings + unsigned int *frame_scanline; + unsigned int save_buffer_position; + unsigned int return_value = 0; + unsigned int x, y, decode_y, burst_bytes; + register unsigned char colour; + + /* Ensure this frame is supposed to be decoded */ + if (gif->frames[frame].display == false) { + return GIF_OK; + } + + /* Ensure the frame is in range to decode */ + if (frame > gif->frame_count_partial) { + return GIF_INSUFFICIENT_DATA; + } + + /* done if frame is already decoded */ + if ((!clear_image) && + ((int)frame == gif->decoded_frame)) { + return GIF_OK; + } + + /* Get the start of our frame data and the end of the GIF data */ + gif_data = gif->gif_data + gif->frames[frame].frame_pointer; + gif_end = gif->gif_data + gif->buffer_size; + gif_bytes = (gif_end - gif_data); + + /* + * Ensure there is a minimal amount of data to proceed. The shortest + * block of data is a 10-byte image descriptor + 1-byte gif trailer + */ + if (gif_bytes < 12) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + + /* Save the buffer position */ + save_buffer_position = gif->buffer_position; + gif->buffer_position = gif_data - gif->gif_data; + + /* Skip any extensions because they have allready been processed */ + if ((return_value = gif_skip_frame_extensions(gif)) != GIF_OK) { + goto gif_decode_frame_exit; + } + gif_data = (gif->gif_data + gif->buffer_position); + gif_bytes = (gif_end - gif_data); + + /* Ensure we have enough data for the 10-byte image descriptor + 1-byte + * gif trailer + */ + if (gif_bytes < 12) { + return_value = GIF_INSUFFICIENT_FRAME_DATA; + goto gif_decode_frame_exit; + } + + /* 10-byte Image Descriptor is: + * + * +0 CHAR Image Separator (0x2c) + * +1 SHORT Image Left Position + * +3 SHORT Image Top Position + * +5 SHORT Width + * +7 SHORT Height + * +9 CHAR __Packed Fields__ + * 1BIT Local Colour Table Flag + * 1BIT Interlace Flag + * 1BIT Sort Flag + * 2BITS Reserved + * 3BITS Size of Local Colour Table + */ + if (gif_data[0] != GIF_IMAGE_SEPARATOR) { + return_value = GIF_DATA_ERROR; + goto gif_decode_frame_exit; + } + offset_x = gif_data[1] | (gif_data[2] << 8); + offset_y = gif_data[3] | (gif_data[4] << 8); + width = gif_data[5] | (gif_data[6] << 8); + height = gif_data[7] | (gif_data[8] << 8); + + /* Boundary checking - shouldn't ever happen except unless the data has + * been modified since initialisation. + */ + if ((offset_x + width > gif->width) || + (offset_y + height > gif->height)) { + return_value = GIF_DATA_ERROR; + goto gif_decode_frame_exit; + } + + /* Decode the flags */ + flags = gif_data[9]; + colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); + interlace = flags & GIF_INTERLACE_MASK; + + /* Advance data pointer to next block either colour table or image + * data. + */ + gif_data += 10; + gif_bytes = (gif_end - gif_data); + + /* Set up the colour table */ + if (flags & GIF_COLOUR_TABLE_MASK) { + if (gif_bytes < (int)(3 * colour_table_size)) { + return_value = GIF_INSUFFICIENT_FRAME_DATA; + goto gif_decode_frame_exit; + } + colour_table = gif->local_colour_table; + if (!clear_image) { + for (index = 0; index < colour_table_size; index++) { + /* Gif colour map contents are r,g,b. + * + * We want to pack them bytewise into the + * colour table, such that the red component + * is in byte 0 and the alpha component is in + * byte 3. + */ + unsigned char *entry = + (unsigned char *) &colour_table[index]; + + entry[0] = gif_data[0]; /* r */ + entry[1] = gif_data[1]; /* g */ + entry[2] = gif_data[2]; /* b */ + entry[3] = 0xff; /* a */ + + gif_data += 3; + } + } else { + gif_data += 3 * colour_table_size; + } + gif_bytes = (gif_end - gif_data); + } else { + colour_table = gif->global_colour_table; + } + + /* Ensure sufficient data remains */ + if (gif_bytes < 1) { + return_value = GIF_INSUFFICIENT_FRAME_DATA; + goto gif_decode_frame_exit; + } + + /* check for an end marker */ + if (gif_data[0] == GIF_TRAILER) { + return_value = GIF_OK; + goto gif_decode_frame_exit; + } + + /* Get the frame data */ + assert(gif->bitmap_callbacks.bitmap_get_buffer); + frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); + if (!frame_data) { + return GIF_INSUFFICIENT_MEMORY; + } + + /* If we are clearing the image we just clear, if not decode */ + if (!clear_image) { + lzw_result res; + const uint8_t *stack_base; + const uint8_t *stack_pos; + + /* Ensure we have enough data for a 1-byte LZW code size + + * 1-byte gif trailer + */ + if (gif_bytes < 2) { + return_value = GIF_INSUFFICIENT_FRAME_DATA; + goto gif_decode_frame_exit; + } + + /* If we only have a 1-byte LZW code size + 1-byte gif trailer, + * we're finished + */ + if ((gif_bytes == 2) && (gif_data[1] == GIF_TRAILER)) { + return_value = GIF_OK; + goto gif_decode_frame_exit; + } + + /* If the previous frame's disposal method requires we restore + * the background colour or this is the first frame, clear + * the frame data + */ + if ((frame == 0) || (gif->decoded_frame == GIF_INVALID_FRAME)) { + memset((char*)frame_data, + GIF_TRANSPARENT_COLOUR, + gif->width * gif->height * sizeof(int)); + gif->decoded_frame = frame; + /* The line below would fill the image with its + * background color, but because GIFs support + * transparency we likely wouldn't want to do that. */ + /* memset((char*)frame_data, colour_table[gif->background_index], gif->width * gif->height * sizeof(int)); */ + } else if ((frame != 0) && + (gif->frames[frame - 1].disposal_method == GIF_FRAME_CLEAR)) { + return_value = gif_internal_decode_frame(gif, + (frame - 1), + true); + if (return_value != GIF_OK) { + goto gif_decode_frame_exit; + } + + } else if ((frame != 0) && + (gif->frames[frame - 1].disposal_method == GIF_FRAME_RESTORE)) { + /* + * If the previous frame's disposal method requires we + * restore the previous image, restore our saved image. + */ + err = gif__recover_previous_frame(gif); + if (err != GIF_OK) { + /* see notes above on transparency + * vs. background color + */ + memset((char*)frame_data, + GIF_TRANSPARENT_COLOUR, + gif->width * gif->height * sizeof(int)); + } + } + + if (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE) { + /* Store the previous frame for later restoration */ + gif__record_previous_frame(gif); + } + + gif->decoded_frame = frame; + gif->buffer_position = (gif_data - gif->gif_data) + 1; + + /* Initialise the LZW decoding */ + res = lzw_decode_init(gif->lzw_ctx, gif->gif_data, + gif->buffer_size, gif->buffer_position, + gif_data[0], &stack_base, &stack_pos); + if (res != LZW_OK) { + return gif_error_from_lzw(res); + } + + /* Decompress the data */ + for (y = 0; y < height; y++) { + if (interlace) { + decode_y = gif_interlaced_line(height, y) + offset_y; + } else { + decode_y = y + offset_y; + } + frame_scanline = frame_data + offset_x + (decode_y * gif->width); + + /* Rather than decoding pixel by pixel, we try to burst + * out streams of data to remove the need for end-of + * data checks every pixel. + */ + x = width; + while (x > 0) { + burst_bytes = (stack_pos - stack_base); + if (burst_bytes > 0) { + if (burst_bytes > x) { + burst_bytes = x; + } + x -= burst_bytes; + while (burst_bytes-- > 0) { + colour = *--stack_pos; + if (((gif->frames[frame].transparency) && + (colour != gif->frames[frame].transparency_index)) || + (!gif->frames[frame].transparency)) { + *frame_scanline = colour_table[colour]; + } + frame_scanline++; + } + } else { + res = lzw_decode(gif->lzw_ctx, &stack_pos); + if (res != LZW_OK) { + /* Unexpected end of frame, try to recover */ + if (res == LZW_OK_EOD) { + return_value = GIF_OK; + } else { + return_value = gif_error_from_lzw(res); + } + goto gif_decode_frame_exit; + } + } + } + } + } else { + /* Clear our frame */ + if (gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) { + for (y = 0; y < height; y++) { + frame_scanline = frame_data + offset_x + ((offset_y + y) * gif->width); + if (gif->frames[frame].transparency) { + memset(frame_scanline, + GIF_TRANSPARENT_COLOUR, + width * 4); + } else { + memset(frame_scanline, + colour_table[gif->background_index], + width * 4); + } + } + } + } +gif_decode_frame_exit: + + /* Check if we should test for optimisation */ + if (gif->frames[frame].virgin) { + if (gif->bitmap_callbacks.bitmap_test_opaque) { + gif->frames[frame].opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image); + } else { + gif->frames[frame].opaque = false; + } + gif->frames[frame].virgin = false; + } + + if (gif->bitmap_callbacks.bitmap_set_opaque) { + gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, gif->frames[frame].opaque); + } + + if (gif->bitmap_callbacks.bitmap_modified) { + gif->bitmap_callbacks.bitmap_modified(gif->frame_image); + } + + /* Restore the buffer position */ + gif->buffer_position = save_buffer_position; + + return return_value; +} + + +/* exported function documented in libnsgif.h */ +void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks) +{ + memset(gif, 0, sizeof(gif_animation)); + gif->bitmap_callbacks = *bitmap_callbacks; + gif->decoded_frame = GIF_INVALID_FRAME; + gif->prev_index = GIF_INVALID_FRAME; +} + + +/* exported function documented in libnsgif.h */ +gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data) +{ + unsigned char *gif_data; + unsigned int index; + gif_result return_value; + + /* Initialize values */ + gif->buffer_size = size; + gif->gif_data = data; + + if (gif->lzw_ctx == NULL) { + lzw_result res = lzw_context_create( + (struct lzw_ctx **)&gif->lzw_ctx); + if (res != LZW_OK) { + return gif_error_from_lzw(res); + } + } + + /* Check for sufficient data to be a GIF (6-byte header + 7-byte + * logical screen descriptor) + */ + if (gif->buffer_size < GIF_STANDARD_HEADER_SIZE) { + return GIF_INSUFFICIENT_DATA; + } + + /* Get our current processing position */ + gif_data = gif->gif_data + gif->buffer_position; + + /* See if we should initialise the GIF */ + if (gif->buffer_position == 0) { + /* We want everything to be NULL before we start so we've no + * chance of freeing bad pointers (paranoia) + */ + gif->frame_image = NULL; + gif->frames = NULL; + gif->local_colour_table = NULL; + gif->global_colour_table = NULL; + + /* The caller may have been lazy and not reset any values */ + gif->frame_count = 0; + gif->frame_count_partial = 0; + gif->decoded_frame = GIF_INVALID_FRAME; + + /* 6-byte GIF file header is: + * + * +0 3CHARS Signature ('GIF') + * +3 3CHARS Version ('87a' or '89a') + */ + if (strncmp((const char *) gif_data, "GIF", 3) != 0) { + return GIF_DATA_ERROR; + } + gif_data += 3; + + /* Ensure GIF reports version 87a or 89a */ + /* + if ((strncmp(gif_data, "87a", 3) != 0) && + (strncmp(gif_data, "89a", 3) != 0)) + LOG(("Unknown GIF format - proceeding anyway")); + */ + gif_data += 3; + + /* 7-byte Logical Screen Descriptor is: + * + * +0 SHORT Logical Screen Width + * +2 SHORT Logical Screen Height + * +4 CHAR __Packed Fields__ + * 1BIT Global Colour Table Flag + * 3BITS Colour Resolution + * 1BIT Sort Flag + * 3BITS Size of Global Colour Table + * +5 CHAR Background Colour Index + * +6 CHAR Pixel Aspect Ratio + */ + gif->width = gif_data[0] | (gif_data[1] << 8); + gif->height = gif_data[2] | (gif_data[3] << 8); + gif->global_colours = (gif_data[4] & GIF_COLOUR_TABLE_MASK); + gif->colour_table_size = (2 << (gif_data[4] & GIF_COLOUR_TABLE_SIZE_MASK)); + gif->background_index = gif_data[5]; + gif->aspect_ratio = gif_data[6]; + gif->loop_count = 1; + gif_data += 7; + + /* Some broken GIFs report the size as the screen size they + * were created in. As such, we detect for the common cases and + * set the sizes as 0 if they are found which results in the + * GIF being the maximum size of the frames. + */ + if (((gif->width == 640) && (gif->height == 480)) || + ((gif->width == 640) && (gif->height == 512)) || + ((gif->width == 800) && (gif->height == 600)) || + ((gif->width == 1024) && (gif->height == 768)) || + ((gif->width == 1280) && (gif->height == 1024)) || + ((gif->width == 1600) && (gif->height == 1200)) || + ((gif->width == 0) || (gif->height == 0)) || + ((gif->width > 2048) || (gif->height > 2048))) { + gif->width = 1; + gif->height = 1; + } + + /* Allocate some data irrespective of whether we've got any + * colour tables. We always get the maximum size in case a GIF + * is lying to us. It's far better to give the wrong colours + * than to trample over some memory somewhere. + */ + gif->global_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int)); + gif->local_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int)); + if ((gif->global_colour_table == NULL) || + (gif->local_colour_table == NULL)) { + gif_finalise(gif); + return GIF_INSUFFICIENT_MEMORY; + } + + /* Set the first colour to a value that will never occur in + * reality so we know if we've processed it + */ + gif->global_colour_table[0] = GIF_PROCESS_COLOURS; + + /* Check if the GIF has no frame data (13-byte header + 1-byte + * termination block) Although generally useless, the GIF + * specification does not expressly prohibit this + */ + if (gif->buffer_size == (GIF_STANDARD_HEADER_SIZE + 1)) { + if (gif_data[0] == GIF_TRAILER) { + return GIF_OK; + } else { + return GIF_INSUFFICIENT_DATA; + } + } + + /* Initialise enough workspace for a frame */ + if ((gif->frames = (gif_frame *)malloc(sizeof(gif_frame))) == NULL) { + gif_finalise(gif); + return GIF_INSUFFICIENT_MEMORY; + } + gif->frame_holders = 1; + + /* Initialise the bitmap header */ + assert(gif->bitmap_callbacks.bitmap_create); + gif->frame_image = gif->bitmap_callbacks.bitmap_create(gif->width, gif->height); + if (gif->frame_image == NULL) { + gif_finalise(gif); + return GIF_INSUFFICIENT_MEMORY; + } + + /* Remember we've done this now */ + gif->buffer_position = gif_data - gif->gif_data; + } + + /* Do the colour map if we haven't already. As the top byte is always + * 0xff or 0x00 depending on the transparency we know if it's been + * filled in. + */ + if (gif->global_colour_table[0] == GIF_PROCESS_COLOURS) { + /* Check for a global colour map signified by bit 7 */ + if (gif->global_colours) { + if (gif->buffer_size < (gif->colour_table_size * 3 + GIF_STANDARD_HEADER_SIZE)) { + return GIF_INSUFFICIENT_DATA; + } + for (index = 0; index < gif->colour_table_size; index++) { + /* Gif colour map contents are r,g,b. + * + * We want to pack them bytewise into the + * colour table, such that the red component + * is in byte 0 and the alpha component is in + * byte 3. + */ + unsigned char *entry = (unsigned char *) &gif-> + global_colour_table[index]; + + entry[0] = gif_data[0]; /* r */ + entry[1] = gif_data[1]; /* g */ + entry[2] = gif_data[2]; /* b */ + entry[3] = 0xff; /* a */ + + gif_data += 3; + } + gif->buffer_position = (gif_data - gif->gif_data); + } else { + /* Create a default colour table with the first two + * colours as black and white + */ + unsigned int *entry = gif->global_colour_table; + + entry[0] = 0x00000000; + /* Force Alpha channel to opaque */ + ((unsigned char *) entry)[3] = 0xff; + + entry[1] = 0xffffffff; + } + } + + /* Repeatedly try to initialise frames */ + while ((return_value = gif_initialise_frame(gif)) == GIF_WORKING); + + /* If there was a memory error tell the caller */ + if ((return_value == GIF_INSUFFICIENT_MEMORY) || + (return_value == GIF_DATA_ERROR)) { + return return_value; + } + + /* If we didn't have some frames then a GIF_INSUFFICIENT_DATA becomes a + * GIF_INSUFFICIENT_FRAME_DATA + */ + if ((return_value == GIF_INSUFFICIENT_DATA) && + (gif->frame_count_partial > 0)) { + return GIF_INSUFFICIENT_FRAME_DATA; + } + + /* Return how many we got */ + return return_value; +} + + +/* exported function documented in libnsgif.h */ +gif_result gif_decode_frame(gif_animation *gif, unsigned int frame) +{ + return gif_internal_decode_frame(gif, frame, false); +} + + +/* exported function documented in libnsgif.h */ +void gif_finalise(gif_animation *gif) +{ + /* Release all our memory blocks */ + if (gif->frame_image) { + assert(gif->bitmap_callbacks.bitmap_destroy); + gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); + } + + gif->frame_image = NULL; + free(gif->frames); + gif->frames = NULL; + free(gif->local_colour_table); + gif->local_colour_table = NULL; + free(gif->global_colour_table); + gif->global_colour_table = NULL; + + free(gif->prev_frame); + gif->prev_frame = NULL; + + lzw_context_destroy(gif->lzw_ctx); + gif->lzw_ctx = NULL; +} diff --git a/libvips/foreign/libnsgif/libnsgif.h b/libvips/foreign/libnsgif/libnsgif.h new file mode 100644 index 00000000..50dc6883 --- /dev/null +++ b/libvips/foreign/libnsgif/libnsgif.h @@ -0,0 +1,192 @@ +/* + * Copyright 2004 Richard Wilson + * Copyright 2008 Sean Fox + * + * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/ + * Licenced under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + */ + +/** + * \file + * Interface to progressive animated GIF file decoding. + */ + +#ifndef _LIBNSGIF_H_ +#define _LIBNSGIF_H_ + +#include +#include + +/* Error return values */ +typedef enum { + GIF_WORKING = 1, + GIF_OK = 0, + GIF_INSUFFICIENT_FRAME_DATA = -1, + GIF_FRAME_DATA_ERROR = -2, + GIF_INSUFFICIENT_DATA = -3, + GIF_DATA_ERROR = -4, + GIF_INSUFFICIENT_MEMORY = -5, + GIF_FRAME_NO_DISPLAY = -6, + GIF_END_OF_FRAME = -7 +} gif_result; + +/** GIF frame data */ +typedef struct gif_frame { + /** whether the frame should be displayed/animated */ + bool display; + /** delay (in cs) before animating the frame */ + unsigned int frame_delay; + + /* Internal members are listed below */ + + /** offset (in bytes) to the GIF frame data */ + unsigned int frame_pointer; + /** whether the frame has previously been used */ + bool virgin; + /** whether the frame is totally opaque */ + bool opaque; + /** whether a forcable screen redraw is required */ + bool redraw_required; + /** how the previous frame should be disposed; affects plotting */ + unsigned char disposal_method; + /** whether we acknoledge transparency */ + bool transparency; + /** the index designating a transparent pixel */ + unsigned char transparency_index; + /** x co-ordinate of redraw rectangle */ + unsigned int redraw_x; + /** y co-ordinate of redraw rectangle */ + unsigned int redraw_y; + /** width of redraw rectangle */ + unsigned int redraw_width; + /** height of redraw rectangle */ + unsigned int redraw_height; +} gif_frame; + +/* API for Bitmap callbacks */ +typedef void* (*gif_bitmap_cb_create)(int width, int height); +typedef void (*gif_bitmap_cb_destroy)(void *bitmap); +typedef unsigned char* (*gif_bitmap_cb_get_buffer)(void *bitmap); +typedef void (*gif_bitmap_cb_set_opaque)(void *bitmap, bool opaque); +typedef bool (*gif_bitmap_cb_test_opaque)(void *bitmap); +typedef void (*gif_bitmap_cb_modified)(void *bitmap); + +/** Bitmap callbacks function table */ +typedef struct gif_bitmap_callback_vt { + /** Create a bitmap. */ + gif_bitmap_cb_create bitmap_create; + /** Free a bitmap. */ + gif_bitmap_cb_destroy bitmap_destroy; + /** Return a pointer to the pixel data in a bitmap. */ + gif_bitmap_cb_get_buffer bitmap_get_buffer; + + /* Members below are optional */ + + /** Sets whether a bitmap should be plotted opaque. */ + gif_bitmap_cb_set_opaque bitmap_set_opaque; + /** Tests whether a bitmap has an opaque alpha channel. */ + gif_bitmap_cb_test_opaque bitmap_test_opaque; + /** The bitmap image has changed, so flush any persistant cache. */ + gif_bitmap_cb_modified bitmap_modified; +} gif_bitmap_callback_vt; + +/** GIF animation data */ +typedef struct gif_animation { + /** LZW decode context */ + void *lzw_ctx; + /** callbacks for bitmap functions */ + gif_bitmap_callback_vt bitmap_callbacks; + /** pointer to GIF data */ + unsigned char *gif_data; + /** width of GIF (may increase during decoding) */ + unsigned int width; + /** heigth of GIF (may increase during decoding) */ + unsigned int height; + /** number of frames decoded */ + unsigned int frame_count; + /** number of frames partially decoded */ + unsigned int frame_count_partial; + /** decoded frames */ + gif_frame *frames; + /** current frame decoded to bitmap */ + int decoded_frame; + /** currently decoded image; stored as bitmap from bitmap_create callback */ + void *frame_image; + /** number of times to loop animation */ + int loop_count; + + /* Internal members are listed below */ + + /** current index into GIF data */ + unsigned int buffer_position; + /** total number of bytes of GIF data available */ + unsigned int buffer_size; + /** current number of frame holders */ + unsigned int frame_holders; + /** index in the colour table for the background colour */ + unsigned int background_index; + /** image aspect ratio (ignored) */ + unsigned int aspect_ratio; + /** size of colour table (in entries) */ + unsigned int colour_table_size; + /** whether the GIF has a global colour table */ + bool global_colours; + /** global colour table */ + unsigned int *global_colour_table; + /** local colour table */ + unsigned int *local_colour_table; + + /** previous frame for GIF_FRAME_RESTORE */ + void *prev_frame; + /** previous frame index */ + int prev_index; + /** previous frame width */ + unsigned prev_width; + /** previous frame height */ + unsigned prev_height; +} gif_animation; + +/** + * Initialises necessary gif_animation members. + */ +void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks); + +/** + * Initialises any workspace held by the animation and attempts to decode + * any information that hasn't already been decoded. + * If an error occurs, all previously decoded frames are retained. + * + * @return Error return value. + * - GIF_FRAME_DATA_ERROR for GIF frame data error + * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to process + * any more frames + * - GIF_INSUFFICIENT_MEMORY for memory error + * - GIF_DATA_ERROR for GIF error + * - GIF_INSUFFICIENT_DATA for insufficient data to do anything + * - GIF_OK for successful decoding + * - GIF_WORKING for successful decoding if more frames are expected + */ +gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data); + +/** + * Decodes a GIF frame. + * + * @return Error return value. If a frame does not contain any image data, + * GIF_OK is returned and gif->current_error is set to + * GIF_FRAME_NO_DISPLAY + * - GIF_FRAME_DATA_ERROR for GIF frame data error + * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame + * - GIF_DATA_ERROR for GIF error (invalid frame header) + * - GIF_INSUFFICIENT_DATA for insufficient data to do anything + * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process + * - GIF_OK for successful decoding + */ +gif_result gif_decode_frame(gif_animation *gif, unsigned int frame); + +/** + * Releases any workspace held by a gif + */ +void gif_finalise(gif_animation *gif); + +#endif diff --git a/libvips/foreign/libnsgif/lzw.c b/libvips/foreign/libnsgif/lzw.c new file mode 100644 index 00000000..31cf7d4e --- /dev/null +++ b/libvips/foreign/libnsgif/lzw.c @@ -0,0 +1,377 @@ +/* + * This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/ + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * + * Copyright 2017 Michael Drake + */ + +#include +#include +#include +#include + +#include "lzw.h" + +/** + * \file + * \brief LZW decompression (implementation) + * + * Decoder for GIF LZW data. + */ + + +/** + * Context for reading LZW data. + * + * LZW data is split over multiple sub-blocks. Each sub-block has a + * byte at the start, which says the sub-block size, and then the data. + * Zero-size sub-blocks have no data, and the biggest sub-block size is + * 255, which means there are 255 bytes of data following the sub-block + * size entry. + * + * Note that an individual LZW code can be split over up to three sub-blocks. + */ +struct lzw_read_ctx { + const uint8_t *data; /**< Pointer to start of input data */ + uint32_t data_len; /**< Input data length */ + uint32_t data_sb_next; /**< Offset to sub-block size */ + + const uint8_t *sb_data; /**< Pointer to current sub-block in data */ + uint32_t sb_bit; /**< Current bit offset in sub-block */ + uint32_t sb_bit_count; /**< Bit count in sub-block */ +}; + +/** + * LZW dictionary entry. + * + * Records in the dictionary are composed of 1 or more entries. + * Entries point to previous entries which can be followed to compose + * the complete record. To compose the record in reverse order, take + * the `last_value` from each entry, and move to the previous entry. + * If the previous_entry's index is < the current clear_code, then it + * is the last entry in the record. + */ +struct lzw_dictionary_entry { + uint8_t last_value; /**< Last value for record ending at entry. */ + uint8_t first_value; /**< First value for entry's record. */ + uint16_t previous_entry; /**< Offset in dictionary to previous entry. */ +}; + +/** + * LZW decompression context. + */ +struct lzw_ctx { + /** Input reading context */ + struct lzw_read_ctx input; + + uint32_t previous_code; /**< Code read from input previously. */ + uint32_t previous_code_first; /**< First value of previous code. */ + + uint32_t initial_code_size; /**< Starting LZW code size. */ + uint32_t current_code_size; /**< Current LZW code size. */ + uint32_t current_code_size_max; /**< Max code value for current size. */ + + uint32_t clear_code; /**< Special Clear code value */ + uint32_t eoi_code; /**< Special End of Information code value */ + + uint32_t current_entry; /**< Next position in table to fill. */ + + /** Output value stack. */ + uint8_t stack_base[1 << LZW_CODE_MAX]; + + /** LZW decode dictionary. Generated during decode. */ + struct lzw_dictionary_entry table[1 << LZW_CODE_MAX]; +}; + + +/* Exported function, documented in lzw.h */ +lzw_result lzw_context_create(struct lzw_ctx **ctx) +{ + struct lzw_ctx *c = malloc(sizeof(*c)); + if (c == NULL) { + return LZW_NO_MEM; + } + + *ctx = c; + return LZW_OK; +} + + +/* Exported function, documented in lzw.h */ +void lzw_context_destroy(struct lzw_ctx *ctx) +{ + free(ctx); +} + + +/** + * Advance the context to the next sub-block in the input data. + * + * \param[in] ctx LZW reading context, updated on success. + * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. + */ +static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx) +{ + uint32_t block_size; + uint32_t next_block_pos = ctx->data_sb_next; + const uint8_t *data_next = ctx->data + next_block_pos; + + if (next_block_pos >= ctx->data_len) { + return LZW_NO_DATA; + } + + block_size = *data_next; + + if ((next_block_pos + block_size) >= ctx->data_len) { + return LZW_NO_DATA; + } + + ctx->sb_bit = 0; + ctx->sb_bit_count = block_size * 8; + + if (block_size == 0) { + ctx->data_sb_next += 1; + return LZW_OK_EOD; + } + + ctx->sb_data = data_next + 1; + ctx->data_sb_next += block_size + 1; + + return LZW_OK; +} + + +/** + * Get the next LZW code of given size from the raw input data. + * + * Reads codes from the input data stream coping with GIF data sub-blocks. + * + * \param[in] ctx LZW reading context, updated. + * \param[in] code_size Size of LZW code to get from data. + * \param[out] code_out Returns an LZW code on success. + * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. + */ +static inline lzw_result lzw__next_code( + struct lzw_read_ctx *ctx, + uint8_t code_size, + uint32_t *code_out) +{ + uint32_t code = 0; + uint8_t current_bit = ctx->sb_bit & 0x7; + uint8_t byte_advance = (current_bit + code_size) >> 3; + + assert(byte_advance <= 2); + + if (ctx->sb_bit + code_size <= ctx->sb_bit_count) { + /* Fast path: code fully inside this sub-block */ + const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3); + switch (byte_advance) { + case 2: code |= data[2] << 16; /* Fall through */ + case 1: code |= data[1] << 8; /* Fall through */ + case 0: code |= data[0] << 0; + } + ctx->sb_bit += code_size; + } else { + /* Slow path: code spans sub-blocks */ + uint8_t byte = 0; + uint8_t bits_remaining_0 = (code_size < (8 - current_bit)) ? + code_size : (8 - current_bit); + uint8_t bits_remaining_1 = code_size - bits_remaining_0; + uint8_t bits_used[3] = { + [0] = bits_remaining_0, + [1] = bits_remaining_1 < 8 ? bits_remaining_1 : 8, + [2] = bits_remaining_1 - 8, + }; + + while (true) { + const uint8_t *data = ctx->sb_data; + lzw_result res; + + /* Get any data from end of this sub-block */ + while (byte <= byte_advance && + ctx->sb_bit < ctx->sb_bit_count) { + code |= data[ctx->sb_bit >> 3] << (byte << 3); + ctx->sb_bit += bits_used[byte]; + byte++; + } + + /* Check if we have all we need */ + if (byte > byte_advance) { + break; + } + + /* Move to next sub-block */ + res = lzw__block_advance(ctx); + if (res != LZW_OK) { + return res; + } + } + } + + *code_out = (code >> current_bit) & ((1 << code_size) - 1); + return LZW_OK; +} + + +/** + * Clear LZW code dictionary. + * + * \param[in] ctx LZW reading context, updated. + * \param[out] stack_pos_out Returns current stack position. + * \return LZW_OK or error code. + */ +static lzw_result lzw__clear_codes( + struct lzw_ctx *ctx, + const uint8_t ** const stack_pos_out) +{ + uint32_t code; + uint8_t *stack_pos; + + /* Reset dictionary building context */ + ctx->current_code_size = ctx->initial_code_size + 1; + ctx->current_code_size_max = (1 << ctx->current_code_size) - 1;; + ctx->current_entry = (1 << ctx->initial_code_size) + 2; + + /* There might be a sequence of clear codes, so process them all */ + do { + lzw_result res = lzw__next_code(&ctx->input, + ctx->current_code_size, &code); + if (res != LZW_OK) { + return res; + } + } while (code == ctx->clear_code); + + /* The initial code must be from the initial dictionary. */ + if (code > ctx->clear_code) { + return LZW_BAD_ICODE; + } + + /* Record this initial code as "previous" code, needed during decode. */ + ctx->previous_code = code; + ctx->previous_code_first = code; + + /* Reset the stack, and add first non-clear code added as first item. */ + stack_pos = ctx->stack_base; + *stack_pos++ = code; + + *stack_pos_out = stack_pos; + return LZW_OK; +} + + +/* Exported function, documented in lzw.h */ +lzw_result lzw_decode_init( + struct lzw_ctx *ctx, + const uint8_t *compressed_data, + uint32_t compressed_data_len, + uint32_t compressed_data_pos, + uint8_t code_size, + const uint8_t ** const stack_base_out, + const uint8_t ** const stack_pos_out) +{ + struct lzw_dictionary_entry *table = ctx->table; + + /* Initialise the input reading context */ + ctx->input.data = compressed_data; + ctx->input.data_len = compressed_data_len; + ctx->input.data_sb_next = compressed_data_pos; + + ctx->input.sb_bit = 0; + ctx->input.sb_bit_count = 0; + + /* Initialise the dictionary building context */ + ctx->initial_code_size = code_size; + + ctx->clear_code = (1 << code_size) + 0; + ctx->eoi_code = (1 << code_size) + 1; + + /* Initialise the standard dictionary entries */ + for (uint32_t i = 0; i < ctx->clear_code; ++i) { + table[i].first_value = i; + table[i].last_value = i; + } + + *stack_base_out = ctx->stack_base; + return lzw__clear_codes(ctx, stack_pos_out); +} + + +/* Exported function, documented in lzw.h */ +lzw_result lzw_decode(struct lzw_ctx *ctx, + const uint8_t ** const stack_pos_out) +{ + lzw_result res; + uint32_t code_new; + uint32_t code_out; + uint8_t last_value; + uint8_t *stack_pos = ctx->stack_base; + uint32_t clear_code = ctx->clear_code; + uint32_t current_entry = ctx->current_entry; + struct lzw_dictionary_entry * const table = ctx->table; + + /* Get a new code from the input */ + res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new); + if (res != LZW_OK) { + return res; + } + + /* Handle the new code */ + if (code_new == clear_code) { + /* Got Clear code */ + return lzw__clear_codes(ctx, stack_pos_out); + + } else if (code_new == ctx->eoi_code) { + /* Got End of Information code */ + return LZW_EOI_CODE; + + } else if (code_new > current_entry) { + /* Code is invalid */ + return LZW_BAD_CODE; + + } else if (code_new < current_entry) { + /* Code is in table */ + code_out = code_new; + last_value = table[code_new].first_value; + } else { + /* Code not in table */ + *stack_pos++ = ctx->previous_code_first; + code_out = ctx->previous_code; + last_value = ctx->previous_code_first; + } + + /* Add to the dictionary, only if there's space */ + if (current_entry < (1 << LZW_CODE_MAX)) { + struct lzw_dictionary_entry *entry = table + current_entry; + entry->last_value = last_value; + entry->first_value = ctx->previous_code_first; + entry->previous_entry = ctx->previous_code; + ctx->current_entry++; + } + + /* Ensure code size is increased, if needed. */ + if (current_entry == ctx->current_code_size_max) { + if (ctx->current_code_size < LZW_CODE_MAX) { + ctx->current_code_size++; + ctx->current_code_size_max = + (1 << ctx->current_code_size) - 1; + } + } + + /* Store details of this code as "previous code" to the context. */ + ctx->previous_code_first = table[code_new].first_value; + ctx->previous_code = code_new; + + /* Put rest of data for this code on output stack. + * Note, in the case of "code not in table", the last entry of the + * current code has already been placed on the stack above. */ + while (code_out > clear_code) { + struct lzw_dictionary_entry *entry = table + code_out; + *stack_pos++ = entry->last_value; + code_out = entry->previous_entry; + } + *stack_pos++ = table[code_out].last_value; + + *stack_pos_out = stack_pos; + return LZW_OK; +} diff --git a/libvips/foreign/libnsgif/lzw.h b/libvips/foreign/libnsgif/lzw.h new file mode 100644 index 00000000..385b4255 --- /dev/null +++ b/libvips/foreign/libnsgif/lzw.h @@ -0,0 +1,105 @@ +/* + * This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/ + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * + * Copyright 2017 Michael Drake + */ + +#ifndef LZW_H_ +#define LZW_H_ + +/** + * \file + * \brief LZW decompression (interface) + * + * Decoder for GIF LZW data. + */ + + +/** Maximum LZW code size in bits */ +#define LZW_CODE_MAX 12 + + +/* Declare lzw internal context structure */ +struct lzw_ctx; + + +/** LZW decoding response codes */ +typedef enum lzw_result { + LZW_OK, /**< Success */ + LZW_OK_EOD, /**< Success; reached zero-length sub-block */ + LZW_NO_MEM, /**< Error: Out of memory */ + LZW_NO_DATA, /**< Error: Out of data */ + LZW_EOI_CODE, /**< Error: End of Information code */ + LZW_BAD_ICODE, /**< Error: Bad initial LZW code */ + LZW_BAD_CODE, /**< Error: Bad LZW code */ +} lzw_result; + + +/** + * Create an LZW decompression context. + * + * \param[out] ctx Returns an LZW decompression context. Caller owned, + * free with lzw_context_destroy(). + * \return LZW_OK on success, or appropriate error code otherwise. + */ +lzw_result lzw_context_create( + struct lzw_ctx **ctx); + +/** + * Destroy an LZW decompression context. + * + * \param[in] ctx The LZW decompression context to destroy. + */ +void lzw_context_destroy( + struct lzw_ctx *ctx); + +/** + * Initialise an LZW decompression context for decoding. + * + * Caller owns neither `stack_base_out` or `stack_pos_out`. + * + * \param[in] ctx The LZW decompression context to initialise. + * \param[in] compressed_data The compressed data. + * \param[in] compressed_data_len Byte length of compressed data. + * \param[in] compressed_data_pos Start position in data. Must be position + * of a size byte at sub-block start. + * \param[in] code_size The initial LZW code size to use. + * \param[out] stack_base_out Returns base of decompressed data stack. + * \param[out] stack_pos_out Returns current stack position. + * There are `stack_pos_out - stack_base_out` + * current stack entries. + * \return LZW_OK on success, or appropriate error code otherwise. + */ +lzw_result lzw_decode_init( + struct lzw_ctx *ctx, + const uint8_t *compressed_data, + uint32_t compressed_data_len, + uint32_t compressed_data_pos, + uint8_t code_size, + const uint8_t ** const stack_base_out, + const uint8_t ** const stack_pos_out); + +/** + * Fill the LZW stack with decompressed data + * + * Ensure anything on the stack is used before calling this, as anything + * on the stack before this call will be trampled. + * + * Caller does not own `stack_pos_out`. + * + * \param[in] ctx LZW reading context, updated. + * \param[out] stack_pos_out Returns current stack position. + * Use with `stack_base_out` value from previous + * lzw_decode_init() call. + * There are `stack_pos_out - stack_base_out` + * current stack entries. + * \return LZW_OK on success, or appropriate error code otherwise. + */ +lzw_result lzw_decode( + struct lzw_ctx *ctx, + const uint8_t ** const stack_pos_out); + + +#endif diff --git a/libvips/foreign/libnsgif/patches/no-input-modify.patch b/libvips/foreign/libnsgif/patches/no-input-modify.patch new file mode 100644 index 00000000..f358f307 --- /dev/null +++ b/libvips/foreign/libnsgif/patches/no-input-modify.patch @@ -0,0 +1,32 @@ +--- libnsgif-orig.c 2021-02-28 14:10:41.818557190 +0000 ++++ libnsgif.c 2021-02-28 14:11:55.942285930 +0000 +@@ -435,20 +435,15 @@ + block_size = gif_data[0] + 1; + /* Check if the frame data runs off the end of the file */ + if ((int)(gif_bytes - block_size) < 0) { +- /* Try to recover by signaling the end of the gif. +- * Once we get garbage data, there is no logical way to +- * determine where the next frame is. It's probably +- * better to partially load the gif than not at all. +- */ +- if (gif_bytes >= 2) { +- gif_data[0] = 0; +- gif_data[1] = GIF_TRAILER; +- gif_bytes = 1; +- ++gif_data; +- break; +- } else { +- return GIF_INSUFFICIENT_FRAME_DATA; +- } ++ /* jcupitt 15/9/19 ++ * ++ * There was code here to set a TRAILER tag. But this ++ * wrote to the input buffer, which will not work for ++ * libvips, where buffers can be mmaped read only files. ++ * ++ * Instead, just signal insufficient frame data. ++ */ ++ return GIF_INSUFFICIENT_FRAME_DATA; + } else { + gif_bytes -= block_size; + gif_data += block_size; diff --git a/libvips/foreign/libnsgif/update.sh b/libvips/foreign/libnsgif/update.sh new file mode 100755 index 00000000..dde02cf0 --- /dev/null +++ b/libvips/foreign/libnsgif/update.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# attempt to update our copy of libnsgif from the upstream repo + +set -e + +git clone git://git.netsurf-browser.org/libnsgif.git + +echo copying out source files ... +cp libnsgif/src/libnsgif.c . +cp libnsgif/include/libnsgif.h . +cp libnsgif/src/lzw.[ch] . +cp libnsgif/src/utils/log.h utils + +echo applying patches ... +for patch in patches/*.patch; do + patch -p0 <$patch +done + +echo cleaning up ... +rm -rf libnsgif + diff --git a/libvips/foreign/libnsgif/utils/log.h b/libvips/foreign/libnsgif/utils/log.h new file mode 100644 index 00000000..1413374c --- /dev/null +++ b/libvips/foreign/libnsgif/utils/log.h @@ -0,0 +1,21 @@ +/* + * Copyright 2003 James Bursa + * Copyright 2004 John Tytgat + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * Licenced under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + */ + +#include + +#ifndef _LIBNSGIF_LOG_H_ +#define _LIBNSGIF_LOG_H_ + +#ifdef NDEBUG +# define LOG(x) ((void) 0) +#else +# define LOG(x) do { fprintf(stderr, x), fputc('\n', stderr); } while (0) +#endif /* NDEBUG */ + +#endif /* _LIBNSGIF_LOG_H_ */ diff --git a/libvips/foreign/nsgifload.c b/libvips/foreign/nsgifload.c new file mode 100644 index 00000000..b9168694 --- /dev/null +++ b/libvips/foreign/nsgifload.c @@ -0,0 +1,909 @@ +/* load a GIF with libnsgif + * + * 6/10/18 + * - from gifload.c + */ + +/* + + 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 VERBOSE +#define VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* TODO: + * + * - libnsgif does not seem to support comment metadata + * + * - it always loads the entire source file into memory + * + * Notes: + * + * - hard to detect mono images -- local_colour_table in libnsgif is only set + * when we decode a frame, so we can't tell just from init whether any + * frames have colour info + * + * - don't bother detecting alpha -- if we can't detect RGB, alpha won't help + * much + * + */ + +#ifdef HAVE_NSGIF + +#include + +#define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_nsgif_get_type()) +#define VIPS_FOREIGN_LOAD_GIF( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgif )) +#define VIPS_FOREIGN_LOAD_GIF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgifClass)) +#define VIPS_IS_FOREIGN_LOAD_GIF( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_GIF )) +#define VIPS_IS_FOREIGN_LOAD_GIF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_GIF )) +#define VIPS_FOREIGN_LOAD_GIF_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgifClass )) + +typedef struct _VipsForeignLoadNsgif { + VipsForeignLoad parent_object; + + /* Load this page (frame number). + */ + int page; + + /* Load this many pages. + */ + int n; + + /* Load from this source (set by subclasses). + */ + VipsSource *source; + + /* The animation created by libnsgif. + */ + gif_animation *anim; + + /* The data/size pair we pass to libnsgif. + */ + unsigned char *data; + size_t size; + + /* The frame_count, after we have removed undisplayable frames. + */ + int frame_count_displayable; + + /* Delays between frames (in milliseconds). Array of length @n. + */ + int *delay; + + /* A single centisecond value for compatibility. + */ + int gif_delay; + +} VipsForeignLoadNsgif; + +typedef VipsForeignLoadClass VipsForeignLoadNsgifClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadNsgif, vips_foreign_load_nsgif, + VIPS_TYPE_FOREIGN_LOAD ); + +static const char * +vips_foreign_load_nsgif_errstr( gif_result result ) +{ + switch( result ) { + case GIF_WORKING: + return( _( "Working" ) ); + + case GIF_OK: + return( _( "OK" ) ); + + case GIF_INSUFFICIENT_FRAME_DATA: + return( _( "Insufficient data to complete frame" ) ); + + case GIF_FRAME_DATA_ERROR: + return( _( "GIF frame data error" ) ); + + case GIF_INSUFFICIENT_DATA: + return( _( "Insufficient data to do anything" ) ); + + case GIF_DATA_ERROR: + return( _( "GIF header data error" ) ); + + case GIF_INSUFFICIENT_MEMORY: + return( _( "Insuficient memory to process" ) ); + + case GIF_FRAME_NO_DISPLAY: + return( _( "No display" ) ); + + case GIF_END_OF_FRAME: + return( _( "At end of frame" ) ); + + default: + return( _( "Unknown error" ) ); + } +} + +static void +vips_foreign_load_nsgif_error( VipsForeignLoadNsgif *gif, gif_result result ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); + + vips_error( class->nickname, "%s", + vips_foreign_load_nsgif_errstr( result ) ); +} + +static void +vips_foreign_load_nsgif_dispose( GObject *gobject ) +{ + VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) gobject; + + VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_dispose:\n" ); + + if( gif->anim ) { + gif_finalise( gif->anim ); + VIPS_FREE( gif->anim ); + } + VIPS_UNREF( gif->source ); + VIPS_FREE( gif->delay ); + + G_OBJECT_CLASS( vips_foreign_load_nsgif_parent_class )-> + dispose( gobject ); +} + +static VipsForeignFlags +vips_foreign_load_nsgif_get_flags_filename( const char *filename ) +{ + return( VIPS_FOREIGN_SEQUENTIAL ); +} + +static VipsForeignFlags +vips_foreign_load_nsgif_get_flags( VipsForeignLoad *load ) +{ + return( VIPS_FOREIGN_SEQUENTIAL ); +} + +static gboolean +vips_foreign_load_nsgif_is_a_source( VipsSource *source ) +{ + const unsigned char *data; + + if( (data = vips_source_sniff( source, 4 )) && + data[0] == 'G' && + data[1] == 'I' && + data[2] == 'F' && + data[3] == '8' ) + return( TRUE ); + + return( FALSE ); +} + +#ifdef VERBOSE +static void +print_frame( gif_frame *frame ) +{ + printf( "frame:\n" ); + printf( " display = %d\n", frame->display ); + printf( " frame_delay = %d\n", frame->frame_delay ); + printf( " virgin = %d\n", frame->virgin ); + printf( " opaque = %d\n", frame->opaque ); + printf( " redraw_required = %d\n", frame->redraw_required ); + printf( " disposal_method = %d\n", frame->disposal_method ); + printf( " transparency = %d\n", frame->transparency ); + printf( " transparency_index = %d\n", frame->transparency_index ); + printf( " redraw_x = %d\n", frame->redraw_x ); + printf( " redraw_y = %d\n", frame->redraw_y ); + printf( " redraw_width = %d\n", frame->redraw_width ); + printf( " redraw_height = %d\n", frame->redraw_height ); +} + +static void +print_animation( gif_animation *anim ) +{ + int i; + + printf( "animation:\n" ); + printf( " width = %d\n", anim->width ); + printf( " height = %d\n", anim->height ); + printf( " frame_count = %d\n", anim->frame_count ); + printf( " frame_count_partial = %d\n", anim->frame_count_partial ); + printf( " decoded_frame = %d\n", anim->decoded_frame ); + printf( " frame_image = %p\n", anim->frame_image ); + printf( " loop_count = %d\n", anim->loop_count ); + printf( " frame_holders = %d\n", anim->frame_holders ); + printf( " background_index = %d\n", anim->background_index ); + printf( " colour_table_size = %d\n", anim->colour_table_size ); + printf( " global_colours = %d\n", anim->global_colours ); + printf( " global_colour_table = %p\n", anim->global_colour_table ); + printf( " local_colour_table = %p\n", anim->local_colour_table ); + + for( i = 0; i < anim->frame_holders; i++ ) { + printf( "%d ", i ); + print_frame( &anim->frames[i] ); + } +} +#endif /*VERBOSE*/ + +static int +vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif, + VipsImage *image ) +{ + VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_set_header:\n" ); + + vips_image_init_fields( image, + gif->anim->width, gif->anim->height * gif->n, 4, + VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, + VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + vips_image_pipelinev( image, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); + + if( vips_object_argument_isset( VIPS_OBJECT( gif ), "n" ) ) + vips_image_set_int( image, + VIPS_META_PAGE_HEIGHT, gif->anim->height ); + vips_image_set_int( image, VIPS_META_N_PAGES, + gif->frame_count_displayable ); + vips_image_set_int( image, "gif-loop", gif->anim->loop_count ); + + vips_image_set_array_int( image, "delay", gif->delay, gif->n ); + + /* The deprecated gif-delay field is in centiseconds. + */ + vips_image_set_int( image, "gif-delay", gif->gif_delay ); + + return( 0 ); +} + +/* Scan the GIF as quickly as we can and extract transparency, bands, pages, + * etc. + * + * Don't flag any errors unless we have to: we want to work for corrupt or + * malformed GIFs. + * + * Close as soon as we can to free up the fd. + */ +static int +vips_foreign_load_nsgif_header( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) load; + + const void *data; + size_t size; + gif_result result; + int i; + + VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_header:\n" ); + + /* We map in the image, then minimise to close any underlying file + * object. This won't unmap. + */ + if( !(data = vips_source_map( gif->source, &size )) ) + return( -1 ); + vips_source_minimise( gif->source ); + + result = gif_initialise( gif->anim, size, (void *) data ); + VIPS_DEBUG_MSG( "gif_initialise() = %d\n", result ); +#ifdef VERBOSE + print_animation( gif->anim ); +#endif /*VERBOSE*/ + if( result != GIF_OK && + result != GIF_WORKING && + result != GIF_INSUFFICIENT_FRAME_DATA ) { + vips_foreign_load_nsgif_error( gif, result ); + return( -1 ); + } + else if( result == GIF_INSUFFICIENT_FRAME_DATA && + load->fail ) { + vips_error( class->nickname, "%s", _( "truncated GIF" ) ); + return( -1 ); + } + + /* Many GIFs have dead frames at the end. Remove these from our count. + */ + for( i = gif->anim->frame_count - 1; + i >= 0 && !gif->anim->frames[i].display; i-- ) + ; + gif->frame_count_displayable = i + 1; +#ifdef VERBOSE + if( gif->frame_count_displayable != gif->anim->frame_count ) + printf( "vips_foreign_load_nsgif_open: " + "removed %d undisplayable frames\n", + gif->anim->frame_count - gif->frame_count_displayable ); +#endif /*VERBOSE*/ + + if( !gif->frame_count_displayable ) { + vips_error( class->nickname, "%s", _( "no frames in GIF" ) ); + return( -1 ); + } + + if( gif->n == -1 ) + gif->n = gif->frame_count_displayable - gif->page; + + if( gif->page < 0 || + gif->n <= 0 || + gif->page + gif->n > gif->frame_count_displayable ) { + vips_error( class->nickname, "%s", _( "bad page number" ) ); + return( -1 ); + } + + /* In ms, frame_delay in cs. + */ + VIPS_FREE( gif->delay ); + if( !(gif->delay = VIPS_ARRAY( NULL, gif->n, int )) ) + return( -1 ); + for( i = 0; i < gif->n; i++ ) + gif->delay[i] = + 10 * gif->anim->frames[gif->page + i].frame_delay; + + gif->gif_delay = gif->anim->frames[0].frame_delay; + + vips_foreign_load_nsgif_set_header( gif, load->out ); + + return( 0 ); +} + +static int +vips_foreign_load_nsgif_generate( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsRect *r = &or->valid; + VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) a; + + int y; + +#ifdef VERBOSE + VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_generate: " + "top = %d, height = %d\n", r->top, r->height ); +#endif /*VERBOSE*/ + + 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->anim->height + gif->page; + int line = (r->top + y) % gif->anim->height; + + gif_result result; + VipsPel *p, *q; + + g_assert( line >= 0 && line < gif->anim->height ); + g_assert( page >= 0 && page < gif->frame_count_displayable ); + + if( gif->anim->decoded_frame != page ) { + result = gif_decode_frame( gif->anim, page ); + VIPS_DEBUG_MSG( " gif_decode_frame(%d) = %d\n", + page, result ); + if( result != GIF_OK ) { + vips_foreign_load_nsgif_error( gif, result ); + return( -1 ); + } +#ifdef VERBOSE + print_animation( gif->anim ); +#endif /*VERBOSE*/ + } + + p = gif->anim->frame_image + + line * gif->anim->width * sizeof( int ); + q = VIPS_REGION_ADDR( or, 0, r->top + y ); + memcpy( q, p, VIPS_REGION_SIZEOF_LINE( or ) ); + } + + return( 0 ); +} + +static int +vips_foreign_load_nsgif_load( VipsForeignLoad *load ) +{ + VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) load; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( load ), 4 ); + + VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_load:\n" ); + + /* Make the output pipeline. + */ + t[0] = vips_image_new(); + if( vips_foreign_load_nsgif_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_nsgif_generate, NULL, gif, NULL ) || + vips_sequential( t[0], &t[1], + "tile_height", VIPS__FATSTRIP_HEIGHT, + NULL ) || + vips_image_write( t[1], load->real ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_nsgif_class_init( VipsForeignLoadNsgifClass *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->dispose = vips_foreign_load_nsgif_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 libnsgif" ); + + /* High priority, so that we handle vipsheader etc. + */ + foreign_class->priority = 50; + + load_class->get_flags_filename = + vips_foreign_load_nsgif_get_flags_filename; + load_class->get_flags = vips_foreign_load_nsgif_get_flags; + load_class->header = vips_foreign_load_nsgif_header; + load_class->load = vips_foreign_load_nsgif_load; + + VIPS_ARG_INT( class, "page", 10, + _( "Page" ), + _( "Load this page from the file" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadNsgif, page ), + 0, 100000, 0 ); + + VIPS_ARG_INT( class, "n", 6, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadNsgif, n ), + -1, 100000, 1 ); + +} + +static void * +vips_foreign_load_nsgif_bitmap_create( int width, int height ) +{ + /* ensure a stupidly large bitmap is not created + */ + + return calloc( width * height, 4 ); +} + +static void +vips_foreign_load_nsgif_bitmap_set_opaque( void *bitmap, bool opaque ) +{ + (void) opaque; /* unused */ + (void) bitmap; /* unused */ + g_assert( bitmap ); +} + +static bool +vips_foreign_load_nsgif_bitmap_test_opaque( void *bitmap ) +{ + (void) bitmap; /* unused */ + g_assert( bitmap ); + + return( false ); +} + +static unsigned char * +vips_foreign_load_nsgif_bitmap_get_buffer( void *bitmap ) +{ + g_assert( bitmap ); + + return( bitmap ); +} + +static void +vips_foreign_load_nsgif_bitmap_destroy( void *bitmap ) +{ + g_assert( bitmap ); + free( bitmap ); +} + +static void +vips_foreign_load_nsgif_bitmap_modified( void *bitmap ) +{ + (void) bitmap; /* unused */ + g_assert( bitmap ); + + return; +} + +static gif_bitmap_callback_vt vips_foreign_load_nsgif_bitmap_callbacks = { + vips_foreign_load_nsgif_bitmap_create, + vips_foreign_load_nsgif_bitmap_destroy, + vips_foreign_load_nsgif_bitmap_get_buffer, + vips_foreign_load_nsgif_bitmap_set_opaque, + vips_foreign_load_nsgif_bitmap_test_opaque, + vips_foreign_load_nsgif_bitmap_modified +}; + +static void +vips_foreign_load_nsgif_init( VipsForeignLoadNsgif *gif ) +{ + gif->anim = g_new0( gif_animation, 1 ); + gif_create( gif->anim, &vips_foreign_load_nsgif_bitmap_callbacks ); + gif->n = 1; +} + +typedef struct _VipsForeignLoadNsgifFile { + VipsForeignLoadNsgif parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadNsgifFile; + +typedef VipsForeignLoadNsgifClass VipsForeignLoadNsgifFileClass; + +G_DEFINE_TYPE( VipsForeignLoadNsgifFile, vips_foreign_load_nsgif_file, + vips_foreign_load_nsgif_get_type() ); + +static int +vips_foreign_load_gif_file_build( VipsObject *object ) +{ + VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object; + VipsForeignLoadNsgifFile *file = (VipsForeignLoadNsgifFile *) object; + + if( file->filename ) + if( !(gif->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static const char *vips_foreign_nsgif_suffs[] = { + ".gif", + NULL +}; + +static gboolean +vips_foreign_load_nsgif_file_is_a( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_nsgif_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_nsgif_file_class_init( + VipsForeignLoadNsgifFileClass *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 = "gifload"; + object_class->description = _( "load GIF with libnsgif" ); + object_class->build = vips_foreign_load_gif_file_build; + + foreign_class->suffs = vips_foreign_nsgif_suffs; + + load_class->is_a = vips_foreign_load_nsgif_file_is_a; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadNsgifFile, filename ), + NULL ); + +} + +static void +vips_foreign_load_nsgif_file_init( VipsForeignLoadNsgifFile *file ) +{ +} + +typedef struct _VipsForeignLoadNsgifBuffer { + VipsForeignLoadNsgif parent_object; + + /* Load from a buffer. + */ + VipsArea *blob; + +} VipsForeignLoadNsgifBuffer; + +typedef VipsForeignLoadNsgifClass VipsForeignLoadNsgifBufferClass; + +G_DEFINE_TYPE( VipsForeignLoadNsgifBuffer, vips_foreign_load_nsgif_buffer, + vips_foreign_load_nsgif_get_type() ); + +static int +vips_foreign_load_nsgif_buffer_build( VipsObject *object ) +{ + VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object; + VipsForeignLoadNsgifBuffer *buffer = + (VipsForeignLoadNsgifBuffer *) object; + + if( buffer->blob && + !(gif->source = vips_source_new_from_memory( + buffer->blob->data, + buffer->blob->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_buffer_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_nsgif_buffer_is_a_buffer( const void *buf, size_t len ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_nsgif_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_nsgif_buffer_class_init( + VipsForeignLoadNsgifBufferClass *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 = "gifload_buffer"; + object_class->description = _( "load GIF with libnsgif" ); + object_class->build = vips_foreign_load_nsgif_buffer_build; + + load_class->is_a_buffer = vips_foreign_load_nsgif_buffer_is_a_buffer; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadNsgifBuffer, blob ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_load_nsgif_buffer_init( VipsForeignLoadNsgifBuffer *buffer ) +{ +} + +typedef struct _VipsForeignLoadNsgifSource { + VipsForeignLoadNsgif parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadNsgifSource; + +typedef VipsForeignLoadClass VipsForeignLoadNsgifSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadNsgifSource, vips_foreign_load_nsgif_source, + vips_foreign_load_nsgif_get_type() ); + +static int +vips_foreign_load_nsgif_source_build( VipsObject *object ) +{ + VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object; + VipsForeignLoadNsgifSource *source = + (VipsForeignLoadNsgifSource *) object; + + if( source->source ) { + gif->source = source->source; + g_object_ref( gif->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_nsgif_source_class_init( + VipsForeignLoadNsgifSourceClass *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 = "gifload_source"; + object_class->description = _( "load gif from source" ); + object_class->build = vips_foreign_load_nsgif_source_build; + + load_class->is_a_source = vips_foreign_load_nsgif_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadNsgifSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_nsgif_source_init( VipsForeignLoadNsgifSource *source ) +{ +} + +#endif /*HAVE_NSGIF*/ + +/** + * 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 libvips 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. Set to -1 to mean "until the end of the + * document". Use vips_grid() to change page layout. + * + * The output image is always RGBA. + * + * See also: vips_image_new_from_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_gifload( const char *filename, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "gifload", 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_gifload_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( "gifload_buffer", ap, blob, out ); + va_end( ap ); + + vips_area_unref( VIPS_AREA( blob ) ); + + return( result ); +} + +/** + * vips_gifload_source: + * @source: source to load + * @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 + * + * Exactly as vips_gifload(), but read from a source. + * + * See also: vips_gifload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_gifload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "gifload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/iofuncs/source.c b/libvips/iofuncs/source.c index d5020ca5..25ea18fe 100644 --- a/libvips/iofuncs/source.c +++ b/libvips/iofuncs/source.c @@ -565,8 +565,6 @@ vips_source_new_from_options( const char *options ) * * Loaders should call this in response to the minimise signal on their output * image. - * - * Returns: 0 on success, or -1 on error. */ void vips_source_minimise( VipsSource *source ) diff --git a/libvips/iofuncs/type.c b/libvips/iofuncs/type.c index ee77bf5b..e477ffc6 100644 --- a/libvips/iofuncs/type.c +++ b/libvips/iofuncs/type.c @@ -900,6 +900,21 @@ transform_array_int_g_string( const GValue *src_value, GValue *dest_value ) g_value_set_string( dest_value, vips_buf_all( &buf ) ); } +static void +transform_array_int_save_string( const GValue *src_value, GValue *dest_value ) +{ + GValue intermediate = { 0 }; + + g_value_init( &intermediate, G_TYPE_STRING ); + + transform_array_int_g_string( src_value, &intermediate ); + + vips_value_set_save_string( dest_value, + g_value_get_string( &intermediate ) ); + + g_value_unset( &intermediate ); +} + /* It'd be great to be able to write a generic string->array function, but * it doesn't seem possible. */ @@ -951,6 +966,21 @@ transform_g_string_array_int( const GValue *src_value, GValue *dest_value ) g_free( str ); } +static void +transform_save_string_array_int( const GValue *src_value, GValue *dest_value ) +{ + GValue intermediate = { 0 }; + + g_value_init( &intermediate, G_TYPE_STRING ); + + g_value_set_string( &intermediate, + vips_value_get_save_string( src_value ) ); + + transform_g_string_array_int( &intermediate, dest_value ); + + g_value_unset( &intermediate ); +} + /* We need a arrayint, we have an int, make a one-element array. */ static void @@ -1006,6 +1036,10 @@ vips_array_int_get_type( void ) transform_double_array_int ); g_value_register_transform_func( VIPS_TYPE_ARRAY_DOUBLE, type, transform_array_double_array_int ); + g_value_register_transform_func( type, VIPS_TYPE_SAVE_STRING, + transform_array_int_save_string ); + g_value_register_transform_func( VIPS_TYPE_SAVE_STRING, type, + transform_save_string_array_int ); } return( type ); diff --git a/po/POTFILES.in b/po/POTFILES.in index c3dc94dc..6152da50 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -237,7 +237,6 @@ libvips/foreign/fits.c libvips/foreign/fitsload.c libvips/foreign/fitssave.c libvips/foreign/foreign.c -libvips/foreign/gifload.c libvips/foreign/heifload.c libvips/foreign/heifsave.c libvips/foreign/jpeg2vips.c @@ -254,6 +253,7 @@ libvips/foreign/matrixload.c libvips/foreign/matrixsave.c libvips/foreign/niftiload.c libvips/foreign/niftisave.c +libvips/foreign/nsgifload.c libvips/foreign/openexr2vips.c libvips/foreign/openexrload.c libvips/foreign/openslide2vips.c @@ -285,6 +285,8 @@ libvips/foreign/vipssave.c libvips/foreign/webp2vips.c libvips/foreign/webpload.c libvips/foreign/webpsave.c +libvips/foreign/libnsgif/libnsgif.c +libvips/foreign/libnsgif/lzw.c libvips/freqfilt/freqfilt.c libvips/freqfilt/freqmult.c libvips/freqfilt/fwfft.c @@ -333,6 +335,7 @@ libvips/iofuncs/sinkmemory.c libvips/iofuncs/sinkscreen.c libvips/iofuncs/source.c libvips/iofuncs/sourcecustom.c +libvips/iofuncs/sourceginput.c libvips/iofuncs/system.c libvips/iofuncs/target.c libvips/iofuncs/targetcustom.c diff --git a/test/test-suite/images/cogs.png b/test/test-suite/images/cogs.png index 2256a6a8..e14f2fd2 100644 Binary files a/test/test-suite/images/cogs.png and b/test/test-suite/images/cogs.png differ diff --git a/test/test-suite/images/dispose-background.png b/test/test-suite/images/dispose-background.png index 0e29eb52..ba31cc16 100644 Binary files a/test/test-suite/images/dispose-background.png and b/test/test-suite/images/dispose-background.png differ diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 42d5eedf..b74142dc 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -1,5 +1,4 @@ # vim: set fileencoding=utf-8 : -import filecmp import sys import os import shutil @@ -38,7 +37,7 @@ class TestForeign: cls.cmyk.remove("icc-profile-data") im = pyvips.Image.new_from_file(GIF_FILE) - cls.onebit = im > 128 + cls.onebit = im[1] > 128 @classmethod def teardown_class(cls): @@ -804,10 +803,10 @@ class TestForeign: def test_gifload(self): def gif_valid(im): a = im(10, 10) - assert_almost_equal_objects(a, [33]) + assert_almost_equal_objects(a, [33, 33, 33, 255]) assert im.width == 159 assert im.height == 203 - assert im.bands == 1 + assert im.bands == 4 self.file_loader("gifload", GIF_FILE, gif_valid) self.buffer_loader("gifload_buffer", GIF_FILE, gif_valid) @@ -828,37 +827,21 @@ class TestForeign: x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, page=1, n=-1) assert x2.height == 4 * x1.height - animation = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) - filename = temp_filename(self.tempdir, '.png') - animation.write_to_file(filename) - # Uncomment to see output file - # animation.write_to_file('cogs.png') - - assert filecmp.cmp(GIF_ANIM_EXPECTED_PNG_FILE, filename, shallow=False) + x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) + x2 = pyvips.Image.new_from_file(GIF_ANIM_EXPECTED_PNG_FILE) + assert (x1 - x2).abs().max() == 0 @skip_if_no("gifload") def test_gifload_animation_dispose_background(self): - animation = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_FILE, n=-1) - - filename = temp_filename(self.tempdir, '.png') - animation.write_to_file(filename) - - # Uncomment to see output file - # animation.write_to_file('dispose-background.png') - - assert filecmp.cmp(GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, filename, shallow=False) + x1 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_FILE, n=-1) + x2 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE) + assert (x1 - x2).abs().max() == 0 @skip_if_no("gifload") def test_gifload_animation_dispose_previous(self): - animation = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_PREVIOUS_FILE, n=-1) - - filename = temp_filename(self.tempdir, '.png') - animation.write_to_file(filename) - - # Uncomment to see output file - # animation.write_to_file('dispose-previous.png') - - assert filecmp.cmp(GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, filename, shallow=False) + x1 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_PREVIOUS_FILE, n=-1) + x2 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE) + assert (x1 - x2).abs().max() == 0 @skip_if_no("svgload") def test_svgload(self):