1707 lines
40 KiB
C
1707 lines
40 KiB
C
/* 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
|
|
*/
|
|
|
|
/*
|
|
|
|
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 <config.h>
|
|
#endif /*HAVE_CONFIG_H*/
|
|
#include <vips/intl.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
|
|
#include <vips/vips.h>
|
|
#include <vips/buf.h>
|
|
#include <vips/internal.h>
|
|
#include <vips/debug.h>
|
|
|
|
#ifdef HAVE_GIFLIB
|
|
|
|
#include <gif_lib.h>
|
|
|
|
/* 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, 0 for EOF.
|
|
*/
|
|
static int
|
|
vips_giflib_read( GifFileType *file, GifByteType *buf, int n )
|
|
{
|
|
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) file->UserData;
|
|
|
|
gint64 read;
|
|
|
|
read = vips_source_read( gif->source, buf, n );
|
|
if( read == 0 )
|
|
gif->eof = TRUE;
|
|
|
|
return( (int) read );
|
|
}
|
|
|
|
/* 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 )
|
|
{
|
|
vips_image_init_fields( image,
|
|
gif->file->SWidth, gif->file->SHeight * gif->n,
|
|
(gif->has_colour ? 3 : 1) + (gif->has_transparency ? 1 : 0),
|
|
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
|
|
gif->has_colour ?
|
|
VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W,
|
|
1.0, 1.0 );
|
|
vips_image_pipelinev( image, VIPS_DEMAND_STYLE_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 );
|
|
|
|
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( "<unknown>" );
|
|
}
|
|
}
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
static int
|
|
vips_foreign_load_gif_extension( VipsForeignLoadGif *gif )
|
|
{
|
|
GifByteType *extension;
|
|
int ext_code;
|
|
|
|
VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension:\n" );
|
|
|
|
if( DGifGetExtension( gif->file, &ext_code, &extension ) ==
|
|
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 );
|
|
}
|