From 7dea8a3ea85f5e526e17566a53a250148cf45ba6 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Thu, 28 Dec 2017 22:26:22 +0100 Subject: [PATCH] added support for writing metadata in PNG files Fixes #3352 --- rtengine/imageio.cc | 94 +++++++++++++++++++++++++++++++++++++++++++++ rtexif/rtexif.cc | 77 +++++++++++++++++++++++++++++++++++++ rtexif/rtexif.h | 1 + 3 files changed, 172 insertions(+) diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index ac2c359ca..a8fe9d0da 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -926,6 +926,77 @@ int ImageIO::loadPPMFromMemory(const char* buffer, int width, int height, bool s return IMIO_SUCCESS; } + +namespace { + +// Taken from Darktable -- src/imageio/format/png.c +// +/* Write EXIF data to PNG file. + * Code copied from DigiKam's libs/dimg/loaders/pngloader.cpp. + * The EXIF embedding is defined by ImageMagicK. + * It is documented in the ExifTool page: + * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html + * + * ..and in turn copied from ufraw. thanks to udi and colleagues + * for making useful code much more readable and discoverable ;) + */ + +void PNGwriteRawProfile(png_struct *ping, png_info *ping_info, const char *profile_type, guint8 *profile_data, png_uint_32 length) +{ + png_textp text; + long i; + guint8 *sp; + png_charp dp; + png_uint_32 allocated_length, description_length; + + const guint8 hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + text = static_cast(png_malloc(ping, sizeof(png_text))); + description_length = strlen(profile_type); + allocated_length = length * 2 + (length >> 5) + 20 + description_length; + + text[0].text = static_cast(png_malloc(ping, allocated_length)); + text[0].key = static_cast(png_malloc(ping, 80)); + text[0].key[0] = '\0'; + + g_strlcat(text[0].key, "Raw profile type ", 80); + g_strlcat(text[0].key, profile_type, 80); + + sp = profile_data; + dp = text[0].text; + *dp++ = '\n'; + + g_strlcpy(dp, profile_type, allocated_length); + + dp += description_length; + *dp++ = '\n'; + *dp = '\0'; + + g_snprintf(dp, allocated_length - strlen(text[0].text), "%8lu ", static_cast(length)); + + dp += 8; + + for(i = 0; i < long(length); i++) + { + if(i % 36 == 0) *dp++ = '\n'; + + *(dp++) = hex[((*sp >> 4) & 0x0f)]; + *(dp++) = hex[((*sp++) & 0x0f)]; + } + + *dp++ = '\n'; + *dp = '\0'; + text[0].text_length = (dp - text[0].text); + text[0].compression = -1; + + if(text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1); + + png_free(ping, text[0].text); + png_free(ping, text[0].key); + png_free(ping, text); +} + +} // namespace + int ImageIO::savePNG (Glib::ustring fname, volatile int bps) { if (getWidth() < 1 || getHeight() < 1) { @@ -994,6 +1065,29 @@ int ImageIO::savePNG (Glib::ustring fname, volatile int bps) png_set_iCCP(png, info, const_cast("icc"), 0, profdata, profileLength); } + { + // buffer for the exif and iptc + unsigned int bufferSize; + unsigned char* buffer = nullptr; // buffer will be allocated in createTIFFHeader + unsigned char* iptcdata = nullptr; + unsigned int iptclen = 0; + + if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen) && iptcdata) { + iptc_data_free_buf (iptc, iptcdata); + iptcdata = nullptr; + } + + int size = rtexif::ExifManager::createPNGMarker(exifRoot, exifChange, width, height, bps, (char*)iptcdata, iptclen, buffer, bufferSize); + + if (iptcdata) { + iptc_data_free_buf (iptc, iptcdata); + } + if (buffer && size) { + PNGwriteRawProfile(png, info, "exif", buffer, size); + delete[] buffer; + } + } + int rowlen = width * 3 * bps / 8; unsigned char *row = new unsigned char [rowlen]; diff --git a/rtexif/rtexif.cc b/rtexif/rtexif.cc index a5d10f762..affd530b5 100644 --- a/rtexif/rtexif.cc +++ b/rtexif/rtexif.cc @@ -3312,6 +3312,83 @@ int ExifManager::createTIFFHeader (const TagDirectory* root, const rtengine::pro return endOffs; } + +int ExifManager::createPNGMarker(const TagDirectory* root, const rtengine::procparams::ExifPairs &changeList, int W, int H, int bps, const char* iptcdata, int iptclen, unsigned char *&buffer, unsigned &bufferSize) +{ +// write tiff header + int offs = 0; + ByteOrder order = HOSTORDER; + + if (root) { + order = root->getOrder (); + } + + TagDirectory* cl; + + if (root) { + cl = (const_cast (root))->clone (nullptr); + // remove some unknown top level tags which produce warnings when opening a tiff + Tag *removeTag = cl->getTag (0x9003); + + if (removeTag) { + removeTag->setKeep (false); + } + + removeTag = cl->getTag (0x9211); + + if (removeTag) { + removeTag->setKeep (false); + } + } else { + cl = new TagDirectory (nullptr, ifdAttribs, HOSTORDER); + } + + if (iptcdata) { + Tag* iptc = new Tag (cl, lookupAttrib (ifdAttribs, "IPTCData")); + iptc->initLongArray (iptcdata, iptclen); + cl->replaceTag (iptc); + } + +// apply list of changes + for (rtengine::procparams::ExifPairs::const_iterator i = changeList.begin(); i != changeList.end(); ++i) { + cl->applyChange (i->first, i->second); + } + + // append default properties + const std::vector defTags = getDefaultTIFFTags (cl); + + defTags[0]->setInt (W, 0, LONG); + defTags[1]->setInt (H, 0, LONG); + defTags[8]->initInt (0, SHORT, 3); + + for (int i = 0; i < 3; i++) { + defTags[8]->setInt (bps, i * 2, SHORT); + } + + for (int i = defTags.size() - 1; i >= 0; i--) { + Tag* defTag = defTags[i]; + cl->replaceTag (defTag->clone (cl)); + delete defTag; + } + + cl->sort (); + bufferSize = cl->calculateSize() + 8; + buffer = new unsigned char[bufferSize]; // this has to be deleted in caller + sset2 ((unsigned short)order, buffer + offs, order); + offs += 2; + sset2 (42, buffer + offs, order); + offs += 2; + sset4 (8, buffer + offs, order); + + int endOffs = cl->write (8, buffer); + +// cl->printAll(); + delete cl; + + return endOffs; +} + + //----------------------------------------------------------------------------- // global functions to read byteorder dependent data //----------------------------------------------------------------------------- diff --git a/rtexif/rtexif.h b/rtexif/rtexif.h index 125d38c94..937945aac 100644 --- a/rtexif/rtexif.h +++ b/rtexif/rtexif.h @@ -361,6 +361,7 @@ public: static std::vector getDefaultTIFFTags (TagDirectory* forthis); static int createJPEGMarker (const TagDirectory* root, const rtengine::procparams::ExifPairs& changeList, int W, int H, unsigned char* buffer); static int createTIFFHeader (const TagDirectory* root, const rtengine::procparams::ExifPairs& changeList, int W, int H, int bps, const char* profiledata, int profilelen, const char* iptcdata, int iptclen, unsigned char *&buffer, unsigned &bufferSize); + static int createPNGMarker(const TagDirectory *root, const rtengine::procparams::ExifPairs &changeList, int W, int H, int bps, const char *iptcdata, int iptclen, unsigned char *&buffer, unsigned &bufferSize); }; class Interpreter