From a8337648780ffa55ba7e1cce555cfe78044ab7b4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 29 Jan 2019 10:27:00 +0000 Subject: [PATCH] start heifsave --- libvips/foreign/Makefile.am | 1 + libvips/foreign/foreign.c | 2 + libvips/foreign/heifload.c | 35 ++-- libvips/foreign/heifsave.c | 326 +++++++++++++++++++++++++++++++++ libvips/foreign/pforeign.h | 3 + libvips/include/vips/foreign.h | 2 + 6 files changed, 357 insertions(+), 12 deletions(-) create mode 100644 libvips/foreign/heifsave.c diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index 9797ab24..990b8797 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -3,6 +3,7 @@ noinst_LTLIBRARIES = libforeign.la libforeign_la_SOURCES = \ pforeign.h \ heifload.c \ + heifsave.c \ niftiload.c \ niftisave.c \ quantise.c \ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 58f3c62a..c53d7c66 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1830,6 +1830,7 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_load_heif_get_type( void ); extern GType vips_foreign_load_heif_file_get_type( void ); extern GType vips_foreign_load_heif_buffer_get_type( void ); + extern GType vips_foreign_save_heif_get_type( void ); extern GType vips_foreign_load_nifti_get_type( void ); extern GType vips_foreign_save_nifti_get_type( void ); extern GType vips_foreign_load_gif_get_type( void ); @@ -1963,6 +1964,7 @@ vips_foreign_operation_init( void ) vips_foreign_load_heif_get_type(); vips_foreign_load_heif_file_get_type(); vips_foreign_load_heif_buffer_get_type(); + vips_foreign_save_heif_get_type(); #endif /*HAVE_HEIF*/ vips__foreign_load_operation = diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index 510dd905..04dfcf3c 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -1,7 +1,7 @@ /* load heif images with libheif * * 19/1/19 - * - from heifload.c + * - from niftiload.c */ /* @@ -132,8 +132,8 @@ vips_foreign_load_heif_dispose( GObject *gobject ) dispose( gobject ); } -static void -vips_heif_error( struct heif_error error ) +void +vips__heif_error( struct heif_error error ) { if( error.code ) vips_error( "heifload", "%s", error.message ); @@ -194,7 +194,7 @@ vips_foreign_load_heif_set_page( VipsForeignLoadHeif *heif, int page_no ) error = heif_context_get_image_handle( heif->ctx, heif->id[page_no], &heif->handle ); if( error.code ) { - vips_heif_error( error ); + vips__heif_error( error ); return( -1 ); } @@ -207,8 +207,6 @@ vips_foreign_load_heif_set_page( VipsForeignLoadHeif *heif, int page_no ) static int vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) { - enum heif_color_profile_type profile_type = - heif_image_handle_get_color_profile_type( heif->handle ); /* FIXME ... never seen this return TRUE on any image, strangely. */ gboolean has_alpha = @@ -248,14 +246,16 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) unsigned char *data; char name[256]; +#ifdef DEBUG printf( "metadata type = %s, length = %zd\n", type, length ); +#endif /*DEBUG*/ if( !(data = VIPS_ARRAY( out, length, unsigned char )) ) return( -1 ); error = heif_image_handle_get_metadata( heif->handle, id[i], data ); if( error.code ) { - vips_heif_error( error ); + vips__heif_error( error ); return( -1 ); } @@ -287,6 +287,11 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) (void) vips__exif_parse( out ); } +#ifdef DEBUG +{ + enum heif_color_profile_type profile_type = + heif_image_handle_get_color_profile_type( heif->handle ); + switch( profile_type ) { case heif_color_profile_type_not_present: printf( "no profile\n" ); @@ -308,6 +313,8 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) printf( "unknown profile type\n" ); break; } +} +#endif /*DEBUG*/ /* FIXME should probably check the profile type ... lcms seems to be * able to load at least rICC and prof. @@ -323,11 +330,13 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) error = heif_image_handle_get_raw_color_profile( heif->handle, data ); if( error.code ) { - vips_heif_error( error ); + vips__heif_error( error ); return( -1 ); } +#ifdef DEBUG printf( "profile data, length = %zd\n", length ); +#endif /*DEBUG*/ vips_image_set_blob( out, VIPS_META_ICC_NAME, (VipsCallbackFn) NULL, data, length ); @@ -355,7 +364,7 @@ vips_foreign_load_heif_header( VipsForeignLoad *load ) */ error = heif_context_get_primary_image_ID( heif->ctx, &primary_id ); if( error.code ) { - vips_heif_error( error ); + vips__heif_error( error ); return( -1 ); } for( i = 0; i < heif->n_top; i++ ) @@ -397,6 +406,7 @@ vips_foreign_load_heif_header( VipsForeignLoad *load ) } } +#ifdef DEBUG printf( "n_top = %d\n", heif->n_top ); for( i = 0; i < heif->n_top; i++ ) { printf( " id[%d] = %d\n", i, heif->id[i] ); @@ -415,6 +425,7 @@ vips_foreign_load_heif_header( VipsForeignLoad *load ) heif_image_handle_get_color_profile_type( heif->handle ) ); } +#endif /*DEBUG*/ if( vips_foreign_load_heif_set_header( heif, load->out ) ) return( -1 ); @@ -458,7 +469,7 @@ vips_foreign_load_heif_generate( VipsRegion *or, options ); heif_decoding_options_free( options ); if( error.code ) { - vips_heif_error( error ); + vips__heif_error( error ); return( -1 ); } } @@ -586,7 +597,7 @@ vips_foreign_load_heif_file_header( VipsForeignLoad *load ) error = heif_context_read_from_file( heif->ctx, file->filename, NULL ); if( error.code ) { - vips_heif_error( error ); + vips__heif_error( error ); return( -1 ); } @@ -667,7 +678,7 @@ vips_foreign_load_heif_buffer_header( VipsForeignLoad *load ) error = heif_context_read_from_memory( heif->ctx, buffer->buf->data, buffer->buf->length, NULL ); if( error.code ) { - vips_heif_error( error ); + vips__heif_error( error ); return( -1 ); } diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c new file mode 100644 index 00000000..d23a0cd7 --- /dev/null +++ b/libvips/foreign/heifsave.c @@ -0,0 +1,326 @@ +/* save to heif + * + * 5/7/18 + * - from niftisave.c + */ + +/* + + 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 + +#ifdef HAVE_HEIF + +#include + +#include "pforeign.h" + +typedef struct _VipsForeignSaveHeif { + VipsForeignSave parent_object; + + /* Filename for save. + */ + char *filename; + + /* Context for this image. + */ + struct heif_context *ctx; + + /* The encoder we use. + */ + struct heif_encoder *encoder; + + struct heif_image *img; + struct heif_image_handle *handle; + + int page_height; + int n_pages; + + VipsImage *memory; + VipsImage *page; + +} VipsForeignSaveHeif; + +typedef VipsForeignSaveClass VipsForeignSaveHeifClass; + +G_DEFINE_TYPE( VipsForeignSaveHeif, vips_foreign_save_heif, + VIPS_TYPE_FOREIGN_SAVE ); + +static void +vips_foreign_save_heif_dispose( GObject *gobject ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) gobject; + + VIPS_UNREF( heif->memory ); + VIPS_UNREF( heif->page ); + VIPS_FREEF( heif_image_release, heif->img ); + VIPS_FREEF( heif_image_handle_release, heif->handle ); + VIPS_FREEF( heif_encoder_release, heif->encoder ); + VIPS_FREEF( heif_context_free, heif->ctx ); + + G_OBJECT_CLASS( vips_foreign_save_heif_parent_class )-> + dispose( gobject ); +} + +/* Make ->nim from the vips header fields. + */ +static int +vips_foreign_save_heif_header_vips( VipsForeignSaveHeif *heif, + VipsImage *image ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( heif ); + + return( 0 ); +} + +static int +vips_foreign_save_heif_page( VipsForeignSaveHeif *heif, int page ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + + struct heif_error error; + const uint8_t *data; + int stride; + + error = heif_image_create( save->ready->Xsize, heif->page_height, + heif_colorspace_RGB, heif_chroma_interleaved_RGB, &heif->img ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + error = heif_image_add_plane( heif->img, heif_channel_interleaved, + save->ready->Xsize, heif->page_height, 8 ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + data = heif_image_get_plane_readonly( heif->img, + heif_channel_interleaved, &stride ); + + if( !(heif->memory = vips_image_new_from_memory( + data, stride * heif->page_height, + save->ready->Xsize, heif->page_height, 3, + VIPS_FORMAT_UCHAR )) ) + return( -1 ); + + if( stride != VIPS_IMAGE_SIZEOF_LINE( heif->memory ) ) { + vips_error( class->nickname, "%s", _( "not contiguous" ) ); + return( -1 ); + } + + if( vips_image_crop( save->ready, &heif->tile, + 0, page * heif->page_height, + save->ready->Xsize, heif->page_height, NULL ) || + vips_image_write( heif->tile, heif->memory ) ) + return( -1 ); + + options = heif_encoding_options_alloc(); + /* FIXME .. should be a save option. + */ + options.save_alpha_channel = 1; + error = heif_context_encode_image( heif->ctx, + heif->img, heif->encoder, options, &handle ); + heif_encoding_options_free( options ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + error = heif_context_set_primary_image( heif->ctx, heif->handle ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + error = heif_context_add_exif_metadata( heif->ctx, + heif->handle, data, size ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + error = heif_context_add_XMP_metadata( heif->ctx, + heif->handle, data, size ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + return( 0 ); +} + +static int +vips_foreign_save_heif_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; + + struct heif_error error; + struct heif_encoding_options *options; + int page; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_parent_class )-> + build( object ) ) + return( -1 ); + + error = heif_context_write_to_file( heif->ctx, heif->filename ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + /* TODO ... should be a param? the other useful one is AVC. + */ + error = heif_context_get_encoder_for_format( heif->ctx, + heif_compression_HEVC, &heif->encoder ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + error = heif_encoder_set_lossy_quality( heif->encoder, heif->Q ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + error = heif_encoder_set_lossless( heif->encoder, heif->lossless ); + if( error.code ) { + vips__heif_error( error ); + return( -1 ); + } + + /* TODO .. support extra per-encoder params with + * heif_encoder_list_parameters(). + */ + + if( vips_image_get_typeof( save->ready, VIPS_META_PAGE_HEIGHT ) ) { + if( vips_image_get_int( save->ready, + VIPS_META_PAGE_HEIGHT, &heif->page_height ) ) + return( -1 ); + } + else + heif->page_height = save->ready->Ysize; + + if( save->ready->Ysize % page_height != 0 ) + heif->page_height = save->ready->Ysize; + heif->n_pages = save->ready->Ysize / heif->page_height; + + for( page = 0; page < heif->n_pages; page++ ) + if( vips_foreign_save_heif_page( heif, page ) ) + return( -1 ); + + return( 0 ); +} + +/* Save a bit of typing. + */ +#define UC VIPS_FORMAT_UCHAR + +static int vips_heif_bandfmt[10] = { +/* UC C US S UI I F X D DX */ + UC, UC, UC, UC, UC, UC, UC, UC, UC, UC +}; + +static void +vips_foreign_save_heif_class_init( VipsForeignSaveHeifClass *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_heif_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "heifsave"; + object_class->description = _( "save image to heif file" ); + object_class->build = vips_foreign_save_heif_build; + + foreign_class->suffs = vips__heif_suffs; + + save_class->saveable = VIPS_SAVEABLE_RGB; + save_class->format_table = vips_heif_bandfmt; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeif, filename ), + NULL ); +} + +static void +vips_foreign_save_heif_init( VipsForeignSaveHeif *heif ) +{ + heif->ctx = heif_context_alloc(); +} + +#endif /*HAVE_HEIF*/ + +/** + * vips_heifsave: (method) + * @in: image to save + * @filename: file to write to + * @...: %NULL-terminated list of optional named arguments + * + * Write a VIPS image to a file in HEIF format. + * + * See also: vips_image_write_to_file(), vips_heifload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_heifsave( VipsImage *in, const char *filename, ... ) +{ + va_list ap; + int result; + + va_start( ap, filename ); + result = vips_call_split( "heifsave", ap, in, filename ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 611300b2..2f180b25 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -272,6 +272,9 @@ typedef void *(*VipsNiftiMapFn)( const char *name, GValue *value, glong offset, void *a, void *b ); void *vips__foreign_nifti_map( VipsNiftiMapFn fn, void *a, void *b ); +extern const char *vips__heif_suffs[]; +void vips__heif_error( struct heif_error error ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 25994384..f49b3f54 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -564,6 +564,8 @@ int vips_heifload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_heifload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_heifsave( VipsImage *in, const char *filename, ... ) + __attribute__((sentinel)); int vips_niftiload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel));