From e3289ad2c19a436221a3cce769483fbce09f7b8b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 18 Nov 2022 11:57:05 +0000 Subject: [PATCH] add exif support to png load/save (#3168) * start adding exif in png the "Exif\0\0" header isn't being added and removed correctly needs tests * all done tested with linpng and libspng --- ChangeLog | 1 + libvips/foreign/spngsave.c | 22 +++++++++++++++++++ libvips/foreign/vipspng.c | 39 +++++++++++++++++++++++++++++++-- test/test-suite/test_foreign.py | 19 ++++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7cfb5b7e..5b31ec2d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,7 @@ master - fits write doesn't duplicate header fields - add @wrap to vips_text() - GIF load supports truncated frames [tlsa] +- EXIF support for PNG load and save 9/11/22 started 8.13.4 - missing include in mosaic_fuzzer [ServOKio] diff --git a/libvips/foreign/spngsave.c b/libvips/foreign/spngsave.c index d372666d..13022897 100644 --- a/libvips/foreign/spngsave.c +++ b/libvips/foreign/spngsave.c @@ -12,6 +12,8 @@ * - use libspng for save * 15/7/22 [lovell] * - default filter to none + * 17/11/22 + * - add exif save */ /* @@ -56,6 +58,7 @@ #include #include +#include #include "pforeign.h" #include "quantise.h" @@ -244,6 +247,25 @@ vips_foreign_save_spng_metadata( VipsForeignSaveSpng *spng, VipsImage *in ) g_free( str ); } + if( vips_image_get_typeof( in, VIPS_META_EXIF_NAME ) ) { + struct spng_exif exif; + + if( vips__exif_update( in ) || + vips_image_get_blob( in, VIPS_META_EXIF_NAME, + (const void **) &exif.data, &exif.length ) ) + return( -1 ); + + /* libspng does not want the JFIF "Exif\0\0" prefix. + */ + if( exif.length >= 6 && + vips_isprefix( "Exif", exif.data ) ) { + exif.data += 6; + exif.length -= 6; + } + + spng_set_exif( spng->ctx, &exif ); + } + if( vips_image_map( in, vips_foreign_save_spng_comment, spng ) ) return( -1 ); diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index d2f8a30e..afef240c 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -85,6 +85,8 @@ * - add "unlimited" flag to png load * 13/1/22 * - raise libpng pixel size limit to VIPS_MAX_COORD + * 17/11/22 + * - add exif read/write */ /* @@ -638,6 +640,17 @@ png2vips_header( Read *read, VipsImage *out ) } #endif /*PNG_bKGD_SUPPORTED*/ +#ifdef PNG_eXIf_SUPPORTED +{ + png_uint_32 num_exif; + png_bytep exif; + + if( png_get_eXIf_1( read->pPng, read->pInfo, &num_exif, &exif ) ) + vips_image_set_blob_copy( out, VIPS_META_EXIF_NAME, + exif, num_exif ); +} +#endif /*PNG_eXIf_SUPPORTED*/ + return( 0 ); } @@ -1173,8 +1186,30 @@ write_vips( Write *write, g_free( str ); } - if( vips_image_map( in, - write_png_comment, write ) ) +#ifdef PNG_eXIf_SUPPORTED + if( vips_image_get_typeof( in, VIPS_META_EXIF_NAME ) ) { + const void *data; + size_t length; + + if( vips__exif_update( in ) || + vips_image_get_blob( in, VIPS_META_EXIF_NAME, + &data, &length ) ) + return( -1 ); + + /* libpng does not want the JFIF "Exif\0\0" prefix. + */ + if( length >= 6 && + vips_isprefix( "Exif", (char *) data ) ) { + data += 6; + length -= 6; + } + + png_set_eXIf_1( write->pPng, write->pInfo, + length, (png_bytep) data ); + } +#endif /*PNG_eXIf_SUPPORTED*/ + + if( vips_image_map( in, write_png_comment, write ) ) return( -1 ); } diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 9cd44c83..882e6d3d 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -457,6 +457,25 @@ class TestForeign: # we can't test palette save since we can't be sure libimagequant is # available and there's no easy test for its presence + # see if we have exif parsing: our test jpg image has this field + x = pyvips.Image.new_from_file(JPEG_FILE) + if x.get_typeof("exif-ifd0-Orientation") != 0: + # we need a copy of the image to set the new metadata on + # otherwise we get caching problems + + # can set, save and load new orientation + x = pyvips.Image.new_from_file(JPEG_FILE) + x = x.copy() + + x.set("orientation", 2) + + filename = temp_filename(self.tempdir, '.png') + x.write_to_file(filename) + + x = pyvips.Image.new_from_file(filename) + y = x.get("orientation") + assert y == 2 + @skip_if_no("tiffload") def test_tiff(self): def tiff_valid(im):