/* save to webp * * 24/11/11 * - wrap a class around the webp writer * 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 * 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 (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_VERBOSE #define DEBUG */ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #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. */ int Q; /* Turn on lossless encode. */ gboolean lossless; /* Lossy compression preset. */ VipsForeignWebpPreset preset; /* Enable smart chroma subsampling. */ gboolean smart_subsample; /* Use preprocessing in lossless mode. */ gboolean near_lossless; /* Alpha quality. */ int alpha_q; /* Level of CPU effort to reduce file size. */ int effort; /* Animated webp options. */ int gif_delay; int *delay; int delay_length; gboolean strip; /* Attempt to minimise size */ gboolean min_size; /* Allow mixed encoding (might reduce file size) */ gboolean mixed; /* Min between key frames. */ int kmin; /* Max between keyframes. */ int kmax; /* Profile to embed. */ 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, 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 ); page_height = vips_image_get_page_height( save->ready ); if( save->ready->Xsize > 16383 || page_height > 16383 ) { vips_error( "webpsave", _( "image too large" ) ); vips_foreign_save_webp_unset( webp ); 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 ); } /* RGB(A) frame as a contiguous buffer. */ size_t frame_size = (size_t) webp->image->Bands * webp->image->Xsize * page_height; webp->frame_bytes = g_try_malloc( frame_size ); if( webp->frame_bytes == NULL ) { vips_error( "webpsave", _( "failed to allocate %zu bytes" ), frame_size ); vips_foreign_save_webp_unset( webp ); return( -1 ); } /* 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. */ static VipsBandFormat bandfmt_webp[10] = { /* Band format: UC C US S UI I F X D DX */ /* Promotion: */ UC, UC, UC, UC, UC, UC, UC, UC, UC, UC }; static void vips_foreign_save_webp_class_init( VipsForeignSaveWebpClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) 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 as WebP" ); object_class->build = vips_foreign_save_webp_build; 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" ), _( "Q factor" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, Q ), 0, 100, 75 ); VIPS_ARG_BOOL( class, "lossless", 11, _( "Lossless" ), _( "Enable lossless compression" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, lossless ), FALSE ); VIPS_ARG_ENUM( class, "preset", 12, _( "Preset" ), _( "Preset for lossy compression" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, preset ), VIPS_TYPE_FOREIGN_WEBP_PRESET, VIPS_FOREIGN_WEBP_PRESET_DEFAULT ); VIPS_ARG_BOOL( class, "smart_subsample", 13, _( "Smart subsampling" ), _( "Enable high quality chroma subsampling" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, smart_subsample ), FALSE ); VIPS_ARG_BOOL( class, "near_lossless", 14, _( "Near lossless" ), _( "Enable preprocessing in lossless mode (uses Q)" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, near_lossless ), FALSE ); VIPS_ARG_INT( class, "alpha_q", 15, _( "Alpha quality" ), _( "Change alpha plane fidelity for lossy compression" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, alpha_q ), 0, 100, 100 ); VIPS_ARG_BOOL( class, "min_size", 16, _( "Minimise size" ), _( "Optimise for minimum size" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, min_size ), FALSE ); VIPS_ARG_INT( class, "kmin", 17, _( "Minimum keyframe spacing" ), _( "Minimum number of frames between key frames" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, kmin ), 0, INT_MAX, INT_MAX - 1 ); VIPS_ARG_INT( class, "kmax", 18, _( "Maximum keyframe spacing" ), _( "Maximum number of frames between key frames" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, kmax ), 0, INT_MAX, INT_MAX ); VIPS_ARG_INT( class, "effort", 19, _( "Effort" ), _( "Level of CPU effort to reduce file size" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, effort ), 0, 6, 4 ); VIPS_ARG_STRING( class, "profile", 20, _( "Profile" ), _( "ICC profile to embed" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, profile ), NULL ); VIPS_ARG_INT( class, "reduction_effort", 21, _( "Reduction effort" ), _( "Level of CPU effort to reduce file size" ), VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsForeignSaveWebp, effort ), 0, 6, 4 ); VIPS_ARG_BOOL( class, "mixed", 22, _( "Mixed encoding" ), _( "Allow mixed encoding (might reduce file size)" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebp, mixed ), FALSE ); } static void vips_foreign_save_webp_init( VipsForeignSaveWebp *webp ) { webp->Q = 75; webp->alpha_q = 100; webp->effort = 4; /* ie. keyframes disabled by default. */ webp->kmin = INT_MAX - 1; webp->kmax = INT_MAX; } typedef struct _VipsForeignSaveWebpTarget { VipsForeignSaveWebp parent_object; VipsTarget *target; } VipsForeignSaveWebpTarget; typedef VipsForeignSaveWebpClass VipsForeignSaveWebpTargetClass; 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 ) { VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; 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 ); return( 0 ); } static void vips_foreign_save_webp_target_class_init( VipsForeignSaveWebpTargetClass *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 = "webpsave_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, G_STRUCT_OFFSET( VipsForeignSaveWebpTarget, target ), VIPS_TYPE_TARGET ); } static void vips_foreign_save_webp_target_init( VipsForeignSaveWebpTarget *target ) { } typedef struct _VipsForeignSaveWebpFile { VipsForeignSaveWebp parent_object; char *filename; } VipsForeignSaveWebpFile; typedef VipsForeignSaveWebpClass VipsForeignSaveWebpFileClass; 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 ) { VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; VipsForeignSaveWebpFile *file = (VipsForeignSaveWebpFile *) object; 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 ); return( 0 ); } static void vips_foreign_save_webp_file_class_init( VipsForeignSaveWebpFileClass *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 = "webpsave"; object_class->build = vips_foreign_save_webp_file_build; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to save to" ), VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsForeignSaveWebpFile, filename ), NULL ); } static void vips_foreign_save_webp_file_init( VipsForeignSaveWebpFile *file ) { } typedef struct _VipsForeignSaveWebpBuffer { VipsForeignSaveWebp parent_object; VipsArea *buf; } VipsForeignSaveWebpBuffer; typedef VipsForeignSaveWebpClass VipsForeignSaveWebpBufferClass; 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 ) { VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; VipsForeignSaveWebpBuffer *buffer = (VipsForeignSaveWebpBuffer *) object; 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 ); g_object_get( webp->target, "blob", &blob, NULL ); g_object_set( buffer, "buffer", blob, NULL ); vips_area_unref( VIPS_AREA( blob ) ); return( 0 ); } static void vips_foreign_save_webp_buffer_class_init( VipsForeignSaveWebpBufferClass *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 = "webpsave_buffer"; object_class->build = vips_foreign_save_webp_buffer_build; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to save to" ), VIPS_ARGUMENT_REQUIRED_OUTPUT, G_STRUCT_OFFSET( VipsForeignSaveWebpBuffer, buf ), VIPS_TYPE_BLOB ); } static void vips_foreign_save_webp_buffer_init( VipsForeignSaveWebpBuffer *buffer ) { } typedef struct _VipsForeignSaveWebpMime { VipsForeignSaveWebp parent_object; } VipsForeignSaveWebpMime; typedef VipsForeignSaveWebpClass VipsForeignSaveWebpMimeClass; 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 ) { VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; 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 ); g_object_get( webp->target, "blob", &blob, NULL ); data = VIPS_AREA( blob )->data; len = VIPS_AREA( blob )->length; vips_area_unref( VIPS_AREA( blob ) ); printf( "Content-length: %zu\r\n", len ); printf( "Content-type: image/webp\r\n" ); printf( "\r\n" ); (void) fwrite( data, sizeof( char ), len, stdout ); fflush( stdout ); VIPS_UNREF( webp->target ); return( 0 ); } static void vips_foreign_save_webp_mime_class_init( VipsForeignSaveWebpMimeClass *class ) { VipsObjectClass *object_class = (VipsObjectClass *) class; object_class->nickname = "webpsave_mime"; object_class->description = _( "save image to webp mime" ); object_class->build = vips_foreign_save_webp_mime_build; } static void vips_foreign_save_webp_mime_init( VipsForeignSaveWebpMime *mime ) { } #endif /*HAVE_LIBWEBP*/ /** * vips_webpsave: (method) * @in: image to save * @filename: file to write to * @...: %NULL-terminated list of optional named arguments * * Optional arguments: * * * @Q: %gint, quality factor * * @lossless: %gboolean, enables lossless compression * * @preset: #VipsForeignWebpPreset, choose lossy compression preset * * @smart_subsample: %gboolean, enables high quality chroma subsampling * * @near_lossless: %gboolean, preprocess in lossless mode (controlled by Q) * * @alpha_q: %gint, set alpha quality in lossless mode * * @effort: %gint, level of CPU effort to reduce file size * * @min_size: %gboolean, minimise size * * @mixed: %gboolean, allow both lossy and lossless encoding * * @kmin: %gint, minimum number of frames between keyframes * * @kmax: %gint, maximum number of frames between keyframes * * @strip: %gboolean, remove all metadata from image * * @profile: %gchararray, filename of ICC profile to attach * * Write an image to a file in WebP format. * * 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. * * Set @smart_subsample to enable high quality chroma subsampling. * * Use @alpha_q to set the quality for the alpha channel in lossy mode. It has * the range 1 - 100, with the default 100. * * Use @effort to control how much CPU time to spend attempting to * reduce file size. A higher value means more effort and therefore CPU time * should be spent. It has the range 0-6 and a default value of 4. * * Set @lossless to use lossless compression, or combine @near_lossless * with @Q 80, 60, 40 or 20 to apply increasing amounts of preprocessing * which improves the near-lossless compression ratio by up to 50%. * * For animated webp output, @min_size will try to optimize for minimum size. * * For animated webp output, @kmax sets the maximum number of frames between * keyframes. Setting 0 means only keyframes. @kmin sets the minimum number of * frames between frames. Setting 0 means no keyframes. By default, keyframes * are disabled. * * For animated webp output, @mixed tries to improve the file size by mixing * 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. * * Use the metadata items `loop` and `delay` to set the number of * loops for the animation and the frame delays. * * The writer will attach ICC, EXIF and XMP metadata, unless @strip is set to * %TRUE. * * See also: vips_webpload(), vips_image_write_to_file(). * * Returns: 0 on success, -1 on error. */ int vips_webpsave( VipsImage *in, const char *filename, ... ) { va_list ap; int result; va_start( ap, filename ); result = vips_call_split( "webpsave", ap, in, filename ); va_end( ap ); return( result ); } /** * vips_webpsave_buffer: (method) * @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 * * Optional arguments: * * * @Q: %gint, quality factor * * @lossless: %gboolean, enables lossless compression * * @preset: #VipsForeignWebpPreset, choose lossy compression preset * * @smart_subsample: %gboolean, enables high quality chroma subsampling * * @near_lossless: %gboolean, preprocess in lossless mode (controlled by Q) * * @alpha_q: %gint, set alpha quality in lossless mode * * @effort: %gint, level of CPU effort to reduce file size * * @min_size: %gboolean, minimise size * * @mixed: %gboolean, allow both lossy and lossless encoding * * @kmin: %gint, minimum number of frames between keyframes * * @kmax: %gint, maximum number of frames between keyframes * * @strip: %gboolean, remove all metadata from image * * @profile: %gchararray, filename of ICC profile to attach * * As vips_webpsave(), but save to a memory buffer. * * 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. * * See also: vips_webpsave(). * * Returns: 0 on success, -1 on error. */ int vips_webpsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) { va_list ap; VipsArea *area; int result; area = NULL; va_start( ap, len ); result = vips_call_split( "webpsave_buffer", ap, in, &area ); va_end( ap ); if( !result && area ) { if( buf ) { *buf = area->data; area->free_fn = NULL; } if( len ) *len = area->length; vips_area_unref( area ); } return( result ); } /** * vips_webpsave_mime: (method) * @in: image to save * @...: %NULL-terminated list of optional named arguments * * Optional arguments: * * * @Q: %gint, quality factor * * @lossless: %gboolean, enables lossless compression * * @preset: #VipsForeignWebpPreset, choose lossy compression preset * * @smart_subsample: %gboolean, enables high quality chroma subsampling * * @near_lossless: %gboolean, preprocess in lossless mode (controlled by Q) * * @alpha_q: %gint, set alpha quality in lossless mode * * @effort: %gint, level of CPU effort to reduce file size * * @min_size: %gboolean, minimise size * * @mixed: %gboolean, allow both lossy and lossless encoding * * @kmin: %gint, minimum number of frames between keyframes * * @kmax: %gint, maximum number of frames between keyframes * * @strip: %gboolean, remove all metadata from image * * @profile: %gchararray, filename of ICC profile to attach * * As vips_webpsave(), but save as a mime webp on stdout. * * See also: vips_webpsave(), vips_image_write_to_file(). * * Returns: 0 on success, -1 on error. */ int vips_webpsave_mime( VipsImage *in, ... ) { va_list ap; int result; va_start( ap, in ); result = vips_call_split( "webpsave_mime", ap, in ); va_end( ap ); return( result ); } /** * vips_webpsave_target: (method) * @in: image to save * @target: save image to this target * @...: %NULL-terminated list of optional named arguments * * Optional arguments: * * * @Q: %gint, quality factor * * @lossless: %gboolean, enables lossless compression * * @preset: #VipsForeignWebpPreset, choose lossy compression preset * * @smart_subsample: %gboolean, enables high quality chroma subsampling * * @near_lossless: %gboolean, preprocess in lossless mode (controlled by Q) * * @alpha_q: %gint, set alpha quality in lossless mode * * @effort: %gint, level of CPU effort to reduce file size * * @min_size: %gboolean, minimise size * * @mixed: %gboolean, allow both lossy and lossless encoding * * @kmin: %gint, minimum number of frames between keyframes * * @kmax: %gint, maximum number of frames between keyframes * * @strip: %gboolean, remove all metadata from image * * @profile: %gchararray, filename of ICC profile to attach * * As vips_webpsave(), but save to a target. * * See also: vips_webpsave(). * * Returns: 0 on success, -1 on error. */ int vips_webpsave_target( VipsImage *in, VipsTarget *target, ... ) { va_list ap; int result; va_start( ap, target ); result = vips_call_split( "webpsave_target", ap, in, target ); va_end( ap ); return( result ); }