From ee0bff4124a7dcadaab4cb262cec88fc8c058941 Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Sat, 16 Sep 2017 22:30:07 +0200 Subject: [PATCH] Updated frame handling with any type of hierarchy (see #4008) Images can now be detected in nested IFDs or as root IFDs. --- rtengine/dcp.cc | 3 +- rtengine/imagedata.cc | 360 +++++++++++---------- rtengine/imagedata.h | 32 +- rtengine/rtengine.h | 11 +- rtengine/simpleprocess.cc | 4 +- rtexif/canonattribs.cc | 17 + rtexif/rtexif.cc | 660 +++++++++++++++++++++++--------------- rtexif/rtexif.h | 59 ++-- rtgui/editorpanel.cc | 3 +- rtgui/exifpanel.cc | 10 +- rtgui/thumbnail.cc | 2 +- 11 files changed, 667 insertions(+), 494 deletions(-) diff --git a/rtengine/dcp.cc b/rtengine/dcp.cc index ee0cf3462..f9cdd1c73 100644 --- a/rtengine/dcp.cc +++ b/rtengine/dcp.cc @@ -697,7 +697,8 @@ DCPProfile::DCPProfile(const Glib::ustring& filename) : } ExifManager exifManager(file, nullptr, true); - std::unique_ptr tagDir(exifManager.parseTIFF(false)); + exifManager.parseTIFF(false); + std::unique_ptr tagDir(exifManager.roots.at(0)); Tag* tag = tagDir->getTag(toUnderlying(TagKey::CALIBRATION_ILLUMINANT_1)); light_source_1 = diff --git a/rtengine/imagedata.cc b/rtengine/imagedata.cc index aaa959c2e..4be057685 100644 --- a/rtengine/imagedata.cc +++ b/rtengine/imagedata.cc @@ -46,86 +46,25 @@ FramesMetaData* FramesMetaData::fromFile (const Glib::ustring& fname, std::uniqu return new FramesData (fname, std::move(rml), firstFrameOnly); } -FrameData::FrameData () - : root(nullptr), iptc(nullptr), time(), timeStamp(), iso_speed(0), aperture(0.), focal_len(0.), focal_len35mm(0.), focus_dist(0.f), +FrameData::FrameData (rtexif::TagDirectory* frameRootDir) + : frameRootDir(frameRootDir), iptc(nullptr), time(), timeStamp(), iso_speed(0), aperture(0.), focal_len(0.), focal_len35mm(0.), focus_dist(0.f), shutter(0.), expcomp(0.), make("Unknown"), model("Unknown"), orientation("Unknown"), lens("Unknown"), sampleFormat(IIOSF_UNKNOWN), isPixelShift(false), isHDR(false) { memset (&time, 0, sizeof(time)); -} -RawFrameData::RawFrameData (rtexif::ExifManager &exifManager) -{ - bool rootCreated = false; - if (exifManager.f && exifManager.rml) { - if (exifManager.rml->exifBase >= 0) { - root = exifManager.parse (); - - if (root) { - rtexif::Tag* t = root->getTag (0x83BB); - - if (t) { - iptc = iptc_data_new_from_data ((unsigned char*)t->getValue (), (unsigned)t->getValueSize ()); - } - extractInfo (); - rootCreated = true; - } - } else if (exifManager.rml->ciffBase >= 0) { - root = exifManager.parseCIFF (); - extractInfo (); - rootCreated = true; - } - } - if (!rootCreated) { - root = new rtexif::TagDirectory (); - } -} - -JpegFrameData::JpegFrameData (rtexif::ExifManager &exifManager) -{ - bool rootCreated = false; - if (exifManager.f) { - root = exifManager.parseJPEG (); - if (root) { - extractInfo (); - rootCreated = true; - } - rewind (exifManager.f); // Not sure this is necessary - iptc = iptc_data_new_from_jpeg_file (exifManager.f); - } - if (!rootCreated) { - root = new rtexif::TagDirectory (); - } -} - -TiffFrameData::TiffFrameData (rtexif::ExifManager &exifManager) -{ - bool rootCreated = false; - if (exifManager.f) { - root = exifManager.parseTIFF (); - extractInfo (); - - if (root) { - rtexif::Tag* t = root->getTag (0x83BB); - - if (t) { - iptc = iptc_data_new_from_data ((unsigned char*)t->getValue (), (unsigned)t->getValueSize ()); - } - rootCreated = true; - } - } - if (!rootCreated) { - root = new rtexif::TagDirectory (); - } + extractInfo(); } void FrameData::extractInfo () { - if (!root) { + if (!frameRootDir) { return; } + rtexif::Tag* tag; + memset(&time, 0, sizeof(time)); timeStamp = 0; iso_speed = 0; @@ -141,8 +80,9 @@ void FrameData::extractInfo () orientation.clear(); lens.clear(); - if (root->getTag("Make")) { - make = root->getTag ("Make")->valueToString(); + tag = frameRootDir->findTagUpward("Make"); + if (tag) { + make = tag->valueToString(); // Same dcraw treatment for (const auto& corp : { "Canon", @@ -174,8 +114,9 @@ void FrameData::extractInfo () make.erase(make.find_last_not_of(' ') + 1); } - if (root->getTag("Model")) { - model = root->getTag("Model")->valueToString(); + tag = frameRootDir->findTagUpward("Model"); + if (tag) { + model = tag->valueToString(); } if (!model.empty()) { @@ -207,66 +148,68 @@ void FrameData::extractInfo () model = "Unknown"; } - if (root->getTag ("Orientation")) { - orientation = root->getTag ("Orientation")->valueToString (); + tag = frameRootDir->findTagUpward("Orientation"); + if (tag) { + orientation = tag->valueToString (); } - rtexif::Tag* mnoteTag = root->findTag("MakerNote"); + tag = frameRootDir->findTagUpward("MakerNote"); rtexif::TagDirectory* mnote = nullptr; - if (mnoteTag) { - mnote = mnoteTag->getDirectory(); + if (tag) { + mnote = tag->getDirectory(); } rtexif::TagDirectory* exif = nullptr; - if (root->getTag ("Exif")) { - exif = root->getTag ("Exif")->getDirectory (); + tag = frameRootDir->findTagUpward("Exif"); + if (tag) { + exif = tag->getDirectory (); } if (exif) { // standard exif tags - if (exif->getTag ("ShutterSpeedValue")) { - shutter = exif->getTag ("ShutterSpeedValue")->toDouble (); + if ((tag = exif->getTag ("ShutterSpeedValue"))) { + shutter = tag->toDouble (); } - if (exif->getTag ("ExposureTime")) { - shutter = exif->getTag ("ExposureTime")->toDouble (); + if ((tag = exif->getTag ("ExposureTime"))) { + shutter = tag->toDouble (); } - if (exif->getTag ("ApertureValue")) { - aperture = exif->getTag ("ApertureValue")->toDouble (); + if ((tag = exif->getTag ("ApertureValue"))) { + aperture = tag->toDouble (); } - if (exif->getTag ("FNumber")) { - aperture = exif->getTag ("FNumber")->toDouble (); + if ((tag = exif->getTag ("FNumber"))) { + aperture = tag->toDouble (); } - if (exif->getTag ("ExposureBiasValue")) { - expcomp = exif->getTag ("ExposureBiasValue")->toDouble (); + if ((tag = exif->getTag ("ExposureBiasValue"))) { + expcomp = tag->toDouble (); } - if (exif->getTag ("FocalLength")) { - focal_len = exif->getTag ("FocalLength")->toDouble (); + if ((tag = exif->getTag ("FocalLength"))) { + focal_len = tag->toDouble (); } - if (exif->getTag ("FocalLengthIn35mmFilm")) { - focal_len35mm = exif->getTag ("FocalLengthIn35mmFilm")->toDouble (); + if ((tag = exif->getTag ("FocalLengthIn35mmFilm"))) { + focal_len35mm = tag->toDouble (); } // Focus distance from EXIF or XMP. MakerNote ones are scattered and partly encrypted int num = -3, denom = -3; // First try, offical EXIF. Set by Adobe on some DNGs - rtexif::Tag* pDst = exif->getTag("SubjectDistance"); + tag = exif->getTag("SubjectDistance"); - if (pDst) { + if (tag) { int num, denom; - pDst->toRational(num, denom); + tag->toRational(num, denom); } else { // Second try, XMP data char sXMPVal[64]; - if (root->getXMPTagValue("aux:ApproximateFocusDistance", sXMPVal)) { + if (frameRootDir->getXMPTagValue("aux:ApproximateFocusDistance", sXMPVal)) { sscanf(sXMPVal, "%d/%d", &num, &denom); } } @@ -279,12 +222,12 @@ void FrameData::extractInfo () } } - if (exif->getTag ("ISOSpeedRatings")) { - iso_speed = exif->getTag ("ISOSpeedRatings")->toDouble (); + if ((tag = exif->getTag ("ISOSpeedRatings"))) { + iso_speed = tag->toDouble (); } - if (exif->getTag ("DateTimeOriginal")) { - if (sscanf ((const char*)exif->getTag("DateTimeOriginal")->getValue(), "%d:%d:%d %d:%d:%d", &time.tm_year, &time.tm_mon, &time.tm_mday, &time.tm_hour, &time.tm_min, &time.tm_sec) == 6) { + if ((tag = exif->getTag ("DateTimeOriginal"))) { + if (sscanf ((const char*)tag->getValue(), "%d:%d:%d %d:%d:%d", &time.tm_year, &time.tm_mon, &time.tm_mday, &time.tm_hour, &time.tm_min, &time.tm_sec) == 6) { time.tm_year -= 1900; time.tm_mon -= 1; time.tm_isdst = -1; @@ -292,14 +235,14 @@ void FrameData::extractInfo () } } - rtexif::Tag *snTag = exif->findTag ("SerialNumber"); + tag = exif->findTag ("SerialNumber"); - if(!snTag) { - snTag = exif->findTag ("InternalSerialNumber"); + if(!tag) { + tag = exif->findTag ("InternalSerialNumber"); } - if ( snTag ) { - serial = snTag->valueToString(); + if (tag) { + serial = tag->valueToString(); } // guess lens... @@ -513,16 +456,21 @@ void FrameData::extractInfo () } } + rtexif::Tag* t = frameRootDir->getTag(0x83BB); + if (t) { + iptc = iptc_data_new_from_data ((unsigned char*)t->getValue (), (unsigned)t->getValueSize ()); + } + // ----------------------- Special file type detection (HDR, PixelShift) ------------------------ - uint16 bitspersample = 0, samplesperpixel = 0, sampleformat = 0, photometric = 0, compression = 0; - rtexif::Tag* bps = root->findTag("BitsPerSample"); - rtexif::Tag* spp = root->findTag("SamplesPerPixel"); - rtexif::Tag* sf = root->findTag("SampleFormat"); - rtexif::Tag* pi = root->findTag("PhotometricInterpretation"); - rtexif::Tag* c = root->findTag("Compression"); + uint16 bitspersample = 0, sampleformat = 0, photometric = 0, compression = 0; + rtexif::Tag* bps = frameRootDir->findTag("BitsPerSample"); + rtexif::Tag* spp = frameRootDir->findTag("SamplesPerPixel"); + rtexif::Tag* sf = frameRootDir->findTag("SampleFormat"); + rtexif::Tag* pi = frameRootDir->findTag("PhotometricInterpretation"); + rtexif::Tag* c = frameRootDir->findTag("Compression"); if (mnote && (!make.compare (0, 6, "PENTAX") || (!make.compare (0, 5, "RICOH") && !model.compare (0, 6, "PENTAX")))) { rtexif::Tag* hdr = mnote->findTag("HDR"); @@ -569,7 +517,6 @@ void FrameData::extractInfo () return; } - samplesperpixel = spp->toInt(); bitspersample = bps->toInt(); photometric = pi->toInt(); @@ -582,30 +529,37 @@ void FrameData::extractInfo () } if (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK) { - if ((samplesperpixel == 1 || samplesperpixel == 3 || samplesperpixel == 4) && sampleformat == SAMPLEFORMAT_UINT) { + if (sampleformat == SAMPLEFORMAT_INT || sampleformat == SAMPLEFORMAT_UINT) { if (bitspersample == 8) { sampleFormat = IIOSF_UNSIGNED_CHAR; - } - - if (bitspersample == 16) { + } else if (bitspersample <= 16) { sampleFormat = IIOSF_UNSIGNED_SHORT; } - } else if (samplesperpixel == 3 && sampleformat == SAMPLEFORMAT_IEEEFP) { + } else if (sampleformat == SAMPLEFORMAT_IEEEFP) { /* * Not yet supported * if (bitspersample==16) { sampleFormat = IIOSF_HALF; + isHDR = true; }*/ - if ((samplesperpixel == 3 || samplesperpixel == 4) && bitspersample == 32) { + if (bitspersample == 32) { sampleFormat = IIOSF_FLOAT; isHDR = true; } } } else if (photometric == PHOTOMETRIC_CFA) { - // Assuming Bayer or X-Trans raw file deliver 10, 12 14 or 16 bits uint, which is the case as of now - sampleFormat = IIOSF_UNSIGNED_SHORT; - } else if (samplesperpixel == 3 && photometric == PHOTOMETRIC_LOGLUV) { + if (sampleformat == SAMPLEFORMAT_IEEEFP) { + sampleFormat = IIOSF_FLOAT; + isHDR = true; + } else if (sampleformat == SAMPLEFORMAT_INT || sampleformat == SAMPLEFORMAT_UINT) { + if (bitspersample == 8) { // shouldn't occur... + sampleFormat = IIOSF_UNSIGNED_CHAR; + } else if (bitspersample <= 16) { + sampleFormat = IIOSF_UNSIGNED_SHORT; + } + } + } else if (photometric == PHOTOMETRIC_LOGLUV) { if (compression == COMPRESSION_SGILOG24) { sampleFormat = IIOSF_LOGLUV24; isHDR = true; @@ -619,26 +573,29 @@ void FrameData::extractInfo () FrameData::~FrameData () { - delete root; - if (iptc) { iptc_data_free (iptc); } } procparams::IPTCPairs FrameData::getIPTCData () const +{ + return getIPTCData(iptc); +} + +procparams::IPTCPairs FrameData::getIPTCData (IptcData* iptc_) { procparams::IPTCPairs iptcc; - if (!iptc) { + if (!iptc_) { return iptcc; } unsigned char buffer[2100]; for (int i = 0; i < 16; i++) { - IptcDataSet* ds = iptc_data_get_next_dataset (iptc, nullptr, IPTC_RECORD_APP_2, strTags[i].tag); + IptcDataSet* ds = iptc_data_get_next_dataset (iptc_, nullptr, IPTC_RECORD_APP_2, strTags[i].tag); if (ds) { iptc_dataset_get_data (ds, buffer, 2100); @@ -653,7 +610,7 @@ procparams::IPTCPairs FrameData::getIPTCData () const IptcDataSet* ds = nullptr; std::vector keywords; - while ((ds = iptc_data_get_next_dataset (iptc, ds, IPTC_RECORD_APP_2, IPTC_TAG_KEYWORDS))) { + while ((ds = iptc_data_get_next_dataset (iptc_, ds, IPTC_RECORD_APP_2, IPTC_TAG_KEYWORDS))) { iptc_dataset_get_data (ds, buffer, 2100); keywords.push_back (to_utf8((char*)buffer)); } @@ -662,7 +619,7 @@ procparams::IPTCPairs FrameData::getIPTCData () const ds = nullptr; std::vector suppCategories; - while ((ds = iptc_data_get_next_dataset (iptc, ds, IPTC_RECORD_APP_2, IPTC_TAG_SUPPL_CATEGORY))) { + while ((ds = iptc_data_get_next_dataset (iptc_, ds, IPTC_RECORD_APP_2, IPTC_TAG_SUPPL_CATEGORY))) { iptc_dataset_get_data (ds, buffer, 2100); suppCategories.push_back (to_utf8((char*)buffer)); iptc_dataset_unref (ds); @@ -687,11 +644,11 @@ IIOSampleFormat FrameData::getSampleFormat () const } rtexif::TagDirectory* FrameData::getExifData () const { - return root; + return frameRootDir; } bool FrameData::hasExif () const { - return root && root->getCount(); + return frameRootDir && frameRootDir->getCount(); } bool FrameData::hasIPTC () const { @@ -761,13 +718,19 @@ void FramesData::setDCRawFrameCount (unsigned int frameCount) dcrawFrameCount = frameCount; } +unsigned int FramesData::getRootCount () const +{ + return roots.size(); +} + unsigned int FramesData::getFrameCount () const { return dcrawFrameCount ? dcrawFrameCount : frames.size(); } + FrameData *FramesData::getFrameData (int frame) const { - return frames.empty() ? nullptr : frames.at(frame); + return frames.empty() || frame >= frames.size() ? nullptr : frames.at(frame); } bool FramesData::getPixelShift (unsigned int frame) const @@ -778,7 +741,7 @@ bool FramesData::getPixelShift (unsigned int frame) const // to evolve //return frames.at(frame)->getPixelShift (); - return frames.empty() ? false : frames.at(0)->getPixelShift (); + return frames.empty() || frame >= frames.size() ? false : frames.at(0)->getPixelShift (); } bool FramesData::getHDR (unsigned int frame) const { @@ -788,40 +751,50 @@ bool FramesData::getHDR (unsigned int frame) const // to evolve //return frames.at(frame)->getHDR (); - return frames.empty() ? false : frames.at(0)->getHDR (); + return frames.empty() || frame >= frames.size() ? false : frames.at(0)->getHDR (); } IIOSampleFormat FramesData::getSampleFormat (unsigned int frame) const { - return frames.empty() ? IIOSF_UNKNOWN : frames.at(frame)->getSampleFormat (); + return frames.empty() || frame >= frames.size() ? IIOSF_UNKNOWN : frames.at(frame)->getSampleFormat (); } -rtexif::TagDirectory* FramesData::getExifData (unsigned int frame) const +rtexif::TagDirectory* FramesData::getFrameExifData (unsigned int frame) const { - return frames.empty() ? nullptr : frames.at(frame)->getExifData (); + return frames.empty() || frame >= frames.size() ? nullptr : frames.at(frame)->getExifData (); } + +rtexif::TagDirectory* FramesData::getRootExifData (unsigned int root) const +{ + return roots.empty() || root >= roots.size() ? nullptr : roots.at(root); +} + procparams::IPTCPairs FramesData::getIPTCData (unsigned int frame) const { - if (frames.empty()) { - procparams::IPTCPairs emptyPairs; - return emptyPairs; + if (frame < frames.size() && frames.at(frame)->hasIPTC()) { + return frames.at(frame)->getIPTCData(); } else { - return frames.at(frame)->getIPTCData (); + if (iptc) { + return FrameData::getIPTCData(iptc); + } else { + procparams::IPTCPairs emptyPairs; + return emptyPairs; + } } } bool FramesData::hasExif (unsigned int frame) const { - return frames.empty() ? false : frames.at(frame)->hasExif (); + return frames.empty() || frame >= frames.size() ? false : frames.at(frame)->hasExif (); } bool FramesData::hasIPTC (unsigned int frame) const { - return frames.empty() ? false : frames.at(frame)->hasIPTC (); + return frames.empty() || frame >= frames.size() ? false : frames.at(frame)->hasIPTC (); } tm FramesData::getDateTime (unsigned int frame) const { - if (frames.empty()) { + if (frames.empty() || frame >= frames.size() ) { tm emptytm = {0, 0, 0, 0, 0, 0, 0, 0, 0}; return emptytm; } else { @@ -830,55 +803,55 @@ tm FramesData::getDateTime (unsigned int frame) const } time_t FramesData::getDateTimeAsTS(unsigned int frame) const { - return frames.empty() ? 0 : frames.at(frame)->getDateTimeAsTS (); + return frames.empty() || frame >= frames.size() ? 0 : frames.at(frame)->getDateTimeAsTS (); } int FramesData::getISOSpeed (unsigned int frame) const { - return frames.empty() ? 0 : frames.at(frame)->getISOSpeed (); + return frames.empty() || frame >= frames.size() ? 0 : frames.at(frame)->getISOSpeed (); } double FramesData::getFNumber (unsigned int frame) const { - return frames.empty() ? 0. : frames.at(frame)->getFNumber (); + return frames.empty() || frame >= frames.size() ? 0. : frames.at(frame)->getFNumber (); } double FramesData::getFocalLen (unsigned int frame) const { - return frames.empty() ? 0. : frames.at(frame)->getFocalLen (); + return frames.empty() || frame >= frames.size() ? 0. : frames.at(frame)->getFocalLen (); } double FramesData::getFocalLen35mm (unsigned int frame) const { - return frames.empty() ? 0. : frames.at(frame)->getFocalLen35mm (); + return frames.empty() || frame >= frames.size() ? 0. : frames.at(frame)->getFocalLen35mm (); } float FramesData::getFocusDist (unsigned int frame) const { - return frames.empty() ? 0.f : frames.at(frame)->getFocusDist (); + return frames.empty() || frame >= frames.size() ? 0.f : frames.at(frame)->getFocusDist (); } double FramesData::getShutterSpeed (unsigned int frame) const { - return frames.empty() ? 0. : frames.at(frame)->getShutterSpeed (); + return frames.empty() || frame >= frames.size() ? 0. : frames.at(frame)->getShutterSpeed (); } double FramesData::getExpComp (unsigned int frame) const { - return frames.empty() ? 0. : frames.at(frame)->getExpComp (); + return frames.empty() || frame >= frames.size() ? 0. : frames.at(frame)->getExpComp (); } std::string FramesData::getMake (unsigned int frame) const { - return frames.empty() ? std::string() : frames.at(frame)->getMake (); + return frames.empty() || frame >= frames.size() ? std::string() : frames.at(frame)->getMake (); } std::string FramesData::getModel (unsigned int frame) const { - return frames.empty() ? std::string() : frames.at(frame)->getModel (); + return frames.empty() || frame >= frames.size() ? std::string() : frames.at(frame)->getModel (); } std::string FramesData::getLens (unsigned int frame) const { - return frames.empty() ? std::string() : frames.at(frame)->getLens (); + return frames.empty() || frame >= frames.size() ? std::string() : frames.at(frame)->getLens (); } std::string FramesData::getSerialNumber (unsigned int frame) const { - return frames.empty() ? std::string() : frames.at(frame)->getSerialNumber (); + return frames.empty() || frame >= frames.size() ? std::string() : frames.at(frame)->getSerialNumber (); } std::string FramesData::getOrientation (unsigned int frame) const { - return frames.empty() ? std::string() : frames.at(frame)->getOrientation (); + return frames.empty() || frame >= frames.size() ? std::string() : frames.at(frame)->getOrientation (); } @@ -1007,7 +980,7 @@ failure: } FramesData::FramesData (const Glib::ustring& fname, std::unique_ptr rml, bool firstFrameOnly, bool loadAll) : - dcrawFrameCount (0) + iptc(nullptr), dcrawFrameCount (0) { if (rml && (rml->exifBase >= 0 || rml->ciffBase >= 0)) { FILE* f = g_fopen (fname.c_str (), "rb"); @@ -1017,14 +990,30 @@ FramesData::FramesData (const Glib::ustring& fname, std::unique_ptrexifBase >= 0) { + exifManager.parseRaw (); + + } else if (exifManager.rml->ciffBase >= 0) { + exifManager.parseCIFF (); + } + } + + // copying roots + roots = exifManager.roots; + + // creating FrameData + for (auto currFrame : exifManager.frames) { + FrameData* fd = new FrameData(currFrame); + + frames.push_back(fd); + } + for (auto currRoot : roots) { + rtexif::Tag* t = currRoot->getTag(0x83BB); + + if (t && !iptc) { + iptc = iptc_data_new_from_data ((unsigned char*)t->getValue (), (unsigned)t->getValueSize ()); + break; } } } @@ -1035,25 +1024,38 @@ FramesData::FramesData (const Glib::ustring& fname, std::unique_ptr(rml); rtexif::ExifManager exifManager (f, std::move(rml), firstFrameOnly); - FrameData *idata = new TiffFrameData (exifManager); - frames.push_back(idata); - if (has_rml && !firstFrameOnly) { - while (exifManager.getNextIFDOffset ()) { - exifManager.setIFDOffset (exifManager.getNextIFDOffset ()); - idata = new TiffFrameData (exifManager); - frames.push_back(idata); + exifManager.parseTIFF(); + + // creating FrameData + for (auto currFrame : exifManager.frames) { + FrameData* fd = new FrameData(currFrame); + + frames.push_back(fd); + } + for (auto currRoot : roots) { + rtexif::Tag* t = currRoot->getTag(0x83BB); + + if (t && !iptc) { + iptc = iptc_data_new_from_data ((unsigned char*)t->getValue (), (unsigned)t->getValueSize ()); + break; } } fclose (f); @@ -1063,7 +1065,11 @@ FramesData::FramesData (const Glib::ustring& fname, std::unique_ptr frames; + // root IFD in the file + std::vector roots; + IptcData* iptc; unsigned int dcrawFrameCount; public: @@ -120,12 +106,14 @@ public: ~FramesData (); void setDCRawFrameCount (unsigned int frameCount); + unsigned int getRootCount () const; unsigned int getFrameCount () const; FrameData *getFrameData (int frame) const; bool getPixelShift (unsigned int frame = 0) const; bool getHDR (unsigned int frame = 0) const; IIOSampleFormat getSampleFormat (unsigned int frame = 0) const; - rtexif::TagDirectory* getExifData (unsigned int frame = 0) const; + rtexif::TagDirectory* getFrameExifData (unsigned int frame = 0) const; + rtexif::TagDirectory* getRootExifData (unsigned int root = 0) const; procparams::IPTCPairs getIPTCData (unsigned int frame = 0) const; bool hasExif (unsigned int frame = 0) const; bool hasIPTC (unsigned int frame = 0) const; diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index 8f5a490d6..235ed2764 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -57,6 +57,8 @@ class FramesMetaData { public: + /** @return Returns the number of root Metadata */ + virtual unsigned int getRootCount () const = 0; /** @return Returns the number of frame contained in the file based on Metadata */ virtual unsigned int getFrameCount () const = 0; @@ -64,8 +66,13 @@ public: * @return Returns true if image contains exif metadata tags */ virtual bool hasExif (unsigned int frame = 0) const = 0; /** Returns the directory of exif metadata tags. + * @param root root number in the metadata tree * @return The directory of exif metadata tags */ - virtual rtexif::TagDirectory* getExifData (unsigned int frame = 0) const = 0; + virtual rtexif::TagDirectory* getRootExifData (unsigned int root = 0) const = 0; + /** Returns the directory of exif metadata tags. + * @param frame frame number in the metadata tree + * @return The directory of exif metadata tags */ + virtual rtexif::TagDirectory* getFrameExifData (unsigned int frame = 0) const = 0; /** Checks the availability of IPTC tags. * @return Returns true if image contains IPTC tags */ virtual bool hasIPTC (unsigned int frame = 0) const = 0; @@ -107,7 +114,7 @@ public: /** @return true if the file is a PixelShift shot (Pentax bodies) */ virtual bool getPixelShift (unsigned int frame = 0) const = 0; - /** @return 0: not ah HDR file ; 1: single shot HDR (e.g. 32 bit float DNG file or Log compressed) ; >1: multi-frame HDR file */ + /** @return false: not an HDR file ; true: single or multi-frame HDR file (e.g. Pentax HDR raw file or 32 bit float DNG file or Log compressed) */ virtual bool getHDR (unsigned int frame = 0) const = 0; /** @return the sample format based on MetaData */ virtual IIOSampleFormat getSampleFormat (unsigned int frame = 0) const = 0; diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 2c5d5a10e..44425c380 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -1277,9 +1277,9 @@ private: } if (tunnelMetaData) { - readyImg->setMetadata (ii->getMetaData()->getExifData ()); + readyImg->setMetadata (ii->getMetaData()->getRootExifData ()); } else { - readyImg->setMetadata (ii->getMetaData()->getExifData (), params.exif, params.iptc); + readyImg->setMetadata (ii->getMetaData()->getRootExifData (), params.exif, params.iptc); } diff --git a/rtexif/canonattribs.cc b/rtexif/canonattribs.cc index 01b00dd53..eaea38378 100644 --- a/rtexif/canonattribs.cc +++ b/rtexif/canonattribs.cc @@ -1068,6 +1068,22 @@ public: }; CAFocalPlaneInterpreter caFocalPlaneInterpreter; +class RawImageSegmentationInterpreter : public Interpreter +{ +public: + virtual std::string toString (Tag* t) + { + int segmentNumber = t->toInt(0, SHORT); + int segmentWidth = t->toInt(2, SHORT); + int lastSegmentWidth = t->toInt(4, SHORT); + + char buffer[32]; + sprintf (buffer, "%d %d %d", segmentNumber, segmentWidth, lastSegmentWidth); + return buffer; + } +}; +RawImageSegmentationInterpreter rawImageSegmentationInterpreter; + class CAExposureTimeInterpreter : public Interpreter { public: @@ -1995,6 +2011,7 @@ const TagAttrib canonAttribs[] = { {1, AC_WRITE, 0, nullptr, 0x4005, AUTO, "UnknownBlock2", &stdInterpreter}, {1, AC_WRITE, 0, nullptr, 0x4008, AUTO, "BlackLevel", &stdInterpreter}, {1, AC_WRITE, 0, canonMicroAdjustAttrib, 0x4013, AUTO, "AFMicroAdj", &stdInterpreter}, + {1, AC_WRITE, 0, nullptr, 0xc640, AUTO, "RawImageSegmentation", &rawImageSegmentationInterpreter}, { -1, AC_DONTWRITE, 0, nullptr, 0, AUTO, "", nullptr} }; } diff --git a/rtexif/rtexif.cc b/rtexif/rtexif.cc index 441d017d4..c5aa44670 100644 --- a/rtexif/rtexif.cc +++ b/rtexif/rtexif.cc @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -45,8 +46,6 @@ Interpreter stdInterpreter; // this class is a collection (an array) of tags //----------------------------------------------------------------------------- -#define TAG_SUBFILETYPE 0x00fe - TagDirectory::TagDirectory () : attribs (ifdAttribs), order (HOSTORDER), parent (nullptr) {} @@ -79,7 +78,7 @@ TagDirectory::TagDirectory (TagDirectory* p, FILE* f, int base, const TagAttrib* int id = newTag->getID(); // detect and possibly ignore tags of directories belonging to the embedded thumbnail image - if (attribs == ifdAttribs && id == TAG_SUBFILETYPE && newTag->toInt() != 0) { + if (attribs == ifdAttribs && id == TIFFTAG_SUBFILETYPE && newTag->toInt() != 0) { thumbdescr = true; } @@ -208,11 +207,14 @@ void TagDirectory::printAll (unsigned int level) const for (size_t i = 0; i < tags.size(); i++) { std::string name = tags[i]->nameToString (); + TagDirectory* currTagDir; if (tags[i]->isDirectory()) { - for (int j = 0; tags[i]->getDirectory (j); j++) { + for (int j = 0; (currTagDir = tags[i]->getDirectory (j)) != nullptr; j++) { printf ("%s+-- DIRECTORY %s[%d]:\n", prefixStr, name.c_str(), j); - tags[i]->getDirectory (j)->printAll (level + 1); + currTagDir->printAll (level + 1); } + } else { + printf ("%s- %s\n", prefixStr, name.c_str()); } } } @@ -448,22 +450,14 @@ Tag* TagDirectory::getTagP (const char* name) const return nullptr; } -Tag* TagDirectory::findTag (const char* name) const +Tag* TagDirectory::findTag (const char* name, bool lookUpward) const { - if (attribs) { - for (int i = 0; attribs[i].ignore != -1; i++) - if (!strcmp (attribs[i].name, name)) { - Tag* t = getTag (attribs[i].ID); - - if (t) { - return t; - } else { - break; - } - } + Tag* t = getTag(name); + if (t) { + return t; } - for (size_t i = 0; i < tags.size(); i++) + for (size_t i = 0; i < tags.size(); i++) { if (tags[i]->isDirectory()) { TagDirectory *dir = tags[i]->getDirectory(); Tag* t = dir->findTag (name); @@ -472,10 +466,97 @@ Tag* TagDirectory::findTag (const char* name) const return t; } } + } + + if (lookUpward && parent) { + Tag* t = parent->findTagUpward(name); + + if (t) { + return t; + } + } return nullptr; } +std::vector TagDirectory::findTags (int ID) +{ + + std::vector tagList; + + //assuming that an entry can only exist once + Tag* t = getTag(ID); + if (t) { + tagList.push_back(t); + } + + for (auto tag : tags) { + if (tag->isDirectory()) { + TagDirectory *dir = tag->getDirectory(); + std::vector subTagList = dir->findTags (ID); + + if (!subTagList.empty()) { + // concatenating the 2 vectors + // not really optimal in a memory efficiency pov + for (auto tag2 : subTagList) { + tagList.push_back(tag2); + } + } + } + } + + return tagList; +} + +std::vector TagDirectory::findTags (const char* name) +{ + + std::vector tagList; + + //assuming that an entry can only exist once + Tag* t = getTag(name); + if (t) { + tagList.push_back(t); + } + + for (auto tag : tags) { + if (tag->isDirectory()) { + TagDirectory *dir = tag->getDirectory(); + std::vector subTagList = dir->findTags (name); + + if (!subTagList.empty()) { + // concatenating the 2 vectors + // not really optimal in a memory efficiency pov + for (auto tag2 : subTagList) { + tagList.push_back(tag2); + } + } + } + } + + return tagList; +} + + +Tag* TagDirectory::findTagUpward (const char* name) const +{ + Tag* t = getTag(name); + if (t) { + return t; + } + + if (parent) { + Tag* t = parent->findTagUpward(name); + + if (t) { + return t; + } + } + + return nullptr; +} + + // Searches a simple value, as either attribute or element // only for simple values, not for entries with special chars or free text bool TagDirectory::getXMPTagValue (const char* name, char* value) const @@ -852,13 +933,11 @@ Tag::Tag (TagDirectory* p, FILE* f, int base) } if (tag == 0x002e) { // location of the embedded preview image in raw files of Panasonic cameras - const TagDirectory* const previewdir = - [&f]() - { - return ExifManager(f, nullptr, true).parseJPEG(ftell(f)); // try to parse the exif data from the preview image - }(); + ExifManager eManager(f, nullptr, true); + eManager.parseJPEG(ftell(f)); // try to parse the exif data from the preview image - if (previewdir) { + if (eManager.roots.size()) { + const TagDirectory* const previewdir = eManager.roots.at(0); if (previewdir->getTag ("Exif")) { if (previewdir->getTag ("Make")) { if (previewdir->getTag ("Make")->valueToString() == "Panasonic") { // "make" is not yet available here, so get it from the preview tags to assure we're doing the right thing @@ -1358,7 +1437,7 @@ void Tag::fromString (const char* v, int size) } } -int Tag::toInt (int ofs, TagType astype) +int Tag::toInt (int ofs, TagType astype) const { if (attrib) { return attrib->interpreter->toInt (this, ofs, astype); @@ -1409,7 +1488,7 @@ int Tag::toInt (int ofs, TagType astype) return 0; } -double Tag::toDouble (int ofs) +double Tag::toDouble (int ofs) const { if (attrib) { return attrib->interpreter->toDouble (this, ofs); @@ -1900,8 +1979,12 @@ const TagAttrib* lookupAttrib (const TagAttrib* dir, const char* field) return nullptr; } +void ExifManager::setIFDOffset(unsigned int offset) +{ + IFDOffset = offset; +} -TagDirectory* ExifManager::parseCIFF () +void ExifManager::parseCIFF () { TagDirectory* root = new TagDirectory (nullptr, ifdAttribs, INTEL); @@ -1913,7 +1996,6 @@ TagDirectory* ExifManager::parseCIFF () exif->getDirectory()->addTag (mn); parseCIFF (rml->ciffLength, root); root->sort (); - return root; } Tag* ExifManager::saveCIFFMNTag (TagDirectory* root, int len, const char* name) @@ -2217,6 +2299,12 @@ void ExifManager::parseCIFF (int length, TagDirectory* root) t->initString (buffer); root->addTag (t); } + + if (root->getTag("RawImageSegmentation")) { // Canon CR2 files + frames.push_back(root); + } + roots.push_back(root); + } static void @@ -2554,15 +2642,24 @@ parse_leafdata (TagDirectory* root, ByteOrder order) } } -TagDirectory* ExifManager::parse (bool skipIgnored) +void ExifManager::parseRaw (bool skipIgnored) { + parse(true, skipIgnored); +} + +void ExifManager::parseStd (bool skipIgnored) { + parse(false, skipIgnored); +} + +// return a root TagDirectory +void ExifManager::parse (bool isRaw, bool skipIgnored) { - int ifd = IFDOffset; + int ifdOffset = IFDOffset; if (!f) { #ifndef NDEBUG std::cerr << "ERROR : no file opened !" << std::endl; #endif - return nullptr; + return; } setlocale (LC_NUMERIC, "C"); // to set decimal point in sscanf @@ -2573,39 +2670,38 @@ TagDirectory* ExifManager::parse (bool skipIgnored) fread (&bo, 1, 2, f); order = (ByteOrder) ((int)bo); get2 (f, order); - if (!ifd) { - ifd = get4 (f, order); + if (!ifdOffset) { + ifdOffset = get4 (f, order); } } - return parseIFD (ifd, skipIgnored); -} + do { + // seek to IFD + fseek (f, rml->exifBase + ifdOffset, SEEK_SET); -TagDirectory* ExifManager::parseIFD (int ifdOffset, bool skipIgnored) -{ - if (!f) { - #ifndef NDEBUG - std::cerr << "ERROR : no file opened !" << std::endl; - #endif - return nullptr; - } + // first read the IFD directory + TagDirectory* root = new TagDirectory (nullptr, f, rml->exifBase, ifdAttribs, order, skipIgnored); - // seek to IFD0 - fseek (f, rml->exifBase + ifdOffset, SEEK_SET); + // fix ISO issue with nikon and panasonic cameras + Tag* make = root->getTag ("Make"); + Tag* exif = root->getTag ("Exif"); - // first read the IFD directory - TagDirectory* root = new TagDirectory (nullptr, f, rml->exifBase, ifdAttribs, order, skipIgnored); + if (exif && !exif->getDirectory()->getTag ("ISOSpeedRatings")) { + if (make && !strncmp ((char*)make->getValue(), "NIKON", 5)) { + Tag* mn = exif->getDirectory()->getTag ("MakerNote"); - // fix ISO issue with nikon and panasonic cameras - Tag* make = root->getTag ("Make"); - Tag* exif = root->getTag ("Exif"); + if (mn) { + Tag* iso = mn->getDirectory()->getTag ("ISOSpeed"); - if (exif && !exif->getDirectory()->getTag ("ISOSpeedRatings")) { - if (make && !strncmp ((char*)make->getValue(), "NIKON", 5)) { - Tag* mn = exif->getDirectory()->getTag ("MakerNote"); - - if (mn) { - Tag* iso = mn->getDirectory()->getTag ("ISOSpeed"); + if (iso) { + std::string isov = iso->valueToString (); + Tag* niso = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings")); + niso->initInt (atoi (isov.c_str()), SHORT); + exif->getDirectory()->addTagFront (niso); + } + } + } else if (make && (!strncmp ((char*)make->getValue(), "Panasonic", 9) || !strncmp ((char*)make->getValue(), "LEICA", 5))) { + Tag* iso = root->getTag ("PanaISO"); if (iso) { std::string isov = iso->valueToString (); @@ -2614,231 +2710,284 @@ TagDirectory* ExifManager::parseIFD (int ifdOffset, bool skipIgnored) exif->getDirectory()->addTagFront (niso); } } - } else if (make && (!strncmp ((char*)make->getValue(), "Panasonic", 9) || !strncmp ((char*)make->getValue(), "LEICA", 5))) { - Tag* iso = root->getTag ("PanaISO"); - - if (iso) { - std::string isov = iso->valueToString (); - Tag* niso = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings")); - niso->initInt (atoi (isov.c_str()), SHORT); - exif->getDirectory()->addTagFront (niso); - } } - } - if (make && !strncmp ((char*)make->getValue(), "Kodak", 5)) { - if (!exif) { - // old Kodak cameras may have exif tags in IFD0, reparse and create an exif subdir - fseek (f, rml->exifBase + ifdOffset, SEEK_SET); - TagDirectory* exifdir = new TagDirectory (nullptr, f, rml->exifBase, exifAttribs, order, true); + if (make && !strncmp ((char*)make->getValue(), "Kodak", 5)) { + if (!exif) { + // old Kodak cameras may have exif tags in IFD0, reparse and create an exif subdir + fseek (f, rml->exifBase + ifdOffset, SEEK_SET); + TagDirectory* exifdir = new TagDirectory (nullptr, f, rml->exifBase, exifAttribs, order, true); - exif = new Tag (root, root->getAttrib ("Exif")); - exif->initSubDir (exifdir); - root->addTagFront (exif); + exif = new Tag (root, root->getAttrib ("Exif")); + exif->initSubDir (exifdir); + root->addTagFront (exif); - if (!exif->getDirectory()->getTag ("ISOSpeedRatings") && exif->getDirectory()->getTag ("ExposureIndex")) { - Tag* niso = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings")); - niso->initInt (exif->getDirectory()->getTag ("ExposureIndex")->toInt(), SHORT); - exif->getDirectory()->addTagFront (niso); + if (!exif->getDirectory()->getTag ("ISOSpeedRatings") && exif->getDirectory()->getTag ("ExposureIndex")) { + Tag* niso = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings")); + niso->initInt (exif->getDirectory()->getTag ("ExposureIndex")->toInt(), SHORT); + exif->getDirectory()->addTagFront (niso); + } + } + + Tag *kodakIFD = root->getTag ("KodakIFD"); + + if (kodakIFD && kodakIFD->getDirectory()->getTag ("TextualInfo")) { + parseKodakIfdTextualInfo (kodakIFD->getDirectory()->getTag ("TextualInfo"), exif); } } - Tag *kodakIFD = root->getTag ("KodakIFD"); + parse_leafdata (root, order); - if (kodakIFD && kodakIFD->getDirectory()->getTag ("TextualInfo")) { - parseKodakIfdTextualInfo (kodakIFD->getDirectory()->getTag ("TextualInfo"), exif); - } - } + if (make && !strncmp ((char*)make->getValue(), "Hasselblad", 10)) { + /* + Figuring out the Hasselblad model is a mess. Hasselblad raw data comes in four slightly + different containers, 3FR (directly from CF card), FFF (same as 3FR but filtered through + Phocus, calibration data applied and a bit different tags), Adobe-generated DNGs and + Phocus-generated DNGs. - parse_leafdata (root, order); + FFF usually has a sane model name in Model (and is used as reference for what we shall + call the different Hasselblad models), but 3FR only says like "Hasselblad H3D" for + all H3D models, or "Flash Sync" if the back has been used on a mechanical camera body. + V-mount backs may have the model name of the V body instead of the back model. Etc... + as said it's a mess. - if (make && !strncmp ((char*)make->getValue(), "Hasselblad", 10)) { - /* - Figuring out the Hasselblad model is a mess. Hasselblad raw data comes in four slightly - different containers, 3FR (directly from CF card), FFF (same as 3FR but filtered through - Phocus, calibration data applied and a bit different tags), Adobe-generated DNGs and - Phocus-generated DNGs. + This code is supposed to handle all raw containers and end up with the same model + regardless of container. - FFF usually has a sane model name in Model (and is used as reference for what we shall - call the different Hasselblad models), but 3FR only says like "Hasselblad H3D" for - all H3D models, or "Flash Sync" if the back has been used on a mechanical camera body. - V-mount backs may have the model name of the V body instead of the back model. Etc... - as said it's a mess. + We don't differ between single shot and multi-shot models, and probably there's no use + of doing so. You need Hasselblad's own software to shoot multi-shot and can only do that + tethered. In single-shot mode they should be exactly the same as the single-shot models. + */ + Tag *subd = root->getTag (0x14a); + Tag *iw = (subd) ? subd->getDirectory()->getTag ("ImageWidth") : nullptr; + int sensorWidth = (iw) ? iw->toInt() : 0; + Tag* tmodel = root->getTag ("Model"); + const char *model = (tmodel) ? (const char *)tmodel->getValue() : ""; - This code is supposed to handle all raw containers and end up with the same model - regardless of container. + if (strstr (model, "Hasselblad ") == model) { + model += 11; + } else { + // if HxD is used in flash sync mode for example, we need to fetch model from this tag + Tag* tmodel3 = root->getTag ("UniqueCameraModel"); + const char *model3 = (tmodel3) ? (const char *)tmodel3->getValue() : ""; - We don't differ between single shot and multi-shot models, and probably there's no use - of doing so. You need Hasselblad's own software to shoot multi-shot and can only do that - tethered. In single-shot mode they should be exactly the same as the single-shot models. - */ - Tag *subd = root->getTag (0x14a); - Tag *iw = (subd) ? subd->getDirectory()->getTag ("ImageWidth") : nullptr; - int sensorWidth = (iw) ? iw->toInt() : 0; - Tag* tmodel = root->getTag ("Model"); - const char *model = (tmodel) ? (const char *)tmodel->getValue() : ""; + if (strstr (model3, "Hasselblad ") == model3) { + model = model3 + 11; + } + } - if (strstr (model, "Hasselblad ") == model) { - model += 11; - } else { - // if HxD is used in flash sync mode for example, we need to fetch model from this tag + // FIXME: due to lack of test files this Hasselblad model identification is not 100% complete + // This needs checking out: CFV-39/CFV-50 3FR, H3DII vs H3D, old CF/CFH models + + if (!strcmp (model, "H3D")) { + // We can't differ between H3D and H3DII for the 22, 31 and 39 models. There's was no H3D-50 so we know that is a + // H3DII-50. At the time of writing I have no test files for the H3D vs H3DII models, so there still may be a chance + // to differ between them. AFAIK Adobe's DNG converter don't differ between them, and actually call the H3DII-50 + // H3D-50 although Hasselblad never released such a model. + switch (sensorWidth) { + case 4096: + tmodel->initString ("H3D-22"); + break; + + case 6542: + tmodel->initString ("H3D-31"); + break; + + case 7262: + tmodel->initString ("H3D-39"); + break; + + case 8282: + tmodel->initString ("H3DII-50"); + break; + } + } else if (!strcmp (model, "H4D")) { + switch (sensorWidth) { + case 6542: + tmodel->initString ("H4D-31"); + break; + + case 7410: + tmodel->initString ("H4D-40"); + break; + + case 8282: + tmodel->initString ("H4D-50"); + break; + + case 9044: + tmodel->initString ("H4D-60"); + break; + } + } else if (!strcmp (model, "H5D")) { + switch (sensorWidth) { + case 7410: + tmodel->initString ("H5D-40"); + break; + + case 8282: + tmodel->initString ("H5D-50"); + break; + + case 8374: + tmodel->initString ("H5D-50c"); + break; + + case 9044: + tmodel->initString ("H5D-60"); + break; + } + } else if (!strcmp (model, "CFV")) { + switch (sensorWidth) { + case 7262: + tmodel->initString ("CFV-39"); + break; + + case 8282: + tmodel->initString ("CFV-50"); + break; + + case 8374: + tmodel->initString ("CFV-50c"); + break; + } + } + + // and a few special cases Tag* tmodel3 = root->getTag ("UniqueCameraModel"); const char *model3 = (tmodel3) ? (const char *)tmodel3->getValue() : ""; if (strstr (model3, "Hasselblad ") == model3) { - model = model3 + 11; - } - } - - // FIXME: due to lack of test files this Hasselblad model identification is not 100% complete - // This needs checking out: CFV-39/CFV-50 3FR, H3DII vs H3D, old CF/CFH models - - if (!strcmp (model, "H3D")) { - // We can't differ between H3D and H3DII for the 22, 31 and 39 models. There's was no H3D-50 so we know that is a - // H3DII-50. At the time of writing I have no test files for the H3D vs H3DII models, so there still may be a chance - // to differ between them. AFAIK Adobe's DNG converter don't differ between them, and actually call the H3DII-50 - // H3D-50 although Hasselblad never released such a model. - switch (sensorWidth) { - case 4096: - tmodel->initString ("H3D-22"); - break; - - case 6542: - tmodel->initString ("H3D-31"); - break; - - case 7262: - tmodel->initString ("H3D-39"); - break; - - case 8282: - tmodel->initString ("H3DII-50"); - break; - } - } else if (!strcmp (model, "H4D")) { - switch (sensorWidth) { - case 6542: - tmodel->initString ("H4D-31"); - break; - - case 7410: - tmodel->initString ("H4D-40"); - break; - - case 8282: - tmodel->initString ("H4D-50"); - break; - - case 9044: - tmodel->initString ("H4D-60"); - break; - } - } else if (!strcmp (model, "H5D")) { - switch (sensorWidth) { - case 7410: - tmodel->initString ("H5D-40"); - break; - - case 8282: - tmodel->initString ("H5D-50"); - break; - - case 8374: - tmodel->initString ("H5D-50c"); - break; - - case 9044: - tmodel->initString ("H5D-60"); - break; - } - } else if (!strcmp (model, "CFV")) { - switch (sensorWidth) { - case 7262: - tmodel->initString ("CFV-39"); - break; - - case 8282: - tmodel->initString ("CFV-50"); - break; - - case 8374: - tmodel->initString ("CFV-50c"); - break; - } - } - - // and a few special cases - Tag* tmodel3 = root->getTag ("UniqueCameraModel"); - const char *model3 = (tmodel3) ? (const char *)tmodel3->getValue() : ""; - - if (strstr (model3, "Hasselblad ") == model3) { - model3 = model3 + 11; - } - - if (!strcmp (model3, "ixpressCF132")) { - tmodel->initString ("CF-22"); - } else if (!strcmp (model3, "Hasselblad96")) { - tmodel->initString ("CFV"); // popularly called CFV-16, but the official name is CFV - } else if (!strcmp (model3, "Hasselblad234")) { - tmodel->initString ("CFV-39"); - } else if (sensorWidth == 4090) { - tmodel->initString ("V96C"); - } - - // and yet some, this is for Adobe-generated DNG files - Tag* tmodel4 = root->getTag ("LocalizedCameraModel"); - - if (tmodel4) { - const char *model4 = (const char *)tmodel4->getValue(); - - if (strstr (model4, "Hasselblad ") == model4) { - model4 = model4 + 11; + model3 = model3 + 11; } - if (!strcmp (model4, "ixpressCF132-22")) { + if (!strcmp (model3, "ixpressCF132")) { tmodel->initString ("CF-22"); - } else if (!strcmp (model4, "Hasselblad96-16")) { - tmodel->initString ("CFV"); - } else if (!strcmp (model4, "Hasselblad234-39")) { + } else if (!strcmp (model3, "Hasselblad96")) { + tmodel->initString ("CFV"); // popularly called CFV-16, but the official name is CFV + } else if (!strcmp (model3, "Hasselblad234")) { tmodel->initString ("CFV-39"); - } else if (!strcmp (model4, "H3D-50")) { - // Adobe names H3DII-50 incorrectly as H3D-50 - tmodel->initString ("H3DII-50"); - } else if (strstr (model4, "H3D-") == model4 || strstr (model4, "H4D-") == model4 || strstr (model4, "H5D-") == model4) { - tmodel->initString (model4); - } - } - } - - if (!root->getTag ("Orientation")) { - if (make && !strncmp ((char*)make->getValue(), "Phase One", 9)) { - int orientation = 0; - Tag *iw = root->getTag ("ImageWidth"); - - if (iw) { - // from dcraw, derive orientation from image width - orientation = "0653"[iw->toInt() & 3] - '0'; + } else if (sensorWidth == 4090) { + tmodel->initString ("V96C"); } - Tag *t = new Tag (root, root->getAttrib ("Orientation")); - t->initInt (orientation, SHORT); - root->addTagFront (t); + // and yet some, this is for Adobe-generated DNG files + Tag* tmodel4 = root->getTag ("LocalizedCameraModel"); + + if (tmodel4) { + const char *model4 = (const char *)tmodel4->getValue(); + + if (strstr (model4, "Hasselblad ") == model4) { + model4 = model4 + 11; + } + + if (!strcmp (model4, "ixpressCF132-22")) { + tmodel->initString ("CF-22"); + } else if (!strcmp (model4, "Hasselblad96-16")) { + tmodel->initString ("CFV"); + } else if (!strcmp (model4, "Hasselblad234-39")) { + tmodel->initString ("CFV-39"); + } else if (!strcmp (model4, "H3D-50")) { + // Adobe names H3DII-50 incorrectly as H3D-50 + tmodel->initString ("H3DII-50"); + } else if (strstr (model4, "H3D-") == model4 || strstr (model4, "H4D-") == model4 || strstr (model4, "H5D-") == model4) { + tmodel->initString (model4); + } + } } + + if (!root->getTag ("Orientation")) { + if (make && !strncmp ((char*)make->getValue(), "Phase One", 9)) { + int orientation = 0; + Tag *iw = root->getTag ("ImageWidth"); + + if (iw) { + // from dcraw, derive orientation from image width + orientation = "0653"[iw->toInt() & 3] - '0'; + } + + Tag *t = new Tag (root, root->getAttrib ("Orientation")); + t->initInt (orientation, SHORT); + root->addTagFront (t); + } + } + + // --- detecting image root IFD based on SubFileType, or if not provided, on PhotometricInterpretation + + bool frameRootDetected = false; + std::vector sftTagList = root->findTags(TIFFTAG_SUBFILETYPE); + + if (!sftTagList.empty()) { + for (auto sft : sftTagList) { + int sftVal = sft->toInt(); + if (sftVal == (isRaw ? 0 : 2)) { + frames.push_back(sft->getParent()); + frameRootDetected = true; + + //printf("\n--------------- FRAME -----------------------------\n\n"); + //sft->getParent()->printAll (); + } + } + } + + if(!frameRootDetected) { + sftTagList = root->findTags(TIFFTAG_OSUBFILETYPE); + if (!sftTagList.empty()) { + for (auto sft : sftTagList) { + int sftVal = sft->toInt(); + if (sftVal == OFILETYPE_IMAGE) { + frames.push_back(sft->getParent()); + frameRootDetected = true; + + //printf("\n--------------- FRAME -----------------------------\n\n"); + //sft->getParent()->printAll (); + } + } + } + } + + if(!frameRootDetected) { + std::vector piTagList = root->findTags("PhotometricInterpretation"); + if (!piTagList.empty()) { + for (auto pi : piTagList) { + int piVal = pi->toInt(); + if (piVal == (isRaw ? 32803 : 2)) { + frames.push_back(pi->getParent()); + //frameRootDetected = true; not used afterward + + //printf("\n--------------- FRAME -----------------------------\n\n"); + //pi->getParent()->printAll (); + } + } + } + } + + // --- getting next sibling root + + ifdOffset = get4 (f, order); + + roots.push_back(root); + + //printf("\n~~~~~~~~~ ROOT ~~~~~~~~~~~~~~~~~~~~~~~~\n\n"); + //root->printAll (); + + } while (ifdOffset && !onlyFirst); + + // Security check : if there's at least one root, there must be at least one image. + // If the following occurs, then image detection above has failed or it's an unsupported file type. + // Yet the result of this should be valid. + if (!roots.empty() && frames.empty()) { + frames.push_back(roots.at(0)); } - - nextIFDOffset = get4 (f, order); - - //root->printAll (); - return root; } -TagDirectory* ExifManager::parseJPEG (int offset) +void ExifManager::parseJPEG (int offset) { if (!f) { #ifndef NDEBUG std::cerr << "ERROR : no file opened !" << std::endl; #endif - return nullptr; + return; } if(!fseek (f, offset, SEEK_SET)) { @@ -2856,7 +3005,7 @@ TagDirectory* ExifManager::parseJPEG (int offset) if (fread (&c, 1, 1, f) && c == 0xe1) { // APP1 marker found if (fread (idbuff, 1, 8, f) < 8) { - return nullptr; + return; } if (!memcmp (idbuff + 2, exifid, 6)) { // Exif info found @@ -2868,29 +3017,26 @@ TagDirectory* ExifManager::parseJPEG (int offset) rml.reset(new rtengine::RawMetaDataLocation(0)); } rml->exifBase = tiffbase; - TagDirectory* tagDir = parse (); + parse (false); if (rmlCreated) { rml.reset(); } - return tagDir; + return; } } } } } - - return nullptr; } -TagDirectory* ExifManager::parseTIFF (bool skipIgnored) +void ExifManager::parseTIFF (bool skipIgnored) { if (!rml) { rml.reset(new rtengine::RawMetaDataLocation(0)); - TagDirectory* const res = parse(skipIgnored); + parse(false, skipIgnored); rml.reset(); - return res; } else { - return parse (skipIgnored); + parse (false,skipIgnored); } } diff --git a/rtexif/rtexif.h b/rtexif/rtexif.h index bc9495187..bd2d82977 100644 --- a/rtexif/rtexif.h +++ b/rtexif/rtexif.h @@ -128,16 +128,31 @@ public: return tags.size (); } const TagAttrib* getAttrib (int id); - const TagAttrib* getAttrib (const char* name); // Find a Tag by scanning the whole tag tree and stopping at the first occurrence - const TagAttrib* getAttribP (const char* name); // Try to get the Tag at a given location. 'name' is a path relative to this directory (e.g. "LensInfo/FocalLength") + // Find a Tag by scanning the whole tag tree and stopping at the first occurrence + const TagAttrib* getAttrib (const char* name); + // Try to get the Tag at a given location. 'name' is a path relative to this directory (e.g. "LensInfo/FocalLength") + const TagAttrib* getAttribP (const char* name); const TagAttrib* getAttribTable() { return attribs; } - Tag* getTag (const char* name) const; // Find a Tag by scanning the whole tag tree and stopping at the first occurrence - Tag* getTagP (const char* name) const; // Try to get the Tag at a given location. 'name' is a path relative to this directory (e.g. "LensInfo/FocalLength") + // Find a Tag by scanning the whole tag tree and stopping at the first occurrence + Tag* getTag (const char* name) const; + // Try to get the Tag at a given location. 'name' is a path relative to this directory (e.g. "LensInfo/FocalLength") + Tag* getTagP (const char* name) const; Tag* getTag (int ID) const; - virtual Tag* findTag (const char* name) const; + + // Try to get the Tag in the current directory and in subdirectories + // if lookUpward = true, it will scan the parents TagDirectory up to the root one, + // but w/o looking into their subdirs + virtual Tag* findTag (const char* name, bool lookUpward = false) const; + // Find a all Tags with the given name by scanning the whole tag tree + std::vector findTags (const char* name); + // Find a all Tags with the given ID by scanning the whole tag tree + std::vector findTags (int ID); + // Try to get the Tag in the current directory and in parent directories + // (won't look into subdirs) + virtual Tag* findTagUpward (const char* name) const; bool getXMPTagValue (const char* name, char* value) const; void keepTag (int ID); @@ -263,9 +278,9 @@ public: } // read/write value - int toInt (int ofs = 0, TagType astype = INVALID); + int toInt (int ofs = 0, TagType astype = INVALID) const; void fromInt (int v); - double toDouble (int ofs = 0); + double toDouble (int ofs = 0) const; double *toDoubleArray (int ofs = 0); void toRational (int& num, int& denom, int ofs = 0); void toString (char* buffer, int ofs = 0); @@ -313,8 +328,8 @@ class ExifManager { Tag* saveCIFFMNTag (TagDirectory* root, int len, const char* name); - TagDirectory* parseIFD (int ifdOffset, bool skipIgnored); void parseCIFF (int length, TagDirectory* root); + void parse (bool isRaw, bool skipIgnored = true); public: FILE* f; @@ -322,27 +337,21 @@ public: ByteOrder order; bool onlyFirst; // Only first IFD unsigned int IFDOffset; - unsigned int nextIFDOffset; + std::vector roots; + std::vector frames; ExifManager (FILE* fHandle, std::unique_ptr _rml, bool onlyFirstIFD) : f(fHandle), rml(std::move(_rml)), order(UNKNOWN), onlyFirst(onlyFirstIFD), - IFDOffset(0), nextIFDOffset(0) {} + IFDOffset(0) {} - void setIFDOffset(unsigned int offset) - { - IFDOffset = offset; - } + void setIFDOffset(unsigned int offset); - unsigned int getNextIFDOffset() - { - return nextIFDOffset; - } - // The following functions parse only one IFD at a time and store the "next IFD offset" - TagDirectory* parse (bool skipIgnored = true); - TagDirectory* parseJPEG (int offset = 0); // offset: to extract exif data from a embedded preview/thumbnail - TagDirectory* parseTIFF (bool skipIgnored = true); - TagDirectory* parseCIFF (); + void parseRaw (bool skipIgnored = true); + void parseStd (bool skipIgnored = true); + void parseJPEG (int offset = 0); // offset: to extract exif data from a embedded preview/thumbnail + void parseTIFF (bool skipIgnored = true); + void parseCIFF (); /// @brief Get default tag for TIFF /// @param forthis The byte order will be taken from the given directory. @@ -379,7 +388,7 @@ public: } } // Get the value as a double - virtual double toDouble (Tag* t, int ofs = 0) + virtual double toDouble (const Tag* t, int ofs = 0) { double ud, dd; @@ -420,7 +429,7 @@ public: } } // Get the value as an int - virtual int toInt (Tag* t, int ofs = 0, TagType astype = INVALID) + virtual int toInt (const Tag* t, int ofs = 0, TagType astype = INVALID) { int a; diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index bb5e77b42..25e37514e 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -1297,7 +1297,7 @@ void EditorPanel::info_toggled () int ww = ipc->getFullWidth(); int hh = ipc->getFullHeight(); //megapixels - infoString = Glib::ustring::compose ("%1\n%2 MP (%2x%3)", + infoString = Glib::ustring::compose ("%1\n%2 MP (%3x%4)", infoString, Glib::ustring::format (std::setw (4), std::fixed, std::setprecision (1), (float)ww * hh / 1000000), ww, hh); @@ -1313,7 +1313,6 @@ void EditorPanel::info_toggled () infoString = Glib::ustring::compose ("%1 / %2", infoString, M(Glib::ustring::compose("SAMPLEFORMAT_%1", sampleFormat))); } } else if (isPixelShift) { - infoString = Glib::ustring::compose ("%1\n" + M("QINFO_HDR"), infoString, numFrames); infoString = Glib::ustring::compose ("%1\n" + M("QINFO_PIXELSHIFT"), infoString, numFrames); } } else { diff --git a/rtgui/exifpanel.cc b/rtgui/exifpanel.cc index 133c3a9ed..9e10b75fa 100644 --- a/rtgui/exifpanel.cc +++ b/rtgui/exifpanel.cc @@ -189,13 +189,13 @@ void ExifPanel::setImageData (const FramesMetaData* id) exifTreeModel->clear (); if (id) { - for (unsigned int frameNum = 0; frameNum < id->getFrameCount (); ++frameNum) { - if ( id->getExifData (frameNum)) { - //id->getExifData ()->printAll (); - if (frameNum > 0) { + for (unsigned int rootNum = 0; rootNum < id->getRootCount (); ++rootNum) { + if ( id->getRootExifData (rootNum)) { + //id->getRootExifData ()->printAll (); + if (rootNum > 0) { addSeparator(); } - addDirectory (id->getExifData (frameNum), exifTreeModel->children()); + addDirectory (id->getRootExifData (rootNum), exifTreeModel->children()); } } } diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index 4bc5bcdb9..fbe8fc7be 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -265,7 +265,7 @@ rtengine::procparams::ProcParams* Thumbnail::createProcParamsForUpdate(bool retu const rtexif::TagDirectory* exifDir = nullptr; - if (imageMetaData && (exifDir = imageMetaData->getExifData())) { + if (imageMetaData && (exifDir = imageMetaData->getRootExifData())) { exifDir->CPBDump(tmpFileName, fname, outFName, defaultPparamsPath == DEFPROFILE_INTERNAL ? DEFPROFILE_INTERNAL : Glib::build_filename(defaultPparamsPath, Glib::path_get_basename(defProf) + paramFileExtension), cfs,