jpegsave_stream mostly done
$ vips jpegsave_buffer x.jpg doesn't work though -- need to rethink how output blobs are made
This commit is contained in:
parent
20cb0da247
commit
d991b73ac5
@ -212,7 +212,7 @@ vips_foreign_load_jpeg_stream_class_init(
|
|||||||
gobject_class->get_property = vips_object_get_property;
|
gobject_class->get_property = vips_object_get_property;
|
||||||
|
|
||||||
object_class->nickname = "jpegload_stream";
|
object_class->nickname = "jpegload_stream";
|
||||||
object_class->description = _( "load jpeg from stream" );
|
object_class->description = _( "load image from jpeg stream" );
|
||||||
|
|
||||||
load_class->is_a_stream = vips_foreign_load_jpeg_stream_is_a;
|
load_class->is_a_stream = vips_foreign_load_jpeg_stream_is_a;
|
||||||
load_class->header = vips_foreign_load_jpeg_stream_header;
|
load_class->header = vips_foreign_load_jpeg_stream_header;
|
||||||
@ -224,6 +224,7 @@ vips_foreign_load_jpeg_stream_class_init(
|
|||||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||||
G_STRUCT_OFFSET( VipsForeignLoadJpegStream, input ),
|
G_STRUCT_OFFSET( VipsForeignLoadJpegStream, input ),
|
||||||
VIPS_TYPE_STREAM_INPUT );
|
VIPS_TYPE_STREAM_INPUT );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -196,7 +196,7 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class )
|
|||||||
|
|
||||||
VIPS_ARG_BOOL( class, "optimize_scans", 17,
|
VIPS_ARG_BOOL( class, "optimize_scans", 17,
|
||||||
_( "Optimize scans" ),
|
_( "Optimize scans" ),
|
||||||
_( "Split the spectrum of DCT coefficients into separate scans" ),
|
_( "Split spectrum of DCT coefficients into separate scans" ),
|
||||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||||
G_STRUCT_OFFSET( VipsForeignSaveJpeg, optimize_scans ),
|
G_STRUCT_OFFSET( VipsForeignSaveJpeg, optimize_scans ),
|
||||||
FALSE );
|
FALSE );
|
||||||
@ -216,6 +216,68 @@ vips_foreign_save_jpeg_init( VipsForeignSaveJpeg *jpeg )
|
|||||||
jpeg->Q = 75;
|
jpeg->Q = 75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct _VipsForeignSaveJpegStream {
|
||||||
|
VipsForeignSaveJpeg parent_object;
|
||||||
|
|
||||||
|
VipsStreamOutput *output;
|
||||||
|
|
||||||
|
} VipsForeignSaveJpegStream;
|
||||||
|
|
||||||
|
typedef VipsForeignSaveJpegClass VipsForeignSaveJpegStreamClass;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE( VipsForeignSaveJpegStream, vips_foreign_save_jpeg_stream,
|
||||||
|
vips_foreign_save_jpeg_get_type() );
|
||||||
|
|
||||||
|
static int
|
||||||
|
vips_foreign_save_jpeg_stream_build( VipsObject *object )
|
||||||
|
{
|
||||||
|
VipsForeignSave *save = (VipsForeignSave *) object;
|
||||||
|
VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object;
|
||||||
|
VipsForeignSaveJpegStream *stream =
|
||||||
|
(VipsForeignSaveJpegStream *) object;
|
||||||
|
|
||||||
|
if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_stream_parent_class )->
|
||||||
|
build( object ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
if( vips__jpeg_write_stream( save->ready, stream->output,
|
||||||
|
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
||||||
|
jpeg->interlace, save->strip, jpeg->no_subsample,
|
||||||
|
jpeg->trellis_quant, jpeg->overshoot_deringing,
|
||||||
|
jpeg->optimize_scans, jpeg->quant_table ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vips_foreign_save_jpeg_stream_class_init(
|
||||||
|
VipsForeignSaveJpegStreamClass *class )
|
||||||
|
{
|
||||||
|
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
||||||
|
VipsObjectClass *object_class = (VipsObjectClass *) class;
|
||||||
|
|
||||||
|
gobject_class->set_property = vips_object_set_property;
|
||||||
|
gobject_class->get_property = vips_object_get_property;
|
||||||
|
|
||||||
|
object_class->nickname = "jpegsave_stream";
|
||||||
|
object_class->description = _( "save image to jpeg stream" );
|
||||||
|
object_class->build = vips_foreign_save_jpeg_stream_build;
|
||||||
|
|
||||||
|
VIPS_ARG_OBJECT( class, "output", 1,
|
||||||
|
_( "Output" ),
|
||||||
|
_( "Stream to save to" ),
|
||||||
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||||
|
G_STRUCT_OFFSET( VipsForeignSaveJpegStream, output ),
|
||||||
|
VIPS_TYPE_STREAM_OUTPUT );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vips_foreign_save_jpeg_stream_init( VipsForeignSaveJpegStream *stream )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct _VipsForeignSaveJpegFile {
|
typedef struct _VipsForeignSaveJpegFile {
|
||||||
VipsForeignSaveJpeg parent_object;
|
VipsForeignSaveJpeg parent_object;
|
||||||
|
|
||||||
@ -237,16 +299,23 @@ vips_foreign_save_jpeg_file_build( VipsObject *object )
|
|||||||
VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object;
|
VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object;
|
||||||
VipsForeignSaveJpegFile *file = (VipsForeignSaveJpegFile *) object;
|
VipsForeignSaveJpegFile *file = (VipsForeignSaveJpegFile *) object;
|
||||||
|
|
||||||
|
VipsStreamOutput *output;
|
||||||
|
|
||||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_file_parent_class )->
|
if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_file_parent_class )->
|
||||||
build( object ) )
|
build( object ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
if( vips__jpeg_write_file( save->ready, file->filename,
|
if( !(output = vips_stream_output_new_from_filename( file->filename )) )
|
||||||
|
return( -1 );
|
||||||
|
if( vips__jpeg_write_stream( save->ready, output,
|
||||||
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
||||||
jpeg->interlace, save->strip, jpeg->no_subsample,
|
jpeg->interlace, save->strip, jpeg->no_subsample,
|
||||||
jpeg->trellis_quant, jpeg->overshoot_deringing,
|
jpeg->trellis_quant, jpeg->overshoot_deringing,
|
||||||
jpeg->optimize_scans, jpeg->quant_table ) )
|
jpeg->optimize_scans, jpeg->quant_table ) ) {
|
||||||
|
VIPS_UNREF( output );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
}
|
||||||
|
VIPS_UNREF( output );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
@ -298,27 +367,31 @@ vips_foreign_save_jpeg_buffer_build( VipsObject *object )
|
|||||||
VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object;
|
VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object;
|
||||||
VipsForeignSaveJpegBuffer *file = (VipsForeignSaveJpegBuffer *) object;
|
VipsForeignSaveJpegBuffer *file = (VipsForeignSaveJpegBuffer *) object;
|
||||||
|
|
||||||
void *obuf;
|
VipsStreamOutput *output;
|
||||||
size_t olen;
|
|
||||||
VipsBlob *blob;
|
VipsBlob *blob;
|
||||||
|
|
||||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_buffer_parent_class )->
|
if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_buffer_parent_class )->
|
||||||
build( object ) )
|
build( object ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
if( vips__jpeg_write_buffer( save->ready,
|
if( !(output = vips_stream_output_new_memory()) )
|
||||||
&obuf, &olen, jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
|
||||||
jpeg->interlace, save->strip, jpeg->no_subsample,
|
|
||||||
jpeg->trellis_quant, jpeg->overshoot_deringing,
|
|
||||||
jpeg->optimize_scans, jpeg->quant_table ) )
|
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
/* obuf is a g_free() buffer, not vips_free().
|
if( vips__jpeg_write_stream( save->ready, output,
|
||||||
*/
|
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
||||||
blob = vips_blob_new( (VipsCallbackFn) g_free, obuf, olen );
|
jpeg->interlace, save->strip, jpeg->no_subsample,
|
||||||
|
jpeg->trellis_quant, jpeg->overshoot_deringing,
|
||||||
|
jpeg->optimize_scans, jpeg->quant_table ) ) {
|
||||||
|
VIPS_UNREF( output );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_get( output, "blob", &blob, NULL );
|
||||||
g_object_set( file, "buffer", blob, NULL );
|
g_object_set( file, "buffer", blob, NULL );
|
||||||
vips_area_unref( VIPS_AREA( blob ) );
|
vips_area_unref( VIPS_AREA( blob ) );
|
||||||
|
|
||||||
|
VIPS_UNREF( output );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,30 +438,39 @@ vips_foreign_save_jpeg_mime_build( VipsObject *object )
|
|||||||
VipsForeignSave *save = (VipsForeignSave *) object;
|
VipsForeignSave *save = (VipsForeignSave *) object;
|
||||||
VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object;
|
VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object;
|
||||||
|
|
||||||
void *obuf;
|
VipsStreamOutput *output;
|
||||||
|
VipsBlob *blob;
|
||||||
|
const unsigned char *obuf;
|
||||||
size_t olen;
|
size_t olen;
|
||||||
|
|
||||||
if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_mime_parent_class )->
|
if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_mime_parent_class )->
|
||||||
build( object ) )
|
build( object ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
if( vips__jpeg_write_buffer( save->ready,
|
if( !(output = vips_stream_output_new_memory()) )
|
||||||
&obuf, &olen, jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
|
||||||
jpeg->interlace, save->strip, jpeg->no_subsample,
|
|
||||||
jpeg->trellis_quant, jpeg->overshoot_deringing,
|
|
||||||
jpeg->optimize_scans, jpeg->quant_table ) )
|
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
|
if( vips__jpeg_write_stream( save->ready, output,
|
||||||
|
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
||||||
|
jpeg->interlace, save->strip, jpeg->no_subsample,
|
||||||
|
jpeg->trellis_quant, jpeg->overshoot_deringing,
|
||||||
|
jpeg->optimize_scans, jpeg->quant_table ) ) {
|
||||||
|
VIPS_UNREF( output );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_get( output, "blob", &blob, NULL );
|
||||||
|
|
||||||
|
obuf = vips_blob_get( blob, &olen );
|
||||||
printf( "Content-length: %zu\r\n", olen );
|
printf( "Content-length: %zu\r\n", olen );
|
||||||
printf( "Content-type: image/jpeg\r\n" );
|
printf( "Content-type: image/jpeg\r\n" );
|
||||||
printf( "\r\n" );
|
printf( "\r\n" );
|
||||||
if( fwrite( obuf, sizeof( char ), olen, stdout ) != olen ) {
|
(void) fwrite( obuf, sizeof( char ), olen, stdout );
|
||||||
vips_error( "VipsJpeg", "%s", _( "error writing output" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
fflush( stdout );
|
fflush( stdout );
|
||||||
|
|
||||||
g_free( obuf );
|
vips_area_unref( VIPS_AREA( blob ) );
|
||||||
|
|
||||||
|
VIPS_UNREF( output );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
@ -527,6 +609,44 @@ vips_jpegsave( VipsImage *in, const char *filename, ... )
|
|||||||
return( result );
|
return( result );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vips_jpegsave_stream: (method)
|
||||||
|
* @in: image to save
|
||||||
|
* @output: save image to this stream
|
||||||
|
* @...: %NULL-terminated list of optional named arguments
|
||||||
|
*
|
||||||
|
* Optional arguments:
|
||||||
|
*
|
||||||
|
* * @Q: %gint, quality factor
|
||||||
|
* * @profile: filename of ICC profile to attach
|
||||||
|
* * @optimize_coding: %gboolean, compute optimal Huffman coding tables
|
||||||
|
* * @interlace: %gboolean, write an interlaced (progressive) jpeg
|
||||||
|
* * @strip: %gboolean, remove all metadata from image
|
||||||
|
* * @no_subsample: %gboolean, disable chroma subsampling
|
||||||
|
* * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block
|
||||||
|
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
|
||||||
|
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
||||||
|
* * @quant_table: %gint, quantization table index
|
||||||
|
*
|
||||||
|
* As vips_jpegsave(), but save to a stream.
|
||||||
|
*
|
||||||
|
* See also: vips_jpegsave(), vips_image_write_to_stream().
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, -1 on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
vips_jpegsave_stream( VipsImage *in, VipsStreamOutput *output, ... )
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
va_start( ap, output );
|
||||||
|
result = vips_call_split( "jpegsave_stream", ap, in, output );
|
||||||
|
va_end( ap );
|
||||||
|
|
||||||
|
return( result );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vips_jpegsave_buffer: (method)
|
* vips_jpegsave_buffer: (method)
|
||||||
* @in: image to save
|
* @in: image to save
|
||||||
|
@ -172,14 +172,8 @@ extern const char *vips__rad_suffs[];
|
|||||||
|
|
||||||
extern const char *vips__jpeg_suffs[];
|
extern const char *vips__jpeg_suffs[];
|
||||||
|
|
||||||
int vips__jpeg_write_file( VipsImage *in,
|
int vips__jpeg_write_stream( VipsImage *in, VipsStreamOutput *output,
|
||||||
const char *filename, int Q, const char *profile,
|
int Q, const char *profile,
|
||||||
gboolean optimize_coding, gboolean progressive, gboolean strip,
|
|
||||||
gboolean no_subsample, gboolean trellis_quant,
|
|
||||||
gboolean overshoot_deringing, gboolean optimize_scans,
|
|
||||||
int quant_table );
|
|
||||||
int vips__jpeg_write_buffer( VipsImage *in,
|
|
||||||
void **obuf, size_t *olen, int Q, const char *profile,
|
|
||||||
gboolean optimize_coding, gboolean progressive, gboolean strip,
|
gboolean optimize_coding, gboolean progressive, gboolean strip,
|
||||||
gboolean no_subsample, gboolean trellis_quant,
|
gboolean no_subsample, gboolean trellis_quant,
|
||||||
gboolean overshoot_deringing, gboolean optimize_scans,
|
gboolean overshoot_deringing, gboolean optimize_scans,
|
||||||
|
@ -207,7 +207,6 @@ static void
|
|||||||
write_destroy( Write *write )
|
write_destroy( Write *write )
|
||||||
{
|
{
|
||||||
jpeg_destroy_compress( &write->cinfo );
|
jpeg_destroy_compress( &write->cinfo );
|
||||||
VIPS_FREEF( fclose, write->eman.fp );
|
|
||||||
VIPS_FREE( write->row_pointer );
|
VIPS_FREE( write->row_pointer );
|
||||||
VIPS_UNREF( write->inverted );
|
VIPS_UNREF( write->inverted );
|
||||||
|
|
||||||
@ -680,59 +679,8 @@ write_vips( Write *write, int qfac, const char *profile,
|
|||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write an image to a jpeg file.
|
#define STREAM_BUFFER_SIZE (4096)
|
||||||
*/
|
|
||||||
int
|
|
||||||
vips__jpeg_write_file( VipsImage *in,
|
|
||||||
const char *filename, int Q, const char *profile,
|
|
||||||
gboolean optimize_coding, gboolean progressive, gboolean strip,
|
|
||||||
gboolean no_subsample, gboolean trellis_quant,
|
|
||||||
gboolean overshoot_deringing, gboolean optimize_scans, int quant_table )
|
|
||||||
{
|
|
||||||
Write *write;
|
|
||||||
|
|
||||||
if( !(write = write_new( in )) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
if( setjmp( write->eman.jmp ) ) {
|
|
||||||
/* Here for longjmp() from new_error_exit().
|
|
||||||
*/
|
|
||||||
write_destroy( write );
|
|
||||||
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Can't do this in write_new(), has to be after we've made the
|
|
||||||
* setjmp().
|
|
||||||
*/
|
|
||||||
jpeg_create_compress( &write->cinfo );
|
|
||||||
|
|
||||||
/* Make output.
|
|
||||||
*/
|
|
||||||
if( !(write->eman.fp = vips__file_open_write( filename, FALSE )) ) {
|
|
||||||
write_destroy( write );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
jpeg_stdio_dest( &write->cinfo, write->eman.fp );
|
|
||||||
|
|
||||||
/* Convert!
|
|
||||||
*/
|
|
||||||
if( write_vips( write,
|
|
||||||
Q, profile, optimize_coding, progressive, strip, no_subsample,
|
|
||||||
trellis_quant, overshoot_deringing, optimize_scans,
|
|
||||||
quant_table ) ) {
|
|
||||||
write_destroy( write );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
write_destroy( write );
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Just like the above, but we write to a memory buffer.
|
|
||||||
*
|
|
||||||
* A memory buffer for the compressed image.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* Public jpeg fields.
|
/* Public jpeg fields.
|
||||||
*/
|
*/
|
||||||
@ -743,113 +691,80 @@ typedef struct {
|
|||||||
|
|
||||||
/* Build the output area here.
|
/* Build the output area here.
|
||||||
*/
|
*/
|
||||||
VipsDbuf dbuf;
|
VipsStreamOutput *output;
|
||||||
|
|
||||||
/* Write the generated area here.
|
/* Our output buffer.
|
||||||
*/
|
*/
|
||||||
void **obuf; /* Allocated buffer, and size */
|
unsigned char buf[STREAM_BUFFER_SIZE];
|
||||||
size_t *olen;
|
} Dest;
|
||||||
} OutputBuffer;
|
|
||||||
|
|
||||||
/* Buffer full method ... allocate a new output block. This is only called
|
/* Buffer full method. This is only called when the output area is exactly
|
||||||
* when the output area is exactly full.
|
* full.
|
||||||
*/
|
*/
|
||||||
METHODDEF(boolean)
|
static jboolean
|
||||||
empty_output_buffer( j_compress_ptr cinfo )
|
empty_output_buffer( j_compress_ptr cinfo )
|
||||||
{
|
{
|
||||||
OutputBuffer *buf = (OutputBuffer *) cinfo->dest;
|
Dest *dest = (Dest *) cinfo->dest;
|
||||||
|
|
||||||
size_t size;
|
if( vips_stream_output_write( dest->output,
|
||||||
|
dest->buf, STREAM_BUFFER_SIZE ) )
|
||||||
|
ERREXIT( cinfo, JERR_FILE_WRITE );
|
||||||
|
|
||||||
vips_dbuf_allocate( &buf->dbuf, 10000 );
|
dest->pub.next_output_byte = dest->buf;
|
||||||
buf->pub.next_output_byte =
|
dest->pub.free_in_buffer = STREAM_BUFFER_SIZE;
|
||||||
(JOCTET *) vips_dbuf_get_write( &buf->dbuf, &size );
|
|
||||||
buf->pub.free_in_buffer = size;
|
|
||||||
|
|
||||||
/* TRUE means we've made some more space.
|
return( TRUE );
|
||||||
*/
|
|
||||||
return( 1 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Init dest method.
|
/* Init dest method.
|
||||||
*/
|
*/
|
||||||
METHODDEF(void)
|
static void
|
||||||
init_destination( j_compress_ptr cinfo )
|
init_destination( j_compress_ptr cinfo )
|
||||||
{
|
{
|
||||||
empty_output_buffer( cinfo );
|
Dest *dest = (Dest *) cinfo->dest;
|
||||||
|
|
||||||
|
dest->pub.next_output_byte = dest->buf;
|
||||||
|
dest->pub.free_in_buffer = STREAM_BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free the buffer writer.
|
/* Flush any remaining bytes to the output.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
buf_destroy( j_compress_ptr cinfo )
|
|
||||||
{
|
|
||||||
if( cinfo->dest ) {
|
|
||||||
OutputBuffer *buf = (OutputBuffer *) cinfo->dest;
|
|
||||||
|
|
||||||
vips_dbuf_destroy( &buf->dbuf );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cleanup. Copy the set of blocks out as a big lump. This is only called by
|
|
||||||
* libjpeg on successful write --- you must call buf_destroy() explicitly to
|
|
||||||
* release resources.
|
|
||||||
*/
|
|
||||||
METHODDEF(void)
|
|
||||||
term_destination( j_compress_ptr cinfo )
|
term_destination( j_compress_ptr cinfo )
|
||||||
{
|
{
|
||||||
OutputBuffer *buf = (OutputBuffer *) cinfo->dest;
|
Dest *dest = (Dest *) cinfo->dest;
|
||||||
|
|
||||||
size_t size;
|
if( vips_stream_output_write( dest->output,
|
||||||
|
dest->buf, STREAM_BUFFER_SIZE - dest->pub.free_in_buffer ) )
|
||||||
|
ERREXIT( cinfo, JERR_FILE_WRITE );
|
||||||
|
|
||||||
/* We probably won't have filled the area that was last allocated in
|
vips_stream_output_finish( dest->output );
|
||||||
* empty_output_buffer(). Chop the data size down to the length that
|
|
||||||
* was actually written.
|
|
||||||
*/
|
|
||||||
vips_dbuf_seek( &buf->dbuf, -buf->pub.free_in_buffer, SEEK_END );
|
|
||||||
vips_dbuf_truncate( &buf->dbuf );
|
|
||||||
|
|
||||||
*(buf->obuf) = vips_dbuf_steal( &buf->dbuf, &size );
|
|
||||||
*(buf->olen) = size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set dest to one of our objects.
|
/* Set dest to one of our objects.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
buf_dest( j_compress_ptr cinfo, void **obuf, size_t *olen )
|
stream_dest( j_compress_ptr cinfo, VipsStreamOutput *output )
|
||||||
{
|
{
|
||||||
OutputBuffer *buf;
|
Dest *dest;
|
||||||
|
|
||||||
/* The destination object is made permanent so that multiple JPEG
|
|
||||||
* images can be written to the same file without re-executing
|
|
||||||
* jpeg_stdio_dest. This makes it dangerous to use this manager and
|
|
||||||
* a different destination manager serially with the same JPEG object,
|
|
||||||
* because their private object sizes may be different.
|
|
||||||
*
|
|
||||||
* Caveat programmer.
|
|
||||||
*/
|
|
||||||
if( !cinfo->dest ) { /* first time for this JPEG object? */
|
if( !cinfo->dest ) { /* first time for this JPEG object? */
|
||||||
cinfo->dest = (struct jpeg_destination_mgr *)
|
cinfo->dest = (struct jpeg_destination_mgr *)
|
||||||
(*cinfo->mem->alloc_small)
|
(*cinfo->mem->alloc_small)
|
||||||
( (j_common_ptr) cinfo, JPOOL_PERMANENT,
|
( (j_common_ptr) cinfo, JPOOL_PERMANENT,
|
||||||
sizeof( OutputBuffer ) );
|
sizeof( Dest ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
buf = (OutputBuffer *) cinfo->dest;
|
dest = (Dest *) cinfo->dest;
|
||||||
buf->pub.init_destination = init_destination;
|
dest->pub.init_destination = init_destination;
|
||||||
buf->pub.empty_output_buffer = empty_output_buffer;
|
dest->pub.empty_output_buffer = empty_output_buffer;
|
||||||
buf->pub.term_destination = term_destination;
|
dest->pub.term_destination = term_destination;
|
||||||
|
dest->output = output;
|
||||||
/* Save output parameters.
|
|
||||||
*/
|
|
||||||
vips_dbuf_init( &buf->dbuf );
|
|
||||||
buf->obuf = obuf;
|
|
||||||
buf->olen = olen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips__jpeg_write_buffer( VipsImage *in,
|
vips__jpeg_write_stream( VipsImage *in, VipsStreamOutput *output,
|
||||||
void **obuf, size_t *olen, int Q, const char *profile,
|
int Q, const char *profile,
|
||||||
gboolean optimize_coding, gboolean progressive,
|
gboolean optimize_coding, gboolean progressive,
|
||||||
gboolean strip, gboolean no_subsample, gboolean trellis_quant,
|
gboolean strip, gboolean no_subsample, gboolean trellis_quant,
|
||||||
gboolean overshoot_deringing, gboolean optimize_scans, int quant_table )
|
gboolean overshoot_deringing, gboolean optimize_scans, int quant_table )
|
||||||
@ -859,17 +774,11 @@ vips__jpeg_write_buffer( VipsImage *in,
|
|||||||
if( !(write = write_new( in )) )
|
if( !(write = write_new( in )) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
/* Clear output parameters.
|
|
||||||
*/
|
|
||||||
*obuf = NULL;
|
|
||||||
*olen = 0;
|
|
||||||
|
|
||||||
/* Make jpeg compression object.
|
/* Make jpeg compression object.
|
||||||
*/
|
*/
|
||||||
if( setjmp( write->eman.jmp ) ) {
|
if( setjmp( write->eman.jmp ) ) {
|
||||||
/* Here for longjmp() during write_vips().
|
/* Here for longjmp() during write_vips().
|
||||||
*/
|
*/
|
||||||
buf_destroy( &write->cinfo );
|
|
||||||
write_destroy( write );
|
write_destroy( write );
|
||||||
|
|
||||||
return( -1 );
|
return( -1 );
|
||||||
@ -878,7 +787,7 @@ vips__jpeg_write_buffer( VipsImage *in,
|
|||||||
|
|
||||||
/* Attach output.
|
/* Attach output.
|
||||||
*/
|
*/
|
||||||
buf_dest( &write->cinfo, obuf, olen );
|
stream_dest( &write->cinfo, output );
|
||||||
|
|
||||||
/* Convert! Write errors come back here as an error return.
|
/* Convert! Write errors come back here as an error return.
|
||||||
*/
|
*/
|
||||||
@ -886,12 +795,9 @@ vips__jpeg_write_buffer( VipsImage *in,
|
|||||||
Q, profile, optimize_coding, progressive, strip, no_subsample,
|
Q, profile, optimize_coding, progressive, strip, no_subsample,
|
||||||
trellis_quant, overshoot_deringing, optimize_scans,
|
trellis_quant, overshoot_deringing, optimize_scans,
|
||||||
quant_table ) ) {
|
quant_table ) ) {
|
||||||
buf_destroy( &write->cinfo );
|
|
||||||
write_destroy( write );
|
write_destroy( write );
|
||||||
|
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
buf_destroy( &write->cinfo );
|
|
||||||
write_destroy( write );
|
write_destroy( write );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
|
@ -173,7 +173,7 @@ VipsStreamInput *vips_stream_input_new_from_memory( const void *data,
|
|||||||
size_t size );
|
size_t size );
|
||||||
|
|
||||||
ssize_t vips_stream_input_read( VipsStreamInput *input,
|
ssize_t vips_stream_input_read( VipsStreamInput *input,
|
||||||
unsigned char *buffer, size_t length );
|
unsigned char *data, size_t length );
|
||||||
int vips_stream_input_rewind( VipsStreamInput *input );
|
int vips_stream_input_rewind( VipsStreamInput *input );
|
||||||
void vips_stream_input_decode( VipsStreamInput *input );
|
void vips_stream_input_decode( VipsStreamInput *input );
|
||||||
gboolean vips_stream_input_eof( VipsStreamInput *input );
|
gboolean vips_stream_input_eof( VipsStreamInput *input );
|
||||||
@ -194,14 +194,18 @@ unsigned char *vips_stream_input_sniff( VipsStreamInput *input, size_t length );
|
|||||||
(G_TYPE_INSTANCE_GET_CLASS( (obj), \
|
(G_TYPE_INSTANCE_GET_CLASS( (obj), \
|
||||||
VIPS_TYPE_STREAM_OUTPUT, VipsStreamOutputClass ))
|
VIPS_TYPE_STREAM_OUTPUT, VipsStreamOutputClass ))
|
||||||
|
|
||||||
/* Read or output to something like a socket or pipe.
|
/* Output to something like a socket, pipe or memory area.
|
||||||
*/
|
*/
|
||||||
typedef struct _VipsStreamOutput {
|
typedef struct _VipsStreamOutput {
|
||||||
VipsStream parent_object;
|
VipsStream parent_object;
|
||||||
|
|
||||||
/*< private >*/
|
/*< private >*/
|
||||||
|
|
||||||
/* For memory output, the blob we write to.
|
/* Write memory output here.
|
||||||
|
*/
|
||||||
|
GByteArray *memory;
|
||||||
|
|
||||||
|
/* And return memory via this blob.
|
||||||
*/
|
*/
|
||||||
VipsBlob *blob;
|
VipsBlob *blob;
|
||||||
|
|
||||||
@ -220,8 +224,10 @@ GType vips_stream_output_get_type( void );
|
|||||||
|
|
||||||
VipsStreamOutput *vips_stream_output_new_from_descriptor( int descriptor );
|
VipsStreamOutput *vips_stream_output_new_from_descriptor( int descriptor );
|
||||||
VipsStreamOutput *vips_stream_output_new_from_filename( const char *filename );
|
VipsStreamOutput *vips_stream_output_new_from_filename( const char *filename );
|
||||||
|
VipsStreamOutput *vips_stream_output_new_memory( void );
|
||||||
int vips_stream_output_write( VipsStreamOutput *stream,
|
int vips_stream_output_write( VipsStreamOutput *stream,
|
||||||
const unsigned char *buffer, size_t buffer_size );
|
const unsigned char *data, size_t length );
|
||||||
|
void vips_stream_output_finish( VipsStreamOutput *output );
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,8 @@
|
|||||||
* - add seekable descriptors
|
* - add seekable descriptors
|
||||||
* - can we test for mmapable and seekable?
|
* - can we test for mmapable and seekable?
|
||||||
* - do we need eof?
|
* - do we need eof?
|
||||||
|
* - need a close() vfunc? output is a bit ugly, and node streams might need
|
||||||
|
* it
|
||||||
* - can we really change all behaviour in the subclass? will we need map and
|
* - can we really change all behaviour in the subclass? will we need map and
|
||||||
* seek as well as read and rewind?
|
* seek as well as read and rewind?
|
||||||
*/
|
*/
|
||||||
@ -111,6 +113,20 @@
|
|||||||
|
|
||||||
G_DEFINE_ABSTRACT_TYPE( VipsStream, vips_stream, VIPS_TYPE_OBJECT );
|
G_DEFINE_ABSTRACT_TYPE( VipsStream, vips_stream, VIPS_TYPE_OBJECT );
|
||||||
|
|
||||||
|
static void
|
||||||
|
vips_stream_close( VipsStream *stream )
|
||||||
|
{
|
||||||
|
if( stream->close_descriptor >= 0 ) {
|
||||||
|
close( stream->close_descriptor );
|
||||||
|
stream->close_descriptor = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( stream->tracked_descriptor >= 0 ) {
|
||||||
|
vips_tracked_close( stream->tracked_descriptor );
|
||||||
|
stream->tracked_descriptor = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
vips_stream_finalize( GObject *gobject )
|
vips_stream_finalize( GObject *gobject )
|
||||||
{
|
{
|
||||||
@ -122,16 +138,7 @@ vips_stream_finalize( GObject *gobject )
|
|||||||
VIPS_DEBUG_MSG( "\n" );
|
VIPS_DEBUG_MSG( "\n" );
|
||||||
#endif /*VIPS_DEBUG*/
|
#endif /*VIPS_DEBUG*/
|
||||||
|
|
||||||
if( stream->close_descriptor >= 0 ) {
|
vips_stream_close( stream );
|
||||||
close( stream->close_descriptor );
|
|
||||||
stream->close_descriptor = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( stream->tracked_descriptor >= 0 ) {
|
|
||||||
vips_tracked_close( stream->tracked_descriptor );
|
|
||||||
stream->tracked_descriptor = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
VIPS_FREE( stream->filename );
|
VIPS_FREE( stream->filename );
|
||||||
|
|
||||||
G_OBJECT_CLASS( vips_stream_parent_class )->finalize( gobject );
|
G_OBJECT_CLASS( vips_stream_parent_class )->finalize( gobject );
|
||||||
@ -222,8 +229,10 @@ vips_stream_input_build( VipsObject *object )
|
|||||||
input->rewindable = TRUE;
|
input->rewindable = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( vips_object_argument_isset( object, "descriptor" ) )
|
if( vips_object_argument_isset( object, "descriptor" ) ) {
|
||||||
|
stream->descriptor = dup( stream->descriptor );
|
||||||
stream->close_descriptor = stream->descriptor;
|
stream->close_descriptor = stream->descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
if( vips_object_argument_isset( object, "blob" ) )
|
if( vips_object_argument_isset( object, "blob" ) )
|
||||||
input->rewindable = TRUE;
|
input->rewindable = TRUE;
|
||||||
@ -242,7 +251,7 @@ vips_stream_input_build( VipsObject *object )
|
|||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
vips_stream_input_read_real( VipsStreamInput *input,
|
vips_stream_input_read_real( VipsStreamInput *input,
|
||||||
unsigned char *buffer, size_t length )
|
unsigned char *data, size_t length )
|
||||||
{
|
{
|
||||||
VipsStream *stream = VIPS_STREAM( input );
|
VipsStream *stream = VIPS_STREAM( input );
|
||||||
|
|
||||||
@ -256,12 +265,12 @@ vips_stream_input_read_real( VipsStreamInput *input,
|
|||||||
if( available <= 0 )
|
if( available <= 0 )
|
||||||
return( 0 );
|
return( 0 );
|
||||||
|
|
||||||
memcpy( buffer, area->data + input->read_position, available );
|
memcpy( data, area->data + input->read_position, available );
|
||||||
|
|
||||||
return( available );
|
return( available );
|
||||||
}
|
}
|
||||||
else if( stream->descriptor != -1 ) {
|
else if( stream->descriptor != -1 ) {
|
||||||
return( read( stream->descriptor, buffer, length ) );
|
return( read( stream->descriptor, data, length ) );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
g_assert( 0 );
|
g_assert( 0 );
|
||||||
@ -594,6 +603,18 @@ vips_stream_input_sniff( VipsStreamInput *input, size_t length )
|
|||||||
|
|
||||||
G_DEFINE_TYPE( VipsStreamOutput, vips_stream_output, VIPS_TYPE_STREAM );
|
G_DEFINE_TYPE( VipsStreamOutput, vips_stream_output, VIPS_TYPE_STREAM );
|
||||||
|
|
||||||
|
static void
|
||||||
|
vips_stream_output_finalize( GObject *gobject )
|
||||||
|
{
|
||||||
|
VipsStreamOutput *output = VIPS_STREAM_OUTPUT( gobject );
|
||||||
|
|
||||||
|
VIPS_DEBUG_MSG( "vips_stream_output_finalize:\n" );
|
||||||
|
|
||||||
|
VIPS_FREEF( g_byte_array_unref, output->memory );
|
||||||
|
|
||||||
|
G_OBJECT_CLASS( vips_stream_output_parent_class )->finalize( gobject );
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
vips_stream_output_build( VipsObject *object )
|
vips_stream_output_build( VipsObject *object )
|
||||||
{
|
{
|
||||||
@ -627,20 +648,44 @@ vips_stream_output_build( VipsObject *object )
|
|||||||
stream->tracked_descriptor = fd;
|
stream->tracked_descriptor = fd;
|
||||||
stream->descriptor = fd;
|
stream->descriptor = fd;
|
||||||
}
|
}
|
||||||
|
else if( vips_object_argument_isset( object, "descriptor" ) ) {
|
||||||
if( vips_object_argument_isset( object, "descriptor" ) )
|
stream->descriptor = dup( stream->descriptor );
|
||||||
stream->close_descriptor = stream->descriptor;
|
stream->close_descriptor = stream->descriptor;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
output->memory = g_byte_array_new();
|
||||||
|
}
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
vips_stream_output_write_real( VipsStreamOutput *output,
|
||||||
|
const unsigned char *data, size_t length )
|
||||||
|
{
|
||||||
|
VipsStream *stream = VIPS_STREAM( output );
|
||||||
|
|
||||||
|
ssize_t len;
|
||||||
|
|
||||||
|
VIPS_DEBUG_MSG( "vips_stream_output_write_real: %zd bytes\n", length );
|
||||||
|
|
||||||
|
if( output->memory ) {
|
||||||
|
g_byte_array_append( output->memory, data, length );
|
||||||
|
len = length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
len = write( stream->descriptor, data, length );
|
||||||
|
|
||||||
|
return( len );
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
vips_stream_output_class_init( VipsStreamOutputClass *class )
|
vips_stream_output_class_init( VipsStreamOutputClass *class )
|
||||||
{
|
{
|
||||||
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
||||||
VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class );
|
VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class );
|
||||||
|
|
||||||
gobject_class->finalize = vips_stream_input_finalize;
|
gobject_class->finalize = vips_stream_output_finalize;
|
||||||
gobject_class->set_property = vips_object_set_property;
|
gobject_class->set_property = vips_object_set_property;
|
||||||
gobject_class->get_property = vips_object_get_property;
|
gobject_class->get_property = vips_object_get_property;
|
||||||
|
|
||||||
@ -649,6 +694,8 @@ vips_stream_output_class_init( VipsStreamOutputClass *class )
|
|||||||
|
|
||||||
object_class->build = vips_stream_output_build;
|
object_class->build = vips_stream_output_build;
|
||||||
|
|
||||||
|
class->write = vips_stream_output_write_real;
|
||||||
|
|
||||||
VIPS_ARG_BOXED( class, "blob", 1,
|
VIPS_ARG_BOXED( class, "blob", 1,
|
||||||
_( "Blob" ),
|
_( "Blob" ),
|
||||||
_( "Blob to save to" ),
|
_( "Blob to save to" ),
|
||||||
@ -728,41 +775,86 @@ vips_stream_output_new_from_filename( const char *filename )
|
|||||||
return( stream );
|
return( stream );
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
/**
|
||||||
vips_stream_output_write( VipsStreamOutput *stream,
|
* vips_stream_output_new_memory:
|
||||||
const unsigned char *buffer, size_t buffer_size )
|
*
|
||||||
|
* Optional args:
|
||||||
|
*
|
||||||
|
* @blob: #VipsBlob, memory area containing output
|
||||||
|
*
|
||||||
|
* Create a stream which will output to a memory area. Use @blob to get
|
||||||
|
* memory output, if this is a memory stream.
|
||||||
|
*
|
||||||
|
* See also: vips_stream_output_write().
|
||||||
|
*
|
||||||
|
* Returns: a new #VipsStream
|
||||||
|
*/
|
||||||
|
VipsStreamOutput *
|
||||||
|
vips_stream_output_new_memory( void )
|
||||||
{
|
{
|
||||||
VipsStreamOutputClass *class = VIPS_STREAM_OUTPUT_GET_CLASS( stream );
|
VipsStreamOutput *stream;
|
||||||
|
|
||||||
while( buffer_size > 0 ) {
|
VIPS_DEBUG_MSG( "vips_stream_output_new_memory:\n" );
|
||||||
ssize_t len;
|
|
||||||
|
|
||||||
if( class->write )
|
stream = VIPS_STREAM_OUTPUT(
|
||||||
len = class->write( stream, buffer, buffer_size );
|
g_object_new( VIPS_TYPE_STREAM_OUTPUT,
|
||||||
else
|
NULL ) );
|
||||||
len = write( VIPS_STREAM( stream )->descriptor,
|
|
||||||
buffer, buffer_size );
|
|
||||||
|
|
||||||
#ifdef VIPS_DEBUG
|
if( vips_object_build( VIPS_OBJECT( stream ) ) ) {
|
||||||
if( len > 0 )
|
VIPS_UNREF( stream );
|
||||||
VIPS_DEBUG_MSG( "vips_stream_output_write: "
|
return( NULL );
|
||||||
"written %zd bytes\n", len );
|
}
|
||||||
#endif /*VIPS_DEBUG*/
|
|
||||||
|
|
||||||
/* len == 0 isn't strictly an error, but we treat it as one to
|
return( stream );
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_stream_output_write( VipsStreamOutput *output,
|
||||||
|
const unsigned char *data, size_t length )
|
||||||
|
{
|
||||||
|
VipsStreamOutputClass *class = VIPS_STREAM_OUTPUT_GET_CLASS( output );
|
||||||
|
|
||||||
|
VIPS_DEBUG_MSG( "vips_stream_output_write: %zd bytes\n", length );
|
||||||
|
|
||||||
|
while( length > 0 ) {
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
|
n = class->write( output, data, length );
|
||||||
|
|
||||||
|
/* n == 0 isn't strictly an error, but we treat it as one to
|
||||||
* make sure we don't get stuck in this loop.
|
* make sure we don't get stuck in this loop.
|
||||||
*/
|
*/
|
||||||
if( len <= 0 ) {
|
if( n <= 0 ) {
|
||||||
vips_error_system( errno,
|
vips_error_system( errno, STREAM_NAME( output ),
|
||||||
VIPS_OBJECT( stream )->nickname,
|
|
||||||
"%s", _( "write error" ) );
|
"%s", _( "write error" ) );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_size -= len;
|
length -= n;
|
||||||
buffer += len;
|
data += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_stream_output_finish( VipsStreamOutput *output )
|
||||||
|
{
|
||||||
|
VIPS_DEBUG_MSG( "vips_stream_output_finish:\n" );
|
||||||
|
|
||||||
|
if( output->memory ) {
|
||||||
|
unsigned char *data;
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
length = output->memory->len;
|
||||||
|
data = g_byte_array_free( output->memory, FALSE );
|
||||||
|
output->memory = NULL;
|
||||||
|
|
||||||
|
g_object_set( output,
|
||||||
|
"blob", vips_blob_new( (VipsCallbackFn) g_free,
|
||||||
|
data, length ),
|
||||||
|
NULL );
|
||||||
|
}
|
||||||
|
|
||||||
|
vips_stream_close( VIPS_STREAM( output ) );
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user