# HG changeset patch # User Matti Hamalainen # Date 1494414547 -10800 # Node ID 377dc78a052d20b5c0eb84e08a25f2d1b6b2ada2 # Parent d0ec05fbab884a8cd98d3a2cb376ab597a9e55b0 Add EXIF metadata conversion from TIFF files to BPG via libexif. diff -r d0ec05fbab88 -r 377dc78a052d Makefile --- a/Makefile Thu Apr 27 15:57:50 2017 +0300 +++ b/Makefile Wed May 10 14:09:07 2017 +0300 @@ -8,6 +8,10 @@ USE_X265=y # Enable the JCTVC code (best quality but slow) for the encoder #USE_JCTVC=y + +# Use libexif (currently only for converting TIFF metadata to EXIF in BPG) +USE_LIBEXIF=y + # Compile bpgview (SDL and SDL_image libraries needed) USE_BPGVIEW=y @@ -48,12 +52,17 @@ LIBJPEG_CFLAGS:=$(shell pkg-config --cflags libjpeg) LIBJPEG_LDFLAGS:=$(shell pkg-config --libs libjpeg) +ifdef USE_LIBEXIF +LIBEXIF_CFLAGS:=$(shell pkg-config --cflags libexif) -DHAVE_LIBEXIF +LIBEXIF_LDFLAGS:=$(shell pkg-config --libs libexif) +endif + LIBTIFF_CFLAGS:=$(shell pkg-config --cflags libtiff-4) LIBTIFF_LDFLAGS:=$(shell pkg-config --libs libtiff-4) CFLAGS:=-Os -Wall -MMD -fno-asynchronous-unwind-tables -fdata-sections -ffunction-sections -fno-math-errno -fno-signed-zeros -fno-tree-vectorize -fomit-frame-pointer CFLAGS+=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_REENTRANT -CFLAGS+=-I. $(LIBSDL_CFLAGS) $(LIBPNG_CFLAGS) $(LIBJPEG_CFLAGS) $(LIBTIFF_CFLAGS) +CFLAGS+=-I. $(LIBSDL_CFLAGS) $(LIBPNG_CFLAGS) $(LIBJPEG_CFLAGS) $(LIBTIFF_CFLAGS) $(LIBEXIF_CFLAGS) CFLAGS+=-DCONFIG_BPG_VERSION=\"$(shell cat VERSION)\" ifdef USE_JCTVC_HIGH_BIT_DEPTH CFLAGS+=-DRExt__HIGH_BIT_DEPTH_SUPPORT @@ -193,7 +202,7 @@ LIBS+=-lm -lpthread BPGDEC_LIBS:=$(LIBPNG_LDFLAGS) $(LIBS) -BPGENC_LIBS+=$(LIBPNG_LDFLAGS) $(LIBJPEG_LDFLAGS) $(LIBTIFF_LDFLAGS) $(LIBS) +BPGENC_LIBS+=$(LIBPNG_LDFLAGS) $(LIBJPEG_LDFLAGS) $(LIBTIFF_LDFLAGS) $(LIBEXIF_LDFLAGS) $(LIBS) BPGVIEW_LIBS:=$(LIBSDL_LDFLAGS) $(LIBS) endif #!CONFIG_WIN32 diff -r d0ec05fbab88 -r 377dc78a052d bpgenc.c --- a/bpgenc.c Thu Apr 27 15:57:50 2017 +0300 +++ b/bpgenc.c Wed May 10 14:09:07 2017 +0300 @@ -32,6 +32,9 @@ #include #include #include +#ifdef HAVE_LIBEXIF +#include +#endif #include "bpgenc.h" @@ -947,6 +950,145 @@ } +#ifdef HAVE_LIBEXIF +typedef struct +{ + int tag; // Note: libtiff TIFF tags for EXIF are same as EXIF tags themselves + char *name; + int ifd; +} TIFFTagConvertInfo; + + +static const TIFFTagConvertInfo tiff_tag_convert_table[] = +{ + { TIFFTAG_ORIENTATION , "Orientation" , EXIF_IFD_0 }, + { TIFFTAG_XRESOLUTION , "XResolution" , EXIF_IFD_0 }, + { TIFFTAG_YRESOLUTION , "YResolution" , EXIF_IFD_0 }, + { TIFFTAG_RESOLUTIONUNIT , "ResolutionUnit" , EXIF_IFD_0 }, + { TIFFTAG_IMAGEDESCRIPTION , "ImageDescription" , EXIF_IFD_0 }, + { TIFFTAG_MAKE , "Make" , EXIF_IFD_0 }, + { TIFFTAG_MODEL , "Model" , EXIF_IFD_0 }, + { TIFFTAG_SOFTWARE , "Software" , EXIF_IFD_0 }, + { TIFFTAG_ARTIST , "Artist" , EXIF_IFD_0 }, + { TIFFTAG_COPYRIGHT , "Copyright" , EXIF_IFD_0 }, + { TIFFTAG_DATETIME , "DateTime" , EXIF_IFD_0 }, + { 0 , NULL , 0 }, +}; + + +static const TIFFTagConvertInfo exif_tag_convert_table[] = +{ + { EXIFTAG_EXPOSURETIME , "ExposureTime" , EXIF_IFD_EXIF }, + { EXIFTAG_FNUMBER , "FNumber" , EXIF_IFD_EXIF }, + { EXIFTAG_EXPOSUREPROGRAM , "ExposureProgram" , EXIF_IFD_EXIF }, + { EXIFTAG_SPECTRALSENSITIVITY , "Spectralsensitivity" , EXIF_IFD_EXIF }, + { EXIFTAG_DATETIMEORIGINAL , "DateTimeOriginal" , EXIF_IFD_EXIF }, + { EXIFTAG_DATETIMEDIGITIZED , "DateTimeDigitized" , EXIF_IFD_EXIF }, + { EXIFTAG_SHUTTERSPEEDVALUE , "ShutterSpeed" , EXIF_IFD_EXIF }, + { EXIFTAG_APERTUREVALUE , "Aperture" , EXIF_IFD_EXIF }, + { EXIFTAG_BRIGHTNESSVALUE , "Brightness" , EXIF_IFD_EXIF }, + { EXIFTAG_EXPOSUREBIASVALUE , "ExposureBias" , EXIF_IFD_EXIF }, + { EXIFTAG_MAXAPERTUREVALUE , "MaxAperture" , EXIF_IFD_EXIF }, + { EXIFTAG_SUBJECTDISTANCE , "SubjectDistance" , EXIF_IFD_EXIF }, + + { EXIFTAG_METERINGMODE , "MeteringMode" , EXIF_IFD_EXIF }, + { EXIFTAG_LIGHTSOURCE , "Lightsource" , EXIF_IFD_EXIF }, + { EXIFTAG_FLASH , "Flash" , EXIF_IFD_EXIF }, + { EXIFTAG_FOCALLENGTH , "FocalLength" , EXIF_IFD_EXIF }, + { EXIFTAG_SUBJECTAREA , "SubjectArea" , EXIF_IFD_EXIF }, + { EXIFTAG_USERCOMMENT , "UserComment" , EXIF_IFD_EXIF }, + { EXIFTAG_SUBSECTIME , "SubSecTime" , EXIF_IFD_EXIF }, + { EXIFTAG_SUBSECTIMEORIGINAL , "SubSecTimeOriginal" , EXIF_IFD_EXIF }, + { EXIFTAG_SUBSECTIMEDIGITIZED , "SubSecTimeDigitized" , EXIF_IFD_EXIF }, + + { EXIFTAG_COLORSPACE , "Colorspace" , EXIF_IFD_EXIF }, + { EXIFTAG_PIXELXDIMENSION , "Validimage" , EXIF_IFD_EXIF }, + { EXIFTAG_PIXELYDIMENSION , "Validimage" , EXIF_IFD_EXIF }, + { EXIFTAG_RELATEDSOUNDFILE , "Relatedaudio" , EXIF_IFD_EXIF }, + { EXIFTAG_FLASHENERGY , "Flashenergy" , EXIF_IFD_EXIF }, + { EXIFTAG_SPATIALFREQUENCYRESPONSE , "SpatialFrequency" , EXIF_IFD_EXIF }, + { EXIFTAG_FOCALPLANEXRESOLUTION , "XResolution" , EXIF_IFD_EXIF }, + { EXIFTAG_FOCALPLANEYRESOLUTION , "YResolution" , EXIF_IFD_EXIF }, + { EXIFTAG_FOCALPLANERESOLUTIONUNIT , "ResolutionUnit" , EXIF_IFD_EXIF }, + { EXIFTAG_SUBJECTLOCATION , "SubjectLocation" , EXIF_IFD_EXIF }, + { EXIFTAG_EXPOSUREINDEX , "ExposureIndex" , EXIF_IFD_EXIF }, + { EXIFTAG_SENSINGMETHOD , "SensingMethod" , EXIF_IFD_EXIF }, + { EXIFTAG_FILESOURCE , "FileSource" , EXIF_IFD_EXIF }, + { EXIFTAG_SCENETYPE , "SceneType" , EXIF_IFD_EXIF }, + { EXIFTAG_CUSTOMRENDERED , "CustomRendered" , EXIF_IFD_EXIF }, + { EXIFTAG_EXPOSUREMODE , "ExposureMode" , EXIF_IFD_EXIF }, + { EXIFTAG_WHITEBALANCE , "WhiteBalance" , EXIF_IFD_EXIF }, + { EXIFTAG_DIGITALZOOMRATIO , "DigitalZoom" , EXIF_IFD_EXIF }, + { EXIFTAG_FOCALLENGTHIN35MMFILM , "FocalLength35mm" , EXIF_IFD_EXIF }, + { EXIFTAG_SCENECAPTURETYPE , "SceneCapture" , EXIF_IFD_EXIF }, + { EXIFTAG_GAINCONTROL , "GainControl" , EXIF_IFD_EXIF }, + { EXIFTAG_CONTRAST , "Contrast" , EXIF_IFD_EXIF }, + { EXIFTAG_SATURATION , "Saturation" , EXIF_IFD_EXIF }, + { EXIFTAG_SHARPNESS , "Sharpness" , EXIF_IFD_EXIF }, + { EXIFTAG_DEVICESETTINGDESCRIPTION , "DeviceSettings" , EXIF_IFD_EXIF }, + { EXIFTAG_SUBJECTDISTANCERANGE , "SubjectDistance" , EXIF_IFD_EXIF }, + { EXIFTAG_IMAGEUNIQUEID , "ImageUniqueID" , EXIF_IFD_EXIF }, + + { 0 , NULL , 0 }, +}; + + +static void tiff_tags_convert(TIFF *tif, const TIFFTagConvertInfo *table, + ExifMem *mem, ExifData *exif, int *added) +{ + const TIFFTagConvertInfo *conv; + + for (conv = table; conv->tag != 0 && conv->name != NULL; conv++) + { + const TIFFField *tfd = TIFFFieldWithTag(tif, conv->tag); + TIFFDataType type = TIFFFieldDataType(tfd); + int len = TIFFDataWidth(type); + uint8_t tmp_buf[256], *data; + char *tmp_str; + int have; + + switch (type) + { + case TIFF_ASCII: + have = TIFFGetField(tif, conv->tag, &tmp_str) && tmp_str != NULL; + if (have) + { + data = (uint8_t *) tmp_str; + len = strlen(tmp_str) + 1; + } + break; + + default: + have = TIFFGetField(tif, conv->tag, &tmp_buf); + data = tmp_buf; + } + + if (have) + { + ExifEntry *entry = exif_entry_new_mem(mem); + if (entry == NULL) + return; + + entry->data = exif_mem_alloc(mem, len); + if (entry->data == NULL) + return; + + memcpy(entry->data, data, len); + entry->size = len; + entry->tag = conv->tag; + entry->components = len; + entry->format = type; + + exif_content_add_entry(exif->ifd[conv->ifd], entry); + exif_mem_unref(mem); + exif_entry_unref(entry); + (*added)++; + } + } +} +#endif + + void tiff_warning_handler(const char *module, const char *fmt, va_list ap) { (void) module; @@ -968,6 +1110,7 @@ uint16_t img_depth, img_spp, img_pconfig, img_pmetric, *buf2 = NULL, *img_colormap_r, *img_colormap_g, *img_colormap_b; int img_alpha, err_spp, offs, x, y, idx; + toff_t tmp_offs; size_t img_linesize; ColorConvertState cvt; RGBConvertFunc *convert_func; @@ -1168,6 +1311,48 @@ if (TIFFGetField(tif, TIFFTAG_XMLPACKET, &tmp_len, &tmp_buf)) bpg_add_md_contents(pmd, BPG_EXTENSION_TAG_XMP, tmp_len, tmp_buf); +#ifdef HAVE_LIBEXIF + // Due to insanity of libtiff's "API", we have to go roundabout way to + // reconstruct the EXIF data through libexif and fetching TIFF tags .. + // even though the data already supposedly exists in desired format .. :( + ExifMem *exif_mem = exif_mem_new_default(); + if (exif_mem == NULL) + goto err; + + ExifData *exif = exif_data_new_mem(exif_mem); + if (exif == NULL) + goto err; + + exif_data_set_option(exif, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION); + exif_data_set_data_type(exif, EXIF_DATA_TYPE_COMPRESSED); + exif_data_set_byte_order(exif, EXIF_BYTE_ORDER_INTEL); + + int added = 0; + tiff_tags_convert(tif, tiff_tag_convert_table, exif_mem, exif, &added); + + if (TIFFGetField(tif, TIFFTAG_EXIFIFD, &tmp_offs)) + { + if (TIFFReadEXIFDirectory(tif, tmp_offs)) + { + tiff_tags_convert(tif, exif_tag_convert_table, exif_mem, exif, &added); + } + } + + if (added) + { + uint8_t *ex_buf = NULL; + unsigned int ex_len = 0; + + exif_data_save_data(exif, &ex_buf, &ex_len); + + // Strip off "Exif\x00\x00" from the data + if (ex_len > 6) + bpg_add_md_contents(pmd, BPG_EXTENSION_TAG_EXIF, ex_len - 6, ex_buf + 6); + + exif_mem_free(exif_mem, ex_buf); + } +#endif + err: if (buf != NULL) _TIFFfree(buf);