Merge pull request #430 from felixbuenemann/use-webp-advanced-encoding-api

Use WebP Advanced Encoding API and make it tunable
This commit is contained in:
John Cupitt 2016-05-01 19:51:12 +01:00
commit c88b94cb29
9 changed files with 350 additions and 111 deletions

View File

@ -677,7 +677,7 @@ AC_ARG_WITH([libwebp],
AS_HELP_STRING([--without-libwebp], [build without libwebp (default: test)]))
if test x"$with_libwebp" != "xno"; then
PKG_CHECK_MODULES(LIBWEBP, libwebp,
PKG_CHECK_MODULES(LIBWEBP, libwebp >= 0.1.3,
[AC_DEFINE(HAVE_LIBWEBP,1,[define if you have libwebp installed.])
with_libwebp=yes
PACKAGES_USED="$PACKAGES_USED libwebp"],
@ -966,6 +966,7 @@ SVG import with librsvg-2.0: $with_rsvg
(requires librsvg-2.0 2.40.0 or later)
file import with cfitsio: $with_cfitsio
file import/export with libwebp: $with_libwebp
(requires libwebp-0.1.3 or later)
text rendering with pangoft2: $with_pangoft2
file import/export with libpng: $with_png
(requires libpng-1.2.9 or later)

View File

@ -2458,6 +2458,11 @@ vips_webpload_buffer( void *buf, size_t len, VipsImage **out, ... )
* Optional arguments:
*
* @Q: quality factor
* @lossless: enables lossless compression
* @preset: #VipsForeignWebpPreset choose lossy compression preset
* @smart_subsample: enables high quality chroma subsampling
* @near_lossless: use preprocessing in lossless mode (controlled by Q)
* @alpha_q: set alpha quality in lossless mode
*
* See also: vips_webpload(), vips_image_write_to_file().
*
@ -2486,6 +2491,17 @@ vips_webpsave( VipsImage *in, const char *filename, ... )
* Optional arguments:
*
* @Q: JPEG quality factor
* @lossless: enables lossless compression
* @preset: #VipsForeignWebpPreset choose lossy compression preset
* @smart_subsample: enables high quality chroma subsampling
* @near_lossless: use preprocessing in lossless mode (controlled by Q)
* @alpha_q: set alpha quality in lossless mode
*
* As vips_webpsave(), but save to a memory buffer.
*
* The address of the buffer is returned in @obuf, the length of the buffer in
* @olen. You are responsible for freeing the buffer with g_free() when you
* are done with it. The buffer is freed for you on error.
*
* See also: vips_webpsave().
*

View File

@ -46,6 +46,7 @@
#ifdef HAVE_LIBWEBP
#include <stdlib.h>
#include <string.h>
#include <vips/vips.h>
@ -53,125 +54,160 @@
#include "webp.h"
typedef size_t (*webp_encoder)( const uint8_t *rgb,
int width, int height, int stride,
float quality_factor, uint8_t **output );
typedef int (*webp_import)( WebPPicture *picture,
const uint8_t *rgb, int stride );
typedef size_t (*webp_encoder_lossless)( const uint8_t *rgb,
int width, int height, int stride, uint8_t **output );
int
vips__webp_write_file( VipsImage *in, const char *filename,
int Q, gboolean lossless )
static WebPPreset
get_preset( VipsForeignWebpPreset preset )
{
VipsImage *memory;
size_t len;
uint8_t *buffer;
FILE *fp;
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 );
if( !(memory = vips_image_copy_memory( in )) )
return( -1 );
if( lossless ) {
webp_encoder_lossless encoder;
if( in->Bands == 4 )
encoder = WebPEncodeLosslessRGBA;
else
encoder = WebPEncodeLosslessRGB;
if( !(len = encoder( VIPS_IMAGE_ADDR( memory, 0, 0 ),
memory->Xsize, memory->Ysize,
VIPS_IMAGE_SIZEOF_LINE( memory ),
&buffer )) ) {
VIPS_UNREF( memory );
vips_error( "vips2webp",
"%s", _( "unable to encode" ) );
return( -1 );
}
}
else {
webp_encoder encoder;
if( in->Bands == 4 )
encoder = WebPEncodeRGBA;
else
encoder = WebPEncodeRGB;
if( !(len = encoder( VIPS_IMAGE_ADDR( memory, 0, 0 ),
memory->Xsize, memory->Ysize,
VIPS_IMAGE_SIZEOF_LINE( memory ),
Q, &buffer )) ) {
VIPS_UNREF( memory );
vips_error( "vips2webp",
"%s", _( "unable to encode" ) );
return( -1 );
}
default:
g_assert_not_reached();
}
VIPS_UNREF( memory );
if( !(fp = vips__file_open_write( filename, FALSE )) ) {
free( buffer );
return( -1 );
}
if( vips__file_write( buffer, len, 1, fp ) ) {
fclose( fp );
free( buffer );
return( -1 );
}
fclose( fp );
free( buffer );
return( 0 );
/* Keep -Wall happy.
*/
return( -1 );
}
int
vips__webp_write_buffer( VipsImage *in, void **obuf, size_t *olen,
int Q, gboolean lossless )
typedef struct {
uint8_t *mem;
size_t size;
size_t max_size;
} VipsWebPMemoryWriter;
static void
init_memory_writer( VipsWebPMemoryWriter *writer ) {
writer->mem = NULL;
writer->size = 0;
writer->max_size = 0;
}
static int
memory_write( const uint8_t *data, size_t data_size,
const WebPPicture *picture ) {
VipsWebPMemoryWriter* const w = (VipsWebPMemoryWriter*) picture->custom_ptr;
size_t next_size;
if( w == NULL )
return( 0 );
next_size = w->size + data_size;
if( next_size > w->max_size ) {
uint8_t *new_mem;
const size_t next_max_size =
VIPS_MAX( 8192, VIPS_MAX( next_size, w->max_size * 2 ) );
new_mem = (uint8_t*) g_try_malloc( next_max_size );
if( new_mem == NULL )
return( 0 );
if( w->size > 0 )
memcpy( new_mem, w->mem, w->size );
g_free( w->mem );
w->mem = new_mem;
w->max_size = next_max_size;
}
if( data_size > 0 ) {
memcpy( w->mem + w->size, data, data_size );
w->size += data_size;
}
return( 1 );
}
static int
write_webp( WebPPicture *pic, VipsImage *in,
int Q, gboolean lossless, VipsForeignWebpPreset preset,
gboolean smart_subsample, gboolean near_lossless,
int alpha_q )
{
VipsImage *memory;
WebPConfig config;
webp_import import;
if ( !WebPConfigPreset(&config, get_preset( preset ), Q) ) {
vips_error( "vips2webp",
"%s", _( "config version error" ) );
return( -1 );
}
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
config.lossless = lossless || near_lossless;
config.alpha_quality = alpha_q;
/* Smart subsampling requires use_argb because
* it is applied during RGB to YUV conversion.
*/
pic->use_argb = lossless || near_lossless || smart_subsample;
#else
if( lossless || near_lossless )
vips_warn( "vips2webp",
"%s", _( "lossless unsupported" ) );
if( alpha_q != 100 )
vips_warn( "vips2webp",
"%s", _( "alpha_q unsupported" ) );
#endif
#if WEBP_ENCODER_ABI_VERSION >= 0x0209
if( near_lossless )
config.near_lossless = Q;
if( smart_subsample )
config.preprocessing |= 4;
#else
if( near_lossless )
vips_warn( "vips2webp",
"%s", _( "near_lossless unsupported" ) );
if( smart_subsample )
vips_warn( "vips2webp",
"%s", _( "smart_subsample unsupported" ) );
#endif
if( !WebPValidateConfig(&config) ) {
vips_error( "vips2webp",
"%s", _( "invalid configuration" ) );
return( -1 );
}
if( !(memory = vips_image_copy_memory( in )) )
return( -1 );
if( lossless ) {
webp_encoder_lossless encoder;
pic->width = memory->Xsize;
pic->height = memory->Ysize;
if( in->Bands == 4 )
encoder = WebPEncodeLosslessRGBA;
else
encoder = WebPEncodeLosslessRGB;
if( in->Bands == 4 )
import = WebPPictureImportRGBA;
else
import = WebPPictureImportRGB;
if( !(*olen = encoder( VIPS_IMAGE_ADDR( memory, 0, 0 ),
memory->Xsize, memory->Ysize,
VIPS_IMAGE_SIZEOF_LINE( memory ),
(uint8_t **) obuf )) ) {
VIPS_UNREF( memory );
vips_error( "vips2webp",
"%s", _( "unable to encode" ) );
return( -1 );
}
if( !import( pic, VIPS_IMAGE_ADDR( memory, 0, 0 ),
VIPS_IMAGE_SIZEOF_LINE( memory ) ) ) {
VIPS_UNREF( memory );
vips_error( "vips2webp",
"%s", _( "picture memory error" ) );
return( -1 );
}
else {
webp_encoder encoder;
if( in->Bands == 4 )
encoder = WebPEncodeRGBA;
else
encoder = WebPEncodeRGB;
if( !(*olen = encoder( VIPS_IMAGE_ADDR( memory, 0, 0 ),
memory->Xsize, memory->Ysize,
VIPS_IMAGE_SIZEOF_LINE( memory ),
Q, (uint8_t **) obuf )) ) {
VIPS_UNREF( memory );
vips_error( "vips2webp",
"%s", _( "unable to encode" ) );
return( -1 );
}
if( !WebPEncode( &config, pic ) ) {
VIPS_UNREF( memory );
vips_error( "vips2webp",
"%s", _( "unable to encode" ) );
return( -1 );
}
VIPS_UNREF( memory );
@ -179,4 +215,85 @@ vips__webp_write_buffer( VipsImage *in, void **obuf, size_t *olen,
return( 0 );
}
int
vips__webp_write_file( VipsImage *in, const char *filename,
int Q, gboolean lossless, VipsForeignWebpPreset preset,
gboolean smart_subsample, gboolean near_lossless,
int alpha_q )
{
WebPPicture pic;
VipsWebPMemoryWriter writer;
FILE *fp;
if( !WebPPictureInit( &pic ) ) {
vips_error( "vips2webp",
"%s", _( "picture version error" ) );
return( -1 );
}
init_memory_writer( &writer );
pic.writer = memory_write;
pic.custom_ptr = &writer;
if( write_webp( &pic, in, Q, lossless, preset, smart_subsample,
near_lossless, alpha_q ) ) {
WebPPictureFree( &pic );
g_free( writer.mem );
return -1;
}
WebPPictureFree( &pic );
if( !(fp = vips__file_open_write( filename, FALSE )) ) {
g_free( writer.mem );
return( -1 );
}
if( vips__file_write( writer.mem, writer.size, 1, fp ) ) {
fclose( fp );
g_free( writer.mem );
return( -1 );
}
fclose( fp );
g_free( writer.mem );
return( 0 );
}
int
vips__webp_write_buffer( VipsImage *in, void **obuf, size_t *olen,
int Q, gboolean lossless, VipsForeignWebpPreset preset,
gboolean smart_subsample, gboolean near_lossless,
int alpha_q )
{
WebPPicture pic;
VipsWebPMemoryWriter writer;
FILE *fp;
if( !WebPPictureInit( &pic ) ) {
vips_error( "vips2webp",
"%s", _( "picture version error" ) );
return( -1 );
}
init_memory_writer( &writer );
pic.writer = memory_write;
pic.custom_ptr = &writer;
if( write_webp( &pic, in, Q, lossless, preset, smart_subsample,
near_lossless, alpha_q ) ) {
WebPPictureFree( &pic );
g_free( writer.mem );
return -1;
}
WebPPictureFree( &pic );
*obuf = writer.mem;
*olen = writer.size;
return( 0 );
}
#endif /*HAVE_LIBWEBP*/

View File

@ -49,9 +49,13 @@ int vips__webp_read_buffer( const void *buf, size_t len,
VipsImage *out, int shrink );
int vips__webp_write_file( VipsImage *out, const char *filename,
int Q, gboolean lossless );
int Q, gboolean lossless, VipsForeignWebpPreset preset,
gboolean smart_subsample, gboolean near_lossless,
int alpha_q );
int vips__webp_write_buffer( VipsImage *out, void **buf, size_t *len,
int Q, gboolean lossless );
int Q, gboolean lossless, VipsForeignWebpPreset preset,
gboolean smart_subsample, gboolean near_lossless,
int alpha_q );
#ifdef __cplusplus
}

View File

@ -60,6 +60,22 @@ typedef struct _VipsForeignSaveWebp {
*/
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;
} VipsForeignSaveWebp;
typedef VipsForeignSaveClass VipsForeignSaveWebpClass;
@ -100,7 +116,7 @@ vips_foreign_save_webp_class_init( VipsForeignSaveWebpClass *class )
_( "Q factor" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveWebp, Q ),
1, 100, 75 );
0, 100, 75 );
VIPS_ARG_BOOL( class, "lossless", 11,
_( "lossless" ),
@ -109,12 +125,41 @@ vips_foreign_save_webp_class_init( VipsForeignSaveWebpClass *class )
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 );
}
static void
vips_foreign_save_webp_init( VipsForeignSaveWebp *webp )
{
webp->Q = 80;
webp->Q = 75;
}
typedef struct _VipsForeignSaveWebpFile {
@ -143,7 +188,9 @@ vips_foreign_save_webp_file_build( VipsObject *object )
return( -1 );
if( vips__webp_write_file( save->ready, file->filename,
webp->Q, webp->lossless ) )
webp->Q, webp->lossless, webp->preset,
webp->smart_subsample, webp->near_lossless,
webp->alpha_q ) )
return( -1 );
return( 0 );
@ -205,7 +252,9 @@ vips_foreign_save_webp_buffer_build( VipsObject *object )
return( -1 );
if( vips__webp_write_buffer( save->ready, &obuf, &olen,
webp->Q, webp->lossless ) )
webp->Q, webp->lossless, webp->preset,
webp->smart_subsample, webp->near_lossless,
webp->alpha_q ) )
return( -1 );
blob = vips_blob_new( (VipsCallbackFn) vips_free, obuf, olen );
@ -266,7 +315,9 @@ vips_foreign_save_webp_mime_build( VipsObject *object )
return( -1 );
if( vips__webp_write_buffer( save->ready, &obuf, &olen,
webp->Q, webp->lossless ) )
webp->Q, webp->lossless, webp->preset,
webp->smart_subsample, webp->near_lossless,
webp->alpha_q ) )
return( -1 );
printf( "Content-length: %zu\r\n", olen );

