add webp metadata write

untested, but it does compile
This commit is contained in:
John Cupitt 2016-11-08 12:30:02 +00:00
parent 47222bc1f5
commit b56b888bf0
5 changed files with 330 additions and 49 deletions

View File

@ -6,7 +6,7 @@
- better vipsheader behaviour with complex field types - better vipsheader behaviour with complex field types
- added vips_image_hasalpha() - added vips_image_hasalpha()
- added vips_thumbnail() / vips_thumbnail_buffer() - added vips_thumbnail() / vips_thumbnail_buffer()
- webpload reads icc, xmp, exif metadata - webpload/webpsave read and write icc, xmp, exif metadata
18/10/16 started 8.4.3 18/10/16 started 8.4.3
- fix error detection in gif_close, thanks aaron42net - fix error detection in gif_close, thanks aaron42net

12
TODO
View File

@ -2,6 +2,18 @@
test with ubuntu ... seems to have libwebpmux2 test with ubuntu ... seems to have libwebpmux2
test webp metadata read and write
webpsave should support strip
update docs
need to check for >4gb overflow ... use guint64 for all size calculations
webpsave needs hooking up to exif.c
- not sure about utf8 error messages on win - not sure about utf8 error messages on win
- strange: - strange:

View File

@ -196,9 +196,14 @@ int vips__png_write_buf( VipsImage *in,
typedef struct _VipsWebPNames { typedef struct _VipsWebPNames {
const char *vips; const char *vips;
const char *webp; const char *webp;
/* The webp flag bit for this chunk of metadata
*/
int flag;
} VipsWebPNames; } VipsWebPNames;
extern const VipsWebPNames vips__webp_names[]; extern const VipsWebPNames vips__webp_names[];
extern const int vips__n_webp_names;
extern const char *vips__webp_suffs[]; extern const char *vips__webp_suffs[];
int vips__iswebp_buffer( const void *buf, size_t len ); int vips__iswebp_buffer( const void *buf, size_t len );

View File

@ -6,6 +6,8 @@
* - buffer write ignored lossless, thanks aaron42net * - buffer write ignored lossless, thanks aaron42net
* 2/5/16 Felix Bünemann * 2/5/16 Felix Bünemann
* - used advanced encoding API, expose controls * - used advanced encoding API, expose controls
* 8/11/16
* - add metadata write
*/ */
/* /*
@ -89,26 +91,22 @@ typedef struct {
uint8_t *mem; uint8_t *mem;
size_t size; size_t size;
size_t max_size; size_t max_size;
} VipsWebPMemoryWriter; } VipsWebPWriter;
static void static void
init_memory_writer( VipsWebPMemoryWriter *writer ) { vips_webp_writer_init( VipsWebPWriter *writer )
{
writer->mem = NULL; writer->mem = NULL;
writer->size = 0; writer->size = 0;
writer->max_size = 0; writer->max_size = 0;
} }
static int static int
memory_write( const uint8_t *data, size_t data_size, vips_webp_writer_append( VipsWebPWriter *writer,
const WebPPicture *picture ) { const uint8_t *data, size_t data_size )
VipsWebPMemoryWriter * const writer = {
(VipsWebPMemoryWriter*) picture->custom_ptr;
size_t next_size; size_t next_size;
if( !writer )
return( 0 );
next_size = writer->size + data_size; next_size = writer->size + data_size;
if( next_size > writer->max_size ) { if( next_size > writer->max_size ) {
@ -117,13 +115,10 @@ memory_write( const uint8_t *data, size_t data_size,
VIPS_MAX( 8192, VIPS_MAX( next_size, VIPS_MAX( 8192, VIPS_MAX( next_size,
writer->max_size * 2 ) ); writer->max_size * 2 ) );
if( !(new_mem = (uint8_t*) g_try_malloc( next_max_size )) ) if( !(new_mem = (uint8_t *)
g_try_realloc( writer->mem, next_max_size )) )
return( 0 ); return( 0 );
if( writer->size > 0 )
memcpy( new_mem, writer->mem, writer->size );
g_free( writer->mem );
writer->mem = new_mem; writer->mem = new_mem;
writer->max_size = next_max_size; writer->max_size = next_max_size;
} }
@ -136,6 +131,78 @@ memory_write( const uint8_t *data, size_t data_size,
return( 1 ); return( 1 );
} }
static int
vips_webp_writer_appendle( VipsWebPWriter *writer, uint32_t val, int n )
{
unsigned char buf[4];
int i;
g_assert( n < 4 );
for( i = 0; i < n; i++ ) {
buf[i] = (unsigned char) (val & 0xff);
val >>= 8;
}
return( vips_webp_writer_append( writer, buf, n ) );
}
static int
vips_webp_writer_appendle32( VipsWebPWriter *writer, uint32_t val )
{
return( vips_webp_writer_appendle( writer, val, 4 ) );
}
static int
vips_webp_writer_appendle24( VipsWebPWriter *writer, uint32_t val )
{
return( vips_webp_writer_appendle( writer, val, 3 ) );
}
static int
vips_webp_writer_appendcc( VipsWebPWriter *writer, const char buf[4] )
{
return( vips_webp_writer_append( writer, (const uint8_t *) buf, 4 ) );
}
static gboolean
vips_webp_writer_appendc( VipsWebPWriter *writer,
const char fourcc[4], const uint8_t *data, size_t data_size )
{
const int zero = 0;
gboolean need_padding = (data_size & 1) != 0;
if( !vips_webp_writer_appendcc( writer, fourcc ) ||
!vips_webp_writer_appendle32( writer, data_size ) ||
!vips_webp_writer_append( writer, data, data_size ) )
return( 0 );
if( need_padding &&
!vips_webp_writer_append( writer, (const uint8_t *) &zero, 1 ) )
return( 0 );
return( 1 );
}
static void
vips_webp_writer_unset( VipsWebPWriter *writer )
{
VIPS_FREE( writer->mem );
}
static int
memory_write( const uint8_t *data, size_t data_size,
const WebPPicture *picture )
{
VipsWebPWriter * const writer =
(VipsWebPWriter *) picture->custom_ptr;
if( !writer )
return( 0 );
return( vips_webp_writer_append( writer, data, data_size ) );
}
static int static int
write_webp( WebPPicture *pic, VipsImage *in, write_webp( WebPPicture *pic, VipsImage *in,
int Q, gboolean lossless, VipsForeignWebpPreset preset, int Q, gboolean lossless, VipsForeignWebpPreset preset,
@ -147,25 +214,23 @@ write_webp( WebPPicture *pic, VipsImage *in,
webp_import import; webp_import import;
if ( !WebPConfigPreset( &config, get_preset( preset ), Q ) ) { if ( !WebPConfigPreset( &config, get_preset( preset ), Q ) ) {
vips_error( "vips2webp", vips_error( "vips2webp", "%s", _( "config version error" ) );
"%s", _( "config version error" ) );
return( -1 ); return( -1 );
} }
#if WEBP_ENCODER_ABI_VERSION >= 0x0100 #if WEBP_ENCODER_ABI_VERSION >= 0x0100
config.lossless = lossless || near_lossless; config.lossless = lossless || near_lossless;
config.alpha_quality = alpha_q; config.alpha_quality = alpha_q;
/* Smart subsampling requires use_argb because /* Smart subsampling needs use_argb because
* it is applied during RGB to YUV conversion. * it is applied during RGB to YUV conversion.
*/ */
pic->use_argb = lossless || near_lossless || smart_subsample; pic->use_argb = lossless || near_lossless || smart_subsample;
#else #else
if( lossless || near_lossless ) if( lossless ||
vips_warn( "vips2webp", near_lossless )
"%s", _( "lossless unsupported" ) ); vips_warn( "vips2webp", "%s", _( "lossless unsupported" ) );
if( alpha_q != 100 ) if( alpha_q != 100 )
vips_warn( "vips2webp", vips_warn( "vips2webp", "%s", _( "alpha_q unsupported" ) );
"%s", _( "alpha_q unsupported" ) );
#endif #endif
#if WEBP_ENCODER_ABI_VERSION >= 0x0209 #if WEBP_ENCODER_ABI_VERSION >= 0x0209
@ -175,16 +240,14 @@ write_webp( WebPPicture *pic, VipsImage *in,
config.preprocessing |= 4; config.preprocessing |= 4;
#else #else
if( near_lossless ) if( near_lossless )
vips_warn( "vips2webp", vips_warn( "vips2webp", "%s", _( "near_lossless unsupported" ) );
"%s", _( "near_lossless unsupported" ) );
if( smart_subsample ) if( smart_subsample )
vips_warn( "vips2webp", vips_warn( "vips2webp",
"%s", _( "smart_subsample unsupported" ) ); "%s", _( "smart_subsample unsupported" ) );
#endif #endif
if( !WebPValidateConfig( &config ) ) { if( !WebPValidateConfig( &config ) ) {
vips_error( "vips2webp", vips_error( "vips2webp", "%s", _( "invalid configuration" ) );
"%s", _( "invalid configuration" ) );
return( -1 ); return( -1 );
} }
@ -202,15 +265,13 @@ 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 );
vips_error( "vips2webp", vips_error( "vips2webp", "%s", _( "picture memory error" ) );
"%s", _( "picture memory error" ) );
return( -1 ); return( -1 );
} }
if( !WebPEncode( &config, pic ) ) { if( !WebPEncode( &config, pic ) ) {
VIPS_UNREF( memory ); VIPS_UNREF( memory );
vips_error( "vips2webp", vips_error( "vips2webp", "%s", _( "unable to encode" ) );
"%s", _( "unable to encode" ) );
return( -1 ); return( -1 );
} }
@ -219,6 +280,196 @@ write_webp( WebPPicture *pic, VipsImage *in,
return( 0 ); return( 0 );
} }
static int
vips_webp_add_chunk( VipsWebPWriter *writer, VipsImage *image,
const char *vips, const char webp[4] )
{
if( vips_image_get_typeof( image, vips) ) {
void *data;
size_t length;
/* We've done this before, it can't fail now.
*/
(void) vips_image_get_blob( image, vips, &data, &length );
if( !vips_webp_writer_appendc( writer, webp, data, length ) )
return( -1 );
}
return( 0 );
}
/* Turn @writer into a VP8X image with metadata from @image.
*
* Based (partly) on cwep.c
*
* We need to know some detail about the webp file format. The encoder will
* make something like this:
*
* 0 - 3 RIFF
* 4 - 7 size of this data chunk (byte 8 to end of file)
* 8 - 11 WEBP
* 12 - 15 VP8L (L for lossless, space for lossy, possibly 8 for alpha)
* 16 - 19 size of chunk (10 for VP8X, something else otherwise)
* 20 - 23 flags (VP8L has alpha in bit 29 of this; byte 20 has flags for VP8X)
* 24 -
*
* If there is metadata to add, we make a VP8X image, which looks like this:
*
* 0 - 3 RIFF
* 4 - 7 size of this data chunk (byte 8 to end of file)
* 8 - 11 WEBP
* 12 - 15 VP8X
* 16 - 19 10 (size of vp8x chunk)
* 20 - 23 flags
* 24 - 26 width - 1 (note: only 3 bytes)
* 27 - 29 height - 1 (note: only 3 bytes)
* 30 -
*
* Followed by ICCP, ANIM, image, EXIF and XMP chunks, in that order.
*
* See:
* https://developers.google.com/speed/webp/docs/riff_container
*/
static int
vips_webp_add_metadata( VipsWebPWriter *writer, VipsImage *image )
{
/* The image in @writer may be VP8X already.
*/
gboolean is_vp8x = !memcmp( writer->mem + 12, "VP8X", 4 );
gboolean is_lossless = !memcmp( writer->mem + 12, "VP8L", 4 );
guint64 metadata_size;
uint32_t flags;
int i;
guint64 new_size;
VipsWebPWriter new;
/* We have to find the size of the block we will write before we can
* start to write it.
*/
metadata_size = 0;
/* If there are any flags there already, we add to them.
*/
flags = is_vp8x ? writer->mem[20] : 0;
for( i = 0; i < vips__n_webp_names; i++ ) {
const char *vips = vips__webp_names[i].vips;
uint32_t flag = vips__webp_names[i].flag;
if( vips_image_get_typeof( image, vips ) ) {
void *data;
size_t length;
if( vips_image_get_blob( image, vips, &data, &length ) )
return( -1 );
/* +8 since we have to prepend each chunk with a type
* char[4] and a length guint32.
*
* Chunks are always rounded up to an even size.
*/
metadata_size += length + 8 + (length & 1);
flags |= flag;
}
}
if( !metadata_size )
/* No metadata to write, so we can just leave the image alone.
*/
return( 0 );
/* 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.
*/
new_size = writer->size - 8 + (is_vp8x ? 0 : 18) + metadata_size;
vips_webp_writer_init( &new );
if( !vips_webp_writer_appendcc( &new, "RIFF" ) ||
!vips_webp_writer_appendle32( &new, new_size ) ||
!vips_webp_writer_appendcc( &new, "WEBP" ) ||
!vips_webp_writer_appendcc( &new, "VP8X" ) ||
!vips_webp_writer_appendle32( &new, 10 ) ) {
vips_webp_writer_unset( &new );
return( -1 );
}
if( is_vp8x ) {
/* Copy the existing VP8X body and update the flag bits.
*/
if( vips_webp_writer_append( &new, writer->mem + 20, 10 ) ) {
vips_webp_writer_unset( &new );
return( -1 );
}
new.mem[20] = flags;
}
else {
/* We have to make a new vp8x header.
*/
/* Presence of alpha is stored in the 29th bit of VP8L
* data.
*
* +12 gets us to the VP8L cc, 4 to skip that,
* another 4 to skip the length, then bit 8 - 3 == 5
*/
if( is_lossless &&
(writer->mem[12 + 8 + 3] & (1 << 5)) )
flags |= 0x010;
/* 10 is the length of the VPX8X header chunk.
*/
if( !vips_webp_writer_appendle32( &new, flags ) ||
!vips_webp_writer_appendle24( &new, image->Xsize - 1 ) ||
!vips_webp_writer_appendle24( &new, image->Ysize- 1 ) ) {
vips_webp_writer_unset( &new );
return( -1 );
}
}
/* Extra chunks have to be in this order.
*/
if( vips_webp_add_chunk( &new, image, VIPS_META_ICC_NAME, "ICCP" ) ) {
vips_webp_writer_unset( &new );
return( -1 );
}
/* The image chunk must come here.
*/
if( is_vp8x ) {
if( !vips_webp_writer_append( &new,
writer->mem + 30, writer->size - 30 ) ) {
vips_webp_writer_unset( &new );
return( -1 );
}
}
else {
if( !vips_webp_writer_append( &new,
writer->mem + 12, writer->size - 12 ) ) {
vips_webp_writer_unset( &new );
return( -1 );
}
}
if( vips_webp_add_chunk( &new, image, VIPS_META_EXIF_NAME, "EXIF" ) ) {
vips_webp_writer_unset( &new );
return( -1 );
}
if( vips_webp_add_chunk( &new, image, VIPS_META_XMP_NAME, "XMP " ) ) {
vips_webp_writer_unset( &new );
return( -1 );
}
vips_webp_writer_unset( writer );
*writer = new;
return( 0 );
}
int int
vips__webp_write_file( VipsImage *in, const char *filename, vips__webp_write_file( VipsImage *in, const char *filename,
int Q, gboolean lossless, VipsForeignWebpPreset preset, int Q, gboolean lossless, VipsForeignWebpPreset preset,
@ -226,7 +477,7 @@ vips__webp_write_file( VipsImage *in, const char *filename,
int alpha_q ) int alpha_q )
{ {
WebPPicture pic; WebPPicture pic;
VipsWebPMemoryWriter writer; VipsWebPWriter writer;
FILE *fp; FILE *fp;
if( !WebPPictureInit( &pic ) ) { if( !WebPPictureInit( &pic ) ) {
@ -235,32 +486,37 @@ vips__webp_write_file( VipsImage *in, const char *filename,
return( -1 ); return( -1 );
} }
init_memory_writer( &writer ); vips_webp_writer_init( &writer );
pic.writer = memory_write; pic.writer = memory_write;
pic.custom_ptr = &writer; pic.custom_ptr = &writer;
if( write_webp( &pic, in, Q, lossless, preset, smart_subsample, if( write_webp( &pic, in, Q, lossless, preset, smart_subsample,
near_lossless, alpha_q ) ) { near_lossless, alpha_q ) ) {
WebPPictureFree( &pic ); WebPPictureFree( &pic );
g_free( writer.mem ); vips_webp_writer_unset( &writer );
return -1; return( -1 );
} }
WebPPictureFree( &pic ); WebPPictureFree( &pic );
if( vips_webp_add_metadata( &writer, in ) ) {
vips_webp_writer_unset( &writer );
return( -1 );
}
if( !(fp = vips__file_open_write( filename, FALSE )) ) { if( !(fp = vips__file_open_write( filename, FALSE )) ) {
g_free( writer.mem ); vips_webp_writer_unset( &writer );
return( -1 ); return( -1 );
} }
if( vips__file_write( writer.mem, writer.size, 1, fp ) ) { if( vips__file_write( writer.mem, writer.size, 1, fp ) ) {
fclose( fp ); fclose( fp );
g_free( writer.mem ); vips_webp_writer_unset( &writer );
return( -1 ); return( -1 );
} }
fclose( fp ); fclose( fp );
g_free( writer.mem ); vips_webp_writer_unset( &writer );
return( 0 ); return( 0 );
} }
@ -272,7 +528,7 @@ vips__webp_write_buffer( VipsImage *in, void **obuf, size_t *olen,
int alpha_q ) int alpha_q )
{ {
WebPPicture pic; WebPPicture pic;
VipsWebPMemoryWriter writer; VipsWebPWriter writer;
if( !WebPPictureInit( &pic ) ) { if( !WebPPictureInit( &pic ) ) {
vips_error( "vips2webp", vips_error( "vips2webp",
@ -280,19 +536,24 @@ vips__webp_write_buffer( VipsImage *in, void **obuf, size_t *olen,
return( -1 ); return( -1 );
} }
init_memory_writer( &writer ); vips_webp_writer_init( &writer );
pic.writer = memory_write; pic.writer = memory_write;
pic.custom_ptr = &writer; pic.custom_ptr = &writer;
if( write_webp( &pic, in, Q, lossless, preset, smart_subsample, if( write_webp( &pic, in, Q, lossless, preset, smart_subsample,
near_lossless, alpha_q ) ) { near_lossless, alpha_q ) ) {
WebPPictureFree( &pic ); WebPPictureFree( &pic );
g_free( writer.mem ); vips_webp_writer_unset( &writer );
return -1; return( -1 );
} }
WebPPictureFree( &pic ); WebPPictureFree( &pic );
if( vips_webp_add_metadata( &writer, in ) ) {
vips_webp_writer_unset( &writer );
return( -1 );
}
*obuf = writer.mem; *obuf = writer.mem;
*olen = writer.size; *olen = writer.size;

View File

@ -212,10 +212,11 @@ read_new( const char *filename, const void *data, size_t length, int shrink )
/* Map vips metadata names to webp names. /* Map vips metadata names to webp names.
*/ */
const VipsWebPNames vips__webp_names[] = { const VipsWebPNames vips__webp_names[] = {
{ VIPS_META_ICC_NAME, "ICCP" }, { VIPS_META_ICC_NAME, "ICCP", 0x20 },
{ VIPS_META_XMP_NAME, "XMP " }, { VIPS_META_XMP_NAME, "XMP ", 0x04 },
{ VIPS_META_EXIF_NAME, "EXIF" } { VIPS_META_EXIF_NAME, "EXIF", 0x08 }
}; };
const int vips__n_webp_names = VIPS_NUMBER( vips__webp_names );
static int static int
read_header( Read *read, VipsImage *out ) read_header( Read *read, VipsImage *out )
@ -245,10 +246,12 @@ read_header( Read *read, VipsImage *out )
} }
for( i = 0; i < VIPS_NUMBER( vips__webp_names ); i++ ) { for( i = 0; i < VIPS_NUMBER( vips__webp_names ); i++ ) {
const char *vips = vips__webp_names[i].vips;
const char *webp = vips__webp_names[i].webp;
WebPData data; WebPData data;
if( WebPMuxGetChunk( mux, vips__webp_names[i].webp, &data ) == if( WebPMuxGetChunk( mux, webp, &data ) == WEBP_MUX_OK ) {
WEBP_MUX_OK ) {
void *blob; void *blob;
if( !(blob = vips_malloc( NULL, data.size )) ) { if( !(blob = vips_malloc( NULL, data.size )) ) {
@ -257,7 +260,7 @@ read_header( Read *read, VipsImage *out )
} }
memcpy( blob, data.bytes, data.size ); memcpy( blob, data.bytes, data.size );
vips_image_set_blob( out, vips__webp_names[i].vips, vips_image_set_blob( out, vips,
(VipsCallbackFn) vips_free, blob, data.size ); (VipsCallbackFn) vips_free, blob, data.size );
} }
} }