libvips/libvips/foreign/nsgifload.c

1012 lines
25 KiB
C

/* load a GIF with libnsgif
*
* 6/10/18
* - from gifload.c
* 3/3/22 tlsa
* - update libnsgif API
* 9/5/22
* - attach GIF palette as metadata
* 26/11/22 kleisauke
* - avoid minimise after mapping -- not reliable on Win32
*/
/*
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 <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.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>
/* 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
*/
#ifdef HAVE_NSGIF
#include <libnsgif/nsgif.h>
#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.
*/
nsgif_t *anim;
/* The data/size pair we pass to libnsgif.
*/
unsigned char *data;
size_t size;
/* Information about the current GIF.
*/
const nsgif_info_t *info;
/* Delays between frames (in milliseconds). Array of length
* @info->frame_count.
*/
int *delay;
/* A single centisecond value for compatibility.
*/
int gif_delay;
/* If the GIF contains any frames with transparent elements.
*/
gboolean has_transparency;
/* If the GIF has any local palettes.
*/
gboolean local_palette;
/* The current frame bitmap and the frame number for it.
*/
nsgif_bitmap_t *bitmap;
int frame_number;
} VipsForeignLoadNsgif;
typedef VipsForeignLoadClass VipsForeignLoadNsgifClass;
G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadNsgif, vips_foreign_load_nsgif,
VIPS_TYPE_FOREIGN_LOAD );
static void
vips_foreign_load_nsgif_error( VipsForeignLoadNsgif *gif, nsgif_error result )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
vips_error( class->nickname, "%s", nsgif_strerror( result ) );
}
static void
vips_foreign_load_nsgif_dispose( GObject *gobject )
{
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) gobject;
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_dispose:\n" );
VIPS_FREEF( nsgif_destroy, 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( const nsgif_frame_info_t *frame_info )
{
if ( frame_info == NULL )
return;
printf( "frame_info:\n" );
printf( " display = %d\n", frame_info->display );
printf( " local_palette = %d\n", frame_info->local_palette );
printf( " transparency = %d\n", frame_info->transparency );
printf( " disposal = %d (%s)\n",
frame_info->disposal,
nsgif_str_disposal( frame_info->disposal ) );
printf( " delay = %d\n", frame_info->delay );
printf( " rect.x0 = %u\n", frame_info->rect.x0 );
printf( " rect.y0 = %u\n", frame_info->rect.y0 );
printf( " rect.x1 = %u\n", frame_info->rect.x1 );
printf( " rect.y1 = %u\n", frame_info->rect.y1 );
}
static void
print_animation( nsgif_t *anim, const nsgif_info_t *info )
{
int i;
const uint8_t *bg = (uint8_t *) &info->background;
printf( "animation:\n" );
printf( " width = %d\n", info->width );
printf( " height = %d\n", info->height );
printf( " frame_count = %d\n", info->frame_count );
printf( " global_palette = %d\n", info->global_palette );
printf( " loop_max = %d\n", info->loop_max );
printf( " background = %d %d %d %d\n",
bg[0], bg[1], bg[2], bg[3] );
for( i = 0; i < info->frame_count; i++ ) {
printf( "%d ", i );
print_frame( nsgif_get_frame_info( anim, i ) );
}
}
#endif /*VERBOSE*/
static int
vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
VipsImage *image )
{
double array[3];
const uint8_t *bg;
size_t entries;
uint32_t table[NSGIF_MAX_COLOURS];
int colours;
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_set_header:\n" );
vips_image_init_fields( image,
gif->info->width, gif->info->height * gif->n,
gif->has_transparency ? 4 : 3,
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
vips_image_pipelinev( image, VIPS_DEMAND_STYLE_FATSTRIP, NULL );
/* Only set page-height if we have more than one page, or this could
* accidentally turn into an animated image later.
*/
if( gif->n > 1 )
vips_image_set_int( image,
VIPS_META_PAGE_HEIGHT, gif->info->height );
vips_image_set_int( image, VIPS_META_N_PAGES,
gif->info->frame_count );
vips_image_set_int( image, "loop", gif->info->loop_max );
vips_image_set_array_int( image, "delay",
gif->delay, gif->info->frame_count );
bg = (uint8_t *) &gif->info->background;
array[0] = bg[0];
array[1] = bg[1];
array[2] = bg[2];
vips_image_set_array_double( image, "background", array, 3 );
VIPS_SETSTR( image->filename,
vips_connection_filename( VIPS_CONNECTION( gif->source ) ) );
/* 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->info->loop_max == 0 ?
0 : gif->info->loop_max - 1 );
/* The deprecated gif-delay field is in centiseconds.
*/
vips_image_set_int( image, "gif-delay", gif->gif_delay );
/* If there are no local palettes, we can attach the global palette as
* metadata.
*/
if( !gif->local_palette ) {
nsgif_global_palette( gif->anim, table, &entries );
vips_image_set_array_int( image, "gif-palette",
(const int *) table, entries );
colours = entries;
}
else {
int i;
colours = 0;
if( gif->info->global_palette ) {
nsgif_global_palette( gif->anim, table, &entries );
colours = entries;
}
for( i = 0; i < gif->info->frame_count; i++ ) {
if( nsgif_local_palette( gif->anim, i, table,
&entries ) )
colours = VIPS_MAX( colours, entries );
}
}
vips_image_set_int( image, "palette-bit-depth",
ceil( log2( colours ) ) );
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.
*/
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;
nsgif_error result;
int i;
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_header:\n" );
/* Map the whole source into memory.
*/
if( !(data = vips_source_map( gif->source, &size )) )
return( -1 );
/* Treat errors from _scan() as warnings. If libnsgif really can't do
* something it'll fail gracefully later when we try to read out
* frame data.
*/
result = nsgif_data_scan( gif->anim, size, (void *) data );
VIPS_DEBUG_MSG( "nsgif_data_scan() = %s\n", nsgif_strerror( result ) );
switch( result ) {
case NSGIF_ERR_END_OF_DATA:
if( load->fail_on >= VIPS_FAIL_ON_TRUNCATED ) {
vips_foreign_load_nsgif_error( gif, result );
return( -1 );
}
else
g_warning( "%s", nsgif_strerror( result ) );
break;
case NSGIF_OK:
break;
default:
if( load->fail_on >= VIPS_FAIL_ON_WARNING ) {
vips_foreign_load_nsgif_error( gif, result );
return( -1 );
}
else
g_warning( "%s", nsgif_strerror( result ) );
break;
}
/* Tell libnsgif that that's all the data we have. This will let us
* read out any truncated final frames.
*/
nsgif_data_complete( gif->anim );
gif->info = nsgif_get_info( gif->anim );
#ifdef VERBOSE
print_animation( gif->anim, gif->info );
#endif /*VERBOSE*/
if( !gif->info->frame_count ) {
vips_error( class->nickname, "%s", _( "no frames in GIF" ) );
return( -1 );
}
/* Check for any transparency.
*/
for( i = 0; i < gif->info->frame_count; i++ ) {
const nsgif_frame_info_t *frame_info;
if( (frame_info = nsgif_get_frame_info( gif->anim, i )) ) {
if( frame_info->transparency )
gif->has_transparency = TRUE;
if( frame_info->local_palette )
gif->local_palette = TRUE;
}
}
if( gif->n == -1 )
gif->n = gif->info->frame_count - gif->page;
if( gif->page < 0 ||
gif->n <= 0 ||
gif->page + gif->n > gif->info->frame_count ) {
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->info->frame_count, int )) )
return( -1 );
for( i = 0; i < gif->info->frame_count; i++ ) {
const nsgif_frame_info_t *frame_info;
frame_info = nsgif_get_frame_info( gif->anim, i );
if ( frame_info == NULL ) {
vips_error( class->nickname, "%s", _( "bad frame" ) );
return( -1 );
}
gif->delay[i] = 10 * frame_info->delay;
}
gif->gif_delay = gif->delay[0] / 10;
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->info->height + gif->page;
int line = (r->top + y) % gif->info->height;
nsgif_error result;
VipsPel *p, *q;
g_assert( line >= 0 && line < gif->info->height );
g_assert( page >= 0 && page < gif->info->frame_count );
if( gif->frame_number != page ) {
result = nsgif_frame_decode( gif->anim,
page, &gif->bitmap );
VIPS_DEBUG_MSG( " nsgif_frame_decode(%d) = %d\n",
page, result );
if( result != NSGIF_OK ) {
vips_foreign_load_nsgif_error( gif, result );
return( -1 );
}
#ifdef VERBOSE
print_frame( nsgif_get_frame_info( gif->anim, page ) );
#endif /*VERBOSE*/
gif->frame_number = page;
}
p = gif->bitmap + line * gif->info->width * sizeof( int );
q = VIPS_REGION_ADDR( or, 0, r->top + y );
if( gif->has_transparency )
memcpy( q, p, VIPS_REGION_SIZEOF_LINE( or ) );
else {
int i;
for( i = 0; i < r->width; i++ ) {
q[0] = p[0];
q[1] = p[1];
q[2] = p[2];
q += 3;
p += 4;
}
}
}
return( 0 );
}
int
vips_foreign_load_nsgif_tile_height( VipsForeignLoadNsgif *gif )
{
int height = gif->info->height;
int i;
/* First, check the perfect size.
*/
if( height % 16 == 0 )
return( 16 );
/* Next, check larger and smaller sizes.
*/
for( i = 1; i < 16; i++ ) {
if( height % (16 + i) == 0 )
return( 16 + i );
if( height % (16 - i) == 0 )
return( 16 - i );
}
return( 1 );
}
static int
vips_foreign_load_nsgif_load( VipsForeignLoad *load )
{
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) load;
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( load ), 2 );
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_foreign_load_nsgif_tile_height( gif ),
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" ),
_( "First page to load" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadNsgif, page ),
0, 100000, 0 );
VIPS_ARG_INT( class, "n", 6,
_( "n" ),
_( "Number of pages to load, -1 for all" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadNsgif, n ),
-1, 100000, 1 );
}
static void *
vips_foreign_load_nsgif_bitmap_create( int width, int height )
{
/* GIF has a limit of 64k per axis -- double-check this.
*/
if( width <= 0 ||
width > 65536 ||
height <= 0 ||
height > 65536 ) {
vips_error( "gifload",
"%s", _( "bad image dimensions") );
return( NULL );
}
return g_malloc0( (gsize) width * height * 4 );
}
static unsigned char *
vips_foreign_load_nsgif_bitmap_get_buffer( void *bitmap )
{
g_assert( bitmap );
return( bitmap );
}
static void
vips_foreign_load_nsgif_bitmap_destroy( void *bitmap )
{
g_assert( bitmap );
g_free( bitmap );
}
static nsgif_bitmap_cb_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,
};
static void
vips_foreign_load_nsgif_init( VipsForeignLoadNsgif *gif )
{
nsgif_error result = nsgif_create(
&vips_foreign_load_nsgif_bitmap_callbacks,
NSGIF_BITMAP_FMT_R8G8B8A8,
&gif->anim );
if (result != NSGIF_OK) {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
vips_error( class->nickname, "%s",
nsgif_strerror( result ) );
return;
}
gif->n = 1;
gif->frame_number = -1;
gif->bitmap = NULL;
}
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;
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( 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;
operation_class->flags |= VIPS_OPERATION_NOCACHE;
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
* * @fail_on: #VipsFailOn, types of read error to fail on
*
* 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.
*
* Use @fail_on to set the type of error that will cause load to fail. By
* default, loaders are permissive, that is, #VIPS_FAIL_ON_NONE.
*
* The output image is RGBA for GIFs containing transparent elements, RGB
* otherwise.
*
* 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
* * @fail_on: #VipsFailOn, types of read error to fail on
*
* 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
* * @fail_on: #VipsFailOn, types of read error to fail on
*
* 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 );
}