View File

@ -14,6 +14,8 @@ GType vips_foreign_flags_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_FLAGS (vips_foreign_flags_get_type())
GType vips_saveable_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_SAVEABLE (vips_saveable_get_type())
GType vips_foreign_webp_preset_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_WEBP_PRESET (vips_foreign_webp_preset_get_type())
GType vips_foreign_tiff_compression_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_TIFF_COMPRESSION (vips_foreign_tiff_compression_get_type())
GType vips_foreign_tiff_predictor_get_type (void) G_GNUC_CONST;

View File

@ -338,6 +338,29 @@ int vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
int vips_jpegsave_mime( VipsImage *in, ... )
__attribute__((sentinel));
/**
* VipsForeignWebpPreset:
* @VIPS_FOREIGN_WEBP_PRESET_DEFAULT: default preset
* @VIPS_FOREIGN_WEBP_PRESET_PICTURE: digital picture, like portrait,
* inner shot
* @VIPS_FOREIGN_WEBP_PRESET_PHOTO: outdoor photograph, with natural lighting
* @VIPS_FOREIGN_WEBP_PRESET_DRAWING: hand or line drawing, with high-contrast
* details
* @VIPS_FOREIGN_WEBP_PRESET_ICON: small-sized colorful images
* @VIPS_FOREIGN_WEBP_PRESET_TEXT: text-like
*
* Tune lossy encoder settings for different image types.
*/
typedef enum {
VIPS_FOREIGN_WEBP_PRESET_DEFAULT,
VIPS_FOREIGN_WEBP_PRESET_PICTURE,
VIPS_FOREIGN_WEBP_PRESET_PHOTO,
VIPS_FOREIGN_WEBP_PRESET_DRAWING,
VIPS_FOREIGN_WEBP_PRESET_ICON,
VIPS_FOREIGN_WEBP_PRESET_TEXT,
VIPS_FOREIGN_WEBP_PRESET_LAST
} VipsForeignWebpPreset;
int vips_webpload( const char *filename, VipsImage **out, ... )
__attribute__((sentinel));
int vips_webpload_buffer( void *buf, size_t len, VipsImage **out, ... )

