diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 672a59e9..4b90557f 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2045,6 +2045,7 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_load_svg_get_type( void ); extern GType vips_foreign_load_svg_file_get_type( void ); extern GType vips_foreign_load_svg_buffer_get_type( void ); + extern GType vips_foreign_load_svg_stream_get_type( void ); extern GType vips_foreign_load_heif_get_type( void ); extern GType vips_foreign_load_heif_file_get_type( void ); extern GType vips_foreign_load_heif_buffer_get_type( void ); @@ -2102,6 +2103,7 @@ vips_foreign_operation_init( void ) vips_foreign_load_svg_get_type(); vips_foreign_load_svg_file_get_type(); vips_foreign_load_svg_buffer_get_type(); + vips_foreign_load_svg_stream_get_type(); #endif /*HAVE_RSVG*/ #ifdef HAVE_GIFLIB diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index f1a8f2b1..ccd8039a 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -456,6 +456,113 @@ vips_foreign_load_svg_init( VipsForeignLoadSvg *svg ) svg->cairo_scale = 1.0; } +typedef struct _VipsForeignLoadSvgStream { + VipsForeignLoadSvg parent_object; + + /* Load from a stream. + */ + VipsStreami *input; + +} VipsForeignLoadSvgStream; + +typedef VipsForeignLoadClass VipsForeignLoadSvgStreamClass; + +G_DEFINE_TYPE( VipsForeignLoadSvgStream, vips_foreign_load_svg_stream, + vips_foreign_load_svg_get_type() ); + +gboolean +vips_foreign_load_svg_stream_is_a( VipsStreami *input ) +{ + ssize_t n; + + if( vips_streami_unminimise( input ) || + vips_streami_rewind( input ) ) + return( FALSE ); + + g_byte_array_set_size( input->sniff, SVG_HEADER_SIZE ); + + /* Can't use vips_streami_sniff here. + */ + if( (n = vips_streami_read( input, input->sniff->data, + SVG_HEADER_SIZE )) == -1 || n == 0 ) + return( FALSE ); + + g_byte_array_set_size( input->sniff, n ); + + return( vips_foreign_load_svg_is_a( input->sniff->data, n ) ); +} + +static int +vips_foreign_load_svg_stream_header( VipsForeignLoad *load ) +{ + VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) load; + VipsForeignLoadSvgStream *stream = + (VipsForeignLoadSvgStream *) load; + RsvgHandleFlags flags = svg->unlimited ? RSVG_HANDLE_FLAG_UNLIMITED : 0; + + GError *error = NULL; + + GInputStream *gstream; + + if( vips_streami_rewind( stream->input ) ) + return( -1 ); + + gstream = g_input_stream_new_from_vips( stream->input ); + if( !(svg->page = rsvg_handle_new_from_stream_sync( + gstream, NULL, flags, NULL, &error )) ) { + g_object_unref( gstream ); + vips_g_error( &error ); + return( -1 ); + } + g_object_unref( gstream ); + + return( vips_foreign_load_svg_header( load ) ); +} + +static int +vips_foreign_load_svg_stream_load( VipsForeignLoad *load ) +{ + VipsForeignLoadSvgStream *stream = (VipsForeignLoadSvgStream *) load; + + if( vips_streami_rewind( stream->input ) || + vips_foreign_load_svg_load( load ) || + vips_streami_decode( stream->input ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_svg_stream_class_init( VipsForeignLoadSvgStreamClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) 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 = "svgload_stream"; + object_class->description = _( "load svg from stream" ); + + load_class->is_a_stream = vips_foreign_load_svg_stream_is_a; + load_class->header = vips_foreign_load_svg_stream_header; + load_class->load = vips_foreign_load_svg_stream_load; + + VIPS_ARG_OBJECT( class, "input", 1, + _( "Input" ), + _( "Stream to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadSvgStream, input ), + VIPS_TYPE_STREAMI ); + +} + +static void +vips_foreign_load_svg_stream_init( VipsForeignLoadSvgStream *stream ) +{ +} + typedef struct _VipsForeignLoadSvgFile { VipsForeignLoadSvg parent_object; @@ -705,3 +812,28 @@ vips_svgload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } +/** + * vips_svgload_stream: + * @input: stream to load from + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_svgload(), but read from a stream. + * + * See also: vips_svgload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_svgload_stream( VipsStreami *input, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "svgload_stream", ap, input, out ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/include/vips/stream.h b/libvips/include/vips/stream.h index 4e61db8c..8831794b 100644 --- a/libvips/include/vips/stream.h +++ b/libvips/include/vips/stream.h @@ -33,6 +33,9 @@ #ifndef VIPS_STREAM_H #define VIPS_STREAM_H +// TODO: #ifdef HAVE_RSVG (?) +#include + #ifdef __cplusplus extern "C" { #endif /*__cplusplus*/ @@ -294,6 +297,44 @@ int vips_streamib_require( VipsStreamib *streamib, int require ); const unsigned char *vips_streamib_get_line( VipsStreamib *streamib ); unsigned char *vips_streamib_get_line_copy( VipsStreamib *streamib ); +// TODO: #ifdef HAVE_RSVG (?) +#define VIPS_TYPE_STREAMIW (vips_streamiw_get_type()) +#define VIPS_STREAMIW( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_STREAMIW, VipsStreamiw )) +#define VIPS_STREAMIW_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_STREAMIW, VipsStreamiwClass)) +#define VIPS_IS_STREAMIW( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_STREAMIW )) +#define VIPS_IS_STREAMIW_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_STREAMIW )) +#define VIPS_STREAMIW_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_STREAMIW, VipsStreamiwClass )) + +/* GInputStream <--> VipsStreami + */ +typedef struct _VipsStreamiw { + GInputStream parent_instance; + + /*< private >*/ + + /* The VipsStreami we wrap. + */ + VipsStreami *streami; + +} VipsStreamiw; + +typedef struct _VipsStreamiwClass { + GInputStreamClass parent_class; + +} VipsStreamiwClass; + +GType vips_streamiw_get_type( void ); + +GInputStream *g_input_stream_new_from_vips( VipsStreami *streami ); + #define VIPS_TYPE_STREAMO (vips_streamo_get_type()) #define VIPS_STREAMO( obj ) \ (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ diff --git a/libvips/iofuncs/Makefile.am b/libvips/iofuncs/Makefile.am index 6ac7db50..62868505 100644 --- a/libvips/iofuncs/Makefile.am +++ b/libvips/iofuncs/Makefile.am @@ -4,6 +4,7 @@ libiofuncs_la_SOURCES = \ stream.c \ streami.c \ streamib.c \ + streamiw.c \ streamo.c \ dbuf.c \ reorder.c \ diff --git a/libvips/iofuncs/streamiw.c b/libvips/iofuncs/streamiw.c new file mode 100644 index 00000000..c510dc82 --- /dev/null +++ b/libvips/iofuncs/streamiw.c @@ -0,0 +1,337 @@ +/* GInputStream <--> VipsStreami + * + * Kleis Auke, 9/11/19 + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* TODO + * + * - Should we conditionally exclude this class? It's only needed for librsvg. + */ + +/* +#define VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static void vips_streamiw_seekable_iface_init( GSeekableIface *iface ); + +G_DEFINE_TYPE_WITH_CODE( VipsStreamiw, vips_streamiw, G_TYPE_INPUT_STREAM, + G_IMPLEMENT_INTERFACE( G_TYPE_SEEKABLE, + vips_streamiw_seekable_iface_init ) ) + +enum +{ + PROP_0, + PROP_STREAM +}; + +static void +vips_streamiw_get_property( GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec ) +{ + VipsStreamiw *streamiw = VIPS_STREAMIW( object ); + + switch (prop_id) { + case PROP_STREAM: + g_value_set_object( value, streamiw->streami ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + } +} + +static void +vips_streamiw_set_property( GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + VipsStreamiw *streamiw = VIPS_STREAMIW( object ); + + switch (prop_id) { + case PROP_STREAM: + streamiw->streami = g_value_dup_object( value ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + } +} + +static void +vips_streamiw_finalize( GObject *object ) +{ + VipsStreamiw *streamiw = VIPS_STREAMIW( object ); + + VIPS_FREEF( g_object_unref, streamiw->streami ); + + G_OBJECT_CLASS( vips_streamiw_parent_class )->finalize( object ); +} + +static goffset +vips_streamiw_tell( GSeekable *seekable ) +{ + VipsStreami *streami; + goffset pos; + + streami = VIPS_STREAMIW( seekable )->streami; + + VIPS_DEBUG_MSG( "vips_streamiw_tell:\n" ); + + pos = vips_streami_seek( streami, 0, SEEK_CUR ); + + if( pos == -1 ) + return 0; + + return pos; +} + +static gboolean +vips_streamiw_can_seek( GSeekable *seekable ) +{ + VipsStreami *streami = VIPS_STREAMIW( seekable )->streami; + + VIPS_DEBUG_MSG( "vips_streamiw_can_seek: %d\n", !streami->is_pipe); + + return !streami->is_pipe; +} + +static int +seek_type_to_lseek( GSeekType type ) +{ + switch (type) { + default: + case G_SEEK_CUR: + return SEEK_CUR; + case G_SEEK_SET: + return SEEK_SET; + case G_SEEK_END: + return SEEK_END; + } +} + +static gboolean +vips_streamiw_seek( GSeekable *seekable, goffset offset, + GSeekType type, GCancellable *cancellable, GError **error ) +{ + VipsStreami *streami = VIPS_STREAMIW( seekable )->streami; + + VIPS_DEBUG_MSG( "vips_streamiw_seek: offset = %" G_GINT64_FORMAT + ", type = %d\n", offset, type ); + + if( vips_streami_seek( streami, offset, + seek_type_to_lseek( type ) ) == -1 ) + { + g_set_error( error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error while seeking: %s"), + vips_error_buffer() ); + return( FALSE ); + } + + + return( TRUE ); +} + +static gboolean +vips_streamiw_can_truncate( GSeekable *seekable ) +{ + return( FALSE ); +} + +static gboolean +vips_streamiw_truncate( GSeekable *seekable, goffset offset, + GCancellable *cancellable, GError **error ) +{ + g_set_error_literal( error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Cannot truncate VipsStreamiw") ); + + return( FALSE ); +} + +static gssize +vips_streamiw_read( GInputStream *stream, void *buffer, gsize count, + GCancellable *cancellable, GError **error ) +{ + VipsStreami *streami; + gssize res; + + streami = VIPS_STREAMIW( stream )->streami; + + VIPS_DEBUG_MSG( "vips_streamiw_read: count: %zd\n", count ); + + if ( g_cancellable_set_error_if_cancelled( cancellable, error ) ) + return( -1 ); + + if( (res = vips_streami_read( streami, buffer, count )) == -1 ) + g_set_error( error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error while reading: %s"), + vips_error_buffer() ); + + return( res ); +} + +static gssize +vips_streamiw_skip( GInputStream *stream, gsize count, + GCancellable *cancellable, GError **error ) +{ + VipsStreami *streami; + goffset start; + goffset end; + + streami = VIPS_STREAMIW( stream )->streami; + + VIPS_DEBUG_MSG( "vips_streamiw_skip: count: %zd\n", count ); + + if( g_cancellable_set_error_if_cancelled( cancellable, error ) ) + return( -1 ); + + start = vips_streami_seek( streami, 0, SEEK_CUR ); + + if( start == -1 ) + { + g_set_error( error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error while seeking: %s"), + vips_error_buffer() ); + return -1; + } + + end = vips_streami_seek( streami, 0, SEEK_END ); + if( end == -1 ) + { + g_set_error( error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error while seeking: %s"), + vips_error_buffer() ); + return -1; + } + + if( end - start > count ) + { + end = vips_streami_seek( streami, count - (end - start), + SEEK_CUR ); + if( end == -1 ) + { + g_set_error( error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error while seeking: %s"), + vips_error_buffer() ); + return -1; + } + } + + return( end - start ); +} + +static gboolean +vips_streamiw_close( GInputStream *stream, + GCancellable *cancellable, GError **error ) +{ + VipsStreamiw *streamiw = VIPS_STREAMIW( stream ); + + vips_streami_minimise( streamiw->streami ); + + return( TRUE ); +} + +static void +vips_streamiw_seekable_iface_init( GSeekableIface *iface ) +{ + iface->tell = vips_streamiw_tell; + iface->can_seek = vips_streamiw_can_seek; + iface->seek = vips_streamiw_seek; + iface->can_truncate = vips_streamiw_can_truncate; + iface->truncate_fn = vips_streamiw_truncate; +} + +static void +vips_streamiw_class_init( VipsStreamiwClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS( class ); + + gobject_class->finalize = vips_streamiw_finalize; + gobject_class->get_property = vips_streamiw_get_property; + gobject_class->set_property = vips_streamiw_set_property; + + istream_class->read_fn = vips_streamiw_read; + istream_class->skip = vips_streamiw_skip; + istream_class->close_fn = vips_streamiw_close; + + g_object_class_install_property( gobject_class, PROP_STREAM, + g_param_spec_object( "input", + _("Input"), + _("Stream to wrap"), + VIPS_TYPE_STREAMI, G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) ); + +} + +static void +vips_streamiw_init( VipsStreamiw *streamiw ) +{ +} + +/** + * g_input_stream_new_from_vips: + * @streami: stream to wrap + * + * Create a new #GInputStream wrapping a #VipsStreami. + * + * Returns: a new #GInputStream + */ +GInputStream * +g_input_stream_new_from_vips( VipsStreami *streami ) +{ + return( g_object_new( VIPS_TYPE_STREAMIW, + "input", streami, + NULL ) ); +}