add restart after minimise support to gifload

see https://github.com/libvips/libvips/issues/1370#issuecomment-533169856
This commit is contained in:
John Cupitt 2019-09-26 18:07:18 +01:00
parent 5df173dd15
commit 4f1e57c040
1 changed files with 206 additions and 113 deletions

View File

@ -206,13 +206,19 @@ typedef struct _VipsForeignLoadGif {
typedef struct _VipsForeignLoadGifClass {
VipsForeignLoadClass parent_class;
/* Close and reopen gif->file.
/* Open the reader (eg. the FILE we are reading from). giflib is
* created in _header and freed in _dispose.
*/
int (*open)( VipsForeignLoadGif *gif );
/* Close any underlying file resource.
/* Rewind the reader, eg. fseek() back to the start.
*/
void (*rewind)( VipsForeignLoadGif *gif );
/* Close the reader.
*/
void (*close)( VipsForeignLoadGif *gif );
} VipsForeignLoadGifClass;
G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadGif, vips_foreign_load_gif,
@ -307,14 +313,92 @@ vips_foreign_load_gif_error( VipsForeignLoadGif *gif )
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 )
{
VipsForeignLoadGifClass *class = VIPS_FOREIGN_LOAD_GIF_GET_CLASS( 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
class->close( gif );
return( 0 );
}
/* Open any underlying file resource, then giflib.
*/
static int
vips_foreign_load_gif_open_giflib( VipsForeignLoadGif *gif )
{
VipsForeignLoadGifClass *class = VIPS_FOREIGN_LOAD_GIF_GET_CLASS( gif );
VIPS_DEBUG_MSG( "vips_foreign_load_gif_open_giflib:\n" );
if( class->open( gif ) )
return( -1 );
/* Must always rewind before opening giflib again.
*/
class->rewind( gif );
#ifdef HAVE_GIFLIB_5
{
int error;
if( !(gif->file = DGifOpen( gif, gif->read_func, &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, gif->read_func )) ) {
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;
return( 0 );
}
static void
vips_foreign_load_gif_dispose( GObject *gobject )
{
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) gobject;
VipsForeignLoadGifClass *class =
(VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif );
class->close( gif );
VIPS_DEBUG_MSG( "vips_foreign_load_gif_dispose:\n" );
vips_foreign_load_gif_close_giflib( gif );
VIPS_UNREF( gif->frame );
VIPS_UNREF( gif->previous );
@ -623,6 +707,8 @@ vips_foreign_load_gif_scan( VipsForeignLoadGif *gif )
GifRecordType record;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_scan:\n" );
gif->n_pages = 0;
do {
@ -675,24 +761,39 @@ vips_foreign_load_gif_scan( VipsForeignLoadGif *gif )
static int
vips_foreign_load_gif_header( VipsForeignLoad *load )
{
VipsForeignLoadGifClass *class =
(VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load;
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadGif *gif = VIPS_FOREIGN_LOAD_GIF( load );
if( class->open( gif ) )
VIPS_DEBUG_MSG( "vips_foreign_load_gif_header: %p\n", gif );
if( vips_foreign_load_gif_open_giflib( gif ) )
return( -1 );
if( vips_foreign_load_gif_scan( gif ) ) {
class->close( gif );
/* 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 );
}
if( vips_foreign_load_gif_set_header( gif, load->out ) ) {
class->close( gif );
/* Allocate a line buffer now that we have the GIF width.
*/
if( !(gif->line =
VIPS_ARRAY( NULL, gif->file->SWidth, GifPixelType )) ||
vips_foreign_load_gif_scan( gif ) ||
vips_foreign_load_gif_set_header( gif, load->out ) ) {
(void) vips_foreign_load_gif_close_giflib( gif );
return( -1 );
}
class->close( gif );
(void) vips_foreign_load_gif_close_giflib( gif );
return( 0 );
}
@ -968,9 +1069,22 @@ vips_foreign_load_gif_generate( VipsRegion *or,
{
VipsRect *r = &or->valid;
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) a;
VipsForeignLoadGifClass *class = VIPS_FOREIGN_LOAD_GIF_GET_CLASS( gif );
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*/
/* May have been minimised. Reopen the fp if necessary.
*/
if( class->open( gif ) )
return( -1 );
for( y = 0; y < r->height; y++ ) {
/* The page for this output line, and the line number in page.
*/
@ -1045,8 +1159,7 @@ vips_foreign_load_gif_generate( VipsRegion *or,
static void
vips_foreign_load_gif_minimise( VipsObject *object, VipsForeignLoadGif *gif )
{
VipsForeignLoadGifClass *class =
(VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif );
VipsForeignLoadGifClass *class = VIPS_FOREIGN_LOAD_GIF_GET_CLASS( gif );
class->close( gif );
}
@ -1054,16 +1167,14 @@ vips_foreign_load_gif_minimise( VipsObject *object, VipsForeignLoadGif *gif )
static int
vips_foreign_load_gif_load( VipsForeignLoad *load )
{
VipsForeignLoadGifClass *class =
(VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load;
VipsForeignLoadGif *gif = VIPS_FOREIGN_LOAD_GIF( load );
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( load ), 4 );
if( class->open( gif ) )
return( -1 );
VIPS_DEBUG_MSG( "vips_foreign_load_gif_load: %p\n", gif );
VIPS_DEBUG_MSG( "vips_foreign_load_gif_load:\n" );
if( vips_foreign_load_gif_open_giflib( gif ) )
return( -1 );
/* Make the memory image we accumulate pixels in. We always accumulate
* to RGBA, then trim down to whatever the output image needs on
@ -1113,65 +1224,17 @@ vips_foreign_load_gif_load( VipsForeignLoad *load )
static int
vips_foreign_load_gif_open( VipsForeignLoadGif *gif )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
#ifdef HAVE_GIFLIB_5
{
int error;
if( !(gif->file = DGifOpen( gif, gif->read_func, &error )) ) {
vips_foreign_load_gif_error_vips( gif, error );
return( -1 );
}
}
#else
if( !(gif->file = DGifOpen( gif, gif->read_func )) ) {
vips_foreign_load_gif_error_vips( gif, GifLastError() );
return( -1 );
}
#endif
gif->eof = FALSE;
gif->current_page = 0;
/* 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" ) );
return( -1 );
}
/* Allocate a line buffer now that we have the GIF width.
*/
VIPS_FREE( gif->line )
if( !(gif->line = VIPS_ARRAY( NULL, gif->file->SWidth, GifPixelType )) )
return( -1 );
return( 0 );
}
static void
vips_foreign_load_gif_rewind( VipsForeignLoadGif *gif )
{
}
static void
vips_foreign_load_gif_close( VipsForeignLoadGif *gif )
{
#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;
}
#else
if( gif->file ) {
if( DGifCloseFile( gif->file ) == GIF_ERROR )
vips_foreign_load_gif_error_vips( gif, GifLastError() );
gif->file = NULL;
}
#endif
}
static void
@ -1195,6 +1258,7 @@ vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class )
load_class->get_flags = vips_foreign_load_gif_get_flags;
class->open = vips_foreign_load_gif_open;
class->rewind = vips_foreign_load_gif_rewind;
class->close = vips_foreign_load_gif_close;
VIPS_ARG_INT( class, "page", 20,
@ -1238,6 +1302,10 @@ typedef struct _VipsForeignLoadGifFile {
*/
FILE *fp;
/* If we close and reopen, save the ftell point here.
*/
long seek_position;
} VipsForeignLoadGifFile;
typedef VipsForeignLoadGifClass VipsForeignLoadGifFileClass;
@ -1262,59 +1330,65 @@ vips_giflib_file_read( GifFileType *gfile, GifByteType *buffer, int n )
return( (int) fread( (void *) buffer, 1, n, file->fp ) );
}
static int
vips_foreign_load_gif_file_header( VipsForeignLoad *load )
{
VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load;
VipsForeignLoadGifClass *class =
(VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load );
if( VIPS_FOREIGN_LOAD_CLASS(
vips_foreign_load_gif_file_parent_class )->header( load ) ) {
/* Close early if header read fails in our base class.
*/
class->close( gif );
return( -1 );
}
return( 0 );
}
/* We have to have _open() as a vfunc since our base class needs to be able to
* make two scans of the gif (scan for header, then scan for pixels), so we
* must be able to close and reopen.
/* We have to have _open() as a vfunc since we want to be able to reopen in
* _generate if we have been closed during _minimise.
*/
static int
vips_foreign_load_gif_file_open( VipsForeignLoadGif *gif )
{
VipsForeignLoad *load = (VipsForeignLoad *) gif;
VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif;
VipsForeignLoadGifClass *class =
(VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load );
class->close( gif );
VIPS_DEBUG_MSG( "vips_foreign_load_gif_file_open:\n" );
if( !(file->fp =
vips__file_open_read( file->filename, NULL, FALSE )) )
return( -1 );
if( !file->fp ) {
if( !(file->fp =
vips__file_open_read( file->filename, NULL, FALSE )) )
return( -1 );
VIPS_SETSTR( load->out->filename, file->filename );
/* Restore the read point if we are reopening.
*/
if( file->seek_position != -1 )
fseek( file->fp, file->seek_position, SEEK_SET );
gif->read_func = vips_giflib_file_read;
VIPS_SETSTR( load->out->filename, file->filename );
gif->read_func = vips_giflib_file_read;
}
return( VIPS_FOREIGN_LOAD_GIF_CLASS(
vips_foreign_load_gif_file_parent_class )->open( gif ) );
}
static void
vips_foreign_load_gif_file_rewind( VipsForeignLoadGif *gif )
{
VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_file_rewind:\n" );
if( file->fp ) {
file->seek_position = 0;
fseek( file->fp, file->seek_position, SEEK_SET );
}
VIPS_FOREIGN_LOAD_GIF_CLASS(
vips_foreign_load_gif_file_parent_class )->rewind( gif );
}
static void
vips_foreign_load_gif_file_close( VipsForeignLoadGif *gif )
{
VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_file_close:\n" );
if( file->fp ) {
file->seek_position = ftell( file->fp );
VIPS_FREEF( fclose, file->fp );
}
VIPS_FOREIGN_LOAD_GIF_CLASS(
vips_foreign_load_gif_file_parent_class )->close( gif );
VIPS_FREEF( fclose, file->fp );
}
static const char *vips_foreign_gif_suffs[] = {
@ -1341,9 +1415,9 @@ vips_foreign_load_gif_file_class_init(
foreign_class->suffs = vips_foreign_gif_suffs;
load_class->is_a = vips_foreign_load_gif_is_a;
load_class->header = vips_foreign_load_gif_file_header;
gif_class->open = vips_foreign_load_gif_file_open;
gif_class->rewind = vips_foreign_load_gif_file_rewind;
gif_class->close = vips_foreign_load_gif_file_close;
VIPS_ARG_STRING( class, "filename", 1,
@ -1358,6 +1432,7 @@ vips_foreign_load_gif_file_class_init(
static void
vips_foreign_load_gif_file_init( VipsForeignLoadGifFile *file )
{
file->seek_position = -1;
}
typedef struct _VipsForeignLoadGifBuffer {
@ -1404,17 +1479,34 @@ static int
vips_foreign_load_gif_buffer_open( VipsForeignLoadGif *gif )
{
VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) gif;
VipsForeignLoadGifClass *class =
(VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif );
class->close( gif );
VIPS_DEBUG_MSG( "vips_foreign_load_gif_buffer_open:\n" );
/* We can open several times -- make sure we don't move the read point
* if we reopen.
*/
if( !buffer->p ) {
buffer->p = buffer->buf->data;
buffer->bytes_to_go = buffer->buf->length;
gif->read_func = vips_giflib_buffer_read;
}
return( VIPS_FOREIGN_LOAD_GIF_CLASS(
vips_foreign_load_gif_buffer_parent_class )->open( gif ) );
}
static void
vips_foreign_load_gif_buffer_rewind( VipsForeignLoadGif *gif )
{
VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) gif;
VIPS_DEBUG_MSG( "vips_foreign_load_gif_buffer_rewind:\n" );
buffer->p = buffer->buf->data;
buffer->bytes_to_go = buffer->buf->length;
gif->read_func = vips_giflib_buffer_read;;
return( VIPS_FOREIGN_LOAD_GIF_CLASS(
vips_foreign_load_gif_file_parent_class )->open( gif ) );
VIPS_FOREIGN_LOAD_GIF_CLASS(
vips_foreign_load_gif_buffer_parent_class )->rewind( gif );
}
static void
@ -1435,6 +1527,7 @@ vips_foreign_load_gif_buffer_class_init(
load_class->is_a_buffer = vips_foreign_load_gif_is_a_buffer;
gif_class->open = vips_foreign_load_gif_buffer_open;
gif_class->rewind = vips_foreign_load_gif_buffer_rewind;
VIPS_ARG_BOXED( class, "buffer", 1,
_( "Buffer" ),