added support for writing metadata in PNG files

Fixes #3352
This commit is contained in:
Alberto Griggio
2017-12-28 22:26:22 +01:00
parent 527f41c254
commit 7dea8a3ea8
3 changed files with 172 additions and 0 deletions

View File

@@ -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_textp>(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_charp>(png_malloc(ping, allocated_length));
text[0].key = static_cast<png_charp>(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<unsigned long int>(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<png_charp>("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];

View File

@@ -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<TagDirectory*> (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<Tag*> 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
//-----------------------------------------------------------------------------

View File

@@ -361,6 +361,7 @@ public:
static std::vector<Tag*> 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