diff --git a/ChangeLog b/ChangeLog index 6bddbf03..fab3bd3a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,7 @@ master - magick load sets "magick-format" metadata [aksdb] - add ".pnm", save as image format [ewelot] - threaded tiff jp2k and jpeg decompress +- improve speed and efficiency of animated WebP write [dloebl] 5/9/22 started 8.13.2 - in dzsave, add add missing include directive for errno/EEXIST [kleisauke] diff --git a/cplusplus/include/vips/VImage8.h b/cplusplus/include/vips/VImage8.h index 691e9670..64ec9a36 100644 --- a/cplusplus/include/vips/VImage8.h +++ b/cplusplus/include/vips/VImage8.h @@ -5960,6 +5960,31 @@ VipsBlob *webpsave_buffer( VOption *options = 0 ) const; */ void webpsave_target( VTarget target, VOption *options = 0 ) const; +/** + * Save image to webp mime. + * + * **Optional parameters** + * - **Q** -- Q factor, int. + * - **lossless** -- Enable lossless compression, bool. + * - **preset** -- Preset for lossy compression, VipsForeignWebpPreset. + * - **smart_subsample** -- Enable high quality chroma subsampling, bool. + * - **near_lossless** -- Enable preprocessing in lossless mode (uses Q), bool. + * - **alpha_q** -- Change alpha plane fidelity for lossy compression, int. + * - **min_size** -- Optimise for minimum size, bool. + * - **kmin** -- Minimum number of frames between key frames, int. + * - **kmax** -- Maximum number of frames between key frames, int. + * - **effort** -- Level of CPU effort to reduce file size, int. + * - **profile** -- ICC profile to embed, const char *. + * - **mixed** -- Allow mixed encoding (might reduce file size), bool. + * - **strip** -- Strip all metadata from image, bool. + * - **background** -- Background value, std::vector. + * - **page_height** -- Set page height for multipage save, int. + * + * @param target Target to save to. + * @param options Set of options. + */ +void webpsave_mime( VOption *options = 0 ) const; + /** * Make a worley noise image. * diff --git a/cplusplus/vips-operators.cpp b/cplusplus/vips-operators.cpp index 9360bd89..33420364 100644 --- a/cplusplus/vips-operators.cpp +++ b/cplusplus/vips-operators.cpp @@ -3687,6 +3687,13 @@ void VImage::webpsave_target( VTarget target, VOption *options ) const set( "target", target ) ); } +void VImage::webpsave_mime( VOption *options ) const +{ + call( "webpsave_mime", + (options ? options : VImage::option())-> + set( "in", *this ) ); +} + VImage VImage::worley( int width, int height, VOption *options ) { VImage out; diff --git a/doc/function-list.xml b/doc/function-list.xml index b395cdb3..665b4b53 100644 --- a/doc/function-list.xml +++ b/doc/function-list.xml @@ -1581,6 +1581,11 @@ Save image to webp target vips_webpsave_target() + + webpsave_mime + Save image to webp mime + vips_webpsave_mime() + worley Make a worley noise image diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 31b788f1..3e11d0a5 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2915,6 +2915,7 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_save_webp_file_get_type( void ); extern GType vips_foreign_save_webp_buffer_get_type( void ); extern GType vips_foreign_save_webp_target_get_type( void ); + extern GType vips_foreign_save_webp_mime_get_type( void ); extern GType vips_foreign_load_pdf_file_get_type( void ); extern GType vips_foreign_load_pdf_buffer_get_type( void ); @@ -3094,6 +3095,7 @@ vips_foreign_operation_init( void ) vips_foreign_save_webp_file_get_type(); vips_foreign_save_webp_buffer_get_type(); vips_foreign_save_webp_target_get_type(); + vips_foreign_save_webp_mime_get_type(); #endif /*HAVE_LIBWEBP*/ #ifdef HAVE_TIFF diff --git a/libvips/foreign/meson.build b/libvips/foreign/meson.build index a72f9c78..71155f61 100644 --- a/libvips/foreign/meson.build +++ b/libvips/foreign/meson.build @@ -51,7 +51,6 @@ foreign_sources = files( 'tiffsave.c', 'vips2jpeg.c', 'vips2tiff.c', - 'vips2webp.c', 'vipsload.c', 'vipspng.c', 'vipssave.c', diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 162c5bb5..3182b35b 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -190,13 +190,6 @@ int vips__webp_read_header_source( VipsSource *source, VipsImage *out, int vips__webp_read_source( VipsSource *source, VipsImage *out, int page, int n, double scale ); -int vips__webp_write_target( VipsImage *image, VipsTarget *target, - int Q, gboolean lossless, VipsForeignWebpPreset preset, - gboolean smart_subsample, gboolean near_lossless, - int alpha_q, int effort, - gboolean min_size, gboolean mixed, int kmin, int kmax, - gboolean strip, const char *profile ); - extern const char *vips_foreign_nifti_suffs[]; VipsBandFormat vips__foreign_nifti_datatype2BandFmt( int datatype ); diff --git a/libvips/foreign/vips2webp.c b/libvips/foreign/vips2webp.c deleted file mode 100644 index 8ec2c6f1..00000000 --- a/libvips/foreign/vips2webp.c +++ /dev/null @@ -1,595 +0,0 @@ -/* wrap libwebp libray for write - * - * 6/8/13 - * - from vips2jpeg.c - * 31/5/16 - * - buffer write ignored lossless, thanks aaron42net - * 2/5/16 Felix Bünemann - * - used advanced encoding API, expose controls - * 8/11/16 - * - add metadata write - * 29/10/18 - * - target libwebp 0.5+ and remove some ifdefs - * - add animated webp write - * - use libwebpmux instead of our own thing, phew - * 6/7/19 [deftomat] - * - support array of delays - * 8/7/19 - * - set loop even if we strip - * 14/10/19 - * - revise for target IO - * 18/7/20 - * - add @profile param to match tiff, jpg, etc. - */ - -/* - - 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 - - */ - -/* -#define DEBUG -#define VIPS_DEBUG - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /*HAVE_CONFIG_H*/ -#include - -#ifdef HAVE_LIBWEBP - -#include -#include - -#include -#include - -#include "pforeign.h" - -#include -#include -#include - -typedef int (*webp_import)( WebPPicture *picture, - const uint8_t *rgb, int stride ); - -typedef struct { - VipsImage *image; - - int Q; - gboolean lossless; - VipsForeignWebpPreset preset; - gboolean smart_subsample; - gboolean near_lossless; - int alpha_q; - int effort; - gboolean min_size; - gboolean mixed; - int kmin; - int kmax; - gboolean strip; - const char *profile; - - WebPConfig config; - - /* Output is written here. We can only support memory write, since we - * handle metadata. - */ - WebPMemoryWriter memory_writer; - - /* Write animated webp here. - */ - WebPAnimEncoder *enc; - - /* Add metadata with this. - */ - WebPMux *mux; -} VipsWebPWrite; - -static WebPPreset -get_preset( VipsForeignWebpPreset preset ) -{ - switch( preset ) { - case VIPS_FOREIGN_WEBP_PRESET_DEFAULT: - return( WEBP_PRESET_DEFAULT ); - case VIPS_FOREIGN_WEBP_PRESET_PICTURE: - return( WEBP_PRESET_PICTURE ); - case VIPS_FOREIGN_WEBP_PRESET_PHOTO: - return( WEBP_PRESET_PHOTO ); - case VIPS_FOREIGN_WEBP_PRESET_DRAWING: - return( WEBP_PRESET_DRAWING ); - case VIPS_FOREIGN_WEBP_PRESET_ICON: - return( WEBP_PRESET_ICON ); - case VIPS_FOREIGN_WEBP_PRESET_TEXT: - return( WEBP_PRESET_TEXT ); - - default: - g_assert_not_reached(); - } - - /* Keep -Wall happy. - */ - return( -1 ); -} - -static void -vips_webp_write_unset( VipsWebPWrite *write ) -{ - WebPMemoryWriterClear( &write->memory_writer ); - VIPS_FREEF( WebPAnimEncoderDelete, write->enc ); - VIPS_FREEF( WebPMuxDelete, write->mux ); - VIPS_UNREF( write->image ); -} - -static int -vips_webp_write_init( VipsWebPWrite *write, VipsImage *image, - int Q, gboolean lossless, VipsForeignWebpPreset preset, - gboolean smart_subsample, gboolean near_lossless, - int alpha_q, int effort, - gboolean min_size, gboolean mixed, int kmin, int kmax, - gboolean strip, const char *profile ) -{ - write->image = NULL; - write->Q = Q; - write->lossless = lossless; - write->preset = preset; - write->smart_subsample = smart_subsample; - write->near_lossless = near_lossless; - write->alpha_q = alpha_q; - write->effort = effort; - write->min_size = min_size; - write->mixed = mixed; - write->kmin = kmin; - write->kmax = kmax; - write->strip = strip; - write->profile = profile; - WebPMemoryWriterInit( &write->memory_writer ); - write->enc = NULL; - write->mux = NULL; - - /* We need a copy of the input image in case we change the metadata - * eg. in vips__exif_update(). - */ - if( vips_copy( image, &write->image, NULL ) ) { - vips_webp_write_unset( write ); - return( -1 ); - } - - if( !WebPConfigInit( &write->config ) ) { - vips_webp_write_unset( write ); - vips_error( "vips2webp", - "%s", _( "config version error" ) ); - return( -1 ); - } - - /* These presets are only for lossy compression. There seems to be - * separate API for lossless or near-lossless, see - * WebPConfigLosslessPreset(). - */ - if( !(lossless || near_lossless) && - !WebPConfigPreset( &write->config, get_preset( preset ), Q ) ) { - vips_webp_write_unset( write ); - vips_error( "vips2webp", "%s", _( "config version error" ) ); - return( -1 ); - } - - write->config.lossless = lossless || near_lossless; - write->config.alpha_quality = alpha_q; - write->config.method = effort; - - if( lossless ) - write->config.quality = Q; - if( near_lossless ) - write->config.near_lossless = Q; - if( smart_subsample ) - write->config.use_sharp_yuv = 1; - - if( !WebPValidateConfig( &write->config ) ) { - vips_webp_write_unset( write ); - vips_error( "vips2webp", "%s", _( "invalid configuration" ) ); - return( -1 ); - } - - return( 0 ); -} - -static gboolean -vips_webp_pic_init( VipsWebPWrite *write, WebPPicture *pic ) -{ - if( !WebPPictureInit( pic ) ) { - vips_error( "vips2webp", "%s", _( "picture version error" ) ); - return( FALSE ); - } - pic->writer = WebPMemoryWrite; - pic->custom_ptr = (void *) &write->memory_writer; - - /* Smart subsampling needs use_argb because it is applied during - * RGB to YUV conversion. - */ - pic->use_argb = write->lossless || - write->near_lossless || - write->smart_subsample; - - return( TRUE ); -} - -/* Write a VipsImage into an unintialised pic. - */ -static int -write_webp_image( VipsWebPWrite *write, VipsImage *image, WebPPicture *pic ) -{ - VipsImage *memory; - webp_import import; - - if( !vips_webp_pic_init( write, pic ) ) - return( -1 ); - - if( !(memory = vips_image_copy_memory( image )) ) { - WebPPictureFree( pic ); - return( -1 ); - } - - pic->width = memory->Xsize; - pic->height = memory->Ysize; - - if( memory->Bands == 4 ) - import = WebPPictureImportRGBA; - else - import = WebPPictureImportRGB; - - if( !import( pic, VIPS_IMAGE_ADDR( memory, 0, 0 ), - VIPS_IMAGE_SIZEOF_LINE( memory ) ) ) { - VIPS_UNREF( memory ); - WebPPictureFree( pic ); - vips_error( "vips2webp", "%s", _( "picture memory error" ) ); - return( -1 ); - } - - VIPS_UNREF( memory ); - - return( 0 ); -} - -/* Write a single image into write->memory_writer. - */ -static int -write_webp_single( VipsWebPWrite *write, VipsImage *image ) -{ - WebPPicture pic; - - if( write_webp_image( write, image, &pic ) ) { - WebPPictureFree( &pic ); - return( -1 ); - } - - if( !WebPEncode( &write->config, &pic ) ) { - WebPPictureFree( &pic ); - vips_error( "vips2webp", "%s", _( "unable to encode" ) ); - return( -1 ); - } - - WebPPictureFree( &pic ); - - return( 0 ); -} - -/* Write a set of animated frames into write->memory_writer. - */ -static int -write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) -{ - WebPAnimEncoderOptions anim_config; - WebPData webp_data; - int gif_delay; - int *delay; - int delay_length; - int top; - int timestamp_ms; - - if( !WebPAnimEncoderOptionsInit( &anim_config ) ) { - vips_error( "vips2webp", - "%s", _( "config version error" ) ); - return( -1 ); - } - - anim_config.minimize_size = write->min_size; - anim_config.allow_mixed = write->mixed; - anim_config.kmin = write->kmin; - anim_config.kmax = write->kmax; - - write->enc = WebPAnimEncoderNew( image->Xsize, page_height, - &anim_config ); - if( !write->enc ) { - vips_error( "vips2webp", - "%s", _( "unable to init animation" ) ); - return( -1 ); - } - - /* There might just be the old gif-delay field. This is centiseconds. - */ - gif_delay = 10; - if( vips_image_get_typeof( image, "gif-delay" ) && - vips_image_get_int( image, "gif-delay", &gif_delay ) ) - return( -1 ); - - /* Force frames with a small or no duration to 100ms - * to be consistent with web browsers and other - * transcoding tools. - */ - if( gif_delay <= 1 ) - gif_delay = 10; - - /* New images have an array of ints instead. - */ - delay = NULL; - if( vips_image_get_typeof( image, "delay" ) && - vips_image_get_array_int( image, "delay", - &delay, &delay_length ) ) - return( -1 ); - - timestamp_ms = 0; - for( top = 0; top < image->Ysize; top += page_height ) { - VipsImage *x; - WebPPicture pic; - int page_index; - - if( vips_crop( image, &x, - 0, top, image->Xsize, page_height, NULL ) ) - return( -1 ); - - if( write_webp_image( write, x, &pic ) ) { - VIPS_UNREF( x ); - return( -1 ); - } - - VIPS_UNREF( x ); - - if( !WebPAnimEncoderAdd( write->enc, - &pic, timestamp_ms, &write->config ) ) { - WebPPictureFree( &pic ); - vips_error( "vips2webp", - "%s", _( "anim add error" ) ); - return( -1 ); - } - - WebPPictureFree( &pic ); - - page_index = top / page_height; - if( delay && - page_index < delay_length ) - timestamp_ms += delay[page_index] <= 10 ? - 100 : delay[page_index]; - else - timestamp_ms += gif_delay * 10; - } - - /* Closes encoder and adds last frame delay. - */ - if( !WebPAnimEncoderAdd( write->enc, - NULL, timestamp_ms, NULL ) ) { - vips_error( "vips2webp", - "%s", _( "anim close error" ) ); - return( -1 ); - } - - if( !WebPAnimEncoderAssemble( write->enc, &webp_data ) ) { - vips_error( "vips2webp", - "%s", _( "anim build error" ) ); - return( -1 ); - } - - /* Terrible. This will only work if the output buffer is currently - * empty. - */ - if( write->memory_writer.mem != NULL ) { - vips_error( "vips2webp", "%s", _( "internal error" ) ); - return( -1 ); - } - write->memory_writer.mem = (uint8_t *) webp_data.bytes; - write->memory_writer.size = webp_data.size; - - return( 0 ); -} - -static int -write_webp( VipsWebPWrite *write ) -{ - int page_height = vips_image_get_page_height( write->image ); - - if( page_height < write->image->Ysize ) - return( write_webp_anim( write, write->image, page_height ) ); - else - return( write_webp_single( write, write->image ) ); -} - -static void -vips_webp_set_count( VipsWebPWrite *write, int loop_count ) -{ - uint32_t features; - - if( WebPMuxGetFeatures( write->mux, &features ) == WEBP_MUX_OK && - (features & ANIMATION_FLAG) ) { - WebPMuxAnimParams params; - - if( WebPMuxGetAnimationParams( write->mux, ¶ms ) == - WEBP_MUX_OK ) { - params.loop_count = loop_count; - WebPMuxSetAnimationParams( write->mux, ¶ms ); - } - } -} - -static int -vips_webp_set_chunk( VipsWebPWrite *write, - const char *webp_name, const void *data, size_t length ) -{ - WebPData chunk; - - chunk.bytes = data; - chunk.size = length; - - if( WebPMuxSetChunk( write->mux, webp_name, &chunk, 1 ) != - WEBP_MUX_OK ) { - vips_error( "vips2webp", - "%s", _( "chunk add error" ) ); - return( -1 ); - } - - return( 0 ); -} - -static int -vips_webp_add_chunks( VipsWebPWrite *write ) -{ - int i; - - for( i = 0; i < vips__n_webp_names; i++ ) { - const char *vips_name = vips__webp_names[i].vips; - const char *webp_name = vips__webp_names[i].webp; - - if( vips_image_get_typeof( write->image, vips_name ) ) { - const void *data; - size_t length; - - if( vips_image_get_blob( write->image, - vips_name, &data, &length ) || - vips_webp_set_chunk( write, - webp_name, data, length ) ) - return( -1 ); - } - } - - return( 0 ); -} - -static int -vips_webp_add_metadata( VipsWebPWrite *write ) -{ - WebPData data; - - data.bytes = write->memory_writer.mem; - data.size = write->memory_writer.size; - - /* Parse what we have. - */ - if( !(write->mux = WebPMuxCreate( &data, 1 )) ) { - vips_error( "vips2webp", "%s", _( "mux error" ) ); - return( -1 ); - } - - if( vips_image_get_typeof( write->image, "loop" ) ) { - int loop; - - if( vips_image_get_int( write->image, "loop", &loop ) ) - return( -1 ); - - vips_webp_set_count( write, loop ); - } - /* DEPRECATED "gif-loop" - */ - else if ( vips_image_get_typeof( write->image, "gif-loop" ) ) { - int gif_loop; - - if( vips_image_get_int( write->image, "gif-loop", &gif_loop ) ) - return( -1 ); - - vips_webp_set_count( write, gif_loop == 0 ? 0 : gif_loop + 1 ); - } - - /* Add extra metadata. - */ - if( !write->strip ) { - /* We need to rebuild exif from the other image tags before - * writing the metadata. - */ - if( vips__exif_update( write->image ) ) - return( -1 ); - - /* Override profile. - */ - if( write->profile && - vips__profile_set( write->image, write->profile ) ) - return( -1 ); - - if( vips_webp_add_chunks( write ) ) - return( -1 ); - } - - if( WebPMuxAssemble( write->mux, &data ) != WEBP_MUX_OK ) { - vips_error( "vips2webp", "%s", _( "mux error" ) ); - return( -1 ); - } - - /* Free old stuff, reinit with new stuff. - */ - WebPMemoryWriterClear( &write->memory_writer ); - write->memory_writer.mem = (uint8_t *) data.bytes; - write->memory_writer.size = data.size; - - return( 0 ); -} - -int -vips__webp_write_target( VipsImage *image, VipsTarget *target, - int Q, gboolean lossless, VipsForeignWebpPreset preset, - gboolean smart_subsample, gboolean near_lossless, - int alpha_q, int effort, - gboolean min_size, gboolean mixed, int kmin, int kmax, - gboolean strip, const char *profile ) -{ - VipsWebPWrite write; - - if( vips_webp_write_init( &write, image, - Q, lossless, preset, smart_subsample, near_lossless, - alpha_q, effort, min_size, mixed, kmin, kmax, strip, - profile ) ) - return( -1 ); - - if( write_webp( &write ) ) { - vips_webp_write_unset( &write ); - return( -1 ); - } - - if( vips_webp_add_metadata( &write ) ) { - vips_webp_write_unset( &write ); - return( -1 ); - } - - if( vips_target_write( target, - write.memory_writer.mem, write.memory_writer.size ) ) { - vips_webp_write_unset( &write ); - return( -1 ); - } - - if( vips_target_end( target ) ) - return( -1 ); - - vips_webp_write_unset( &write ); - - return( 0 ); -} - -#endif /*HAVE_LIBWEBP*/ diff --git a/libvips/foreign/webpsave.c b/libvips/foreign/webpsave.c index 13d60ddd..5a8e778b 100644 --- a/libvips/foreign/webpsave.c +++ b/libvips/foreign/webpsave.c @@ -2,20 +2,42 @@ * * 24/11/11 * - wrap a class around the webp writer - * 29/10/18 + * 6/8/13 + * - from vips2jpeg.c + * 31/5/16 + * - buffer write ignored lossless, thanks aaron42net + * 2/5/16 Felix Bünemann + * - used advanced encoding API, expose controls + * 8/11/16 + * - add metadata write + * 29/10/18 * - add animated webp support + * 29/10/18 + * - target libwebp 0.5+ and remove some ifdefs + * - add animated webp write + * - use libwebpmux instead of our own thing, phew * 15/1/19 lovell - * - add @effort + * - add @effort + * 6/7/19 [deftomat] + * - support array of delays + * 8/7/19 + * - set loop even if we strip + * 14/10/19 + * - revise for target IO + * 18/7/20 + * - add @profile param to match tiff, jpg, etc. * 18/7/20 * - add @profile param to match tiff, jpg, etc. * 30/7/21 * - rename "reduction_effort" as "effort" + * 7/9/22 dloebl + * - switch to sink_disc */ /* 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 @@ -49,16 +71,41 @@ #endif /*HAVE_CONFIG_H*/ #include +#ifdef HAVE_LIBWEBP + +#include #include +#include #include +#include #include "pforeign.h" -#ifdef HAVE_LIBWEBP +#include +#include +#include + +typedef int (*webp_import)( WebPPicture *picture, + const uint8_t *rgb, int stride ); + +typedef enum _VipsForeignSaveWebpMode { + VIPS_FOREIGN_SAVE_WEBP_MODE_SINGLE, + VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM +} VipsForeignSaveWebpMode; typedef struct _VipsForeignSaveWebp { VipsForeignSave parent_object; + VipsTarget *target; + + /* Animated or single image write mode? + * Important, because we use a different API + * for animated WebP write. + */ + VipsForeignSaveWebpMode mode; + VipsImage *image; + + int timestamp_ms; /* Quality factor. */ @@ -91,6 +138,11 @@ typedef struct _VipsForeignSaveWebp { /* Animated webp options. */ + int gif_delay; + int *delay; + int delay_length; + gboolean strip; + /* Attempt to minimise size */ gboolean min_size; @@ -109,15 +161,555 @@ typedef struct _VipsForeignSaveWebp { /* Profile to embed. */ - char *profile; + const char *profile; + WebPConfig config; + + /* Output is written here. We can only support memory write, since we + * handle metadata. + */ + WebPMemoryWriter memory_writer; + + /* Write animated webp here. + */ + WebPAnimEncoder *enc; + + /* Add metadata with this. + */ + WebPMux *mux; + + /* The current y position in the frame and the current page index. + */ + int write_y; + int page_number; + + /* VipsRegion is not always contiguous, but we need contiguous RGB(A) + * for libwebp. We need to copy each frame to a local buffer. + */ + VipsPel *frame_bytes; } VipsForeignSaveWebp; typedef VipsForeignSaveClass VipsForeignSaveWebpClass; -G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveWebp, vips_foreign_save_webp, +G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveWebp, vips_foreign_save_webp, VIPS_TYPE_FOREIGN_SAVE ); +static void +vips_foreign_save_webp_unset( VipsForeignSaveWebp *write ) +{ + WebPMemoryWriterClear( &write->memory_writer ); + VIPS_FREEF( WebPAnimEncoderDelete, write->enc ); + VIPS_FREEF( WebPMuxDelete, write->mux ); + VIPS_UNREF( write->image ); +} + +static void +vips_foreign_save_webp_dispose( GObject *gobject ) +{ + VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) gobject; + + VIPS_UNREF( webp->target ); + + VIPS_FREE( webp->frame_bytes ); + + G_OBJECT_CLASS( vips_foreign_save_webp_parent_class )-> + dispose( gobject ); +} + +static gboolean +vips_foreign_save_webp_pic_init( VipsForeignSaveWebp *write, WebPPicture *pic ) +{ + if( !WebPPictureInit( pic ) ) { + vips_error( "webpsave", "%s", _( "picture version error" ) ); + return( FALSE ); + } + pic->writer = WebPMemoryWrite; + pic->custom_ptr = (void *) &write->memory_writer; + + /* Smart subsampling needs use_argb because it is applied during + * RGB to YUV conversion. + */ + pic->use_argb = write->lossless || + write->near_lossless || + write->smart_subsample; + + return( TRUE ); +} + +/* Write a VipsImage into an unintialised pic. + */ +static int +vips_foreign_save_webp_write_webp_image( VipsForeignSaveWebp *write, + const VipsPel *imagedata, WebPPicture *pic ) +{ + webp_import import; + int page_height = vips_image_get_page_height( write->image ); + + if( !vips_foreign_save_webp_pic_init( write, pic ) ) + return( -1 ); + + pic->width = write->image->Xsize; + pic->height = page_height; + + if( write->image->Bands == 4 ) + import = WebPPictureImportRGBA; + else + import = WebPPictureImportRGB; + + if( !import( pic, imagedata, + write->image->Xsize * write->image->Bands ) ) { + WebPPictureFree( pic ); + vips_error( "webpsave", "%s", _( "picture memory error" ) ); + return( -1 ); + } + + return( 0 ); +} + +/* We have a complete frame --- write! + */ +static int +vips_foreign_save_webp_write_frame( VipsForeignSaveWebp *webp) +{ + WebPPicture pic; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( webp ); + + if( vips_foreign_save_webp_write_webp_image( webp, webp->frame_bytes, + &pic ) ) + return( -1 ); + + /* Animated write + */ + if( webp->mode == VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM ) { + if( !WebPAnimEncoderAdd( webp->enc, + &pic, webp->timestamp_ms, &webp->config ) ) { + WebPPictureFree( &pic ); + vips_error( class->nickname, + "%s", _( "anim add error" ) ); + return( -1 ); + } + /* Adjust current timestamp + */ + if( webp->delay && + webp->page_number < webp->delay_length ) + webp->timestamp_ms += webp->delay[webp->page_number]; + else + webp->timestamp_ms += webp->gif_delay * 10; + + } + else { + /* Single image write + */ + if( !WebPEncode( &webp->config, &pic ) ) { + WebPPictureFree( &pic ); + vips_error( "webpsave", "%s", + _( "unable to encode" ) ); + return( -1 ); + } + } + + WebPPictureFree( &pic ); + + return( 0 ); +} + +/* Another chunk of pixels have arrived from the pipeline. Add to frame, and + * if the frame completes, compress and write to the target. + */ +static int +vips_foreign_save_webp_sink_disc( VipsRegion *region, VipsRect *area, void *a ) +{ + VipsForeignSaveWebp *webp = (VipsForeignSaveWebp*) a; + int i; + int page_height = vips_image_get_page_height( webp->image ); + + /* Write the new pixels into the frame. + */ + for( i = 0; i < area->height; i++ ) { + memcpy( webp->frame_bytes + area->width * + webp->write_y * webp->image->Bands, + VIPS_REGION_ADDR( region, 0, area->top + i ), + area->width * webp->image->Bands ); + + webp->write_y += 1; + + /* If we've filled the frame, write and move it down. + */ + if( webp->write_y == page_height ) { + if( vips_foreign_save_webp_write_frame( webp ) ) + return( -1 ); + + webp->write_y = 0; + webp->page_number += 1; + } + } + + return( 0 ); +} + +static WebPPreset +get_preset( VipsForeignWebpPreset preset ) +{ + switch( preset ) { + case VIPS_FOREIGN_WEBP_PRESET_DEFAULT: + return( WEBP_PRESET_DEFAULT ); + case VIPS_FOREIGN_WEBP_PRESET_PICTURE: + return( WEBP_PRESET_PICTURE ); + case VIPS_FOREIGN_WEBP_PRESET_PHOTO: + return( WEBP_PRESET_PHOTO ); + case VIPS_FOREIGN_WEBP_PRESET_DRAWING: + return( WEBP_PRESET_DRAWING ); + case VIPS_FOREIGN_WEBP_PRESET_ICON: + return( WEBP_PRESET_ICON ); + case VIPS_FOREIGN_WEBP_PRESET_TEXT: + return( WEBP_PRESET_TEXT ); + + default: + g_assert_not_reached(); + } + + /* Keep -Wall happy. + */ + return( -1 ); +} + +static void +vips_webp_set_count( VipsForeignSaveWebp *write, int loop_count ) +{ + uint32_t features; + + if( WebPMuxGetFeatures( write->mux, &features ) == WEBP_MUX_OK && + (features & ANIMATION_FLAG) ) { + WebPMuxAnimParams params; + + if( WebPMuxGetAnimationParams( write->mux, ¶ms ) == + WEBP_MUX_OK ) { + params.loop_count = loop_count; + WebPMuxSetAnimationParams( write->mux, ¶ms ); + } + } +} + +static int +vips_webp_set_chunk( VipsForeignSaveWebp *write, + const char *webp_name, const void *data, size_t length ) +{ + WebPData chunk; + + chunk.bytes = data; + chunk.size = length; + + if( WebPMuxSetChunk( write->mux, webp_name, &chunk, 1 ) != + WEBP_MUX_OK ) { + vips_error( "webpsave", + "%s", _( "chunk add error" ) ); + return( -1 ); + } + + return( 0 ); +} + +static int +vips_webp_add_chunks( VipsForeignSaveWebp *write ) +{ + int i; + + for( i = 0; i < vips__n_webp_names; i++ ) { + const char *vips_name = vips__webp_names[i].vips; + const char *webp_name = vips__webp_names[i].webp; + + if( vips_image_get_typeof( write->image, vips_name ) ) { + const void *data; + size_t length; + + if( vips_image_get_blob( write->image, + vips_name, &data, &length ) || + vips_webp_set_chunk( write, + webp_name, data, length ) ) + return( -1 ); + } + } + + return( 0 ); +} + +static int +vips_webp_add_metadata( VipsForeignSaveWebp *write ) +{ + WebPData data; + + data.bytes = write->memory_writer.mem; + data.size = write->memory_writer.size; + + /* Parse what we have. + */ + if( !(write->mux = WebPMuxCreate( &data, 1 )) ) { + vips_error( "webpsave", "%s", _( "mux error" ) ); + return( -1 ); + } + + if( vips_image_get_typeof( write->image, "loop" ) ) { + int loop; + + if( vips_image_get_int( write->image, "loop", &loop ) ) + return( -1 ); + + vips_webp_set_count( write, loop ); + } + /* DEPRECATED "gif-loop" + */ + else if ( vips_image_get_typeof( write->image, "gif-loop" ) ) { + int gif_loop; + + if( vips_image_get_int( write->image, "gif-loop", &gif_loop ) ) + return( -1 ); + + vips_webp_set_count( write, gif_loop == 0 ? 0 : gif_loop + 1 ); + } + + /* Add extra metadata. + */ + if( !write->strip ) { + /* We need to rebuild exif from the other image tags before + * writing the metadata. + */ + if( vips__exif_update( write->image ) ) + return( -1 ); + + /* Override profile. + */ + if( write->profile && + vips__profile_set( write->image, write->profile ) ) + return( -1 ); + + if( vips_webp_add_chunks( write ) ) + return( -1 ); + } + + if( WebPMuxAssemble( write->mux, &data ) != WEBP_MUX_OK ) { + vips_error( "webpsave", "%s", _( "mux error" ) ); + return( -1 ); + } + + /* Free old stuff, reinit with new stuff. + */ + WebPMemoryWriterClear( &write->memory_writer ); + write->memory_writer.mem = (uint8_t *) data.bytes; + write->memory_writer.size = data.size; + + return( 0 ); +} + +static int +vips_foreign_save_webp_init_config( VipsForeignSaveWebp *webp ) { + /* Init WebP config. + */ + WebPMemoryWriterInit( &webp->memory_writer ); + if( !WebPConfigInit( &webp->config ) ) { + vips_foreign_save_webp_unset( webp ); + vips_error( "webpsave", + "%s", _( "config version error" ) ); + return( -1 ); + } + + /* These presets are only for lossy compression. There seems to be + * separate API for lossless or near-lossless, see + * WebPConfigLosslessPreset(). + */ + if( !(webp->lossless || webp->near_lossless) && + !WebPConfigPreset( &webp->config, get_preset( webp->preset ), + webp->Q ) ) { + vips_foreign_save_webp_unset( webp ); + vips_error( "webpsave", "%s", _( "config version error" ) ); + return( -1 ); + } + + webp->config.lossless = webp->lossless || webp->near_lossless; + webp->config.alpha_quality = webp->alpha_q; + webp->config.method = webp->effort; + + if( webp->lossless ) + webp->config.quality = webp->Q; + if( webp->near_lossless ) + webp->config.near_lossless = webp->Q; + if( webp->smart_subsample ) + webp->config.use_sharp_yuv = 1; + + if( !WebPValidateConfig( &webp->config ) ) { + vips_foreign_save_webp_unset( webp ); + vips_error( "webpsave", "%s", _( "invalid configuration" ) ); + return( -1 ); + } + + return( 0 ); +} + +static int +vips_foreign_save_webp_init_anim_enc( VipsForeignSaveWebp *webp ) { + WebPAnimEncoderOptions anim_config; + int i; + int page_height = vips_image_get_page_height( webp->image ); + + /* Init config for animated write + */ + if( !WebPAnimEncoderOptionsInit( &anim_config ) ) { + vips_error( "webpsave", + "%s", _( "config version error" ) ); + return( -1 ); + } + + anim_config.minimize_size = webp->min_size; + anim_config.allow_mixed = webp->mixed; + anim_config.kmin = webp->kmin; + anim_config.kmax = webp->kmax; + webp->enc = WebPAnimEncoderNew( webp->image->Xsize, page_height, + &anim_config ); + if( !webp->enc ) { + vips_error( "webpsave", + "%s", _( "unable to init animation" ) ); + return( -1 ); + } + + /* Get delay array + * + * There might just be the old gif-delay field. This is centiseconds. + */ + webp->gif_delay = 10; + if( vips_image_get_typeof( webp->image, "gif-delay" ) && + vips_image_get_int( webp->image, "gif-delay", + &webp->gif_delay ) ) + return( -1 ); + + /* New images have an array of ints instead. + */ + webp->delay = NULL; + if( vips_image_get_typeof( webp->image, "delay" ) && + vips_image_get_array_int( webp->image, "delay", + &webp->delay, &webp->delay_length ) ) + return( -1 ); + + /* Force frames with a small or no duration to 100ms + * to be consistent with web browsers and other + * transcoding tools. + */ + if( webp->gif_delay <= 1 ) + webp->gif_delay = 10; + + for( i = 0; i < webp->delay_length; i++ ) + if( webp->delay[i] <= 10 ) + webp->delay[i] = 100; + + return( 0 ); +} + +static int +vips_foreign_save_webp_finish_anim( VipsForeignSaveWebp *webp ) { + WebPData webp_data; + + /* Closes animated encoder and adds last frame delay. + */ + if( !WebPAnimEncoderAdd( webp->enc, + NULL, webp->timestamp_ms, NULL ) ) { + vips_error( "webpsave", + "%s", _( "anim close error" ) ); + return( -1 ); + } + + if( !WebPAnimEncoderAssemble( webp->enc, &webp_data ) ) { + vips_error( "webpsave", + "%s", _( "anim build error" ) ); + return( -1 ); + } + + /* Terrible. This will only work if the output buffer is currently + * empty. + */ + if( webp->memory_writer.mem != NULL ) { + vips_error( "webpsave", "%s", _( "internal error" ) ); + return( -1 ); + } + + webp->memory_writer.mem = (uint8_t *) webp_data.bytes; + webp->memory_writer.size = webp_data.size; + + return( 0 ); +} + +static int +vips_foreign_save_webp_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; + + int page_height; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_parent_class )-> + build( object ) ) + return( -1 ); + + /* We need a copy of the input image in case we change the metadata + * eg. in vips__exif_update(). + */ + if( vips_copy( save->ready, &webp->image, NULL ) ) { + vips_foreign_save_webp_unset( webp ); + return( -1 ); + } + + page_height = vips_image_get_page_height( webp->image ); + + /* RGB(A) frame as a contiguous buffer. + */ + webp->frame_bytes = g_malloc( (size_t) webp->image->Bands * + webp->image->Xsize * page_height ); + + /* Init generic WebP config + */ + if( vips_foreign_save_webp_init_config( webp ) ) + return( -1 ); + + /* Determine the write mode (single image or animated write) + */ + webp->mode = VIPS_FOREIGN_SAVE_WEBP_MODE_SINGLE; + if( page_height != webp->image->Ysize ) + webp->mode = VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM; + + /* Init config for animated write (if necessary) + */ + if( webp->mode == VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM ) + if( vips_foreign_save_webp_init_anim_enc( webp ) ) + return( -1 ); + + if( vips_sink_disc( webp->image, + vips_foreign_save_webp_sink_disc, webp ) ) + return( -1 ); + + /* Finish animated write + */ + if( webp->mode == VIPS_FOREIGN_SAVE_WEBP_MODE_ANIM ) + if( vips_foreign_save_webp_finish_anim( webp ) ) + return( -1 ); + + if( vips_webp_add_metadata( webp ) ) { + vips_foreign_save_webp_unset( webp ); + return( -1 ); + } + + if( vips_target_write( webp->target, + webp->memory_writer.mem, webp->memory_writer.size ) ) { + vips_foreign_save_webp_unset( webp ); + return( -1 ); + } + + if( vips_target_end( webp->target ) ) + return( -1 ); + + vips_foreign_save_webp_unset( webp ); + + return( 0 ); +} + +static const char *vips__save_webp_suffs[] = { ".webp", NULL }; + #define UC VIPS_FORMAT_UCHAR /* Type promotion for save ... just always go to uchar. @@ -135,30 +727,32 @@ vips_foreign_save_webp_class_init( VipsForeignSaveWebpClass *class ) VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + gobject_class->dispose = vips_foreign_save_webp_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "webpsave_base"; - object_class->description = _( "save webp" ); + object_class->description = _( "save as WebP" ); + object_class->build = vips_foreign_save_webp_build; - foreign_class->suffs = vips__webp_suffs; + foreign_class->suffs = vips__save_webp_suffs; save_class->saveable = VIPS_SAVEABLE_RGBA_ONLY; save_class->format_table = bandfmt_webp; - VIPS_ARG_INT( class, "Q", 10, - _( "Q" ), + VIPS_ARG_INT( class, "Q", 10, + _( "Q" ), _( "Q factor" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, Q ), 0, 100, 75 ); - VIPS_ARG_BOOL( class, "lossless", 11, - _( "Lossless" ), + VIPS_ARG_BOOL( class, "lossless", 11, + _( "Lossless" ), _( "Enable lossless compression" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, lossless ), - FALSE ); + FALSE ); VIPS_ARG_ENUM( class, "preset", 12, _( "Preset" ), @@ -217,8 +811,8 @@ vips_foreign_save_webp_class_init( VipsForeignSaveWebpClass *class ) G_STRUCT_OFFSET( VipsForeignSaveWebp, effort ), 0, 6, 4 ); - VIPS_ARG_STRING( class, "profile", 20, - _( "Profile" ), + VIPS_ARG_STRING( class, "profile", 20, + _( "Profile" ), _( "ICC profile to embed" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, profile ), @@ -256,39 +850,32 @@ typedef struct _VipsForeignSaveWebpTarget { VipsForeignSaveWebp parent_object; VipsTarget *target; - } VipsForeignSaveWebpTarget; typedef VipsForeignSaveWebpClass VipsForeignSaveWebpTargetClass; -G_DEFINE_TYPE( VipsForeignSaveWebpTarget, vips_foreign_save_webp_target, +G_DEFINE_TYPE( VipsForeignSaveWebpTarget, vips_foreign_save_webp_target, vips_foreign_save_webp_get_type() ); static int vips_foreign_save_webp_target_build( VipsObject *object ) { - VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; - VipsForeignSaveWebpTarget *target = + VipsForeignSaveWebpTarget *target = (VipsForeignSaveWebpTarget *) object; + webp->target = target->target; + g_object_ref( webp->target ); + if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_target_parent_class )-> build( object ) ) return( -1 ); - if( vips__webp_write_target( save->ready, target->target, - webp->Q, webp->lossless, webp->preset, - webp->smart_subsample, webp->near_lossless, - webp->alpha_q, webp->effort, - webp->min_size, webp->mixed, webp->kmin, webp->kmax, - save->strip, webp->profile ) ) - return( -1 ); - return( 0 ); } static void -vips_foreign_save_webp_target_class_init( +vips_foreign_save_webp_target_class_init( VipsForeignSaveWebpTargetClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); @@ -298,15 +885,15 @@ vips_foreign_save_webp_target_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "webpsave_target"; - object_class->description = _( "save image to webp target" ); object_class->build = vips_foreign_save_webp_target_build; VIPS_ARG_OBJECT( class, "target", 1, _( "Target" ), _( "Target to save to" ), - VIPS_ARGUMENT_REQUIRED_INPUT, + VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebpTarget, target ), VIPS_TYPE_TARGET ); + } static void @@ -314,47 +901,29 @@ vips_foreign_save_webp_target_init( VipsForeignSaveWebpTarget *target ) { } - typedef struct _VipsForeignSaveWebpFile { VipsForeignSaveWebp parent_object; - - /* Filename for save. - */ - char *filename; - + char *filename; } VipsForeignSaveWebpFile; typedef VipsForeignSaveWebpClass VipsForeignSaveWebpFileClass; -G_DEFINE_TYPE( VipsForeignSaveWebpFile, vips_foreign_save_webp_file, +G_DEFINE_TYPE( VipsForeignSaveWebpFile, vips_foreign_save_webp_file, vips_foreign_save_webp_get_type() ); static int vips_foreign_save_webp_file_build( VipsObject *object ) { - VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; VipsForeignSaveWebpFile *file = (VipsForeignSaveWebpFile *) object; - VipsTarget *target; + if( !(webp->target = vips_target_new_to_file( file->filename )) ) + return( -1 ); if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_file_parent_class )-> build( object ) ) return( -1 ); - if( !(target = vips_target_new_to_file( file->filename )) ) - return( -1 ); - if( vips__webp_write_target( save->ready, target, - webp->Q, webp->lossless, webp->preset, - webp->smart_subsample, webp->near_lossless, - webp->alpha_q, webp->effort, - webp->min_size, webp->mixed, webp->kmin, webp->kmax, - save->strip, webp->profile ) ) { - VIPS_UNREF( target ); - return( -1 ); - } - VIPS_UNREF( target ); - return( 0 ); } @@ -368,13 +937,12 @@ vips_foreign_save_webp_file_class_init( VipsForeignSaveWebpFileClass *class ) gobject_class->get_property = vips_object_get_property; object_class->nickname = "webpsave"; - object_class->description = _( "save image to webp file" ); object_class->build = vips_foreign_save_webp_file_build; - VIPS_ARG_STRING( class, "filename", 1, + VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to save to" ), - VIPS_ARGUMENT_REQUIRED_INPUT, + VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebpFile, filename ), NULL ); } @@ -386,57 +954,39 @@ vips_foreign_save_webp_file_init( VipsForeignSaveWebpFile *file ) typedef struct _VipsForeignSaveWebpBuffer { VipsForeignSaveWebp parent_object; - - /* Save to a buffer. - */ VipsArea *buf; - } VipsForeignSaveWebpBuffer; typedef VipsForeignSaveWebpClass VipsForeignSaveWebpBufferClass; -G_DEFINE_TYPE( VipsForeignSaveWebpBuffer, vips_foreign_save_webp_buffer, +G_DEFINE_TYPE( VipsForeignSaveWebpBuffer, vips_foreign_save_webp_buffer, vips_foreign_save_webp_get_type() ); static int vips_foreign_save_webp_buffer_build( VipsObject *object ) { - VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; - VipsForeignSaveWebpBuffer *buffer = + VipsForeignSaveWebpBuffer *buffer = (VipsForeignSaveWebpBuffer *) object; - VipsTarget *target; VipsBlob *blob; + if( !(webp->target = vips_target_new_to_memory()) ) + return( -1 ); + if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_buffer_parent_class )-> build( object ) ) return( -1 ); - if( !(target = vips_target_new_to_memory()) ) - return( -1 ); - - if( vips__webp_write_target( save->ready, target, - webp->Q, webp->lossless, webp->preset, - webp->smart_subsample, webp->near_lossless, - webp->alpha_q, webp->effort, - webp->min_size, webp->mixed, webp->kmin, webp->kmax, - save->strip, webp->profile ) ) { - VIPS_UNREF( target ); - return( -1 ); - } - - g_object_get( target, "blob", &blob, NULL ); + g_object_get( webp->target, "blob", &blob, NULL ); g_object_set( buffer, "buffer", blob, NULL ); vips_area_unref( VIPS_AREA( blob ) ); - VIPS_UNREF( target ); - return( 0 ); } static void -vips_foreign_save_webp_buffer_class_init( +vips_foreign_save_webp_buffer_class_init( VipsForeignSaveWebpBufferClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); @@ -446,19 +996,18 @@ vips_foreign_save_webp_buffer_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "webpsave_buffer"; - object_class->description = _( "save image to webp buffer" ); object_class->build = vips_foreign_save_webp_buffer_build; - VIPS_ARG_BOXED( class, "buffer", 1, + VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to save to" ), - VIPS_ARGUMENT_REQUIRED_OUTPUT, + VIPS_ARGUMENT_REQUIRED_OUTPUT, G_STRUCT_OFFSET( VipsForeignSaveWebpBuffer, buf ), VIPS_TYPE_BLOB ); } static void -vips_foreign_save_webp_buffer_init( VipsForeignSaveWebpBuffer *file ) +vips_foreign_save_webp_buffer_init( VipsForeignSaveWebpBuffer *buffer ) { } @@ -469,38 +1018,26 @@ typedef struct _VipsForeignSaveWebpMime { typedef VipsForeignSaveWebpClass VipsForeignSaveWebpMimeClass; -G_DEFINE_TYPE( VipsForeignSaveWebpMime, vips_foreign_save_webp_mime, +G_DEFINE_TYPE( VipsForeignSaveWebpMime, vips_foreign_save_webp_mime, vips_foreign_save_webp_get_type() ); static int vips_foreign_save_webp_mime_build( VipsObject *object ) { - VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; - VipsTarget *target; VipsBlob *blob; void *data; size_t len; + if( !(webp->target = vips_target_new_to_memory()) ) + return( -1 ); + if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_mime_parent_class )-> build( object ) ) return( -1 ); - if( !(target = vips_target_new_to_memory()) ) - return( -1 ); - - if( vips__webp_write_target( save->ready, target, - webp->Q, webp->lossless, webp->preset, - webp->smart_subsample, webp->near_lossless, - webp->alpha_q, webp->effort, - webp->min_size, webp->mixed, webp->kmin, webp->kmax, - save->strip, webp->profile ) ) { - VIPS_UNREF( target ); - return( -1 ); - } - - g_object_get( target, "blob", &blob, NULL ); + g_object_get( webp->target, "blob", &blob, NULL ); data = VIPS_AREA( blob )->data; len = VIPS_AREA( blob )->length; vips_area_unref( VIPS_AREA( blob ) ); @@ -511,7 +1048,7 @@ vips_foreign_save_webp_mime_build( VipsObject *object ) (void) fwrite( data, sizeof( char ), len, stdout ); fflush( stdout ); - VIPS_UNREF( target ); + VIPS_UNREF( webp->target ); return( 0 ); } @@ -536,8 +1073,8 @@ vips_foreign_save_webp_mime_init( VipsForeignSaveWebpMime *mime ) /** * vips_webpsave: (method) - * @in: image to save - * @filename: file to write to + * @in: image to save + * @filename: file to write to * @...: %NULL-terminated list of optional named arguments * * Optional arguments: @@ -556,14 +1093,14 @@ vips_foreign_save_webp_mime_init( VipsForeignSaveWebpMime *mime ) * * @strip: %gboolean, remove all metadata from image * * @profile: %gchararray, filename of ICC profile to attach * - * Write an image to a file in WebP format. + * Write an image to a file in WebP format. * - * By default, images are saved in lossy format, with + * By default, images are saved in lossy format, with * @Q giving the WebP quality factor. It has the range 0 - 100, with the * default 75. * * Use @preset to hint the image type to the lossy compressor. The default is - * #VIPS_FOREIGN_WEBP_PRESET_DEFAULT. + * #VIPS_FOREIGN_WEBP_PRESET_DEFAULT. * * Set @smart_subsample to enable high quality chroma subsampling. * @@ -589,8 +1126,8 @@ vips_foreign_save_webp_mime_init( VipsForeignSaveWebpMime *mime ) * both lossy and lossless encoding. * * Use @profile to give the name of a profile to be embedded in the file. - * This does not affect the pixels which are written, just the way - * they are tagged. See vips_profile_load() for details on profile naming. + * This does not affect the pixels which are written, just the way + * they are tagged. See vips_profile_load() for details on profile naming. * * Use the metadata items `loop` and `delay` to set the number of * loops for the animation and the frame delays. @@ -617,7 +1154,7 @@ vips_webpsave( VipsImage *in, const char *filename, ... ) /** * vips_webpsave_buffer: (method) - * @in: image to save + * @in: image to save * @buf: (out) (array length=len) (element-type guint8): return output buffer here * @len: return output length here * @...: %NULL-terminated list of optional named arguments @@ -642,7 +1179,7 @@ vips_webpsave( VipsImage *in, const char *filename, ... ) * * The address of the buffer is returned in @buf, the length of the buffer in * @len. You are responsible for freeing the buffer with g_free() when you - * are done with it. + * are done with it. * * See also: vips_webpsave(). * @@ -655,19 +1192,19 @@ vips_webpsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) VipsArea *area; int result; - area = NULL; + area = NULL; va_start( ap, len ); result = vips_call_split( "webpsave_buffer", ap, in, &area ); va_end( ap ); if( !result && - area ) { + area ) { if( buf ) { *buf = area->data; area->free_fn = NULL; } - if( len ) + if( len ) *len = area->length; vips_area_unref( area ); @@ -678,7 +1215,7 @@ vips_webpsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) /** * vips_webpsave_mime: (method) - * @in: image to save + * @in: image to save * @...: %NULL-terminated list of optional named arguments * * Optional arguments: @@ -718,7 +1255,7 @@ vips_webpsave_mime( VipsImage *in, ... ) /** * vips_webpsave_target: (method) - * @in: image to save + * @in: image to save * @target: save image to this target * @...: %NULL-terminated list of optional named arguments *