From 98410042ac564ac7c7fbc77794649c09b4c2b460 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 15 Oct 2019 15:46:37 +0100 Subject: [PATCH] add webpload stream webp uses the new VipsStreamInput mmap interface --- libvips/deprecated/im_png2vips.c | 1 - libvips/deprecated/im_webp2vips.c | 42 +++++-- libvips/foreign/foreign.c | 2 + libvips/foreign/pforeign.h | 12 +- libvips/foreign/webp2vips.c | 165 +++++---------------------- libvips/foreign/webpload.c | 180 ++++++++++++++++++++++++++++-- libvips/include/vips/foreign.h | 2 + libvips/include/vips/stream.h | 1 + libvips/iofuncs/stream.c | 11 +- 9 files changed, 241 insertions(+), 175 deletions(-) diff --git a/libvips/deprecated/im_png2vips.c b/libvips/deprecated/im_png2vips.c index 410da878..8cfa2add 100644 --- a/libvips/deprecated/im_png2vips.c +++ b/libvips/deprecated/im_png2vips.c @@ -85,7 +85,6 @@ png2vips( const char *name, IMAGE *out, gboolean header_only ) #ifdef HAVE_PNG { VipsStreamInput *input; - int result; if( !(input = vips_stream_input_new_from_filename( filename )) ) diff --git a/libvips/deprecated/im_webp2vips.c b/libvips/deprecated/im_webp2vips.c index 9ad166ed..60e1fbf0 100644 --- a/libvips/deprecated/im_webp2vips.c +++ b/libvips/deprecated/im_webp2vips.c @@ -51,14 +51,21 @@ webp2vips( const char *name, IMAGE *out, gboolean header_only ) im_filename_split( name, filename, mode ); #ifdef HAVE_LIBWEBP - if( header_only ) { - if( vips__webp_read_file_header( filename, out, 0, 1, 1 ) ) - return( -1 ); - } - else { - if( vips__webp_read_file( filename, out, 0, 1, 1 ) ) - return( -1 ); - } +{ + VipsStreamInput *input; + int result; + + if( !(input = vips_stream_input_new_from_filename( filename )) ) + return( -1 ); + if( header_only ) + result = vips__webp_read_header_stream( input, out, 0, 1, 1 ); + else + result = vips__webp_read_stream( input, out, 0, 1, 1 ); + VIPS_UNREF( input ); + + if( result ) + return( result ); +} #else vips_error( "im_webp2vips", "%s", _( "no webp support in your libvips" ) ); @@ -69,6 +76,25 @@ webp2vips( const char *name, IMAGE *out, gboolean header_only ) return( 0 ); } +static gboolean +vips__iswebp( const char *filename ) +{ + gboolean result; + +#ifdef HAVE_LIBWEBP + VipsStreamInput *input; + + if( !(input = vips_stream_input_new_from_filename( filename )) ) + return( FALSE ); + result = vips__png_ispng_stream( input ); + VIPS_UNREF( input ); +#else /*!HAVE_LIBWEBP*/ + result = -1; +#endif /*HAVE_LIBWEBP*/ + + return( result ); +} + int im_webp2vips( const char *name, IMAGE *out ) { diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 013fff33..fa8254e2 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2022,6 +2022,7 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_save_magick_buffer_get_type( void ); extern GType vips_foreign_save_dz_file_get_type( void ); extern GType vips_foreign_save_dz_buffer_get_type( void ); + extern GType vips_foreign_load_webp_stream_get_type( void ); extern GType vips_foreign_load_webp_file_get_type( void ); extern GType vips_foreign_load_webp_buffer_get_type( void ); extern GType vips_foreign_save_webp_file_get_type( void ); @@ -2122,6 +2123,7 @@ vips_foreign_operation_init( void ) #endif /*HAVE_JPEG*/ #ifdef HAVE_LIBWEBP + vips_foreign_load_webp_stream_get_type(); vips_foreign_load_webp_file_get_type(); vips_foreign_load_webp_buffer_get_type(); vips_foreign_save_webp_file_get_type(); diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 5446e937..8ce58817 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -207,17 +207,11 @@ extern const VipsWebPNames vips__webp_names[]; extern const int vips__n_webp_names; extern const char *vips__webp_suffs[]; -int vips__iswebp_buffer( const void *buf, size_t len ); -int vips__iswebp( const char *filename ); +int vips__iswebp_stream( VipsStreamInput *input ); -int vips__webp_read_file_header( const char *name, VipsImage *out, +int vips__webp_read_header_stream( VipsStreamInput *input, VipsImage *out, int page, int n, double scale ); -int vips__webp_read_file( const char *name, VipsImage *out, - int page, int n, double scale ); - -int vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, - int page, int n, double scale ); -int vips__webp_read_buffer( const void *buf, size_t len, VipsImage *out, +int vips__webp_read_stream( VipsStreamInput *input, VipsImage *out, int page, int n, double scale ); int vips__webp_write_file( VipsImage *out, const char *filename, diff --git a/libvips/foreign/webp2vips.c b/libvips/foreign/webp2vips.c index 90bf3a04..e68a364f 100644 --- a/libvips/foreign/webp2vips.c +++ b/libvips/foreign/webp2vips.c @@ -24,6 +24,8 @@ * - disable alpha output if all frame fill the canvas and are solid * 6/7/19 [deftomat] * - support array of delays + * 14/10/19 + * - revise for stream IO */ /* @@ -79,15 +81,11 @@ /* What we track during a read. */ typedef struct { - /* File source. - */ - char *filename; + VipsStreamInput *input; - /* Memory source. We use gint64 rather than size_t since we use - * vips_file_length() and vips__mmap() for file sources. + /* The data we load, as a webp object. */ - const void *data; - gint64 length; + WebPData data; /* Load this page (frame number). */ @@ -128,10 +126,6 @@ typedef struct { */ int *delays; - /* If we are opening a file object, the fd. - */ - int fd; - /* Parse with this. */ WebPDemuxer *demux; @@ -317,57 +311,24 @@ vips_image_paint_image( VipsImage *frame, } int -vips__iswebp_buffer( const void *buf, size_t len ) +vips__iswebp_stream( VipsStreamInput *input ) { + const unsigned char *p; + /* WebP is "RIFF xxxx WEBP" at the start, so we need 12 bytes. */ - if( len >= 12 && - vips_isprefix( "RIFF", (char *) buf ) && - vips_isprefix( "WEBP", (char *) buf + 8 ) ) + if( (p = vips_stream_input_sniff( input, 12 )) && + vips_isprefix( "RIFF", (char *) p ) && + vips_isprefix( "WEBP", (char *) p + 8 ) ) return( 1 ); return( 0 ); } -int -vips__iswebp( const char *filename ) -{ - /* Magic number, see above. - */ - unsigned char header[12]; - - if( vips__get_bytes( filename, header, 12 ) == 12 && - vips__iswebp_buffer( header, 12 ) ) - return( 1 ); - - return( 0 ); -} - -static void -read_minimise( Read *read ) -{ - WebPDemuxReleaseIterator( &read->iter ); - VIPS_UNREF( read->frame ); - VIPS_FREEF( WebPDemuxDelete, read->demux ); - WebPFreeDecBuffer( &read->config.output ); - - if( read->fd > 0 && - read->data && - read->length > 0 ) { - vips__munmap( read->data, read->length ); - read->data = NULL; - read->length = 0; - } - - VIPS_FREEF( vips_tracked_close, read->fd ); -} - static int read_free( Read *read ) { - read_minimise( read ); - - VIPS_FREE( read->filename ); + VIPS_UNREF( read->input ); VIPS_FREE( read->delays ); VIPS_FREE( read ); @@ -375,46 +336,34 @@ read_free( Read *read ) } static Read * -read_new( const char *filename, const void *data, size_t length, - int page, int n, double scale ) +read_new( VipsStreamInput *input, int page, int n, double scale ) { Read *read; if( !(read = VIPS_NEW( NULL, Read )) ) return( NULL ); - read->filename = g_strdup( filename ); - read->data = data; - read->length = length; + read->input = input; + g_object_ref( input ); read->page = page; read->n = n; read->scale = scale; read->delays = NULL; - read->fd = 0; read->demux = NULL; read->frame = NULL; read->dispose_method = WEBP_MUX_DISPOSE_NONE; read->frame_no = 0; - if( read->filename ) { - /* libwebp makes streaming from a file source very hard. We - * have to read to a full memory buffer, then copy to out. - * - * mmap the input file, it's slightly quicker. - */ - if( (read->fd = vips__open_image_read( read->filename )) < 0 || - (read->length = vips_file_length( read->fd )) < 0 || - !(read->data = vips__mmap( read->fd, - FALSE, read->length, 0 )) ) { - read_free( read ); - return( NULL ); - } - } - WebPInitDecoderConfig( &read->config ); read->config.options.use_threads = 1; read->config.output.is_external_memory = 1; + if( !(read->data.bytes = + vips_stream_input_map( input, &read->data.size )) ) { + read_free( read ); + return( NULL ); + } + return( read ); } @@ -430,13 +379,10 @@ const int vips__n_webp_names = VIPS_NUMBER( vips__webp_names ); static int read_header( Read *read, VipsImage *out ) { - WebPData data; int flags; int i; - data.bytes = read->data; - data.size = read->length; - if( !(read->demux = WebPDemux( &data )) ) { + if( !(read->demux = WebPDemux( &read->data )) ) { vips_error( "webp", "%s", _( "unable to parse image" ) ); return( -1 ); } @@ -599,28 +545,6 @@ read_header( Read *read, VipsImage *out ) return( 0 ); } -int -vips__webp_read_file_header( const char *filename, VipsImage *out, - int page, int n, double scale ) -{ - Read *read; - - if( !(read = read_new( filename, NULL, 0, page, n, scale )) ) { - vips_error( "webp2vips", - _( "unable to open \"%s\"" ), filename ); - return( -1 ); - } - - if( read_header( read, out ) ) { - read_free( read ); - return( -1 ); - } - - read_free( read ); - - return( 0 ); -} - /* Read a single frame -- a width * height block of pixels. This will get * blended into the accumulator at some offset. */ @@ -803,12 +727,6 @@ read_webp_generate( VipsRegion *or, return( 0 ); } -static void -read_minimise_cb( VipsObject *object, Read *read ) -{ - read_minimise( read ); -} - static int read_image( Read *read, VipsImage *out ) { @@ -819,9 +737,6 @@ read_image( Read *read, VipsImage *out ) if( read_header( read, t[0] ) ) return( -1 ); - g_signal_connect( t[0], "minimise", - G_CALLBACK( read_minimise_cb ), read ); - if( vips_image_generate( t[0], NULL, read_webp_generate, NULL, read, NULL ) || vips_sequential( t[0], &t[1], NULL ) || @@ -832,38 +747,13 @@ read_image( Read *read, VipsImage *out ) } int -vips__webp_read_file( const char *filename, VipsImage *out, +vips__webp_read_header_stream( VipsStreamInput *input, VipsImage *out, int page, int n, double scale ) { Read *read; - if( !(read = read_new( filename, NULL, 0, page, n, scale )) ) { - vips_error( "webp2vips", - _( "unable to open \"%s\"" ), filename ); + if( !(read = read_new( input, page, n, scale )) ) return( -1 ); - } - - if( read_image( read, out ) ) { - read_free( read ); - return( -1 ); - } - - read_free( read ); - - return( 0 ); -} - -int -vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, - int page, int n, double scale ) -{ - Read *read; - - if( !(read = read_new( NULL, buf, len, page, n, scale )) ) { - vips_error( "webp2vips", - "%s", _( "unable to open buffer" ) ); - return( -1 ); - } if( read_header( read, out ) ) { read_free( read ); @@ -876,16 +766,13 @@ vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, } int -vips__webp_read_buffer( const void *buf, size_t len, VipsImage *out, +vips__webp_read_stream( VipsStreamInput *input, VipsImage *out, int page, int n, double scale ) { Read *read; - if( !(read = read_new( NULL, buf, len, page, n, scale )) ) { - vips_error( "webp2vips", - "%s", _( "unable to open buffer" ) ); + if( !(read = read_new( input, page, n, scale )) ) return( -1 ); - } if( read_image( read, out ) ) { read_free( read ); diff --git a/libvips/foreign/webpload.c b/libvips/foreign/webpload.c index 545fc793..02ba2204 100644 --- a/libvips/foreign/webpload.c +++ b/libvips/foreign/webpload.c @@ -153,6 +153,81 @@ vips_foreign_load_webp_init( VipsForeignLoadWebp *webp ) webp->scale = 1.0; } +typedef struct _VipsForeignLoadWebpStream { + VipsForeignLoadWebp parent_object; + + VipsStreamInput *input; + +} VipsForeignLoadWebpStream; + +typedef VipsForeignLoadWebpClass VipsForeignLoadWebpStreamClass; + +G_DEFINE_TYPE( VipsForeignLoadWebpStream, vips_foreign_load_webp_stream, + vips_foreign_load_webp_get_type() ); + +static int +vips_foreign_load_webp_stream_header( VipsForeignLoad *load ) +{ + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; + VipsForeignLoadWebpStream *stream = (VipsForeignLoadWebpStream *) load; + + if( vips__webp_read_header_stream( stream->input, load->out, + webp->page, webp->n, webp->scale ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_webp_stream_load( VipsForeignLoad *load ) +{ + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; + VipsForeignLoadWebpStream *stream = (VipsForeignLoadWebpStream *) load; + + if( vips__webp_read_stream( stream->input, load->real, + webp->page, webp->n, webp->scale ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_webp_stream_class_init( + VipsForeignLoadWebpStreamClass *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 = "webpload_stream"; + object_class->description = _( "load webp from stream" ); + + /* is_a() is not that quick ... lower the priority. + */ + foreign_class->priority = -50; + + load_class->is_a_stream = vips__iswebp_stream; + load_class->header = vips_foreign_load_webp_stream_header; + load_class->load = vips_foreign_load_webp_stream_load; + + VIPS_ARG_OBJECT( class, "input", 1, + _( "Input" ), + _( "Stream to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadWebpStream, input ), + VIPS_TYPE_STREAM_INPUT ); + +} + +static void +vips_foreign_load_webp_stream_init( VipsForeignLoadWebpStream *buffer ) +{ +} + typedef struct _VipsForeignLoadWebpFile { VipsForeignLoadWebp parent_object; @@ -176,7 +251,15 @@ vips_foreign_load_webp_file_get_flags_filename( const char *filename ) static gboolean vips_foreign_load_webp_file_is_a( const char *filename ) { - return( vips__iswebp( filename ) ); + VipsStreamInput *input; + gboolean result; + + if( !(input = vips_stream_input_new_from_filename( filename )) ) + return( FALSE ); + result = vips__iswebp_stream( input ); + VIPS_UNREF( input ); + + return( result ); } static int @@ -185,6 +268,8 @@ vips_foreign_load_webp_file_header( VipsForeignLoad *load ) VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) load; + VipsStreamInput *input; + /* BC for the old API. */ if( !vips_object_argument_isset( VIPS_OBJECT( load ), "scale" ) && @@ -192,9 +277,14 @@ vips_foreign_load_webp_file_header( VipsForeignLoad *load ) webp->shrink != 0 ) webp->scale = 1.0 / webp->shrink; - if( vips__webp_read_file_header( file->filename, load->out, - webp->page, webp->n, webp->scale ) ) + if( !(input = vips_stream_input_new_from_filename( file->filename )) ) return( -1 ); + if( vips__webp_read_header_stream( input, load->out, + webp->page, webp->n, webp->scale ) ) { + VIPS_UNREF( input ); + return( -1 ); + } + VIPS_UNREF( input ); VIPS_SETSTR( load->out->filename, file->filename ); @@ -207,9 +297,16 @@ vips_foreign_load_webp_file_load( VipsForeignLoad *load ) VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) load; - if( vips__webp_read_file( file->filename, load->real, - webp->page, webp->n, webp->scale ) ) + VipsStreamInput *input; + + if( !(input = vips_stream_input_new_from_filename( file->filename )) ) return( -1 ); + if( vips__webp_read_stream( input, load->real, + webp->page, webp->n, webp->scale ) ) { + VIPS_UNREF( input ); + return( -1 ); + } + VIPS_UNREF( input ); return( 0 ); } @@ -265,16 +362,37 @@ typedef VipsForeignLoadWebpClass VipsForeignLoadWebpBufferClass; G_DEFINE_TYPE( VipsForeignLoadWebpBuffer, vips_foreign_load_webp_buffer, vips_foreign_load_webp_get_type() ); +static gboolean +vips_foreign_load_webp_buffer_is_a_buffer( const void *buf, size_t len ) +{ + VipsStreamInput *input; + gboolean result; + + if( !(input = vips_stream_input_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips__iswebp_stream( input ); + VIPS_UNREF( input ); + + return( result ); +} + static int vips_foreign_load_webp_buffer_header( VipsForeignLoad *load ) { VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; VipsForeignLoadWebpBuffer *buffer = (VipsForeignLoadWebpBuffer *) load; - if( vips__webp_read_buffer_header( buffer->buf->data, - buffer->buf->length, load->out, - webp->page, webp->n, webp->scale ) ) + VipsStreamInput *input; + + if( !(input = vips_stream_input_new_from_memory( buffer->buf->data, + buffer->buf->length )) ) + return( FALSE ); + if( vips__webp_read_header_stream( input, load->out, + webp->page, webp->n, webp->scale ) ) { + VIPS_UNREF( input ); return( -1 ); + } + VIPS_UNREF( input ); return( 0 ); } @@ -285,10 +403,17 @@ vips_foreign_load_webp_buffer_load( VipsForeignLoad *load ) VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; VipsForeignLoadWebpBuffer *buffer = (VipsForeignLoadWebpBuffer *) load; - if( vips__webp_read_buffer( buffer->buf->data, buffer->buf->length, - load->real, - webp->page, webp->n, webp->scale ) ) + VipsStreamInput *input; + + if( !(input = vips_stream_input_new_from_memory( buffer->buf->data, + buffer->buf->length )) ) + return( FALSE ); + if( vips__webp_read_stream( input, load->real, + webp->page, webp->n, webp->scale ) ) { + VIPS_UNREF( input ); return( -1 ); + } + VIPS_UNREF( input ); return( 0 ); } @@ -312,7 +437,7 @@ vips_foreign_load_webp_buffer_class_init( */ foreign_class->priority = -50; - load_class->is_a_buffer = vips__iswebp_buffer; + load_class->is_a_buffer = vips_foreign_load_webp_buffer_is_a_buffer; load_class->header = vips_foreign_load_webp_buffer_header; load_class->load = vips_foreign_load_webp_buffer_load; @@ -416,3 +541,34 @@ vips_webpload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } + +/** + * vips_webpload_stream: + * @input: stream to load from + * @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 + * * @scale: %gdouble, scale by this much on load + * + * Exactly as vips_webpload(), but read from a stream. + * + * See also: vips_webpload() + * + * Returns: 0 on success, -1 on error. + */ +int +vips_webpload_stream( VipsStreamInput *input, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "webpload_stream", ap, input, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index ae7c77b5..8cdf4071 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -399,6 +399,8 @@ typedef enum { VIPS_FOREIGN_WEBP_PRESET_LAST } VipsForeignWebpPreset; +int vips_webpload_stream( VipsStreamInput *input, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_webpload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_webpload_buffer( void *buf, size_t len, VipsImage **out, ... ) diff --git a/libvips/include/vips/stream.h b/libvips/include/vips/stream.h index 1b7f7ee4..152e9087 100644 --- a/libvips/include/vips/stream.h +++ b/libvips/include/vips/stream.h @@ -191,6 +191,7 @@ VipsStreamInput *vips_stream_input_new_from_options( const char *options ); ssize_t vips_stream_input_read( VipsStreamInput *input, unsigned char *data, size_t length ); +const void *vips_stream_input_map( VipsStreamInput *input, size_t *length ); int vips_stream_input_rewind( VipsStreamInput *input ); void vips_stream_input_minimise( VipsStreamInput *input ); void vips_stream_input_decode( VipsStreamInput *input ); diff --git a/libvips/iofuncs/stream.c b/libvips/iofuncs/stream.c index 62ee5d0d..a0f6060b 100644 --- a/libvips/iofuncs/stream.c +++ b/libvips/iofuncs/stream.c @@ -1,5 +1,5 @@ /* A byte source/sink .. it can be a pipe, file descriptor, memory area, - * socket, or perhaps a node.js stream. + * socket, node.js stream, etc. * * J.Cupitt, 19/6/14 */ @@ -34,11 +34,10 @@ /* TODO * * - filename encoding - * - add mmapable descriptors - * - add seekable descriptors - * - test for mmapable - * - can we really change all behaviour in the subclass? will we need map and - * seek as well as read and rewind? + * - add seekable input sources + * - need to be able to set seekable and mapable via constructor + * - test we can really change all behaviour in the subclass ... add callbacks + * as well to make it simpler for language bindings */ /*