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 #ifdef HAVE_CONFIG_H
#include <config.h> #include <config.h>
@ -93,19 +93,6 @@
#define DISPOSE_PREVIOUS 3 #define DISPOSE_PREVIOUS 3
#endif #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_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_gif_get_type())
#define VIPS_FOREIGN_LOAD_GIF( obj ) \ #define VIPS_FOREIGN_LOAD_GIF( obj ) \
(G_TYPE_CHECK_INSTANCE_CAST( (obj), \ (G_TYPE_CHECK_INSTANCE_CAST( (obj), \
@ -157,6 +144,10 @@ typedef struct _VipsForeignLoadGif {
*/ */
char *comment; 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 /* 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. * 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 static int
vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif ) vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif )
{ {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
GifFileType *file = gif->file; GifFileType *file = gif->file;
ColorMapObject *map = file->Image.ColorMap ? ColorMapObject *map = file->Image.ColorMap ?
file->Image.ColorMap : file->SColorMap; file->Image.ColorMap : file->SColorMap;
GifByteType *extension; 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. /* Test for a non-greyscale colourmap for this frame.
*/ */
if( !gif->has_colour && if( !gif->has_colour &&
@ -508,10 +515,13 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif )
switch( ext_code ) { switch( ext_code ) {
case GRAPHICS_EXT_FUNC_CODE: case GRAPHICS_EXT_FUNC_CODE:
if( extension[0] == 4 && if( extension[0] == 4 &&
extension[1] & 0x1 ) extension[1] & 0x1 ) {
VIPS_DEBUG_MSG( "gifload: has transp.\n" );
gif->has_transparency = TRUE; gif->has_transparency = TRUE;
}
if( !gif->has_delay ) { if( !gif->has_delay ) {
VIPS_DEBUG_MSG( "gifload: has delay\n" );
gif->has_delay = TRUE; gif->has_delay = TRUE;
gif->delay = extension[2] | (extension[3] << 8); gif->delay = extension[2] | (extension[3] << 8);
} }
@ -548,29 +558,52 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif )
return( 0 ); return( 0 );
} }
/* Attempt to quickly scan a GIF and discover: static int
* - number of frames (pages) vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image )
* - mono or colour (check colourmap on each page) {
* - has transparency (check bit in ext block on every page) 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 static int
vips_foreign_load_gif_header( VipsForeignLoad *load ) 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 ); (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load;
GifRecordType record; GifRecordType record;
int n_pages;
printf( "vips_foreign_load_gif_header:\n" ); printf( "vips_foreign_load_gif_header:\n" );
if( class->open( gif ) ) if( gif_class->open( gif ) )
return( -1 ); return( -1 );
printf( "vips_foreign_load_gif_header: starting page scan\n" ); printf( "vips_foreign_load_gif_header: starting page scan\n" );
n_pages = 0; gif->n_pages = 0;
do { do {
if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { 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 ) ) if( vips_foreign_load_gif_scan_image_record( gif ) )
return( -1 ); return( -1 );
n_pages += 1; gif->n_pages += 1;
break; break;
@ -622,40 +655,23 @@ vips_foreign_load_gif_header( VipsForeignLoad *load )
} }
} while( !gif->eof ); } 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 if( gif->n == -1 )
* to RGBA, then trim down to whatever the output image needs on gif->n = gif->n_pages - gif->page;
* _generate.
*/
gif->frame = vips_image_new_memory();
vips_image_init_fields( gif->frame,
gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
vips_image_pipelinev( gif->frame, VIPS_DEMAND_STYLE_ANY, NULL );
if( vips_image_write_prepare( gif->frame ) )
return( -1 );
/* The real output image is long, and made on demand in _generate. if( gif->page < 0 ||
*/ gif->n <= 0 ||
vips_image_init_fields( load->out, gif->page + gif->n > gif->n_pages ) {
gif->file->SWidth, gif->file->SHeight * n_pages, vips_error( class->nickname, "%s", _( "bad page number" ) );
(gif->has_colour ? 3 : 1) + (gif->has_transparency ? 1 : 0), return( -1 );
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
gif->has_colour ?
VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_B_W,
1.0, 1.0 );
vips_image_pipelinev( load->out, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
if( n_pages > 1 ) {
vips_image_set_int( load->out,
VIPS_META_PAGE_HEIGHT, gif->file->SHeight );
vips_image_set_int( load->out, VIPS_META_N_PAGES, n_pages );
} }
vips_image_set_int( load->out, "gif-delay", gif->delay );
vips_image_set_int( load->out, "gif-loop", gif->loop ); /* And set the output vips header from what we've learned.
if( gif->comment ) */
vips_image_set_string( load->out, "gif-comment", gif->comment ); if( vips_foreign_load_gif_set_header( gif, load->out ) )
return( -1 );
return( 0 ); return( 0 );
} }
@ -710,39 +726,9 @@ vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif,
* depending on the current dispose mode. * depending on the current dispose mode.
*/ */
static int static int
vips_foreign_load_gif_render( VipsForeignLoadGif *gif, vips_foreign_load_gif_render( VipsForeignLoadGif *gif )
VipsImage *previous, VipsImage *out )
{ {
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
GifFileType *file = gif->file; 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 ) { if( file->Image.Interlace ) {
int i; int i;
@ -758,7 +744,7 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif,
for( y = InterlacedOffset[i]; for( y = InterlacedOffset[i];
y < file->Image.Height; y < file->Image.Height;
y += InterlacedJumps[i] ) { y += InterlacedJumps[i] ) {
VipsPel *q = VIPS_IMAGE_ADDR( out, VipsPel *q = VIPS_IMAGE_ADDR( gif->frame,
file->Image.Left, file->Image.Top + y ); file->Image.Left, file->Image.Top + y );
if( DGifGetLine( gif->file, gif->line, if( DGifGetLine( gif->file, gif->line,
@ -781,7 +767,7 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif,
file->Image.Left, file->Image.Top ); file->Image.Left, file->Image.Top );
for( y = 0; y < file->Image.Height; y++ ) { 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 ); file->Image.Left, file->Image.Top + y );
if( DGifGetLine( gif->file, gif->line, if( DGifGetLine( gif->file, gif->line,
@ -836,19 +822,15 @@ vips_foreign_load_gif_extension( VipsForeignLoadGif *gif )
return( 0 ); return( 0 );
} }
/* Write the next page, if there is one, to @page. Set EOF if we hit the end of /* Read the next page from the file into @frame.
* the file. @page must be a memory image of the right size. @previous is the
* previous frame, if any.
*/ */
static int static int
vips_foreign_load_gif_page( VipsForeignLoadGif *gif, vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif )
VipsImage *previous, VipsImage *out )
{ {
GifRecordType record; GifRecordType record;
int n_pages; gboolean have_read_frame;
n_pages = 0;
have_read_frame = FALSE;
do { do {
if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif ); vips_foreign_load_gif_error( gif );
@ -864,13 +846,10 @@ vips_foreign_load_gif_page( VipsForeignLoadGif *gif,
return( -1 ); return( -1 );
} }
if( vips_foreign_load_gif_render( gif, previous, out ) ) if( vips_foreign_load_gif_render( gif ) )
return( -1 ); return( -1 );
n_pages += 1; have_read_frame = TRUE;
VIPS_DEBUG_MSG( "gifload: page %d\n",
gif->current_page + n_pages );
break; break;
@ -895,198 +874,40 @@ vips_foreign_load_gif_page( VipsForeignLoadGif *gif,
default: default:
break; break;
} }
} while( n_pages < 1 && } while( !have_read_frame &&
!gif->eof ); !gif->eof );
gif->current_page += n_pages;
return( 0 ); 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 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; /* The page for this generate, and the line number in page.
VipsImage *frame;
VipsImage *previous;
VipsImage **t;
int n_frames;
int i;
frames = NULL;
previous = NULL;
/* Accumulate any start stuff up to the first frame we need.
*/ */
if( !(frame = vips_foreign_load_gif_new_page( gif )) ) int page = r->top / gif->file->SHeight + gif->page;
return( -1 ); int line = r->top % gif->file->SHeight;
do {
if( vips_foreign_load_gif_page( gif, NULL, frame ) ) { #ifdef DEBUG_VERBOSE
g_object_unref( frame ); 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 ); return( -1 );
}
} while( !gif->eof &&
gif->current_page <= gif->page );
if( gif->eof ) { gif->current_page += 1;
vips_error( class->nickname,
"%s", _( "too few frames in GIF file" ) );
g_object_unref( frame );
return( -1 );
} }
frames = g_slist_append( frames, frame ); memcpy( VIPS_REGION_ADDR( or, 0, r->top ),
previous = frame; VIPS_IMAGE_ADDR( gif->frame, 0, line ),
VIPS_IMAGE_SIZEOF_LINE( gif->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 );
return( 0 ); return( 0 );
} }
@ -1100,53 +921,35 @@ vips_foreign_load_gif_load( VipsForeignLoad *load )
VipsImage **t = (VipsImage **) VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( load ), 4 ); vips_object_local_array( VIPS_OBJECT( load ), 4 );
VipsImage *im; printf( "vips_foreign_load_gif_load:\n" );
/* Rewind.
*/
if( class->open( gif ) ) if( class->open( gif ) )
return( -1 ); return( -1 );
printf( "vips_foreign_load_gif_load:\n" ); /* Make the memory image we accumulate pixels in. We always accumulate
* to RGBA, then trim down to whatever the output image needs on
if( vips_foreign_load_gif_pages( gif, &t[0] ) ) * _generate.
return( -1 );
im = t[0];
/* Depending on what we found, transform and write to load->real.
*/ */
if( gif->has_colour && gif->frame = vips_image_new_memory();
gif->has_transparency ) { vips_image_init_fields( gif->frame,
/* Nothing to do. 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 );
else if( gif->has_colour ) { if( vips_image_write_prepare( gif->frame ) )
/* RGB. return( -1 );
*/
if( vips_extract_band( im, &t[1], 0,
"n", 3,
NULL ) )
return( -1 );
im = t[1];
}
else if( gif->has_transparency ) {
/* GA. Take BA so we have neighboring channels.
*/
if( vips_extract_band( im, &t[1], 2,
"n", 2,
NULL ) )
return( -1 );
im = t[1];
im->Type = VIPS_INTERPRETATION_B_W;
}
else {
/* G.
*/
if( vips_extract_band( im, &t[1], 0, NULL ) )
return( -1 );
im = t[1];
im->Type = VIPS_INTERPRETATION_B_W;
}
if( vips_image_write( im, load->out ) ) /* 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( -1 );
return( 0 ); return( 0 );
@ -1174,13 +977,14 @@ vips_foreign_load_gif_open( VipsForeignLoadGif *gif )
#endif #endif
gif->eof = FALSE; gif->eof = FALSE;
gif->current_page = 0;
/* Allocate a line buffer now that we have the GIF width. /* Allocate a line buffer now that we have the GIF width.
*/ */
if( !gif->line ) VIPS_FREE( gif->line )
if( !(gif->line = VIPS_ARRAY( gif, if( !(gif->line = VIPS_ARRAY( gif,
gif->file->SWidth, GifPixelType )) ) gif->file->SWidth, GifPixelType )) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }