diff --git a/rtengine/dcraw.cc b/rtengine/dcraw.cc index efa119d07..4fd01259c 100644 --- a/rtengine/dcraw.cc +++ b/rtengine/dcraw.cc @@ -6958,48 +6958,8 @@ it under the terms of the one of two licenses as you choose: break; } case 51009: /* OpcodeList2 */ - { - meta_offset = ftell(ifp); - const unsigned oldOrder = order; - order = 0x4d4d; // always big endian per definition in https://www.adobe.com/content/dam/acom/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf chapter 7 - unsigned ntags = get4(); // read the number of opcodes - if (ntags < ifp->size / 12) { // rough check for wrong value (happens for example with DNG files from DJI FC6310) - while (ntags-- && !ifp->eof) { - unsigned opcode = get4(); - if (opcode == 9 && gainMaps.size() < 4) { - fseek(ifp, 4, SEEK_CUR); // skip 4 bytes as we know that the opcode 4 takes 4 byte - fseek(ifp, 8, SEEK_CUR); // skip 8 bytes as they don't interest us currently - GainMap gainMap; - gainMap.Top = get4(); - gainMap.Left = get4(); - gainMap.Bottom = get4(); - gainMap.Right = get4(); - gainMap.Plane = get4(); - gainMap.Planes = get4(); - gainMap.RowPitch = get4(); - gainMap.ColPitch = get4(); - gainMap.MapPointsV = get4(); - gainMap.MapPointsH = get4(); - gainMap.MapSpacingV = getreal(12); - gainMap.MapSpacingH = getreal(12); - gainMap.MapOriginV = getreal(12); - gainMap.MapOriginH = getreal(12); - gainMap.MapPlanes = get4(); - 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(getreal(11)); - } - gainMaps.push_back(std::move(gainMap)); - } else { - fseek(ifp, 8, SEEK_CUR); // skip 8 bytes as they don't interest us currently - fseek(ifp, get4(), SEEK_CUR); - } - } - } - order = oldOrder; - break; - } + meta_offset = ftell(ifp); + break; case 64772: /* Kodak P-series */ if (len < 13) break; fseek (ifp, 16, SEEK_CUR); @@ -11123,70 +11083,6 @@ void CLASS nikon_14bit_load_raw() free(buf); } -bool CLASS isGainMapSupported() const { - if (!(dng_version && isBayer())) { - return false; - } - const auto n = gainMaps.size(); - if (n != 4) { // we need 4 gainmaps for bayer files - if (rtengine::settings->verbose) { - std::cout << "GainMap has " << n << " maps, but 4 are needed" << std::endl; - } - return false; - } - unsigned int check = 0; - bool noOp = true; - for (const auto &m : gainMaps) { - if (m.MapGain.size() < 1) { - if (rtengine::settings->verbose) { - std::cout << "GainMap has invalid size of " << m.MapGain.size() << std::endl; - } - return false; - } - if (m.MapGain.size() != static_cast(m.MapPointsV) * static_cast(m.MapPointsH) * static_cast(m.MapPlanes)) { - if (rtengine::settings->verbose) { - std::cout << "GainMap has size of " << m.MapGain.size() << ", but needs " << m.MapPointsV * m.MapPointsH * m.MapPlanes << std::endl; - } - return false; - } - if (m.RowPitch != 2 || m.ColPitch != 2) { - if (rtengine::settings->verbose) { - std::cout << "GainMap needs Row/ColPitch of 2/2, but has " << m.RowPitch << "/" << m.ColPitch << std::endl; - } - return false; - } - if (m.Top == 0){ - if (m.Left == 0) { - check += 1; - } else if (m.Left == 1) { - check += 2; - } - } else if (m.Top == 1) { - if (m.Left == 0) { - check += 4; - } else if (m.Left == 1) { - check += 8; - } - } - for (size_t i = 0; noOp && i < m.MapGain.size(); ++i) { - if (m.MapGain[i] != 1.f) { // we have at least one value != 1.f => map is not a nop - noOp = false; - } - } - } - if (noOp || check != 15) { // all maps are nops or the structure of the combination of 4 maps is not correct - if (rtengine::settings->verbose) { - if (noOp) { - std::cout << "GainMap is a nop" << std::endl; - } else { - std::cout << "GainMap has unsupported type : " << check << std::endl; - } - } - return false; - } - return true; -} - /* RT: Delete from here */ /*RT*/#undef SQR /*RT*/#undef MAX diff --git a/rtengine/dcraw.h b/rtengine/dcraw.h index 4501aef67..2bf8f2965 100644 --- a/rtengine/dcraw.h +++ b/rtengine/dcraw.h @@ -24,7 +24,6 @@ #include "myfile.h" #include -#include "dnggainmap.h" #include "settings.h" class DCraw @@ -170,7 +169,6 @@ protected: PanasonicRW2Info(): bpp(0), encoding(0) {} }; PanasonicRW2Info RT_pana_info; - std::vector gainMaps; public: struct CanonCR3Data { @@ -206,12 +204,6 @@ public: return (filters != 0 && filters != 9); } - const std::vector& getGainMaps() const { - return gainMaps; - } - - bool isGainMapSupported() const; - struct CanonLevelsData { unsigned cblack[4]; unsigned white; diff --git a/rtengine/imagedata.cc b/rtengine/imagedata.cc index 735519d99..eff903ce6 100644 --- a/rtengine/imagedata.cc +++ b/rtengine/imagedata.cc @@ -16,6 +16,7 @@ * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ +#include #include #include #include @@ -27,6 +28,7 @@ #include #include +#include "dnggainmap.h" #include "imagedata.h" #include "imagesource.h" #include "metadata.h" @@ -63,6 +65,154 @@ auto to_long(const Iterator &iter, Integer n = Integer{0}) -> decltype( #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; + } +}; + +GainMap readGainMap(TagValueReader &reader) +{ + reader.seekRelative(4); // skip 4 bytes as we know that the opcode 4 takes 4 byte + reader.seekRelative(8); // skip 8 bytes as they don't interest us currently + 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::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 == 9 && 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 { @@ -675,6 +825,18 @@ FramesData::FramesData(const Glib::ustring &fname, time_t ts) : #endif } } + + 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)); + } + } + + // Read gain maps. + if (dngVersion && (find_exif_tag("Exif.SubImage1.OpcodeList2") || find_exif_tag("Exif.Image.OpcodeList2"))) { + readOpcodesList(pos->value(), gain_maps_); + } } catch (const std::exception& e) { if (settings->verbose) { std::cerr << "EXIV2 ERROR: " << e.what() << std::endl; @@ -916,6 +1078,10 @@ void FramesData::fillBasicTags(Exiv2::ExifData &exif) const set_exif(exif, "Exif.Photo.DateTimeOriginal", buf); } +std::vector FramesData::getGainMaps() const +{ + return gain_maps_; +} void FramesData::getDimensions(int &w, int &h) const { diff --git a/rtengine/imagedata.h b/rtengine/imagedata.h index 08f55bd62..434b96a52 100644 --- a/rtengine/imagedata.h +++ b/rtengine/imagedata.h @@ -22,6 +22,7 @@ #include #include +#include "dnggainmap.h" #include "imageio.h" #include "metadata.h" @@ -59,6 +60,7 @@ private: time_t modTimeStamp; bool isPixelShift; bool isHDR; + std::vector gain_maps_; int w_; int h_; @@ -88,6 +90,7 @@ public: std::string getOrientation() const override; Glib::ustring getFileName() const override; int getRating() const override; + std::vector getGainMaps() const override; void getDimensions(int &w, int &h) const override; void fillBasicTags(Exiv2::ExifData &exif) const; diff --git a/rtengine/imagesource.h b/rtengine/imagesource.h index 95fd77d21..058ef8ff2 100644 --- a/rtengine/imagesource.h +++ b/rtengine/imagesource.h @@ -177,15 +177,15 @@ public: return dirpyrdenoiseExpComp; } // functions inherited from the InitialImage interface - Glib::ustring getFileName () final + Glib::ustring getFileName() const final override { return fileName; } - cmsHPROFILE getEmbeddedProfile () final + cmsHPROFILE getEmbeddedProfile() const final override { return embProfile; } - const FramesMetaData* getMetaData () final + const FramesMetaData *getMetaData() const final override { return idata; } diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index eabeb2fc9..7c77d57c6 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -1503,7 +1503,7 @@ void RawImageSource::preprocess(const RAWParams &raw, const LensProfParams &lens //FLATFIELD end if (raw.ff_FromMetaData && isGainMapSupported()) { - applyDngGainMap(c_black, ri->getGainMaps()); + applyDngGainMap(c_black, getMetaData()->getGainMaps()); } // Always correct camera badpixels from .badpixels file @@ -8308,7 +8308,68 @@ void RawImageSource::getRawValues(int x, int y, int rotate, int &R, int &G, int bool RawImageSource::isGainMapSupported() const { - return ri->isGainMapSupported(); + if (!(ri->DNGVERSION() && ri->isBayer())) { + return false; + } + const auto &gainMaps = getMetaData()->getGainMaps(); + const auto n = gainMaps.size(); + if (n != 4) { // we need 4 gainmaps for bayer files + if (rtengine::settings->verbose) { + std::cout << "GainMap has " << n << " maps, but 4 are needed" << std::endl; + } + return false; + } + unsigned int check = 0; + bool noOp = true; + for (const auto &m : gainMaps) { + if (m.MapGain.size() < 1) { + if (rtengine::settings->verbose) { + std::cout << "GainMap has invalid size of " << m.MapGain.size() << std::endl; + } + return false; + } + if (m.MapGain.size() != static_cast(m.MapPointsV) * static_cast(m.MapPointsH) * static_cast(m.MapPlanes)) { + if (rtengine::settings->verbose) { + std::cout << "GainMap has size of " << m.MapGain.size() << ", but needs " << m.MapPointsV * m.MapPointsH * m.MapPlanes << std::endl; + } + return false; + } + if (m.RowPitch != 2 || m.ColPitch != 2) { + if (rtengine::settings->verbose) { + std::cout << "GainMap needs Row/ColPitch of 2/2, but has " << m.RowPitch << "/" << m.ColPitch << std::endl; + } + return false; + } + if (m.Top == 0) { + if (m.Left == 0) { + check += 1; + } else if (m.Left == 1) { + check += 2; + } + } else if (m.Top == 1) { + if (m.Left == 0) { + check += 4; + } else if (m.Left == 1) { + check += 8; + } + } + for (size_t i = 0; noOp && i < m.MapGain.size(); ++i) { + if (m.MapGain[i] != 1.f) { // we have at least one value != 1.f => map is not a nop + noOp = false; + } + } + } + if (noOp || check != 15) { // all maps are nops or the structure of the combination of 4 maps is not correct + if (rtengine::settings->verbose) { + if (noOp) { + std::cout << "GainMap is a nop" << std::endl; + } else { + std::cout << "GainMap has unsupported type : " << check << std::endl; + } + } + return false; + } + return true; } void RawImageSource::applyDngGainMap(const float black[4], const std::vector &gainMaps) diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index ba4d2ed60..0db06849a 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -51,6 +51,7 @@ class LUT; using LUTu = LUT; class EditDataProvider; +class GainMap; namespace rtengine { @@ -158,6 +159,7 @@ public: static FramesMetaData* fromFile(const Glib::ustring& fname); virtual Glib::ustring getFileName() const = 0; + virtual std::vector getGainMaps() const = 0; virtual void getDimensions(int &w, int &h) const = 0; }; @@ -192,13 +194,13 @@ class InitialImage public: /** Returns the file name of the image. * @return The file name of the image */ - virtual Glib::ustring getFileName () = 0; + virtual Glib::ustring getFileName() const = 0; /** Returns the embedded icc profile of the image. * @return The handle of the embedded profile */ - virtual cmsHPROFILE getEmbeddedProfile () = 0; + virtual cmsHPROFILE getEmbeddedProfile() const = 0; /** Returns a class providing access to the exif and iptc metadata tags of all frames of the image. * @return An instance of the FramesMetaData class */ - virtual const FramesMetaData* getMetaData () = 0; + virtual const FramesMetaData *getMetaData() const = 0; /** This is a function used for internal purposes only. */ virtual ImageSource* getImageSource () = 0; /** This class has manual reference counting. You have to call this function each time to make a new reference to an instance. */ diff --git a/rtgui/cacheimagedata.cc b/rtgui/cacheimagedata.cc index bfc4e920a..13a2e31e0 100644 --- a/rtgui/cacheimagedata.cc +++ b/rtgui/cacheimagedata.cc @@ -362,3 +362,7 @@ int CacheImageData::save (const Glib::ustring& fname) } } +std::vector CacheImageData::getGainMaps() const +{ + return std::vector(); +} diff --git a/rtgui/cacheimagedata.h b/rtgui/cacheimagedata.h index 8c0fa6513..12c4ed476 100644 --- a/rtgui/cacheimagedata.h +++ b/rtgui/cacheimagedata.h @@ -22,6 +22,7 @@ #include "options.h" +#include "../rtengine/dnggainmap.h" #include "../rtengine/imageformat.h" #include "../rtengine/rtengine.h" @@ -116,6 +117,7 @@ public: bool getHDR() const override { return isHDR; } std::string getImageType() const override { return isPixelShift ? "PS" : isHDR ? "HDR" : "STD"; } rtengine::IIOSampleFormat getSampleFormat() const override { return sampleFormat; } + std::vector getGainMaps() const override; void getDimensions(int &w, int &h) const override { w = width;