/* * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath * * RawTherapee is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RawTherapee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "dnggainmap.h" #include "imagedata.h" #include "imagesource.h" #include "metadata.h" #include "rt_math.h" #include "utils.h" #pragma GCC diagnostic warning "-Wextra" #define PRINT_HDR_PS_DETECTION 0 using namespace rtengine; namespace { const std::string& validateUft8(const std::string& str, const std::string& on_error = "???") { if (Glib::ustring(str).validate()) { return str; } return on_error; } template auto to_long(const Iterator &iter, Integer n = Integer{0}) -> decltype( #if EXIV2_TEST_VERSION(0,28,0) iter->toInt64() ) { return iter->toInt64(n); #else iter->toLong() ) { return iter->toLong(n); #endif } /** * Convenience class for reading data from a metadata tag's bytes value. * * It maintains an offset. Data is read starting from the offset, then the * offset is advanced to the byte after the last byte read. */ class TagValueReader { using DataContainer = std::vector; using DataOffset = DataContainer::difference_type; DataContainer data; DataOffset offset{0}; Exiv2::ByteOrder defaultByteOrder; /** * Reads a value at the current offset. * * @tparam T Value's type. * @tparam getter Function that interprets the data using a given byte order * and returns the value at a given location. * @return The value. */ template T readValue() { T value = getter(data.data() + offset, defaultByteOrder); offset += sizeof(T); return value; } public: /** * Creates a reader for the given value with the given byte order. * * @param value The value. * @param defaultByteOrder The byte order of the value's data. */ TagValueReader(const Exiv2::Value &value, Exiv2::ByteOrder defaultByteOrder = Exiv2::bigEndian) : data(value.size()), defaultByteOrder(defaultByteOrder) { value.copy(data.data(), Exiv2::invalidByteOrder); } /** * Returns the value's size in bytes. */ std::size_t size() const { return data.size(); } /** * Checks if the current offset is at or beyond the end of the data. */ bool isEnd() const { return offset > 0 && static_cast(offset) >= data.size(); } /** * Reads a double from the current offset and advances the offset. */ double readDouble() { return readValue(); } /** * Reads a float from the current offset and advances the offset. */ float readFloat() { return readValue(); } /** * Reads an unsigned integer from the current offset and advances the * offset. */ std::uint32_t readUInt() { return readValue(); } /** * Sets the offset. */ void seekAbsolute(DataOffset newOffset) { offset = newOffset; } /** * Advances the offset by the given amount. */ void seekRelative(DataOffset offsetDifference) { offset += offsetDifference; } }; std::uint32_t readFixBadPixelsConstant(TagValueReader &reader) { reader.seekRelative(12); // Skip DNG spec version, flags, and tag size. return reader.readUInt(); } GainMap readGainMap(TagValueReader &reader) { reader.seekRelative(12); // Skip DNG spec version, flags, and tag size. GainMap gainMap; gainMap.Top = reader.readUInt(); gainMap.Left = reader.readUInt(); gainMap.Bottom = reader.readUInt(); gainMap.Right = reader.readUInt(); gainMap.Plane = reader.readUInt(); gainMap.Planes = reader.readUInt(); gainMap.RowPitch = reader.readUInt(); gainMap.ColPitch = reader.readUInt(); gainMap.MapPointsV = reader.readUInt(); gainMap.MapPointsH = reader.readUInt(); gainMap.MapSpacingV = reader.readDouble(); gainMap.MapSpacingH = reader.readDouble(); gainMap.MapOriginV = reader.readDouble(); gainMap.MapOriginH = reader.readDouble(); gainMap.MapPlanes = reader.readUInt(); const std::size_t n = static_cast(gainMap.MapPointsV) * static_cast(gainMap.MapPointsH) * static_cast(gainMap.MapPlanes); gainMap.MapGain.reserve(n); for (std::size_t i = 0; i < n; ++i) { gainMap.MapGain.push_back(reader.readFloat()); } return gainMap; } void readOpcodesList( const Exiv2::Value &value, std::uint32_t *fixBadPixelsConstant, bool *hasFixBadPixelsConstant, std::vector *gainMaps) { TagValueReader reader(value); std::uint32_t ntags = reader.readUInt(); // read the number of opcodes if (ntags >= reader.size() / 12) { // rough check for wrong value (happens for example with DNG files from DJI FC6310) return; } while (ntags-- && !reader.isEnd()) { unsigned opcode = reader.readUInt(); if (opcode == 4 && (fixBadPixelsConstant || hasFixBadPixelsConstant)) { const auto constant = readFixBadPixelsConstant(reader); if (fixBadPixelsConstant) { *fixBadPixelsConstant = constant; } if (hasFixBadPixelsConstant) { *hasFixBadPixelsConstant = true; } } else if (opcode == 9 && gainMaps && gainMaps->size() < 4) { gainMaps->push_back(readGainMap(reader)); } else { reader.seekRelative(8); // skip 8 bytes as they don't interest us currently reader.seekRelative(reader.readUInt()); } } } } namespace rtengine { extern const Settings *settings; } // namespace rtengine FramesMetaData* FramesMetaData::fromFile(const Glib::ustring& fname) { return new FramesData(fname); } static struct tm timeFromTS(const time_t ts) { #if !defined(_WIN32) struct tm tm; return *gmtime_r(&ts, &tm); #else return *gmtime(&ts); #endif } FramesData::FramesData(const Glib::ustring &fname, time_t ts) : ok_(false), fname_(fname), dcrawFrameCount(0), time{timeFromTS(ts)}, timeStamp{ts}, 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"), rating(0), lens("Unknown"), sampleFormat(IIOSF_UNKNOWN), isPixelShift(false), isHDR(false), isDNG(false), w_(-1), h_(-1) { GStatBuf statbuf = {}; g_stat(fname.c_str(), &statbuf); modTimeStamp = statbuf.st_mtime; modTime = timeFromTS(modTimeStamp); make.clear(); model.clear(); serial.clear(); orientation.clear(); lens.clear(); try { Exiv2Metadata meta(fname); meta.load(); const auto& exif = meta.exifData(); ok_ = true; // taken and adapted from darktable (src/common/exif.cc) /* This file is part of darktable, copyright (c) 2009--2013 johannes hanika. copyright (c) 2011 henrik andersson. copyright (c) 2012-2017 tobias ellinghaus. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . */ Exiv2::ExifData::const_iterator pos; const auto find_exif_tag = [&exif, &pos](const std::string &name) -> bool { try { pos = exif.findKey(Exiv2::ExifKey(name)); return pos != exif.end() && pos->size(); } catch (std::exception &e) { if (settings->verbose) { std::cerr << "Exiv2 WARNING -- error finding tag " << name << ": " << e.what() << std::endl; } return false; } }; const auto find_tag = [&exif, &pos](decltype(Exiv2::make) func) -> bool { pos = func(exif); return pos != exif.end() && pos->size(); }; // List of tag names taken from exiv2's printSummary() in actions.cpp if (find_tag(Exiv2::make)) { make = validateUft8(pos->print(&exif)); } if (find_tag(Exiv2::model)) { model = validateUft8(pos->print(&exif)); } if (make.size() > 0) { for (const auto& corp : { "Canon", "NIKON", "EPSON", "KODAK", "Kodak", "OLYMPUS", "PENTAX", "RICOH", "MINOLTA", "Minolta", "Konica", "CASIO", "Sinar", "Phase One", "SAMSUNG", "Mamiya", "MOTOROLA", "Leaf", "Panasonic" }) { if (make.find(corp) != std::string::npos) { // Simplify company names make = corp; break; } } } std::string::size_type nonspace_pos = make.find_last_not_of(' '); if (nonspace_pos != std::string::npos && nonspace_pos + 1 < make.size()) { make.erase(nonspace_pos + 1); } nonspace_pos = model.find_last_not_of(' '); if (nonspace_pos != std::string::npos && nonspace_pos + 1 < model.size()) { model.erase(nonspace_pos + 1); } if (!make.empty() && model.find(make + ' ') == 0) { model.erase(0, make.size() + 1); } if (find_tag(Exiv2::exposureTime)) { shutter = pos->toFloat(); } if (find_tag(Exiv2::fNumber)) { aperture = pos->toFloat(); } // Read ISO speed - Nikon happens to return a pair for Lo and Hi modes if (find_tag(Exiv2::isoSpeed)) { // If standard exif iso tag, use the old way of interpreting the return value to be more regression-save if (pos->key() == "Exif.Photo.ISOSpeedRatings") { const long isofield = pos->count() > 1 ? 1 : 0; iso_speed = pos->toFloat(isofield); } else { iso_speed = std::atof(pos->print().c_str()); } } // Some newer cameras support iso settings that exceed the 16 bit of exif's ISOSpeedRatings if (iso_speed == 65535 || iso_speed == 0) { if (find_exif_tag("Exif.PentaxDng.ISO") || find_exif_tag("Exif.Pentax.ISO")) { iso_speed = std::atof(pos->print().c_str()); } else if ( ( make == "SONY" || make == "Canon" ) && find_exif_tag("Exif.Photo.RecommendedExposureIndex") ) { iso_speed = pos->toFloat(); } } if (find_tag(Exiv2::serialNumber)) { serial = validateUft8(pos->toString()); } else { const std::vector serial_number_tags{ "Exif.Photo.BodySerialNumber", "Exif.Canon.SerialNumber", "Exif.Fujifilm.SerialNumber", "Exif.Nikon3.SerialNumber", "Exif.Nikon3.SerialNO", "Exif.Olympus.SerialNumber2", "Exif.OlympusEq.SerialNumber", "Exif.Pentax.SerialNumber", "Exif.PentaxDng.SerialNumber", "Exif.Sigma.SerialNumber", "Exif.Canon.InternalSerialNumber", "Exif.OlympusEq.InternalSerialNumber", "Exif.Panasonic.InternalSerialNumber", }; if (serial_number_tags.cend() != std::find_if(serial_number_tags.cbegin(), serial_number_tags.cend(), find_exif_tag)) { serial = validateUft8(pos->toString()); } else if (find_exif_tag("Exif.Minolta.WBInfoA100") || find_exif_tag("Exif.SonyMinolta.WBInfoA100")) { const long index = 18908; const int length = 12; if (pos->count() >= index + length) { for (int i = 0; i < length; ++i) { serial += static_cast(to_long(pos, index + i)); } serial = validateUft8(serial); } } else if (find_exif_tag("Exif.Pentax.CameraInfo") || find_exif_tag("Exif.PentaxDng.CameraInfo")) { const long index = 4; if (pos->count() >= index) { serial = validateUft8(pos->toString(index)); } } // TODO: Serial number from tags not supported by Exiv2. } if (find_tag(Exiv2::focalLength)) { // This works around a bug in exiv2 the developers refuse to fix // For details see http://dev.exiv2.org/issues/1083 if (pos->key() == "Exif.Canon.FocalLength" && pos->count() == 4) { focal_len = pos->toFloat(1); } else { focal_len = pos->toFloat(); } } if (find_exif_tag("Exif.Photo.FocalLengthIn35mmFilm")) { focal_len35mm = pos->toFloat(); } // if (find_tag(Exiv2::subjectDistance)) { // focus_dist = pos->toFloat(); // } /* * Get the focus distance in meters. */ if (Exiv2::testVersion(0, 27, 4) && find_exif_tag("Exif.NikonLd4.LensID") && to_long(pos) != 0) { // Z lens, need to specifically look for the second instance of // Exif.NikonLd4.FocusDistance unless using Exiv2 0.28.x and later // (also expanded to 2 bytes of precision since 0.28.1). #if EXIV2_TEST_VERSION(0, 28, 0) if (find_exif_tag("Exif.NikonLd4.FocusDistance2")) { float value = pos->toFloat(); if (Exiv2::testVersion(0, 28, 1)) { value /= 256.f; } #else pos = exif.end(); for (auto it = exif.begin(); it != exif.end(); it++) { if (it->key() == "Exif.NikonLd4.FocusDistance") { pos = it; } } if (pos != exif.end() && pos->size()) { float value = pos->toFloat(); #endif focus_dist = 0.01 * std::pow(10, value / 40); } } else if (find_exif_tag("Exif.NikonLd2.FocusDistance") || find_exif_tag("Exif.NikonLd3.FocusDistance") || (Exiv2::testVersion(0, 27, 4) && find_exif_tag("Exif.NikonLd4.FocusDistance"))) { float value = pos->toFloat(); focus_dist = (0.01 * std::pow(10, value / 40)); } else if (find_exif_tag("Exif.OlympusFi.FocusDistance")) { /* the distance is stored as a rational (fraction). according to * http://www.dpreview.com/forums/thread/1173960?page=4 * some Olympus cameras have a wrong denominator of 10 in there * while the nominator is always in mm. thus we ignore the * denominator and divide with 1000. * "I've checked a number of E-1 and E-300 images, and I agree * that the FocusDistance looks like it is in mm for the * E-1. However, it looks more like cm for the E-300. * For both cameras, this value is stored as a rational. With * the E-1, the denominator is always 1, while for the E-300 it * is 10. * Therefore, it looks like the numerator in both cases is in mm * (which makes a bit of sense, in an odd sort of way). So I * think what I will do in ExifTool is to take the numerator and * divide by 1000 to display the focus distance in meters." -- * Boardhead, dpreview forums in 2005 */ int nominator = pos->toRational(0).first; focus_dist = std::max(0.0, (0.001 * nominator)); } else if (find_exif_tag("Exif.CanonFi.FocusDistanceUpper")) { const float FocusDistanceUpper = pos->toFloat(); if (FocusDistanceUpper <= 0.0f || (int)FocusDistanceUpper >= 0xffff) { focus_dist = 0.0f; } else { focus_dist = FocusDistanceUpper / 100.0; if (find_exif_tag("Exif.CanonFi.FocusDistanceLower")) { const float FocusDistanceLower = pos->toFloat(); if (FocusDistanceLower > 0.0f && (int)FocusDistanceLower < 0xffff) { focus_dist += FocusDistanceLower / 100.0; focus_dist /= 2.0; } } } } else if (find_exif_tag("Exif.CanonSi.SubjectDistance")) { focus_dist = pos->toFloat() / 100.0; } else if (find_tag(Exiv2::subjectDistance)) { focus_dist = pos->toFloat(); } else if (Exiv2::testVersion(0,27,2) && find_exif_tag("Exif.Sony2Fp.FocusPosition2")) { const float focus_position = pos->toFloat(); if (focus_position && find_exif_tag("Exif.Photo.FocalLengthIn35mmFilm")) { const float focal_length_35mm = pos->toFloat(); /* http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,3688.msg29653.html#msg29653 */ focus_dist = (std::pow(2, focus_position / 16 - 5) + 1) * focal_length_35mm / 1000; } } if (find_tag(Exiv2::orientation)) { static const std::vector ormap = { "Unknown", "Horizontal (normal)", "Mirror horizontal", "Rotate 180", "Mirror vertical", "Mirror horizontal and rotate 270 CW", "Rotate 90 CW", "Mirror horizontal and rotate 90 CW", "Rotate 270 CW", "Unknown" }; auto idx = to_long(pos); if (idx >= 0 && idx < long(ormap.size())) { orientation = ormap[idx]; } //orientation = pos->print(&exif); } if (!make.compare(0, 5, "NIKON")) { if (find_exif_tag("Exif.NikonLd4.LensID")) { if (!to_long(pos)) { // No data, look in LensIDNumber. const auto p = pos; if (!find_exif_tag("Exif.NikonLd4.LensIDNumber")) { pos = p; // Tag not found, so reset pos. } } lens = pos->print(&exif); if (lens == std::to_string(to_long(pos))) { // Not known to Exiv2. lens.clear(); } else { lens = validateUft8(lens); } } } else if (!make.compare(0, 4, "SONY")) { // ExifTool prefers LensType2 over LensType (called // Exif.Sony2.LensID by Exiv2). Exiv2 doesn't support LensType2 yet, // so we let Exiv2 try it's best. For non ILCE/NEX cameras which // likely don't have LensType2, we use Exif.Sony2.LensID because // Exif.Photo.LensModel may be incorrect (see // https://discuss.pixls.us/t/call-for-testing-rawtherapee-metadata-handling-with-exiv2-includes-cr3-support/36240/36). if ( // Camera model is neither a ILCE, ILME, nor NEX. (!find_exif_tag("Exif.Image.Model") || (pos->toString().compare(0, 4, "ILCE") && pos->toString().compare(0, 4, "ILME") && pos->toString().compare(0, 3, "NEX"))) && // LensID exists. 0xFFFF could be one of many lenses. find_exif_tag("Exif.Sony2.LensID") && to_long(pos) && to_long(pos) != 0xFFFF) { lens = pos->print(&exif); if (lens == std::to_string(to_long(pos))) { // Not known to Exiv2. lens.clear(); } else { lens = validateUft8(lens); } } } if (!lens.empty()) { // Already found the lens name. } else if (find_tag(Exiv2::lensName)) { lens = validateUft8(pos->print(&exif)); auto p = pos; if (find_exif_tag("Exif.CanonFi.RFLensType") && find_exif_tag("Exif.Canon.LensModel")) { lens = validateUft8(pos->print(&exif)); } else if (p->count() == 1 && lens == std::to_string(to_long(p))) { if (find_exif_tag("Exif.Canon.LensModel")) { lens = validateUft8(pos->print(&exif)); } else if (find_exif_tag("Exif.Photo.LensModel")) { lens = validateUft8(p->print(&exif)); } } } else if (find_exif_tag("Exif.Photo.LensSpecification") && pos->count() == 4) { const auto round = [](float f) -> float { return int(f * 10.f + 0.5f) / 10.f; }; float fl_lo = round(pos->toFloat(0)); float fl_hi = round(pos->toFloat(1)); float fn_lo = round(pos->toFloat(2)); float fn_hi = round(pos->toFloat(3)); std::ostringstream buf; buf << fl_lo; if (fl_lo < fl_hi) { buf << "-" << fl_hi; } buf << "mm F" << fn_lo; if (fn_lo < fn_hi) { buf << "-" << fn_hi; } lens = validateUft8(buf.str()); } if (lens.empty() || lens.find_first_not_of('-') == std::string::npos) { lens = "Unknown"; } std::string datetime_taken; if (find_exif_tag("Exif.Image.DateTimeOriginal")) { datetime_taken = pos->print(&exif); } else if (find_exif_tag("Exif.Photo.DateTimeOriginal")) { datetime_taken = pos->print(&exif); } else if (find_exif_tag("Exif.Photo.DateTimeDigitized")) { datetime_taken = pos->print(&exif); } else if (find_exif_tag("Exif.Image.DateTime")) { datetime_taken = validateUft8(pos->print(&exif)); } if (sscanf(datetime_taken.c_str(), "%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; timeStamp = mktime(&time); } if (find_exif_tag("Exif.Image.ExposureBiasValue")) { expcomp = pos->toFloat(); } else if (find_exif_tag("Exif.Photo.ExposureBiasValue")) { expcomp = pos->toFloat(); } if (find_exif_tag("Exif.Image.Rating")) { rating = to_long(pos); } else { auto it = meta.xmpData().findKey(Exiv2::XmpKey("Xmp.xmp.Rating")); if (it != meta.xmpData().end() && it->size()) { rating = to_long(it); } } // try getting some metadata from ImageDescription if (!make.compare(0, 5, "KODAK") && !getISOSpeed() && !getFNumber() && !getFocalLen() && !getShutterSpeed() && find_exif_tag("Exif.Image.ImageDescription")) { std::string s = pos->toString(); std::string line; std::smatch m; const auto d = [&m]() -> double { std::string s = m[1]; return atof(s.c_str()); }; while (true) { auto p = s.find('\r'); if (p == std::string::npos) { break; } auto line = s.substr(0, p); s = s.substr(p+1); if (std::regex_match(line, m, std::regex("ISO: +([0-9]+) *"))) { iso_speed = d(); } else if (std::regex_match(line, m, std::regex("Aperture: +F([0-9.]+) *"))) { aperture = d(); } else if (std::regex_match(line, m, std::regex("Shutter: +([0-9.]+) *"))) { shutter = d(); if (shutter) { shutter = 1.0/shutter; } } else if (std::regex_match(line, m, std::regex("Lens \\(mm\\): +([0-9.]+) *"))) { focal_len = d(); } else if (std::regex_match(line, m, std::regex("Exp Comp: +([0-9.]+) *"))) { expcomp = d(); } } } meta.getDimensions(w_, h_); // ----------------------- // Special file type detection (HDR, PixelShift) // ------------------------ std::uint16_t bitspersample = 0, samplesperpixel = 0, sampleformat = 0, photometric = 0, compression = 0; const auto bps = exif.findKey(Exiv2::ExifKey("Exif.Image.BitsPerSample")); const auto spp = exif.findKey(Exiv2::ExifKey("Exif.Image.SamplesPerPixel")); const auto sf = exif.findKey(Exiv2::ExifKey("Exif.Image.SampleFormat")); const auto pi = exif.findKey(Exiv2::ExifKey("Exif.Image.PhotometricInterpretation")); const auto c = exif.findKey(Exiv2::ExifKey("Exif.Image.Compression")); if ( !make.compare(0, 6, "PENTAX") || ( !make.compare(0, 5, "RICOH") && !model.compare (0, 6, "PENTAX") ) ) { if (find_exif_tag("Exif.Pentax.DriveMode")) { std::string buf = pos->toString(3); if (buf == "HDR") { isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> DriveMode = \"HDR\"\n"); #endif } } if ( !isHDR && ( find_exif_tag("Exif.Pentax.Quality") || find_exif_tag("Exif.PentaxDng.Quality") ) && ( to_long(pos) == 7 || to_long(pos) == 8 ) ) { isPixelShift = true; #if PRINT_HDR_PS_DETECTION printf("PixelShift detected ! -> \"Quality\" = 7\n"); #endif } } if (make == "SONY") { if (find_exif_tag("Exif.SubImage1.BitsPerSample") && to_long(pos) == 14) { if (find_exif_tag("Exif.SubImage1.SamplesPerPixel") && to_long(pos) == 4 && find_exif_tag("Exif.SubImage1.PhotometricInterpretation") && to_long(pos) == 32892 && find_exif_tag("Exif.SubImage1.Compression") && to_long(pos) == 1) { isPixelShift = true; } } else if (bps != exif.end() && (to_long(bps) == 14 || to_long(bps) == 16) && spp != exif.end() && to_long(spp) == 4 && c != exif.end() && to_long(c) == 1 && find_exif_tag("Exif.Image.Software") && pos->toString() == "make_arq") { isPixelShift = true; } } else if (make == "FUJIFILM") { if (bps != exif.end() && to_long(bps) == 16 && spp != exif.end() && to_long(spp) == 4 && c != exif.end() && to_long(c) == 1 && find_exif_tag("Exif.Image.Software") && pos->toString() == "make_arq") { isPixelShift = true; } } sampleFormat = IIOSF_UNKNOWN; if (sf == exif.end()) /* * WARNING: This is a dirty hack! * We assume that files which doesn't contain the TIFFTAG_SAMPLEFORMAT tag * (which is the case with uncompressed TIFFs produced by RT!) are RGB files, * but that may be not true. --- Hombre */ { sampleformat = SAMPLEFORMAT_UINT; } else { sampleformat = to_long(sf); } if (bps == exif.end() || spp == exif.end() || pi == exif.end()) { return; } bitspersample = to_long(bps); samplesperpixel = to_long(spp); photometric = to_long(pi); if (photometric == PHOTOMETRIC_LOGLUV) { if (c == exif.end()) { compression = COMPRESSION_NONE; } else { compression = to_long(c); } } if (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK) { if (sampleformat == SAMPLEFORMAT_INT || sampleformat == SAMPLEFORMAT_UINT) { if (bitspersample == 8) { sampleFormat = IIOSF_UNSIGNED_CHAR; } else if (bitspersample <= 16) { sampleFormat = IIOSF_UNSIGNED_SHORT; } } else if (sampleformat == SAMPLEFORMAT_IEEEFP) { if (bitspersample==16) { sampleFormat = IIOSF_FLOAT16; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d (16-bit)\n", sampleFormat); #endif } else if (bitspersample == 24) { sampleFormat = IIOSF_FLOAT24; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d (24-bit)\n", sampleFormat); #endif } else if (bitspersample == 32) { sampleFormat = IIOSF_FLOAT32; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d (32-bit)\n", sampleFormat); #endif } } } else if (photometric == PHOTOMETRIC_CFA) { if (sampleformat == SAMPLEFORMAT_IEEEFP) { if (bitspersample == 16) { sampleFormat = IIOSF_FLOAT16; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d (16-bit)\n", sampleFormat); #endif } else if (bitspersample == 24) { sampleFormat = IIOSF_FLOAT24; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d (24-bit)\n", sampleFormat); #endif } else if (bitspersample == 32) { sampleFormat = IIOSF_FLOAT32; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d (32-bit)\n", sampleFormat); #endif } } 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 == 34892 || photometric == 32892 /* Linear RAW (see DNG spec ; 32892 seem to be a flaw from Sony's ARQ files) */) { if (sampleformat == SAMPLEFORMAT_IEEEFP) { sampleFormat = IIOSF_FLOAT32; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d\n", sampleFormat); #endif } 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; if (find_exif_tag("Exif.Photo.MakerNote") && (!make.compare (0, 4, "SONY")) && bitspersample >= 12 && samplesperpixel == 4) { isPixelShift = true; #if PRINT_HDR_PS_DETECTION printf("PixelShift detected ! -> \"Make\" = SONY, bitsPerPixel > 8, samplesPerPixel == 4\n"); #endif } } } } else if (photometric == PHOTOMETRIC_LOGLUV) { if (compression == COMPRESSION_SGILOG24) { sampleFormat = IIOSF_LOGLUV24; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d\n", sampleFormat); #endif } else if (compression == COMPRESSION_SGILOG) { sampleFormat = IIOSF_LOGLUV32; isHDR = true; #if PRINT_HDR_PS_DETECTION printf("HDR detected ! -> sampleFormat = %d\n", sampleFormat); #endif } } std::uint32_t dngVersion = 0; if (find_exif_tag("Exif.Image.DNGVersion") && pos->count() == 4) { for (int i = 0; i < 4; i++) { dngVersion = (dngVersion << 8) + static_cast(to_long(pos, i)); } } isDNG = dngVersion; // Read DNG OpcodeList1. if (dngVersion && (find_exif_tag("Exif.SubImage1.OpcodeList1") || find_exif_tag("Exif.Image.OpcodeList1"))) { readOpcodesList(pos->value(), &fixBadPixelsConstant, &hasFixBadPixelsConstant_, nullptr); } // Read DNG OpcodeList2. if (dngVersion && (find_exif_tag("Exif.SubImage1.OpcodeList2") || find_exif_tag("Exif.Image.OpcodeList2"))) { readOpcodesList(pos->value(), nullptr, nullptr, &gain_maps_); } } catch (const std::exception& e) { if (settings->verbose) { std::cerr << "EXIV2 ERROR: " << e.what() << std::endl; } ok_ = false; } } bool FramesData::getPixelShift() const { return isPixelShift; } bool FramesData::getHDR() const { return isHDR; } bool FramesData::getDNG() const { return isDNG; } std::string FramesData::getImageType() const { return isPixelShift ? "PS" : isHDR ? "HDR" : "STD"; } IIOSampleFormat FramesData::getSampleFormat() const { return sampleFormat; } bool FramesData::hasExif() const { return ok_; } tm FramesData::getDateTime() const { return time; } time_t FramesData::getDateTimeAsTS() const { return timeStamp; } int FramesData::getISOSpeed() const { return iso_speed; } double FramesData::getFNumber() const { return aperture; } double FramesData::getFocalLen() const { return focal_len; } double FramesData::getFocalLen35mm() const { return focal_len35mm; } float FramesData::getFocusDist() const { return focus_dist; } double FramesData::getShutterSpeed() const { return shutter; } double FramesData::getExpComp() const { return expcomp; } std::string FramesData::getMake() const { return make; } std::string FramesData::getModel() const { return model; } std::string FramesData::getLens() const { return lens; } std::string FramesData::getSerialNumber() const { return serial; } std::string FramesData::getOrientation() const { return orientation; } void FramesData::setDCRawFrameCount(unsigned int frameCount) { dcrawFrameCount = frameCount; } unsigned int FramesData::getFrameCount() const { return std::max(1U, dcrawFrameCount); } Glib::ustring FramesData::getFileName() const { return fname_; } int FramesData::getRating() const { return rating; } //------inherited functions--------------// std::string FramesMetaData::apertureToString(double aperture) { // TODO: Replace sprintf() char buffer[256]; snprintf(buffer, sizeof(buffer), "%0.1f", aperture); return buffer; } std::string FramesMetaData::shutterToString(double shutter) { char buffer[256]; if (shutter > 0.0 && shutter <= 0.5) { snprintf(buffer, sizeof(buffer), "1/%0.0f", 1.0 / shutter); } else if (int(shutter) == shutter) { snprintf(buffer, sizeof(buffer), "%d", int(shutter)); } else { snprintf(buffer, sizeof(buffer), "%0.1f", shutter); } return buffer; } std::string FramesMetaData::expcompToString(double expcomp, bool maskZeroexpcomp) { char buffer[256]; if (maskZeroexpcomp) { if (expcomp != 0.0) { snprintf(buffer, sizeof(buffer), "%0.2f", expcomp); return buffer; } else { return ""; } } else { snprintf(buffer, sizeof(buffer), "%0.2f", expcomp); return buffer; } } double FramesMetaData::shutterFromString(std::string s) { const std::string::size_type i = s.find_first_of('/'); if (i == std::string::npos) { return std::atof(s.c_str()); } else { const double denominator = std::atof(s.substr(i + 1).c_str()); return denominator ? std::atof(s.substr(0, i).c_str()) / denominator : 0.0; } } double FramesMetaData::apertureFromString(std::string s) { return std::atof(s.c_str()); } namespace { template void set_exif(Exiv2::ExifData &exif, const std::string &key, T val) { try { exif[key] = val; } catch (std::exception &exc) { if (settings->verbose) { std::cout << "Exif -- error setting " << key << " to " << val << ": " << exc.what() << std::endl; } } } } // namespace void FramesData::fillBasicTags(Exiv2::ExifData &exif) const { if (!hasExif()) { return; } set_exif(exif, "Exif.Photo.ISOSpeedRatings", getISOSpeed()); set_exif(exif, "Exif.Photo.FNumber", Exiv2::URationalValue(Exiv2::URational(round(getFNumber() * 10), 10))); auto s = shutterToString(getShutterSpeed()); auto p = s.find('.'); if (p != std::string::npos) { assert(p == s.length()-2); s = s.substr(0, p) + s.substr(p+1) + "/10"; } else if (s.find('/') == std::string::npos) { s += "/1"; } set_exif(exif, "Exif.Photo.ExposureTime", s); set_exif(exif, "Exif.Photo.FocalLength", Exiv2::URationalValue(Exiv2::URational(getFocalLen() * 10, 10))); set_exif(exif, "Exif.Photo.ExposureBiasValue", Exiv2::RationalValue(Exiv2::Rational(round(getExpComp() * 100), 100))); set_exif(exif, "Exif.Image.Make", getMake()); set_exif(exif, "Exif.Image.Model", getModel()); set_exif(exif, "Exif.Photo.LensModel", getLens()); char buf[256]; auto t = getDateTime(); strftime(buf, 256, "%Y:%m:%d %H:%M:%S", &t); set_exif(exif, "Exif.Photo.DateTimeOriginal", buf); } std::uint32_t FramesData::getFixBadPixelsConstant() const { return fixBadPixelsConstant; } bool FramesData::hasFixBadPixelsConstant() const { return hasFixBadPixelsConstant_; } std::vector FramesData::getGainMaps() const { return gain_maps_; } void FramesData::getDimensions(int &w, int &h) const { w = w_; h = h_; } void FramesData::setDimensions(int w, int h) { w_ = w; h_ = h; }