parent
67740c5d37
commit
75da1472e8
40
configure.ac
40
configure.ac
|
@ -1029,44 +1029,22 @@ if test x"$with_cfitsio" != x"no"; then
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# libwebp
|
# libwebp ... target 0.5+ to reduce complication
|
||||||
# some platforms, like ubuntu 12.04, are missing the .pc files for libwebp, so
|
# webp has the stuff for handling metadata in two separate libraries -- we
|
||||||
# we fall back to FIND_LIBWEBP
|
# insit on having all of them
|
||||||
AC_ARG_WITH([libwebp],
|
AC_ARG_WITH([libwebp],
|
||||||
AS_HELP_STRING([--without-libwebp], [build without libwebp (default: test)]))
|
AS_HELP_STRING([--without-libwebp], [build without libwebp (default: test)]))
|
||||||
|
|
||||||
if test x"$with_libwebp" != x"no"; then
|
if test x"$with_libwebp" != x"no"; then
|
||||||
PKG_CHECK_MODULES(LIBWEBP, libwebp >= 0.1.3,
|
PKG_CHECK_MODULES(LIBWEBP, libwebp >= 0.5 libwebpmux >= 0.5 libwebpdemux >= 0.5,
|
||||||
[AC_DEFINE(HAVE_LIBWEBP,1,[define if you have libwebp installed.])
|
[AC_DEFINE(HAVE_LIBWEBP,1,[define if you have libwebp/libwebpmux/libwebpdemux installed.])
|
||||||
with_libwebp=yes
|
with_libwebp=yes
|
||||||
PACKAGES_USED="$PACKAGES_USED libwebp"
|
PACKAGES_USED="$PACKAGES_USED libwebp libwebpmux libwebpdemux"
|
||||||
],
|
],
|
||||||
[FIND_LIBWEBP(
|
[AC_MSG_WARN([libwebp, mux, demux not found; disabling WEBP support])
|
||||||
[with_libwebp="yes (found by search)"
|
|
||||||
],
|
|
||||||
[AC_MSG_WARN([libwebp not found; disabling WEBP support])
|
|
||||||
with_libwebp=no
|
with_libwebp=no
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
]
|
|
||||||
)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# webp has the stuff for pulling out ICC profile etc in a separate library
|
|
||||||
#
|
|
||||||
# we can build with libwebpmux back to 0.3, but it's not until libwebp 0.5 that
|
|
||||||
# we can read that metadata back successfully ... insist on 0.5 so that tests
|
|
||||||
# can work smoothly
|
|
||||||
if test x"$with_libwebp" != x"no"; then
|
|
||||||
PKG_CHECK_MODULES(LIBWEBPMUX, libwebpmux >= 0.5.0,
|
|
||||||
[AC_DEFINE(HAVE_LIBWEBPMUX,1,[define if you have libwebpmux installed.])
|
|
||||||
with_libwebpmux=yes
|
|
||||||
PACKAGES_USED="$PACKAGES_USED libwebpmux"
|
|
||||||
],
|
|
||||||
[AC_MSG_WARN([libwebpmux not found; disabling webp metadata support])
|
|
||||||
with_libwebpmux=no
|
|
||||||
]
|
|
||||||
)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# pangoft2
|
# pangoft2
|
||||||
|
@ -1419,9 +1397,7 @@ SVG import with librsvg-2.0: $with_rsvg
|
||||||
zlib: $with_zlib
|
zlib: $with_zlib
|
||||||
file import with cfitsio: $with_cfitsio
|
file import with cfitsio: $with_cfitsio
|
||||||
file import/export with libwebp: $with_libwebp
|
file import/export with libwebp: $with_libwebp
|
||||||
(requires libwebp-0.1.3 or later)
|
(requires libwebp, libwebpmux, libwebpdemux 0.5.0 or later)
|
||||||
support webp metadata: $with_libwebpmux
|
|
||||||
(requires libwebpmux-0.5 or later)
|
|
||||||
text rendering with pangoft2: $with_pangoft2
|
text rendering with pangoft2: $with_pangoft2
|
||||||
file import/export with libpng: $with_png
|
file import/export with libpng: $with_png
|
||||||
(requires libpng-1.2.9 or later)
|
(requires libpng-1.2.9 or later)
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
* - used advanced encoding API, expose controls
|
* - used advanced encoding API, expose controls
|
||||||
* 8/11/16
|
* 8/11/16
|
||||||
* - add metadata write
|
* - add metadata write
|
||||||
|
* 29/10/18
|
||||||
|
* - target libwebp 0.5+ and remove some ifdefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -58,10 +60,35 @@
|
||||||
#include "pforeign.h"
|
#include "pforeign.h"
|
||||||
|
|
||||||
#include <webp/encode.h>
|
#include <webp/encode.h>
|
||||||
|
#include <webp/types.h>
|
||||||
|
#include <webp/mux.h>
|
||||||
|
|
||||||
typedef int (*webp_import)( WebPPicture *picture,
|
typedef int (*webp_import)( WebPPicture *picture,
|
||||||
const uint8_t *rgb, int stride );
|
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;
|
||||||
|
gboolean strip;
|
||||||
|
|
||||||
|
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;
|
||||||
|
} VipsWebPWrite;
|
||||||
|
|
||||||
static WebPPreset
|
static WebPPreset
|
||||||
get_preset( VipsForeignWebpPreset preset )
|
get_preset( VipsForeignWebpPreset preset )
|
||||||
{
|
{
|
||||||
|
@ -88,63 +115,80 @@ get_preset( VipsForeignWebpPreset preset )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t *mem;
|
|
||||||
|
|
||||||
/* We want to be able to detect >4gb even on machines that have size_t
|
|
||||||
* as uint32.
|
|
||||||
*/
|
|
||||||
guint64 size;
|
|
||||||
guint64 max_size;
|
|
||||||
} VipsWebPWriter;
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
vips_webp_writer_init( VipsWebPWriter *writer )
|
vips_webp_write_unset( VipsWebPWrite *write )
|
||||||
{
|
{
|
||||||
writer->mem = NULL;
|
WebPMemoryWriterClear( &write->memory_writer );
|
||||||
writer->size = 0;
|
VIPS_FREEF( WebPAnimEncoderDelete, write->enc );
|
||||||
writer->max_size = 0;
|
}
|
||||||
|
|
||||||
|
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, gboolean strip )
|
||||||
|
{
|
||||||
|
write->image = image;
|
||||||
|
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->strip = strip;
|
||||||
|
WebPMemoryWriterInit( &write->memory_writer );
|
||||||
|
write->enc = NULL;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if( near_lossless )
|
||||||
|
write->config.near_lossless = Q;
|
||||||
|
if( smart_subsample )
|
||||||
|
write->config.preprocessing |= 4;
|
||||||
|
|
||||||
|
if( !WebPValidateConfig( &write->config ) ) {
|
||||||
|
vips_webp_write_unset( write );
|
||||||
|
vips_error( "vips2webp", "%s", _( "invalid configuration" ) );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
vips_webp_writer_append( VipsWebPWriter *writer,
|
vips_webp_write_append( VipsWebPWrite *write,
|
||||||
const uint8_t *data, guint64 data_size )
|
const uint8_t *data, guint64 data_size )
|
||||||
{
|
{
|
||||||
guint64 next_size;
|
WebPPicture pic;
|
||||||
|
|
||||||
next_size = writer->size + data_size;
|
/* Yuk! Sadly libwebp does not have a proper interface to this type.
|
||||||
|
|
||||||
if( next_size > writer->max_size ) {
|
|
||||||
uint8_t *new_mem;
|
|
||||||
const guint64 next_max_size =
|
|
||||||
VIPS_MAX( 8192, VIPS_MAX( next_size,
|
|
||||||
writer->max_size * 2 ) );
|
|
||||||
|
|
||||||
/* We should let it creep up to 4gb rather than just
|
|
||||||
* blocking when max goes over, but no one will make a >2gb
|
|
||||||
* webp image.
|
|
||||||
*/
|
*/
|
||||||
if( next_max_size > UINT_MAX ) {
|
pic.custom_ptr = (void *) &write->memory_writer;
|
||||||
|
if( WebPMemoryWrite( data, data_size, &pic ) ) {
|
||||||
vips_error( "webp",
|
vips_error( "webp",
|
||||||
"%s", _( "output webp image too large" ) );
|
"%s", _( "output webp image too large" ) );
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !(new_mem = (uint8_t *)
|
|
||||||
g_try_realloc( writer->mem, next_max_size )) ) {
|
|
||||||
vips_error( "webp", "%s", _( "out of memory" ) );
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
writer->mem = new_mem;
|
|
||||||
writer->max_size = next_max_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( data_size > 0 ) {
|
|
||||||
memcpy( writer->mem + writer->size, data, data_size );
|
|
||||||
writer->size += data_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return( 1 );
|
return( 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,9 +198,8 @@ vips_webp_writer_append( VipsWebPWriter *writer,
|
||||||
* Only attach metadata if we have something to read it back, otherwise
|
* Only attach metadata if we have something to read it back, otherwise
|
||||||
* lots of our tests start failing.
|
* lots of our tests start failing.
|
||||||
*/
|
*/
|
||||||
#ifdef HAVE_LIBWEBPMUX
|
|
||||||
static gboolean
|
static gboolean
|
||||||
vips_webp_writer_appendle( VipsWebPWriter *writer, uint32_t val, int n )
|
vips_webp_write_appendle( VipsWebPWrite *write, uint32_t val, int n )
|
||||||
{
|
{
|
||||||
unsigned char buf[4];
|
unsigned char buf[4];
|
||||||
int i;
|
int i;
|
||||||
|
@ -168,131 +211,86 @@ vips_webp_writer_appendle( VipsWebPWriter *writer, uint32_t val, int n )
|
||||||
val >>= 8;
|
val >>= 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
return( vips_webp_writer_append( writer, buf, n ) );
|
return( vips_webp_write_append( write, buf, n ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
vips_webp_writer_appendle32( VipsWebPWriter *writer, uint32_t val )
|
vips_webp_write_appendle32( VipsWebPWrite *write, uint32_t val )
|
||||||
{
|
{
|
||||||
return( vips_webp_writer_appendle( writer, val, 4 ) );
|
return( vips_webp_write_appendle( write, val, 4 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
vips_webp_writer_appendle24( VipsWebPWriter *writer, uint32_t val )
|
vips_webp_write_appendle24( VipsWebPWrite *write, uint32_t val )
|
||||||
{
|
{
|
||||||
return( vips_webp_writer_appendle( writer, val, 3 ) );
|
return( vips_webp_write_appendle( write, val, 3 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
vips_webp_writer_appendcc( VipsWebPWriter *writer, const char buf[4] )
|
vips_webp_write_appendcc( VipsWebPWrite *write, const char buf[4] )
|
||||||
{
|
{
|
||||||
return( vips_webp_writer_append( writer, (const uint8_t *) buf, 4 ) );
|
return( vips_webp_write_append( write, (const uint8_t *) buf, 4 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
vips_webp_writer_appendc( VipsWebPWriter *writer,
|
vips_webp_write_appendc( VipsWebPWrite *write,
|
||||||
const char fourcc[4], const uint8_t *data, guint64 data_size )
|
const char fourcc[4], const uint8_t *data, guint64 data_size )
|
||||||
{
|
{
|
||||||
const int zero = 0;
|
const int zero = 0;
|
||||||
gboolean need_padding = (data_size & 1) != 0;
|
gboolean need_padding = (data_size & 1) != 0;
|
||||||
|
|
||||||
if( !vips_webp_writer_appendcc( writer, fourcc ) ||
|
if( !vips_webp_write_appendcc( write, fourcc ) ||
|
||||||
!vips_webp_writer_appendle32( writer, data_size ) ||
|
!vips_webp_write_appendle32( write, data_size ) ||
|
||||||
!vips_webp_writer_append( writer, data, data_size ) )
|
!vips_webp_write_append( write, data, data_size ) )
|
||||||
return( 0 );
|
return( 0 );
|
||||||
|
|
||||||
if( need_padding &&
|
if( need_padding &&
|
||||||
!vips_webp_writer_append( writer, (const uint8_t *) &zero, 1 ) )
|
!vips_webp_write_append( write, (const uint8_t *) &zero, 1 ) )
|
||||||
return( 0 );
|
return( 0 );
|
||||||
|
|
||||||
return( 1 );
|
return( 1 );
|
||||||
}
|
}
|
||||||
#endif /*HAVE_LIBWEBPMUX*/
|
|
||||||
|
|
||||||
static void
|
|
||||||
vips_webp_writer_unset( VipsWebPWriter *writer )
|
|
||||||
{
|
|
||||||
VIPS_FREE( writer->mem );
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
memory_write( const uint8_t *data, size_t data_size,
|
vips_webp_pic_init( VipsWebPWrite *write, WebPPicture *pic )
|
||||||
const WebPPicture *picture )
|
|
||||||
{
|
{
|
||||||
VipsWebPWriter * const writer =
|
if( !WebPPictureInit( pic ) ) {
|
||||||
(VipsWebPWriter *) picture->custom_ptr;
|
vips_error( "vips2webp", "%s", _( "picture version error" ) );
|
||||||
|
return( FALSE );
|
||||||
|
}
|
||||||
|
pic->writer = WebPMemoryWrite;
|
||||||
|
pic->custom_ptr = (void *) &write->memory_writer;
|
||||||
|
|
||||||
if( !writer )
|
/* Smart subsampling needs use_argb because it is applied during
|
||||||
return( 0 );
|
* RGB to YUV conversion.
|
||||||
|
*/
|
||||||
|
pic->use_argb = write->lossless ||
|
||||||
|
write->near_lossless ||
|
||||||
|
write->smart_subsample;
|
||||||
|
|
||||||
return( vips_webp_writer_append( writer, data, data_size ) );
|
return( TRUE );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Write a VipsImage into an unintialised pic.
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
write_webp( WebPPicture *pic, VipsImage *in,
|
write_webp_image( VipsWebPWrite *write, VipsImage *image, WebPPicture *pic )
|
||||||
int Q, gboolean lossless, VipsForeignWebpPreset preset,
|
|
||||||
gboolean smart_subsample, gboolean near_lossless,
|
|
||||||
int alpha_q )
|
|
||||||
{
|
{
|
||||||
VipsImage *memory;
|
VipsImage *memory;
|
||||||
WebPConfig config;
|
|
||||||
webp_import import;
|
webp_import import;
|
||||||
|
|
||||||
if( !WebPConfigInit( &config ) ) {
|
if( !vips_webp_pic_init( write, pic ) )
|
||||||
vips_error( "vips2webp",
|
return( -1 );
|
||||||
"%s", _( "config version error" ) );
|
|
||||||
|
if( !(memory = vips_image_copy_memory( image )) ) {
|
||||||
|
WebPPictureFree( pic );
|
||||||
return( -1 );
|
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( &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 needs use_argb because
|
|
||||||
* it is applied during RGB to YUV conversion.
|
|
||||||
*/
|
|
||||||
pic->use_argb = lossless || near_lossless || smart_subsample;
|
|
||||||
#else
|
|
||||||
if( lossless ||
|
|
||||||
near_lossless )
|
|
||||||
g_warning( "%s", _( "lossless unsupported" ) );
|
|
||||||
if( alpha_q != 100 )
|
|
||||||
g_warning( "%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 )
|
|
||||||
g_warning( "%s", _( "near_lossless unsupported" ) );
|
|
||||||
if( smart_subsample )
|
|
||||||
g_warning( "%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 );
|
|
||||||
|
|
||||||
pic->width = memory->Xsize;
|
pic->width = memory->Xsize;
|
||||||
pic->height = memory->Ysize;
|
pic->height = memory->Ysize;
|
||||||
|
|
||||||
if( in->Bands == 4 )
|
if( memory->Bands == 4 )
|
||||||
import = WebPPictureImportRGBA;
|
import = WebPPictureImportRGBA;
|
||||||
else
|
else
|
||||||
import = WebPPictureImportRGB;
|
import = WebPPictureImportRGB;
|
||||||
|
@ -300,24 +298,138 @@ write_webp( WebPPicture *pic, VipsImage *in,
|
||||||
if( !import( pic, VIPS_IMAGE_ADDR( memory, 0, 0 ),
|
if( !import( pic, VIPS_IMAGE_ADDR( memory, 0, 0 ),
|
||||||
VIPS_IMAGE_SIZEOF_LINE( memory ) ) ) {
|
VIPS_IMAGE_SIZEOF_LINE( memory ) ) ) {
|
||||||
VIPS_UNREF( memory );
|
VIPS_UNREF( memory );
|
||||||
|
WebPPictureFree( pic );
|
||||||
vips_error( "vips2webp", "%s", _( "picture memory error" ) );
|
vips_error( "vips2webp", "%s", _( "picture memory error" ) );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !WebPEncode( &config, pic ) ) {
|
|
||||||
VIPS_UNREF( memory );
|
|
||||||
vips_error( "vips2webp", "%s", _( "unable to encode" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
VIPS_UNREF( memory );
|
VIPS_UNREF( memory );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_LIBWEBPMUX
|
/* Write a single image into write->memory_writer.
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
vips_webp_add_chunk( VipsWebPWriter *writer, VipsImage *image,
|
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 )
|
||||||
|
{
|
||||||
|
int delay;
|
||||||
|
WebPAnimEncoderOptions anim_config;
|
||||||
|
WebPData webp_data;
|
||||||
|
int top;
|
||||||
|
int timestamp_ms;
|
||||||
|
|
||||||
|
/* FIXME get delay
|
||||||
|
*/
|
||||||
|
delay = 16;
|
||||||
|
|
||||||
|
if( !WebPAnimEncoderOptionsInit( &anim_config ) ) {
|
||||||
|
vips_error( "vips2webp",
|
||||||
|
"%s", _( "config version error" ) );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
write->enc = WebPAnimEncoderNew( image->Xsize, page_height,
|
||||||
|
&anim_config );
|
||||||
|
if( !write->enc ) {
|
||||||
|
vips_error( "vips2webp",
|
||||||
|
"%s", _( "unable to init animation" ) );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp_ms = 0;
|
||||||
|
for( top = 0; top < image->Ysize; top += page_height ) {
|
||||||
|
VipsImage *x;
|
||||||
|
WebPPicture pic;
|
||||||
|
|
||||||
|
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 );
|
||||||
|
|
||||||
|
timestamp_ms += delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a last fake frame to signal the last duration.
|
||||||
|
*/
|
||||||
|
if( !WebPAnimEncoderAdd( write->enc, NULL, timestamp_ms, NULL ) ||
|
||||||
|
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, VipsImage *image )
|
||||||
|
{
|
||||||
|
int page_height;
|
||||||
|
|
||||||
|
page_height = 0;
|
||||||
|
if( vips_image_get_typeof( image, VIPS_META_PAGE_HEIGHT ) &&
|
||||||
|
vips_image_get_int( image, VIPS_META_PAGE_HEIGHT,
|
||||||
|
&page_height ) )
|
||||||
|
;
|
||||||
|
|
||||||
|
if( page_height > 0 )
|
||||||
|
return( write_webp_anim( write, image, page_height ) );
|
||||||
|
else
|
||||||
|
return( write_webp_single( write, image ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
vips_webp_add_chunk( VipsWebPWrite *write, VipsImage *image,
|
||||||
const char *vips, const char webp[4] )
|
const char *vips, const char webp[4] )
|
||||||
{
|
{
|
||||||
if( vips_image_get_typeof( image, vips ) ) {
|
if( vips_image_get_typeof( image, vips ) ) {
|
||||||
|
@ -328,14 +440,14 @@ vips_webp_add_chunk( VipsWebPWriter *writer, VipsImage *image,
|
||||||
*/
|
*/
|
||||||
(void) vips_image_get_blob( image, vips, &data, &length );
|
(void) vips_image_get_blob( image, vips, &data, &length );
|
||||||
|
|
||||||
if( !vips_webp_writer_appendc( writer, webp, data, length ) )
|
if( !vips_webp_write_appendc( write, webp, data, length ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Turn @writer into a VP8X image with metadata from @image.
|
/* Turn @write into a VP8X image with metadata from @image.
|
||||||
*
|
*
|
||||||
* Based (partly) on cwep.c
|
* Based (partly) on cwep.c
|
||||||
*
|
*
|
||||||
|
@ -368,18 +480,26 @@ vips_webp_add_chunk( VipsWebPWriter *writer, VipsImage *image,
|
||||||
* https://developers.google.com/speed/webp/docs/riff_container
|
* https://developers.google.com/speed/webp/docs/riff_container
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image )
|
vips_webp_add_metadata( VipsWebPWrite *write, VipsImage *image )
|
||||||
{
|
{
|
||||||
/* The image in @writer may be VP8X already.
|
/* The chunk of data we've written so far. This is awful: we have to
|
||||||
|
* rewrite the entire image, pasting back bits from the first time
|
||||||
|
* around.
|
||||||
*/
|
*/
|
||||||
gboolean is_vp8x = !memcmp( writer->mem + 12, "VP8X", 4 );
|
WebPMemoryWriter old_memory_writer = write->memory_writer;
|
||||||
gboolean is_lossless = !memcmp( writer->mem + 12, "VP8L", 4 );
|
uint8_t *old = old_memory_writer.mem;
|
||||||
|
size_t old_size = old_memory_writer.size;
|
||||||
|
|
||||||
|
/* The image in @write may be VP8X already.
|
||||||
|
*/
|
||||||
|
gboolean is_vp8x = !memcmp( old + 12, "VP8X", 4 );
|
||||||
|
gboolean is_lossless = !memcmp( old + 12, "VP8L", 4 );
|
||||||
|
|
||||||
guint64 metadata_size;
|
guint64 metadata_size;
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
guint64 new_size;
|
guint64 new_size;
|
||||||
VipsWebPWriter new;
|
|
||||||
|
|
||||||
/* Rebuild the EXIF block, if any, ready for writing.
|
/* Rebuild the EXIF block, if any, ready for writing.
|
||||||
*/
|
*/
|
||||||
|
@ -393,7 +513,7 @@ vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image )
|
||||||
|
|
||||||
/* If there are any flags there already, we add to them.
|
/* If there are any flags there already, we add to them.
|
||||||
*/
|
*/
|
||||||
flags = is_vp8x ? writer->mem[20] : 0;
|
flags = is_vp8x ? old[20] : 0;
|
||||||
|
|
||||||
for( i = 0; i < vips__n_webp_names; i++ ) {
|
for( i = 0; i < vips__n_webp_names; i++ ) {
|
||||||
const char *vips = vips__webp_names[i].vips;
|
const char *vips = vips__webp_names[i].vips;
|
||||||
|
@ -425,27 +545,32 @@ vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image )
|
||||||
/* If it's not already vp8x, we'll need to add a vp8x header, and
|
/* If it's not already vp8x, we'll need to add a vp8x header, and
|
||||||
* that'll add 18 bytes. -8 since size includes the RIFF header.
|
* that'll add 18 bytes. -8 since size includes the RIFF header.
|
||||||
*/
|
*/
|
||||||
new_size = writer->size - 8 + (is_vp8x ? 0 : 18) + metadata_size;
|
new_size = old_size - 8 + (is_vp8x ? 0 : 18) + metadata_size;
|
||||||
|
|
||||||
vips_webp_writer_init( &new );
|
write->memory_writer.mem = NULL;
|
||||||
|
write->memory_writer.size = 0;
|
||||||
|
write->memory_writer.max_size = 0;
|
||||||
|
|
||||||
if( !vips_webp_writer_appendcc( &new, "RIFF" ) ||
|
if( !vips_webp_write_appendcc( write, "RIFF" ) ||
|
||||||
!vips_webp_writer_appendle32( &new, new_size ) ||
|
!vips_webp_write_appendle32( write, new_size ) ||
|
||||||
!vips_webp_writer_appendcc( &new, "WEBP" ) ||
|
!vips_webp_write_appendcc( write, "WEBP" ) ||
|
||||||
!vips_webp_writer_appendcc( &new, "VP8X" ) ||
|
!vips_webp_write_appendcc( write, "VP8X" ) ||
|
||||||
!vips_webp_writer_appendle32( &new, 10 ) ) {
|
!vips_webp_write_appendle32( write, 10 ) ) {
|
||||||
vips_webp_writer_unset( &new );
|
WebPMemoryWriterClear( &old_memory_writer );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FIXME loop count has to go in somewhere too.
|
||||||
|
*/
|
||||||
|
|
||||||
if( is_vp8x ) {
|
if( is_vp8x ) {
|
||||||
/* Copy the existing VP8X body and update the flag bits.
|
/* Copy the existing VP8X body and update the flag bits.
|
||||||
*/
|
*/
|
||||||
if( !vips_webp_writer_append( &new, writer->mem + 20, 10 ) ) {
|
if( !vips_webp_write_append( write, old + 20, 10 ) ) {
|
||||||
vips_webp_writer_unset( &new );
|
WebPMemoryWriterClear( &old_memory_writer );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
new.mem[20] = flags;
|
write->memory_writer.mem[20] = flags;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* We have to make a new vp8x header.
|
/* We have to make a new vp8x header.
|
||||||
|
@ -458,152 +583,131 @@ vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image )
|
||||||
* another 4 to skip the length, then bit 8 - 3 == 5
|
* another 4 to skip the length, then bit 8 - 3 == 5
|
||||||
*/
|
*/
|
||||||
if( is_lossless &&
|
if( is_lossless &&
|
||||||
(writer->mem[12 + 8 + 3] & (1 << 5)) )
|
(old[12 + 8 + 3] & (1 << 5)) )
|
||||||
flags |= 0x010;
|
flags |= 0x010;
|
||||||
|
|
||||||
/* 10 is the length of the VPX8X header chunk.
|
/* 10 is the length of the VPX8X header chunk.
|
||||||
*/
|
*/
|
||||||
if( !vips_webp_writer_appendle32( &new, flags ) ||
|
if( !vips_webp_write_appendle32( write, flags ) ||
|
||||||
!vips_webp_writer_appendle24( &new, image->Xsize - 1 ) ||
|
!vips_webp_write_appendle24( write,
|
||||||
!vips_webp_writer_appendle24( &new, image->Ysize- 1 ) ) {
|
image->Xsize - 1 ) ||
|
||||||
vips_webp_writer_unset( &new );
|
!vips_webp_write_appendle24( write,
|
||||||
|
image->Ysize - 1 ) ) {
|
||||||
|
WebPMemoryWriterClear( &old_memory_writer );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Extra chunks have to be in this order.
|
/* Extra chunks have to be in this order.
|
||||||
*/
|
*/
|
||||||
if( vips_webp_add_chunk( &new, image, VIPS_META_ICC_NAME, "ICCP" ) ) {
|
if( vips_webp_add_chunk( write, image, VIPS_META_ICC_NAME, "ICCP" ) ) {
|
||||||
vips_webp_writer_unset( &new );
|
WebPMemoryWriterClear( &old_memory_writer );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The image chunk must come here.
|
/* The image chunk must come here.
|
||||||
*/
|
*/
|
||||||
if( is_vp8x ) {
|
if( is_vp8x ) {
|
||||||
if( !vips_webp_writer_append( &new,
|
if( !vips_webp_write_append( write,
|
||||||
writer->mem + 30, writer->size - 30 ) ) {
|
old + 30, old_size - 30 ) ) {
|
||||||
vips_webp_writer_unset( &new );
|
WebPMemoryWriterClear( &old_memory_writer );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if( !vips_webp_writer_append( &new,
|
if( !vips_webp_write_append( write,
|
||||||
writer->mem + 12, writer->size - 12 ) ) {
|
old + 12, old_size - 12 ) ) {
|
||||||
vips_webp_writer_unset( &new );
|
WebPMemoryWriterClear( &old_memory_writer );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( vips_webp_add_chunk( &new, image, VIPS_META_EXIF_NAME, "EXIF" ) ) {
|
WebPMemoryWriterClear( &old_memory_writer );
|
||||||
vips_webp_writer_unset( &new );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( vips_webp_add_chunk( &new, image, VIPS_META_XMP_NAME, "XMP " ) ) {
|
if( vips_webp_add_chunk( write, image, VIPS_META_EXIF_NAME, "EXIF" ) )
|
||||||
vips_webp_writer_unset( &new );
|
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
|
||||||
|
|
||||||
vips_webp_writer_unset( writer );
|
if( vips_webp_add_chunk( write, image, VIPS_META_XMP_NAME, "XMP " ) )
|
||||||
*writer = new;
|
return( -1 );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
#endif /*HAVE_LIBWEBPMUX*/
|
|
||||||
|
|
||||||
int
|
int
|
||||||
vips__webp_write_file( VipsImage *in, const char *filename,
|
vips__webp_write_file( VipsImage *image, const char *filename,
|
||||||
int Q, gboolean lossless, VipsForeignWebpPreset preset,
|
int Q, gboolean lossless, VipsForeignWebpPreset preset,
|
||||||
gboolean smart_subsample, gboolean near_lossless,
|
gboolean smart_subsample, gboolean near_lossless,
|
||||||
int alpha_q, gboolean strip )
|
int alpha_q, gboolean strip )
|
||||||
{
|
{
|
||||||
WebPPicture pic;
|
VipsWebPWrite write;
|
||||||
VipsWebPWriter writer;
|
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
|
||||||
if( !WebPPictureInit( &pic ) ) {
|
if( vips_webp_write_init( &write, image,
|
||||||
vips_error( "vips2webp",
|
Q, lossless, preset, smart_subsample, near_lossless,
|
||||||
"%s", _( "picture version error" ) );
|
alpha_q, strip ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
if( write_webp( &write, image ) ) {
|
||||||
|
vips_webp_write_unset( &write );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
vips_webp_writer_init( &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 );
|
|
||||||
vips_webp_writer_unset( &writer );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
WebPPictureFree( &pic );
|
|
||||||
|
|
||||||
#ifdef HAVE_LIBWEBPMUX
|
|
||||||
if( !strip &&
|
if( !strip &&
|
||||||
vips_webp_add_metadata( &writer, in ) ) {
|
vips_webp_add_metadata( &write, image ) ) {
|
||||||
vips_webp_writer_unset( &writer );
|
vips_webp_write_unset( &write );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
#endif /*HAVE_LIBWEBPMUX*/
|
|
||||||
|
|
||||||
if( !(fp = vips__file_open_write( filename, FALSE )) ) {
|
if( !(fp = vips__file_open_write( filename, FALSE )) ) {
|
||||||
vips_webp_writer_unset( &writer );
|
vips_webp_write_unset( &write );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( vips__file_write( writer.mem, writer.size, 1, fp ) ) {
|
if( vips__file_write(
|
||||||
|
write.memory_writer.mem, write.memory_writer.size, 1, fp ) ) {
|
||||||
fclose( fp );
|
fclose( fp );
|
||||||
vips_webp_writer_unset( &writer );
|
vips_webp_write_unset( &write );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose( fp );
|
fclose( fp );
|
||||||
vips_webp_writer_unset( &writer );
|
|
||||||
|
vips_webp_write_unset( &write );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
vips__webp_write_buffer( VipsImage *in, void **obuf, size_t *olen,
|
vips__webp_write_buffer( VipsImage *image, void **obuf, size_t *olen,
|
||||||
int Q, gboolean lossless, VipsForeignWebpPreset preset,
|
int Q, gboolean lossless, VipsForeignWebpPreset preset,
|
||||||
gboolean smart_subsample, gboolean near_lossless,
|
gboolean smart_subsample, gboolean near_lossless,
|
||||||
int alpha_q, gboolean strip )
|
int alpha_q, gboolean strip )
|
||||||
{
|
{
|
||||||
WebPPicture pic;
|
VipsWebPWrite write;
|
||||||
VipsWebPWriter writer;
|
|
||||||
|
|
||||||
if( !WebPPictureInit( &pic ) ) {
|
if( vips_webp_write_init( &write, image,
|
||||||
vips_error( "vips2webp",
|
Q, lossless, preset, smart_subsample, near_lossless,
|
||||||
"%s", _( "picture version error" ) );
|
alpha_q, strip ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
if( write_webp( &write, image ) ) {
|
||||||
|
vips_webp_write_unset( &write );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
vips_webp_writer_init( &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 );
|
|
||||||
vips_webp_writer_unset( &writer );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
WebPPictureFree( &pic );
|
|
||||||
|
|
||||||
#ifdef HAVE_LIBWEBPMUX
|
|
||||||
if( !strip &&
|
if( !strip &&
|
||||||
vips_webp_add_metadata( &writer, in ) ) {
|
vips_webp_add_metadata( &write, image ) ) {
|
||||||
vips_webp_writer_unset( &writer );
|
vips_webp_write_unset( &write );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
#endif /*HAVE_LIBWEBPMUX*/
|
|
||||||
|
|
||||||
*obuf = writer.mem;
|
*obuf = write.memory_writer.mem;
|
||||||
*olen = writer.size;
|
*olen = write.memory_writer.size;
|
||||||
|
write.memory_writer.mem = NULL;
|
||||||
|
write.memory_writer.size = 0;
|
||||||
|
write.memory_writer.max_size = 0;
|
||||||
|
|
||||||
|
vips_webp_write_unset( &write );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue