almost loading

This commit is contained in:
John Cupitt 2019-02-13 18:04:30 +00:00
parent e12b44e790
commit 617d910379

View File

@ -50,8 +50,8 @@
*/
/*
#define VIPS_DEBUG
*/
#define VIPS_DEBUG
#ifdef HAVE_CONFIG_H
#include <config.h>
@ -93,19 +93,6 @@
#define DISPOSE_PREVIOUS 3
#endif
/* TODO:
*
* - once we know this, we can set the header of the output image
*
* - second slow scan driven by _generate to render pages as required, with a
* single page held as a memory image
*
* Try the fast scan idea first -- can we get everything we need quickly?
*
* Need vmethods for open/close/rewind in base class, implement in file and
* buffer.
*/
#define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_gif_get_type())
#define VIPS_FOREIGN_LOAD_GIF( obj ) \
(G_TYPE_CHECK_INSTANCE_CAST( (obj), \
@ -157,6 +144,10 @@ typedef struct _VipsForeignLoadGif {
*/
char *comment;
/* The number of pages (frame) in the image.
*/
int n_pages;
/* A memory image the sized of one frame ... we accumulate to this as
* we scan the image, and copy lines to the output on generate.
*/
@ -404,12 +395,28 @@ vips_foreign_load_gif_code_next( VipsForeignLoadGif *gif,
static int
vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
GifFileType *file = gif->file;
ColorMapObject *map = file->Image.ColorMap ?
file->Image.ColorMap : file->SColorMap;
GifByteType *extension;
/* Check that the frame looks sane. Perhaps giflib checks
* this for us.
*/
if( file->Image.Left < 0 ||
file->Image.Width < 1 ||
file->Image.Width > 10000 ||
file->Image.Left + file->Image.Width > file->SWidth ||
file->Image.Top < 0 ||
file->Image.Height < 1 ||
file->Image.Height > 10000 ||
file->Image.Top + file->Image.Height > file->SHeight ) {
vips_error( class->nickname, "%s", _( "bad frame size" ) );
return( -1 );
}
/* Test for a non-greyscale colourmap for this frame.
*/
if( !gif->has_colour &&
@ -508,10 +515,13 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif )
switch( ext_code ) {
case GRAPHICS_EXT_FUNC_CODE:
if( extension[0] == 4 &&
extension[1] & 0x1 )
extension[1] & 0x1 ) {
VIPS_DEBUG_MSG( "gifload: has transp.\n" );
gif->has_transparency = TRUE;
}
if( !gif->has_delay ) {
VIPS_DEBUG_MSG( "gifload: has delay\n" );
gif->has_delay = TRUE;
gif->delay = extension[2] | (extension[3] << 8);
}
@ -548,29 +558,52 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif )
return( 0 );
}
/* Attempt to quickly scan a GIF and discover:
* - number of frames (pages)
* - mono or colour (check colourmap on each page)
* - has transparency (check bit in ext block on every page)
static int
vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image )
{
vips_image_init_fields( image,
gif->file->SWidth, gif->file->SHeight * gif->n,
(gif->has_colour ? 3 : 1) + (gif->has_transparency ? 1 : 0),
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
gif->has_colour ?
VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W,
1.0, 1.0 );
vips_image_pipelinev( image, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
if( gif->n_pages > 1 ) {
vips_image_set_int( image,
VIPS_META_PAGE_HEIGHT, gif->file->SHeight );
vips_image_set_int( image, VIPS_META_N_PAGES, gif->n_pages );
}
vips_image_set_int( image, "gif-delay", gif->delay );
vips_image_set_int( image, "gif-loop", gif->loop );
if( gif->comment )
vips_image_set_string( image, "gif-comment", gif->comment );
return( 0 );
}
/* Attempt to quickly scan a GIF and discover what we need for our header. We
* need to scan the whole file to get n_pages, transparency and colour.
*/
static int
vips_foreign_load_gif_header( VipsForeignLoad *load )
{
VipsForeignLoadGifClass *class =
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadGifClass *gif_class =
(VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load;
GifRecordType record;
int n_pages;
printf( "vips_foreign_load_gif_header:\n" );
if( class->open( gif ) )
if( gif_class->open( gif ) )
return( -1 );
printf( "vips_foreign_load_gif_header: starting page scan\n" );
n_pages = 0;
gif->n_pages = 0;
do {
if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) {
@ -592,7 +625,7 @@ vips_foreign_load_gif_header( VipsForeignLoad *load )
if( vips_foreign_load_gif_scan_image_record( gif ) )
return( -1 );
n_pages += 1;
gif->n_pages += 1;
break;
@ -622,40 +655,23 @@ vips_foreign_load_gif_header( VipsForeignLoad *load )
}
} while( !gif->eof );
printf( "vips_foreign_load_gif_header: found %d pages\n", n_pages );
printf( "vips_foreign_load_gif_header: found %d pages\n",
gif->n_pages );
/* Make the memory image we accumulate pixels in. We always accumulate
* to RGBA, then trim down to whatever the output image needs on
* _generate.
*/
gif->frame = vips_image_new_memory();
vips_image_init_fields( gif->frame,
gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
vips_image_pipelinev( gif->frame, VIPS_DEMAND_STYLE_ANY, NULL );
if( vips_image_write_prepare( gif->frame ) )
return( -1 );
if( gif->n == -1 )
gif->n = gif->n_pages - gif->page;
/* The real output image is long, and made on demand in _generate.
*/
vips_image_init_fields( load->out,
gif->file->SWidth, gif->file->SHeight * n_pages,
(gif->has_colour ? 3 : 1) + (gif->has_transparency ? 1 : 0),
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
gif->has_colour ?
VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W,
1.0, 1.0 );
vips_image_pipelinev( load->out, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
if( n_pages > 1 ) {
vips_image_set_int( load->out,
VIPS_META_PAGE_HEIGHT, gif->file->SHeight );
vips_image_set_int( load->out, VIPS_META_N_PAGES, n_pages );
if( gif->page < 0 ||
gif->n <= 0 ||
gif->page + gif->n > gif->n_pages ) {
vips_error( class->nickname, "%s", _( "bad page number" ) );
return( -1 );
}
vips_image_set_int( load->out, "gif-delay", gif->delay );
vips_image_set_int( load->out, "gif-loop", gif->loop );
if( gif->comment )
vips_image_set_string( load->out, "gif-comment", gif->comment );
/* And set the output vips header from what we've learned.
*/
if( vips_foreign_load_gif_set_header( gif, load->out ) )
return( -1 );
return( 0 );
}
@ -710,39 +726,9 @@ vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif,
* depending on the current dispose mode.
*/
static int
vips_foreign_load_gif_render( VipsForeignLoadGif *gif,
VipsImage *previous, VipsImage *out )
vips_foreign_load_gif_render( VipsForeignLoadGif *gif )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
GifFileType *file = gif->file;
ColorMapObject *map = file->Image.ColorMap ?
file->Image.ColorMap : file->SColorMap;
/* Check that the frame lies within our image.
*/
if( file->Image.Left < 0 ||
file->Image.Left + file->Image.Width > out->Xsize ||
file->Image.Top < 0 ||
file->Image.Top + file->Image.Height > out->Ysize ) {
vips_error( class->nickname,
"%s", _( "frame is outside image area" ) );
return( -1 );
}
/* Check if we have a non-greyscale colourmap for this frame.
*/
if( !gif->has_colour &&
map ) {
int i;
for( i = 0; i < map->ColorCount; i++ )
if( map->Colors[i].Red != map->Colors[i].Green ||
map->Colors[i].Green != map->Colors[i].Blue ) {
VIPS_DEBUG_MSG( "gifload: not mono\n" );
gif->has_colour = TRUE;
break;
}
}
if( file->Image.Interlace ) {
int i;
@ -758,7 +744,7 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif,
for( y = InterlacedOffset[i];
y < file->Image.Height;
y += InterlacedJumps[i] ) {
VipsPel *q = VIPS_IMAGE_ADDR( out,
VipsPel *q = VIPS_IMAGE_ADDR( gif->frame,
file->Image.Left, file->Image.Top + y );
if( DGifGetLine( gif->file, gif->line,
@ -781,7 +767,7 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif,
file->Image.Left, file->Image.Top );
for( y = 0; y < file->Image.Height; y++ ) {
VipsPel *q = VIPS_IMAGE_ADDR( out,
VipsPel *q = VIPS_IMAGE_ADDR( gif->frame,
file->Image.Left, file->Image.Top + y );
if( DGifGetLine( gif->file, gif->line,
@ -836,19 +822,15 @@ vips_foreign_load_gif_extension( VipsForeignLoadGif *gif )
return( 0 );
}
/* Write the next page, if there is one, to @page. Set EOF if we hit the end of
* the file. @page must be a memory image of the right size. @previous is the
* previous frame, if any.
/* Read the next page from the file into @frame.
*/
static int
vips_foreign_load_gif_page( VipsForeignLoadGif *gif,
VipsImage *previous, VipsImage *out )
vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif )
{
GifRecordType record;
int n_pages;
n_pages = 0;
gboolean have_read_frame;
have_read_frame = FALSE;
do {
if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
@ -864,13 +846,10 @@ vips_foreign_load_gif_page( VipsForeignLoadGif *gif,
return( -1 );
}
if( vips_foreign_load_gif_render( gif, previous, out ) )
if( vips_foreign_load_gif_render( gif ) )
return( -1 );
n_pages += 1;
VIPS_DEBUG_MSG( "gifload: page %d\n",
gif->current_page + n_pages );
have_read_frame = TRUE;
break;
@ -895,198 +874,40 @@ vips_foreign_load_gif_page( VipsForeignLoadGif *gif,
default:
break;
}
} while( n_pages < 1 &&
} while( !have_read_frame &&
!gif->eof );
gif->current_page += n_pages;
return( 0 );
}
static VipsImage *
vips_foreign_load_gif_new_page( VipsForeignLoadGif *gif )
{
VipsImage *out;
out = vips_image_new_memory();
vips_image_init_fields( out,
gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
/* We will have the whole GIF frame in memory, so we can render any
* area.
*/
vips_image_pipelinev( out, VIPS_DEMAND_STYLE_ANY, NULL );
/* Turn out into a memory image which we then render the GIF frames
* into.
*/
if( vips_image_write_prepare( out ) ) {
g_object_unref( out );
return( NULL );
}
/* Some GIFs may not clear the background, so we must start
* transparent.
*/
memset( VIPS_IMAGE_ADDR( out, 0, 0 ),
0,
VIPS_IMAGE_SIZEOF_IMAGE( out ) );
return( out );
}
static void *
unref_object( void *data, void *a, void *b )
{
g_object_unref( G_OBJECT( data ) );
return( NULL );
}
static void
unref_array( GSList *list )
{
/* g_slist_free_full() was added in 2.28 and we have to work with
* 2.6 :(
*/
vips_slist_map2( list, unref_object, NULL, NULL );
g_slist_free( list );
}
/* We render each frame to a separate memory image held in a linked
* list, then assemble to out. We don't know the number of frames in advance,
* so we can't just allocate a large area.
*/
static int
vips_foreign_load_gif_pages( VipsForeignLoadGif *gif, VipsImage **out )
vips_foreign_load_gif_generate( VipsRegion *or,
void *seq, void *a, void *b, gboolean *stop )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
VipsRect *r = &or->valid;
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) a;
GSList *frames;
VipsImage *frame;
VipsImage *previous;
VipsImage **t;
int n_frames;
int i;
frames = NULL;
previous = NULL;
/* Accumulate any start stuff up to the first frame we need.
/* The page for this generate, and the line number in page.
*/
if( !(frame = vips_foreign_load_gif_new_page( gif )) )
return( -1 );
do {
if( vips_foreign_load_gif_page( gif, NULL, frame ) ) {
g_object_unref( frame );
int page = r->top / gif->file->SHeight + gif->page;
int line = r->top % gif->file->SHeight;
#ifdef DEBUG_VERBOSE
printf( "vips_foreign_load_gif_generate: line %d\n", r->top );
#endif /*DEBUG_VERBOSE*/
g_assert( r->height == 1 );
while( gif->current_page < page ) {
if( vips_foreign_load_gif_next_page( gif ) )
return( -1 );
}
} while( !gif->eof &&
gif->current_page <= gif->page );
if( gif->eof ) {
vips_error( class->nickname,
"%s", _( "too few frames in GIF file" ) );
g_object_unref( frame );
return( -1 );
gif->current_page += 1;
}
frames = g_slist_append( frames, frame );
previous = frame;
while( gif->n == -1 ||
gif->current_page < gif->page + gif->n ) {
/* We might need a frame for this read to render to.
*/
if( !(frame = vips_foreign_load_gif_new_page( gif )) ) {
unref_array( frames );
return( -1 );
}
if( gif->dispose == DISPOSE_BACKGROUND )
/* BACKGROUND means the bg shows through, ie. (in web
* terms) everything is transparent.
*/
memset( VIPS_IMAGE_ADDR( frame, 0, 0 ),
0,
VIPS_IMAGE_SIZEOF_IMAGE( frame ) );
else
memcpy( VIPS_IMAGE_ADDR( frame, 0, 0 ),
VIPS_IMAGE_ADDR( previous, 0, 0 ),
VIPS_IMAGE_SIZEOF_IMAGE( frame ) );
if( vips_foreign_load_gif_page( gif, previous, frame ) ) {
g_object_unref( frame );
unref_array( frames );
return( -1 );
}
if( gif->eof ) {
/* Nope, didn't need the new frame.
*/
g_object_unref( frame );
break;
}
else {
frames = g_slist_append( frames, frame );
/* These two dispose modes set new background frames.
*/
if( gif->dispose == DISPOSAL_UNSPECIFIED ||
gif->dispose == DISPOSE_DO_NOT )
previous = frame;
}
}
n_frames = g_slist_length( frames );
if( gif->eof &&
gif->n != -1 &&
n_frames < gif->n ) {
unref_array( frames );
vips_error( class->nickname,
"%s", _( "too few frames in GIF file" ) );
return( -1 );
}
/* We've rendered to a set of memory images ... we can shut down the GIF
* reader now.
*/
vips_foreign_load_gif_close( gif );
if( !(t = VIPS_ARRAY( gif, n_frames, VipsImage * )) ) {
unref_array( frames );
return( -1 );
}
for( i = 0; i < n_frames; i++ )
t[i] = (VipsImage *) g_slist_nth_data( frames, i );
if( vips_arrayjoin( t, out, n_frames,
"across", 1,
NULL ) ) {
unref_array( frames );
return( -1 );
}
unref_array( frames );
if( n_frames > 1 ) {
vips_image_set_int( *out, VIPS_META_PAGE_HEIGHT, t[0]->Ysize );
/* n-pages is supposed to be the number of pages in the file,
* but we'd need to scan the entire image to find that :( so
* just set it to the number of pages we've read so far.
*/
vips_image_set_int( *out, VIPS_META_N_PAGES,
gif->current_page );
}
vips_image_set_int( *out, "gif-delay", gif->delay );
vips_image_set_int( *out, "gif-loop", gif->loop );
if( gif->comment )
vips_image_set_string( *out, "gif-comment", gif->comment );
memcpy( VIPS_REGION_ADDR( or, 0, r->top ),
VIPS_IMAGE_ADDR( gif->frame, 0, line ),
VIPS_IMAGE_SIZEOF_LINE( gif->frame ) );
return( 0 );
}
@ -1100,53 +921,35 @@ vips_foreign_load_gif_load( VipsForeignLoad *load )
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( load ), 4 );
VipsImage *im;
printf( "vips_foreign_load_gif_load:\n" );
/* Rewind.
*/
if( class->open( gif ) )
return( -1 );
printf( "vips_foreign_load_gif_load:\n" );
if( vips_foreign_load_gif_pages( gif, &t[0] ) )
return( -1 );
im = t[0];
/* Depending on what we found, transform and write to load->real.
/* Make the memory image we accumulate pixels in. We always accumulate
* to RGBA, then trim down to whatever the output image needs on
* _generate.
*/
if( gif->has_colour &&
gif->has_transparency ) {
/* Nothing to do.
*/
}
else if( gif->has_colour ) {
/* RGB.
*/
if( vips_extract_band( im, &t[1], 0,
"n", 3,
NULL ) )
return( -1 );
im = t[1];
}
else if( gif->has_transparency ) {
/* GA. Take BA so we have neighboring channels.
*/
if( vips_extract_band( im, &t[1], 2,
"n", 2,
NULL ) )
return( -1 );
im = t[1];
im->Type = VIPS_INTERPRETATION_B_W;
}
else {
/* G.
*/
if( vips_extract_band( im, &t[1], 0, NULL ) )
return( -1 );
im = t[1];
im->Type = VIPS_INTERPRETATION_B_W;
}
gif->frame = vips_image_new_memory();
vips_image_init_fields( gif->frame,
gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
vips_image_pipelinev( gif->frame, VIPS_DEMAND_STYLE_ANY, NULL );
if( vips_image_write_prepare( gif->frame ) )
return( -1 );
if( vips_image_write( im, load->out ) )
/* Make the output pipeline.
*/
t[0] = vips_image_new();
if( vips_foreign_load_gif_set_header( gif, t[0] ) )
return( -1 );
if( vips_image_generate( t[0],
NULL, vips_foreign_load_gif_generate, NULL, gif, NULL ) ||
vips_sequential( t[0], &t[1], NULL ) ||
vips_image_write( t[1], load->real ) )
return( -1 );
return( 0 );
@ -1174,13 +977,14 @@ vips_foreign_load_gif_open( VipsForeignLoadGif *gif )
#endif
gif->eof = FALSE;
gif->current_page = 0;
/* Allocate a line buffer now that we have the GIF width.
*/
if( !gif->line )
if( !(gif->line = VIPS_ARRAY( gif,
gif->file->SWidth, GifPixelType )) )
return( -1 );
VIPS_FREE( gif->line )
if( !(gif->line = VIPS_ARRAY( gif,
gif->file->SWidth, GifPixelType )) )
return( -1 );
return( 0 );
}