View File

@ -70,6 +70,28 @@ vips_saveable_get_type( void )
return( etype );
}
GType
vips_foreign_webp_preset_get_type( void )
{
static GType etype = 0;
if( etype == 0 ) {
static const GEnumValue values[] = {
{VIPS_FOREIGN_WEBP_PRESET_DEFAULT, "VIPS_FOREIGN_WEBP_PRESET_DEFAULT", "default"},
{VIPS_FOREIGN_WEBP_PRESET_PICTURE, "VIPS_FOREIGN_WEBP_PRESET_PICTURE", "picture"},
{VIPS_FOREIGN_WEBP_PRESET_PHOTO, "VIPS_FOREIGN_WEBP_PRESET_PHOTO", "photo"},
{VIPS_FOREIGN_WEBP_PRESET_DRAWING, "VIPS_FOREIGN_WEBP_PRESET_DRAWING", "drawing"},
{VIPS_FOREIGN_WEBP_PRESET_ICON, "VIPS_FOREIGN_WEBP_PRESET_ICON", "icon"},
{VIPS_FOREIGN_WEBP_PRESET_TEXT, "VIPS_FOREIGN_WEBP_PRESET_TEXT", "text"},
{VIPS_FOREIGN_WEBP_PRESET_LAST, "VIPS_FOREIGN_WEBP_PRESET_LAST", "last"},
{0, NULL, NULL}
};
etype = g_enum_register_static( "VipsForeignWebpPreset", values );
}
return( etype );
}
GType
vips_foreign_tiff_compression_get_type( void )
{
static GType etype = 0;

View File

@ -186,6 +186,10 @@ fi
if test_supported jpegload; then
test_format $image jpg 90
fi
if test_supported webpload; then
test_format $image webp 90
test_format $image webp 0 [lossless]
fi
test_format $image ppm 0
test_format $image pfm 0
if test_supported fitsload; then
@ -220,4 +224,3 @@ if test_supported dzsave; then
test_saver copy $image .dz
test_saver copy $image .dz[container=zip]
fi