add webpload stream

webp uses the new VipsStreamInput mmap interface
This commit is contained in:
John Cupitt 2019-10-15 15:46:37 +01:00
parent 5a788b89cf
commit 98410042ac
9 changed files with 241 additions and 175 deletions

View File

@ -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 )) )

View File

@ -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 )
{

View File

@ -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();

View File

@ -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,

View File

@ -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 );

View File

@ -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 );
}

View File

@ -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, ... )

View File

@ -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 );

View File

@ -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
*/
/*