Implement DNG gain map for LibRaw

This commit is contained in:
Lawrence Lee 2023-12-17 14:45:03 -08:00
parent 20d3311931
commit f296991419
No known key found for this signature in database
GPG Key ID: 048FF2B76A63895F
9 changed files with 248 additions and 122 deletions

View File

@ -6958,48 +6958,8 @@ it under the terms of the one of two licenses as you choose:
break; break;
} }
case 51009: /* OpcodeList2 */ case 51009: /* OpcodeList2 */
{ meta_offset = ftell(ifp);
meta_offset = ftell(ifp); break;
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<std::size_t>(gainMap.MapPointsV) * static_cast<std::size_t>(gainMap.MapPointsH) * static_cast<std::size_t>(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;
}
case 64772: /* Kodak P-series */ case 64772: /* Kodak P-series */
if (len < 13) break; if (len < 13) break;
fseek (ifp, 16, SEEK_CUR); fseek (ifp, 16, SEEK_CUR);
@ -11123,70 +11083,6 @@ void CLASS nikon_14bit_load_raw()
free(buf); 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<std::size_t>(m.MapPointsV) * static_cast<std::size_t>(m.MapPointsH) * static_cast<std::size_t>(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: Delete from here */
/*RT*/#undef SQR /*RT*/#undef SQR
/*RT*/#undef MAX /*RT*/#undef MAX

View File

@ -24,7 +24,6 @@
#include "myfile.h" #include "myfile.h"
#include <csetjmp> #include <csetjmp>
#include "dnggainmap.h"
#include "settings.h" #include "settings.h"
class DCraw class DCraw
@ -170,7 +169,6 @@ protected:
PanasonicRW2Info(): bpp(0), encoding(0) {} PanasonicRW2Info(): bpp(0), encoding(0) {}
}; };
PanasonicRW2Info RT_pana_info; PanasonicRW2Info RT_pana_info;
std::vector<GainMap> gainMaps;
public: public:
struct CanonCR3Data { struct CanonCR3Data {
@ -206,12 +204,6 @@ public:
return (filters != 0 && filters != 9); return (filters != 0 && filters != 9);
} }
const std::vector<GainMap>& getGainMaps() const {
return gainMaps;
}
bool isGainMapSupported() const;
struct CanonLevelsData { struct CanonLevelsData {
unsigned cblack[4]; unsigned cblack[4];
unsigned white; unsigned white;

View File

@ -16,6 +16,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with RawTherapee. If not, see <https://www.gnu.org/licenses/>. * along with RawTherapee. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <cstdint>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <regex> #include <regex>
@ -27,6 +28,7 @@
#include <glib/gstdio.h> #include <glib/gstdio.h>
#include <glibmm/convert.h> #include <glibmm/convert.h>
#include "dnggainmap.h"
#include "imagedata.h" #include "imagedata.h"
#include "imagesource.h" #include "imagesource.h"
#include "metadata.h" #include "metadata.h"
@ -63,6 +65,154 @@ auto to_long(const Iterator &iter, Integer n = Integer{0}) -> decltype(
#endif #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<Exiv2::byte>;
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 <typename T, T (&getter)(const Exiv2::byte *, Exiv2::ByteOrder)>
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<std::size_t>(offset) >= data.size();
}
/**
* Reads a double from the current offset and advances the offset.
*/
double readDouble()
{
return readValue<double, Exiv2::getDouble>();
}
/**
* Reads a float from the current offset and advances the offset.
*/
float readFloat()
{
return readValue<float, Exiv2::getFloat>();
}
/**
* Reads an unsigned integer from the current offset and advances the
* offset.
*/
std::uint32_t readUInt()
{
return readValue<std::uint32_t, Exiv2::getULong>();
}
/**
* 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<std::size_t>(gainMap.MapPointsV) * static_cast<std::size_t>(gainMap.MapPointsH) * static_cast<std::size_t>(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<GainMap> &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 { namespace rtengine {
@ -675,6 +825,18 @@ FramesData::FramesData(const Glib::ustring &fname, time_t ts) :
#endif #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<uint32_t>(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) { } catch (const std::exception& e) {
if (settings->verbose) { if (settings->verbose) {
std::cerr << "EXIV2 ERROR: " << e.what() << std::endl; 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); set_exif(exif, "Exif.Photo.DateTimeOriginal", buf);
} }
std::vector<GainMap> FramesData::getGainMaps() const
{
return gain_maps_;
}
void FramesData::getDimensions(int &w, int &h) const void FramesData::getDimensions(int &w, int &h) const
{ {

View File

@ -22,6 +22,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "dnggainmap.h"
#include "imageio.h" #include "imageio.h"
#include "metadata.h" #include "metadata.h"
@ -59,6 +60,7 @@ private:
time_t modTimeStamp; time_t modTimeStamp;
bool isPixelShift; bool isPixelShift;
bool isHDR; bool isHDR;
std::vector<GainMap> gain_maps_;
int w_; int w_;
int h_; int h_;
@ -88,6 +90,7 @@ public:
std::string getOrientation() const override; std::string getOrientation() const override;
Glib::ustring getFileName() const override; Glib::ustring getFileName() const override;
int getRating() const override; int getRating() const override;
std::vector<GainMap> getGainMaps() const override;
void getDimensions(int &w, int &h) const override; void getDimensions(int &w, int &h) const override;
void fillBasicTags(Exiv2::ExifData &exif) const; void fillBasicTags(Exiv2::ExifData &exif) const;

View File

@ -177,15 +177,15 @@ public:
return dirpyrdenoiseExpComp; return dirpyrdenoiseExpComp;
} }
// functions inherited from the InitialImage interface // functions inherited from the InitialImage interface
Glib::ustring getFileName () final Glib::ustring getFileName() const final override
{ {
return fileName; return fileName;
} }
cmsHPROFILE getEmbeddedProfile () final cmsHPROFILE getEmbeddedProfile() const final override
{ {
return embProfile; return embProfile;
} }
const FramesMetaData* getMetaData () final const FramesMetaData *getMetaData() const final override
{ {
return idata; return idata;
} }

View File

@ -1503,7 +1503,7 @@ void RawImageSource::preprocess(const RAWParams &raw, const LensProfParams &lens
//FLATFIELD end //FLATFIELD end
if (raw.ff_FromMetaData && isGainMapSupported()) { if (raw.ff_FromMetaData && isGainMapSupported()) {
applyDngGainMap(c_black, ri->getGainMaps()); applyDngGainMap(c_black, getMetaData()->getGainMaps());
} }
// Always correct camera badpixels from .badpixels file // 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 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<std::size_t>(m.MapPointsV) * static_cast<std::size_t>(m.MapPointsH) * static_cast<std::size_t>(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<GainMap> &gainMaps) void RawImageSource::applyDngGainMap(const float black[4], const std::vector<GainMap> &gainMaps)

View File

@ -51,6 +51,7 @@ class LUT;
using LUTu = LUT<uint32_t>; using LUTu = LUT<uint32_t>;
class EditDataProvider; class EditDataProvider;
class GainMap;
namespace rtengine namespace rtengine
{ {
@ -158,6 +159,7 @@ public:
static FramesMetaData* fromFile(const Glib::ustring& fname); static FramesMetaData* fromFile(const Glib::ustring& fname);
virtual Glib::ustring getFileName() const = 0; virtual Glib::ustring getFileName() const = 0;
virtual std::vector<GainMap> getGainMaps() const = 0;
virtual void getDimensions(int &w, int &h) const = 0; virtual void getDimensions(int &w, int &h) const = 0;
}; };
@ -192,13 +194,13 @@ class InitialImage
public: public:
/** Returns the file name of the image. /** Returns the file name of the image.
* @return 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. /** Returns the embedded icc profile of the image.
* @return The handle of the embedded profile */ * @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. /** 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 */ * @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. */ /** This is a function used for internal purposes only. */
virtual ImageSource* getImageSource () = 0; 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. */ /** This class has manual reference counting. You have to call this function each time to make a new reference to an instance. */

View File

@ -362,3 +362,7 @@ int CacheImageData::save (const Glib::ustring& fname)
} }
} }
std::vector<GainMap> CacheImageData::getGainMaps() const
{
return std::vector<GainMap>();
}

View File

@ -22,6 +22,7 @@
#include "options.h" #include "options.h"
#include "../rtengine/dnggainmap.h"
#include "../rtengine/imageformat.h" #include "../rtengine/imageformat.h"
#include "../rtengine/rtengine.h" #include "../rtengine/rtengine.h"
@ -116,6 +117,7 @@ public:
bool getHDR() const override { return isHDR; } bool getHDR() const override { return isHDR; }
std::string getImageType() const override { return isPixelShift ? "PS" : isHDR ? "HDR" : "STD"; } std::string getImageType() const override { return isPixelShift ? "PS" : isHDR ? "HDR" : "STD"; }
rtengine::IIOSampleFormat getSampleFormat() const override { return sampleFormat; } rtengine::IIOSampleFormat getSampleFormat() const override { return sampleFormat; }
std::vector<GainMap> getGainMaps() const override;
void getDimensions(int &w, int &h) const override void getDimensions(int &w, int &h) const override
{ {
w = width; w = width;