rawTherapee/rtengine/imagedata.cc
Alberto Griggio df39e13cf7
properly handle exif orientation
translate the tag value to string using the exiftool way, to be compatible with RT.
Fixes issue #4

(cherry picked from commit a4621f54b2ac82b679cf9d865a0a3e4a2ed9c468)
2022-12-04 17:08:34 -08:00

694 lines
20 KiB
C++

/*
* This file is part of RawTherapee.
*
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <functional>
#include <strings.h>
#include <tiff.h>
#include <glib/gstdio.h>
#include <glibmm/convert.h>
#include "imagedata.h"
#include "imagesource.h"
#include "rt_math.h"
#include "metadata.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;
}
}
namespace rtengine {
extern const Settings *settings;
} // namespace rtengine
FramesMetaData* FramesMetaData::fromFile(const Glib::ustring& fname)
{
return new FramesData(fname);
}
FramesData::FramesData(const Glib::ustring &fname) :
ok_(false),
fname_(fname),
dcrawFrameCount(0),
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"),
rating(0), // FIXME: Implement
lens("Unknown"),
sampleFormat(IIOSF_UNKNOWN),
isPixelShift(false),
isHDR(false)
{
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 <http://www.gnu.org/licenses/>.
*/
Exiv2::ExifData::const_iterator pos;
const auto find_exif_tag =
[&exif, &pos](const std::string &name) -> bool
{
pos = exif.findKey(Exiv2::ExifKey(name));
return pos != exif.end() && pos->size();
};
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)); // validateUft8 (#5923) still needed?
}
if (find_tag(Exiv2::model)) {
model = validateUft8(pos->print(&exif)); // validateUft8 (#5923) still needed?
}
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;
}
}
}
/*
TODO: Implement ratings in exiv2 situations. See PR #5325
// Look for Rating metadata in the following order:
// 1. EXIF
// 2. XMP
// 3. pp3 sidecar file
tag = newFrameRootDir->findTagUpward("Rating");
if (tag && tag->toInt() != 0) {
rating = tag->toInt();
}
char sXMPRating[64];
if (newFrameRootDir->getXMPTagValue("xmp:Rating", sXMPRating)) {
// Guard against out-of-range values (<0, >5)
rating = rtengine::max(0, rtengine::min(5, atoi(sXMPRating)));
// Currently, Rating=-1 is not supported. A value of -1 should mean
// "Rejected" according to the specification. Maybe in the future, Rating=-1
// sets InTrash=true?
}
*/
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::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 = (0.01 * std::pow(10, pos->toFloat() / 40));
}
if (find_tag(Exiv2::orientation)) {
static const std::vector<std::string> 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 = pos->toLong();
if (idx >= 0 && idx < long(ormap.size())) {
orientation = ormap[idx];
}
//orientation = pos->print(&exif);
}
if (find_tag(Exiv2::lensName)) {
lens = validateUft8(pos->print(&exif)); // validateUft8 (#5923) still needed?
} 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 = 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);
}
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();
}
if (find_exif_tag("Exif.Image.Rating")) {
rating = pos->toLong();
}
// -----------------------
// Special file type detection (HDR, PixelShift)
// ------------------------
uint16 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);
buf[3] = 0;
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")
)
&& (
pos->toLong() == 7
|| pos->toLong() == 8
)
) {
isPixelShift = true;
#if PRINT_HDR_PS_DETECTION
printf("PixelShift detected ! -> \"Quality\" = 7\n");
#endif
}
}
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 = sf->toLong();
}
if (bps == exif.end() || spp == exif.end() || pi == exif.end()) {
return;
}
bitspersample = bps->toLong();
samplesperpixel = spp->toLong();
photometric = pi->toLong();
if (photometric == PHOTOMETRIC_LOGLUV) {
if (c == exif.end()) {
compression = COMPRESSION_NONE;
} else {
compression = c->toLong();
}
}
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
}
}
} catch (const Exiv2::AnyError& 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;
}
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 {
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());
}