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;
}
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<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 */
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<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*/#undef SQR
/*RT*/#undef MAX

View File

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

View File

@ -16,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with RawTherapee. If not, see <https://www.gnu.org/licenses/>.
*/
#include <cstdint>
#include <functional>
#include <iostream>
#include <regex>
@ -27,6 +28,7 @@
#include <glib/gstdio.h>
#include <glibmm/convert.h>
#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<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 {
@ -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<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) {
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<GainMap> FramesData::getGainMaps() const
{
return gain_maps_;
}
void FramesData::getDimensions(int &w, int &h) const
{

View File

@ -22,6 +22,7 @@
#include <memory>
#include <string>
#include "dnggainmap.h"
#include "imageio.h"
#include "metadata.h"
@ -59,6 +60,7 @@ private:
time_t modTimeStamp;
bool isPixelShift;
bool isHDR;
std::vector<GainMap> 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<GainMap> getGainMaps() const override;
void getDimensions(int &w, int &h) const override;
void fillBasicTags(Exiv2::ExifData &exif) const;

View File

@ -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;
}

View File

@ -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<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)

View File

@ -51,6 +51,7 @@ class LUT;
using LUTu = LUT<uint32_t>;
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<GainMap> 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. */

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 "../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<GainMap> getGainMaps() const override;
void getDimensions(int &w, int &h) const override
{
w = width;