changeset 24:377dc78a052d

Add EXIF metadata conversion from TIFF files to BPG via libexif.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 10 May 2017 14:09:07 +0300
parents d0ec05fbab88
children a6e6f87414ea
files Makefile bpgenc.c
diffstat 2 files changed, 196 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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 <png.h>
 #include <jpeglib.h>
 #include <tiffio.h>
+#ifdef HAVE_LIBEXIF
+#include <exif-data.h>
+#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);