Implement DNG gain map for LibRaw
This commit is contained in:
parent
20d3311931
commit
f296991419
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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. */
|
||||
|
@ -362,3 +362,7 @@ int CacheImageData::save (const Glib::ustring& fname)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<GainMap> CacheImageData::getGainMaps() const
|
||||
{
|
||||
return std::vector<GainMap>();
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user