Sets UTF-8 as default charset for IPTC + suppress one of the 2 methods

for saving TIFF images.
This commit is contained in:
Hombre 2018-01-01 13:51:48 +01:00
parent 157da7f9ba
commit 96863bb956
3 changed files with 182 additions and 326 deletions

View File

@ -75,15 +75,6 @@ FILE* g_fopen_withBinaryAndLock(const Glib::ustring& fname)
return f;
}
Glib::ustring to_utf8 (const std::string& str)
{
try {
return Glib::locale_to_utf8 (str);
} catch (Glib::Error&) {
return Glib::convert_with_fallback (str, "UTF-8", "ISO-8859-1", "?");
}
}
}
Glib::ustring ImageIO::errorMsg[6] = {"Success", "Cannot read file.", "Invalid header.", "Error while reading header.", "File reading error", "Image format not supported."};
@ -136,13 +127,19 @@ void ImageIO::setMetadata (const rtexif::TagDirectory* eroot, const rtengine::pr
iptc = iptc_data_new ();
const unsigned char utf8Esc[] = {0x1B, '%', 'G'};
IptcDataSet * ds = iptc_dataset_new ();
iptc_dataset_set_tag (ds, IPTC_RECORD_OBJECT_ENV, IPTC_TAG_CHARACTER_SET);
iptc_dataset_set_data (ds, utf8Esc, 3, IPTC_DONT_VALIDATE);
iptc_data_add_dataset (iptc, ds);
iptc_dataset_unref (ds);
for (rtengine::procparams::IPTCPairs::const_iterator i = iptcc.begin(); i != iptcc.end(); ++i) {
if (i->first == "Keywords" && !(i->second.empty())) {
for (unsigned int j = 0; j < i->second.size(); j++) {
IptcDataSet * ds = iptc_dataset_new ();
iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, IPTC_TAG_KEYWORDS);
std::string loc = to_utf8(i->second.at(j));
iptc_dataset_set_data (ds, (unsigned char*)loc.c_str(), min(static_cast<size_t>(64), loc.size()), IPTC_DONT_VALIDATE);
iptc_dataset_set_data (ds, (unsigned char*)i->second.at(j).c_str(), min(static_cast<size_t>(64), i->second.at(j).bytes()), IPTC_DONT_VALIDATE);
iptc_data_add_dataset (iptc, ds);
iptc_dataset_unref (ds);
}
@ -152,8 +149,7 @@ void ImageIO::setMetadata (const rtexif::TagDirectory* eroot, const rtengine::pr
for (unsigned int j = 0; j < i->second.size(); j++) {
IptcDataSet * ds = iptc_dataset_new ();
iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, IPTC_TAG_SUPPL_CATEGORY);
std::string loc = to_utf8(i->second.at(j));
iptc_dataset_set_data (ds, (unsigned char*)loc.c_str(), min(static_cast<size_t>(32), loc.size()), IPTC_DONT_VALIDATE);
iptc_dataset_set_data (ds, (unsigned char*)i->second.at(j).c_str(), min(static_cast<size_t>(32), i->second.at(j).bytes()), IPTC_DONT_VALIDATE);
iptc_data_add_dataset (iptc, ds);
iptc_dataset_unref (ds);
}
@ -165,8 +161,7 @@ void ImageIO::setMetadata (const rtexif::TagDirectory* eroot, const rtengine::pr
if (i->first == strTags[j].field && !(i->second.empty())) {
IptcDataSet * ds = iptc_dataset_new ();
iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, strTags[j].tag);
std::string loc = to_utf8(i->second.at(0));
iptc_dataset_set_data (ds, (unsigned char*)loc.c_str(), min(strTags[j].size, loc.size()), IPTC_DONT_VALIDATE);
iptc_dataset_set_data (ds, (unsigned char*)i->second.at(0).c_str(), min(strTags[j].size, i->second.at(0).bytes()), IPTC_DONT_VALIDATE);
iptc_data_add_dataset (iptc, ds);
iptc_dataset_unref (ds);
}
@ -1121,13 +1116,13 @@ int ImageIO::saveJPEG (Glib::ustring fname, int quality, int subSamp)
int bytes = 0;
if (!error && (bytes = iptc_jpeg_ps3_save_iptc (nullptr, 0, iptcdata, size, buffer, 65532)) < 0) {
if (iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
}
error = true;
}
if (iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
}
if (!error) {
jpeg_write_marker(&cinfo, JPEG_APP0 + 13, buffer, bytes);
}
@ -1212,219 +1207,198 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed)
int lineWidth = width * 3 * bps / 8;
unsigned char* linebuffer = new unsigned char[lineWidth];
// TODO the following needs to be looked into - do we really need two ways to write a Tiff file ?
if (exifRoot && uncompressed) {
FILE *file = g_fopen_withBinaryAndLock (fname);
// little hack to get libTiff to use proper byte order (see TIFFClienOpen()):
const char *mode = !exifRoot ? "w" : (exifRoot->getOrder() == rtexif::INTEL ? "wl" : "wb");
#ifdef WIN32
FILE *file = g_fopen_withBinaryAndLock (fname);
int fileno = _fileno(file);
int osfileno = _get_osfhandle(fileno);
TIFF* out = TIFFFdOpen (osfileno, fname.c_str(), mode);
#else
TIFF* out = TIFFOpen(fname.c_str(), mode);
int fileno = TIFFFileno (out);
#endif
if (!file) {
delete [] linebuffer;
return IMIO_CANNOTWRITEFILE;
if (!out) {
delete [] linebuffer;
return IMIO_CANNOTWRITEFILE;
}
if (pl) {
pl->setProgressStr ("PROGRESSBAR_SAVETIFF");
pl->setProgress (0.0);
}
if (exifRoot) {
rtexif::TagDirectory* cl = (const_cast<rtexif::TagDirectory*> (exifRoot))->clone (nullptr);
// ------------------ remove some unknown top level tags which produce warnings when opening a tiff (might be useless) -----------------
rtexif::Tag *removeTag = cl->getTag (0x9003);
if (removeTag) {
removeTag->setKeep (false);
}
if (pl) {
pl->setProgressStr ("PROGRESSBAR_SAVETIFF");
pl->setProgress (0.0);
removeTag = cl->getTag (0x9211);
if (removeTag) {
removeTag->setKeep (false);
}
// 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;
// ------------------ Apply list of change -----------------
if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen) && iptcdata) {
for (auto currExifChange : exifChange) {
cl->applyChange (currExifChange.first, currExifChange.second);
}
rtexif::Tag *tag = cl->getTag (TIFFTAG_EXIFIFD);
if (tag && tag->isDirectory()) {
rtexif::TagDirectory *exif = tag->getDirectory();
if (exif) {
int exif_size = exif->calculateSize();
unsigned char *buffer = new unsigned char[exif_size + 8];
// TIFFOpen writes out the header and sets file pointer at position 8
exif->write (8, buffer);
write (fileno, buffer + 8, exif_size);
delete [] buffer;
// let libtiff know that scanlines or any other following stuff should go
// at a different offset:
TIFFSetWriteOffset (out, exif_size + 8);
TIFFSetField (out, TIFFTAG_EXIFIFD, 8);
}
}
//TODO Even though we are saving EXIF IFD - MakerNote still comes out screwed.
if ((tag = cl->getTag (TIFFTAG_MODEL)) != nullptr) {
TIFFSetField (out, TIFFTAG_MODEL, tag->getValue());
}
if ((tag = cl->getTag (TIFFTAG_MAKE)) != nullptr) {
TIFFSetField (out, TIFFTAG_MAKE, tag->getValue());
}
if ((tag = cl->getTag (TIFFTAG_DATETIME)) != nullptr) {
TIFFSetField (out, TIFFTAG_DATETIME, tag->getValue());
}
if ((tag = cl->getTag (TIFFTAG_ARTIST)) != nullptr) {
TIFFSetField (out, TIFFTAG_ARTIST, tag->getValue());
}
if ((tag = cl->getTag (TIFFTAG_COPYRIGHT)) != nullptr) {
TIFFSetField (out, TIFFTAG_COPYRIGHT, tag->getValue());
}
delete cl;
}
unsigned char* iptcdata = nullptr;
unsigned int iptclen = 0;
if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen)) {
if (iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
iptcdata = nullptr;
}
}
int size = rtexif::ExifManager::createTIFFHeader (exifRoot, exifChange, width, height, bps, profileData, profileLength, (char*)iptcdata, iptclen, buffer, bufferSize);
if (iptcdata) {
iptc_data_free_buf (iptc, iptcdata);
if (iptcdata) {
rtexif::Tag* iptcTag = new rtexif::Tag (nullptr, rtexif::lookupAttrib (rtexif::ifdAttribs, "IPTCData"));
iptcTag->initLongArray((char*)iptcdata, iptclen);
#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
bool needsReverse = exifRoot && exifRoot->getOrder() == rtexif::MOTOROLA;
#else
bool needsReverse = exifRoot && exifRoot->getOrder() == rtexif::INTEL;
#endif
if (needsReverse) {
unsigned char *ptr = iptcTag->getValue();
for (int a = 0; a < iptcTag->getCount(); ++a) {
unsigned char cc;
cc = ptr[3];
ptr[3] = ptr[0];
ptr[0] = cc;
cc = ptr[2];
ptr[2] = ptr[1];
ptr[1] = cc;
ptr += 4;
}
}
TIFFSetField (out, TIFFTAG_RICHTIFFIPTC, iptcTag->getCount(), (long*)iptcTag->getValue());
iptc_data_free_buf (iptc, iptcdata);
}
// The maximum lenght is strangely not the same than for the JPEG file...
// Which maximum length is the good one ?
if (size > 0 && size <= static_cast<int>(bufferSize)) {
fwrite (buffer, size, 1, file);
}
TIFFSetField (out, TIFFTAG_SOFTWARE, "RawTherapee " RTVERSION);
TIFFSetField (out, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField (out, TIFFTAG_IMAGELENGTH, height);
TIFFSetField (out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField (out, TIFFTAG_SAMPLESPERPIXEL, 3);
TIFFSetField (out, TIFFTAG_ROWSPERSTRIP, height);
TIFFSetField (out, TIFFTAG_BITSPERSAMPLE, bps);
TIFFSetField (out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
TIFFSetField (out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField (out, TIFFTAG_COMPRESSION, uncompressed ? COMPRESSION_NONE : COMPRESSION_DEFLATE);
TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, bps == 32 ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT);
if (!uncompressed) {
TIFFSetField (out, TIFFTAG_PREDICTOR, bps == 32 ? PREDICTOR_FLOATINGPOINT : PREDICTOR_HORIZONTAL);
}
if (profileData) {
TIFFSetField (out, TIFFTAG_ICCPROFILE, profileLength, profileData);
}
#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::MOTOROLA;
bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::MOTOROLA;
#else
bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::INTEL;
bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::INTEL;
#endif
for (int i = 0; i < height; i++) {
getScanline (i, linebuffer, bps);
for (int row = 0; row < height; row++) {
getScanline (row, linebuffer, bps);
if (needsReverse) {
if (bps == 16) {
for (int i = 0; i < lineWidth; i += 2) {
char c = linebuffer[i];
linebuffer[i] = linebuffer[i + 1];
linebuffer[i + 1] = c;
}
} else {
for (int i = 0; i < lineWidth; i += 4) {
std::swap(linebuffer[i], linebuffer[i+3]);
std::swap(linebuffer[i+1], linebuffer[i+2]);
}
if (needsReverse) {
if (bps == 16) {
for (int i = 0; i < lineWidth; i += 2) {
char c = linebuffer[i];
linebuffer[i] = linebuffer[i + 1];
linebuffer[i + 1] = c;
}
} else {
for (int i = 0; i < lineWidth; i += 4) {
std::swap(linebuffer[i], linebuffer[i+3]);
std::swap(linebuffer[i+1], linebuffer[i+2]);
}
}
fwrite (linebuffer, lineWidth, 1, file);
if (pl && !(i % 100)) {
pl->setProgress ((double)(i + 1) / height);
}
}
if(buffer) {
delete [] buffer;
}
if (ferror(file)) {
writeOk = false;
}
fclose (file);
} else {
// little hack to get libTiff to use proper byte order (see TIFFClienOpen()):
const char *mode = !exifRoot ? "w" : (exifRoot->getOrder() == rtexif::INTEL ? "wl" : "wb");
#ifdef WIN32
FILE *file = g_fopen_withBinaryAndLock (fname);
int fileno = _fileno(file);
int osfileno = _get_osfhandle(fileno);
TIFF* out = TIFFFdOpen (osfileno, fname.c_str(), mode);
#else
TIFF* out = TIFFOpen(fname.c_str(), mode);
int fileno = TIFFFileno (out);
#endif
if (!out) {
if (TIFFWriteScanline (out, linebuffer, row, 0) < 0) {
TIFFClose (out);
delete [] linebuffer;
return IMIO_CANNOTWRITEFILE;
}
if (pl) {
pl->setProgressStr ("PROGRESSBAR_SAVETIFF");
pl->setProgress (0.0);
if (pl && !(row % 100)) {
pl->setProgress ((double)(row + 1) / height);
}
if (exifRoot) {
rtexif::Tag *tag = exifRoot->getTag (TIFFTAG_EXIFIFD);
if (tag && tag->isDirectory()) {
rtexif::TagDirectory *exif = tag->getDirectory();
if (exif) {
int exif_size = exif->calculateSize();
unsigned char *buffer = new unsigned char[exif_size + 8];
// TIFFOpen writes out the header and sets file pointer at position 8
exif->write (8, buffer);
write (fileno, buffer + 8, exif_size);
delete [] buffer;
// let libtiff know that scanlines or any other following stuff should go
// at a different offset:
TIFFSetWriteOffset (out, exif_size + 8);
TIFFSetField (out, TIFFTAG_EXIFIFD, 8);
}
}
//TODO Even though we are saving EXIF IFD - MakerNote still comes out screwed.
if ((tag = exifRoot->getTag (TIFFTAG_MODEL)) != nullptr) {
TIFFSetField (out, TIFFTAG_MODEL, tag->getValue());
}
if ((tag = exifRoot->getTag (TIFFTAG_MAKE)) != nullptr) {
TIFFSetField (out, TIFFTAG_MAKE, tag->getValue());
}
if ((tag = exifRoot->getTag (TIFFTAG_DATETIME)) != nullptr) {
TIFFSetField (out, TIFFTAG_DATETIME, tag->getValue());
}
if ((tag = exifRoot->getTag (TIFFTAG_ARTIST)) != nullptr) {
TIFFSetField (out, TIFFTAG_ARTIST, tag->getValue());
}
if ((tag = exifRoot->getTag (TIFFTAG_COPYRIGHT)) != nullptr) {
TIFFSetField (out, TIFFTAG_COPYRIGHT, tag->getValue());
}
}
TIFFSetField (out, TIFFTAG_SOFTWARE, "RawTherapee " RTVERSION);
TIFFSetField (out, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField (out, TIFFTAG_IMAGELENGTH, height);
TIFFSetField (out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField (out, TIFFTAG_SAMPLESPERPIXEL, 3);
TIFFSetField (out, TIFFTAG_ROWSPERSTRIP, height);
TIFFSetField (out, TIFFTAG_BITSPERSAMPLE, bps);
TIFFSetField (out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
TIFFSetField (out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField (out, TIFFTAG_COMPRESSION, uncompressed ? COMPRESSION_NONE : COMPRESSION_DEFLATE);
TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, bps == 32 ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT);
if (!uncompressed) {
TIFFSetField (out, TIFFTAG_PREDICTOR, bps == 32 ? PREDICTOR_FLOATINGPOINT : PREDICTOR_HORIZONTAL);
}
if (profileData) {
TIFFSetField (out, TIFFTAG_ICCPROFILE, profileLength, profileData);
}
#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::MOTOROLA;
#else
bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::INTEL;
#endif
for (int row = 0; row < height; row++) {
getScanline (row, linebuffer, bps);
if (needsReverse) {
if (bps == 16) {
for (int i = 0; i < lineWidth; i += 2) {
char c = linebuffer[i];
linebuffer[i] = linebuffer[i + 1];
linebuffer[i + 1] = c;
}
} else {
for (int i = 0; i < lineWidth; i += 4) {
std::swap(linebuffer[i], linebuffer[i+3]);
std::swap(linebuffer[i+1], linebuffer[i+2]);
}
}
}
if (TIFFWriteScanline (out, linebuffer, row, 0) < 0) {
TIFFClose (out);
delete [] linebuffer;
return IMIO_CANNOTWRITEFILE;
}
if (pl && !(row % 100)) {
pl->setProgress ((double)(row + 1) / height);
}
}
if (TIFFFlush(out) != 1) {
writeOk = false;
}
TIFFClose (out);
#ifdef WIN32
fclose (file);
#endif
}
if (TIFFFlush(out) != 1) {
writeOk = false;
}
TIFFClose (out);
#ifdef WIN32
fclose (file);
#endif
delete [] linebuffer;
if (pl) {

View File

@ -739,11 +739,12 @@ void TagDirectory::applyChange (std::string name, Glib::ustring value)
} else {
const TagAttrib* attrib = nullptr;
for (int i = 0; attribs[i].ignore != -1; i++)
for (int i = 0; attribs[i].ignore != -1; i++) {
if (!strcmp (attribs[i].name, fseg.c_str())) {
attrib = &attribs[i];
break;
}
}
if (attrib) {
Tag* nt = new Tag (this, attrib);
@ -1663,15 +1664,11 @@ void Tag::toString (char* buffer, int ofs)
return;
}
size_t maxcount = 4;
if (count < 4) {
maxcount = count;
}
size_t maxcount = rtengine::min<size_t>(count, 10);
strcpy (buffer, "");
for (ssize_t i = 0; i < std::min<int>(maxcount, valuesize - ofs); i++) {
for (ssize_t i = 0; i < rtengine::min<int>(maxcount, valuesize - ofs); i++) {
if (i > 0) {
strcat (buffer, ", ");
}
@ -3199,121 +3196,6 @@ int ExifManager::createJPEGMarker (const TagDirectory* root, const rtengine::pro
return size + 6;
}
int ExifManager::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)
{
// 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);
}
// add tiff strip data
int rps = 8;
int strips = ceil ((double)H / rps);
cl->replaceTag (new Tag (cl, lookupAttrib (ifdAttribs, "RowsPerStrip"), rps, LONG));
Tag* stripBC = new Tag (cl, lookupAttrib (ifdAttribs, "StripByteCounts"));
stripBC->initInt (0, LONG, strips);
cl->replaceTag (stripBC);
Tag* stripOffs = new Tag (cl, lookupAttrib (ifdAttribs, "StripOffsets"));
stripOffs->initInt (0, LONG, strips);
cl->replaceTag (stripOffs);
Tag *sampleFormat = new Tag (cl, lookupAttrib (ifdAttribs, "SampleFormat"), bps == 32 ? 3 : 1, SHORT);
cl->replaceTag (sampleFormat);
for (int i = 0; i < strips - 1; i++) {
stripBC->setInt (rps * W * 3 * bps / 8, i * 4);
}
int remaining = (H - rps * floor ((double)H / rps)) * W * 3 * bps / 8;
if (remaining) {
stripBC->setInt (remaining, (strips - 1) * 4);
} else {
stripBC->setInt (rps * W * 3 * bps / 8, (strips - 1) * 4);
}
if (profiledata) {
Tag* icc = new Tag (cl, lookupAttrib (ifdAttribs, "ICCProfile"));
icc->initUndefArray (profiledata, profilelen);
cl->replaceTag (icc);
}
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;
}
// calculate strip offsets
int size = cl->calculateSize ();
int byps = bps / 8;
for (int i = 0; i < strips; i++) {
stripOffs->setInt (size + 8 + i * rps * W * 3 * byps, i * 4);
}
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

@ -809,10 +809,10 @@ const TagAttrib ifdAttribs[] = {
{0, AC_WRITE, 0, nullptr, 0x828e, AUTO, "CFAPattern", &cfaInterpreter},
{0, AC_WRITE, 0, kodakIfdAttribs, 0x8290, AUTO, "KodakIFD", &stdInterpreter},
{0, AC_WRITE, 1, nullptr, 0x8298, AUTO, "Copyright", &stdInterpreter},
{0, AC_SYSTEM, 0, nullptr, 0x83BB, AUTO, "IPTCData", &stdInterpreter},
{0, AC_DONTWRITE, 0, nullptr, 0x8606, AUTO, "LeafData", &stdInterpreter}, // is actually a subdir, but a proprietary format
{0, AC_WRITE, 0, exifAttribs, 0x8769, AUTO, "Exif", &stdInterpreter},
{0, AC_SYSTEM, 0, nullptr, 0x8773, AUTO, "ICCProfile", &stdInterpreter},
{0, AC_SYSTEM, 0, nullptr, 0x83BB, AUTO, "IPTCData", &stdInterpreter},
{0, AC_WRITE, 0, gpsAttribs, 0x8825, AUTO, "GPSInfo", &stdInterpreter},
{0, AC_WRITE, 0, nullptr, 0x9003, AUTO, "DateTimeOriginal", &stdInterpreter},
{0, AC_WRITE, 0, nullptr, 0x9004, AUTO, "DateTimeDigitized", &stdInterpreter},