* Remove unfilled formatting placeholders in KLT parser. * Fix presumably unintentionally missing reference on PlanatPtr and ChunkyPtr call operators. * Fix catching pointer and make use-after-free of workimg easier to spot. Make sure all of our thrown exceptions derive from std::exception and then catch by reference instead of by pointer. * Fix mismatch between array form new and non-array form delete. * Simplify memory management of embedded color profiles by unifying allocation to use operator new.
1173 lines
39 KiB
C++
1173 lines
39 KiB
C++
/*
|
|
* This file is part of RawTherapee.
|
|
*
|
|
* Copyright (c) 2012 Oliver Duis <www.oliverduis.de>
|
|
*
|
|
* 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 <algorithm>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
|
|
#include <glibmm/ustring.h>
|
|
#include <glibmm/fileutils.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#ifdef WIN32
|
|
#include <shlobj.h>
|
|
#endif
|
|
|
|
#include "lcp.h"
|
|
|
|
#include "opthelper.h"
|
|
#include "procparams.h"
|
|
#include "rt_math.h"
|
|
#include "settings.h"
|
|
#include "utils.h"
|
|
|
|
class rtengine::LCPProfile::LCPPersModel
|
|
{
|
|
public:
|
|
LCPPersModel();
|
|
bool hasModeData(LCPCorrectionMode mode) const;
|
|
|
|
float focLen;
|
|
float focDist;
|
|
float aperture; // this is what it refers to
|
|
|
|
LCPModelCommon base; // base perspective correction
|
|
LCPModelCommon chromRG;
|
|
LCPModelCommon chromG;
|
|
LCPModelCommon chromBG; // red/green, green, blue/green (may be empty)
|
|
LCPModelCommon vignette; // vignette (may be empty)
|
|
};
|
|
|
|
rtengine::LCPModelCommon::LCPModelCommon() :
|
|
foc_len_x(-1.0f),
|
|
foc_len_y(-1.0f),
|
|
img_center_x(0.5f),
|
|
img_center_y(0.5f),
|
|
param{{}},
|
|
scale_factor(1.0f),
|
|
mean_error(0.0),
|
|
bad_error(false),
|
|
x0(0.0f),
|
|
y0(0.0f),
|
|
fx(0.0f),
|
|
fy(0.0f),
|
|
rfx(0.0f),
|
|
rfy(0.0f),
|
|
vign_param{{}}
|
|
{
|
|
}
|
|
|
|
bool rtengine::LCPModelCommon::empty() const
|
|
{
|
|
return
|
|
param[0] == 0.0f
|
|
&& param[1] == 0.0f
|
|
&& param[2] == 0.0f;
|
|
}
|
|
|
|
// weighted merge two parameters
|
|
void rtengine::LCPModelCommon::merge(const LCPModelCommon& a, const LCPModelCommon& b, float facA)
|
|
{
|
|
foc_len_x = rtengine::intp<float>(facA, a.foc_len_x, b.foc_len_x);
|
|
foc_len_y = rtengine::intp<float>(facA, a.foc_len_y, b.foc_len_y);
|
|
img_center_x = rtengine::intp<float>(facA, a.img_center_x, b.img_center_x);
|
|
img_center_y = rtengine::intp<float>(facA, a.img_center_y, b.img_center_y);
|
|
scale_factor = rtengine::intp<float>(facA, a.scale_factor, b.scale_factor);
|
|
mean_error = rtengine::intp<float>(facA, a.mean_error, b.mean_error);
|
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
param[i] = rtengine::intp<float>(facA, a.param[i], b.param[i]);
|
|
}
|
|
|
|
const float param0Sqr = param[0] * param[0];
|
|
|
|
vign_param[0] = -param[0];
|
|
vign_param[1] = param0Sqr - param[1];
|
|
vign_param[2] = param0Sqr * param[0] - 2.0f * param[0] * param[1] + param[2];
|
|
vign_param[3] = param0Sqr * param0Sqr + param[1] * param[1] + 2.0f * param[0] * param[2] - 3.0f * param0Sqr * param[1];
|
|
|
|
}
|
|
|
|
void rtengine::LCPModelCommon::prepareParams(
|
|
int fullWidth,
|
|
int fullHeight,
|
|
float focalLength,
|
|
float focalLength35mm,
|
|
float sensorFormatFactor,
|
|
bool swapXY,
|
|
bool mirrorX,
|
|
bool mirrorY
|
|
)
|
|
{
|
|
// Mention that the Adobe technical paper has a bug here, the DMAX is handled differently for focLen and imgCenter
|
|
const int Dmax = std::max(fullWidth, fullHeight);
|
|
|
|
// correct focLens
|
|
if (foc_len_x < 0.0f) { // they may not be given
|
|
// and 35mm may not be given either
|
|
if (focalLength35mm < 1.0f) {
|
|
focalLength35mm = focalLength * sensorFormatFactor;
|
|
}
|
|
|
|
foc_len_x = foc_len_y = focalLength / (35.0f * focalLength / focalLength35mm); // focLen must be calculated in pixels
|
|
}
|
|
|
|
if (swapXY) {
|
|
x0 = (mirrorX ? 1.0f - img_center_y : img_center_y) * fullWidth;
|
|
y0 = (mirrorY ? 1.0f - img_center_x : img_center_x) * fullHeight;
|
|
fx = foc_len_y * Dmax;
|
|
fy = foc_len_x * Dmax;
|
|
} else {
|
|
x0 = (mirrorX ? 1.0f - img_center_x : img_center_x) * fullWidth;
|
|
y0 = (mirrorY ? 1.0f - img_center_y : img_center_y) * fullHeight;
|
|
fx = foc_len_x * Dmax;
|
|
fy = foc_len_y * Dmax;
|
|
}
|
|
rfx = 1.0f / fx;
|
|
rfy = 1.0f / fy;
|
|
|
|
}
|
|
|
|
rtengine::LCPProfile::LCPPersModel::LCPPersModel() :
|
|
focLen(0.f),
|
|
focDist(0.f),
|
|
aperture(0.f)
|
|
{
|
|
}
|
|
|
|
bool rtengine::LCPProfile::LCPPersModel::hasModeData(LCPCorrectionMode mode) const
|
|
{
|
|
switch (mode) {
|
|
case LCPCorrectionMode::VIGNETTE: {
|
|
return !vignette.empty() && !vignette.bad_error;
|
|
}
|
|
|
|
case LCPCorrectionMode::DISTORTION: {
|
|
return !base.empty() && !base.bad_error;
|
|
}
|
|
|
|
case LCPCorrectionMode::CA: {
|
|
return
|
|
!chromRG.empty()
|
|
&& !chromG.empty()
|
|
&& !chromBG.empty()
|
|
&& !chromRG.bad_error
|
|
&& !chromG.bad_error
|
|
&& !chromBG.bad_error;
|
|
}
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
rtengine::LCPProfile::LCPProfile(const Glib::ustring& fname) :
|
|
isFisheye(false),
|
|
sensorFormatFactor(1.f),
|
|
persModelCount(0),
|
|
inCamProfiles(false),
|
|
firstLIDone(false),
|
|
inPerspect(false),
|
|
inAlternateLensID(false),
|
|
inAlternateLensNames(false),
|
|
lastTag{},
|
|
inInvalidTag{},
|
|
pCurPersModel(nullptr),
|
|
pCurCommon(nullptr),
|
|
aPersModel{}
|
|
{
|
|
|
|
XML_Parser parser = XML_ParserCreate(nullptr);
|
|
|
|
if (!parser) {
|
|
throw std::runtime_error("Couldn't allocate memory for XML parser");
|
|
}
|
|
|
|
XML_SetElementHandler(parser, XmlStartHandler, XmlEndHandler);
|
|
XML_SetCharacterDataHandler(parser, XmlTextHandler);
|
|
XML_SetUserData(parser, static_cast<void*>(this));
|
|
|
|
FILE* const pFile = g_fopen(fname.c_str (), "rb");
|
|
|
|
if (pFile) {
|
|
constexpr int BufferSize = 8192;
|
|
char buf[BufferSize];
|
|
bool done;
|
|
|
|
do {
|
|
int bytesRead = fread(buf, 1, BufferSize, pFile);
|
|
done = feof(pFile);
|
|
|
|
if (XML_Parse(parser, buf, bytesRead, done) == XML_STATUS_ERROR) {
|
|
XML_ParserFree(parser);
|
|
throw std::runtime_error("Invalid XML in LCP file");
|
|
}
|
|
} while (!done);
|
|
|
|
fclose(pFile);
|
|
}
|
|
|
|
XML_ParserFree(parser);
|
|
|
|
if (settings->verbose) {
|
|
std::printf("Parsing %s\n", fname.c_str());
|
|
}
|
|
// Two phase filter: first filter out the very rough ones, that distord the average a lot
|
|
// force it, even if there are few frames (community profiles)
|
|
filterBadFrames(LCPCorrectionMode::VIGNETTE, 2.0, 0);
|
|
filterBadFrames(LCPCorrectionMode::CA, 2.0, 0);
|
|
// from the non-distorded, filter again on new average basis, but only if there are enough frames left
|
|
filterBadFrames(LCPCorrectionMode::VIGNETTE, 1.5, 50);
|
|
filterBadFrames(LCPCorrectionMode::CA, 1.5, 50);
|
|
}
|
|
|
|
rtengine::LCPProfile::~LCPProfile()
|
|
{
|
|
delete pCurPersModel;
|
|
|
|
for (int i = 0; i < MaxPersModelCount; ++i) {
|
|
delete aPersModel[i];
|
|
}
|
|
}
|
|
|
|
void rtengine::LCPProfile::calcParams(
|
|
LCPCorrectionMode mode,
|
|
float focalLength,
|
|
float focusDist,
|
|
float aperture,
|
|
LCPModelCommon* pCorr1,
|
|
LCPModelCommon* pCorr2,
|
|
LCPModelCommon* pCorr3
|
|
) const
|
|
{
|
|
const float euler = std::exp(1.0);
|
|
|
|
// find the frames with the least distance, focal length wise
|
|
LCPPersModel* pLow = nullptr;
|
|
LCPPersModel* pHigh = nullptr;
|
|
|
|
const float focalLengthLog = std::log(focalLength); //, apertureLog=aperture>0 ? std::log(aperture) : 0;
|
|
const float focusDistLog = focusDist > 0 ? std::log(focusDist) + euler : 0;
|
|
|
|
// Pass 1: determining best focal length, if possible different focusDistances (for the focDist is not given case)
|
|
for (int pm = 0; pm < persModelCount; ++pm) {
|
|
const float f = aPersModel[pm]->focLen;
|
|
|
|
if (aPersModel[pm]->hasModeData(mode)) {
|
|
if (
|
|
f <= focalLength
|
|
&& (
|
|
pLow == nullptr
|
|
|| f > pLow->focLen
|
|
|| (
|
|
focusDist == 0
|
|
&& f == pLow->focLen
|
|
&& pLow->focDist > aPersModel[pm]->focDist
|
|
)
|
|
)
|
|
) {
|
|
pLow = aPersModel[pm];
|
|
}
|
|
|
|
if (
|
|
f >= focalLength
|
|
&& (
|
|
pHigh == nullptr
|
|
|| f < pHigh->focLen
|
|
|| (
|
|
focusDist == 0
|
|
&& f == pHigh->focLen
|
|
&& pHigh->focDist < aPersModel[pm]->focDist
|
|
)
|
|
)
|
|
) {
|
|
pHigh = aPersModel[pm];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!pLow) {
|
|
pLow = pHigh;
|
|
}
|
|
else if (!pHigh) {
|
|
pHigh = pLow;
|
|
}
|
|
else {
|
|
// Pass 2: We have some, so take the best aperture for vignette and best focus for CA and distortion
|
|
// there are usually several frame per focal length. In the end pLow will have both flen and apterure/focdis below the target,
|
|
// and vice versa pHigh
|
|
const float bestFocLenLow = pLow->focLen;
|
|
const float bestFocLenHigh = pHigh->focLen;
|
|
|
|
for (int pm = 0; pm < persModelCount; ++pm) {
|
|
const float aper = aPersModel[pm]->aperture; // float aperLog=std::log(aper);
|
|
const float focDist = aPersModel[pm]->focDist;
|
|
const float focDistLog = std::log(focDist) + euler;
|
|
|
|
if (aPersModel[pm]->hasModeData(mode)) {
|
|
double meanErr = 0.0;
|
|
double lowMeanErr = 0.0;
|
|
double highMeanErr = 0.0;
|
|
|
|
switch (mode) {
|
|
case LCPCorrectionMode::VIGNETTE: {
|
|
meanErr = aPersModel[pm]->vignette.mean_error;
|
|
lowMeanErr = pLow->vignette.mean_error;
|
|
highMeanErr = pHigh->vignette.mean_error;
|
|
break;
|
|
}
|
|
|
|
case LCPCorrectionMode::DISTORTION: {
|
|
meanErr = aPersModel[pm]->base.mean_error;
|
|
lowMeanErr = pLow->base.mean_error;
|
|
highMeanErr = pHigh->base.mean_error;
|
|
break;
|
|
}
|
|
|
|
case LCPCorrectionMode::CA: {
|
|
meanErr = aPersModel[pm]->chromG.mean_error;
|
|
lowMeanErr = pLow->chromG.mean_error;
|
|
highMeanErr = pHigh->chromG.mean_error;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (aperture > 0 && mode != LCPCorrectionMode::CA) {
|
|
if (
|
|
aPersModel[pm]->focLen == bestFocLenLow
|
|
&& (
|
|
(
|
|
aper == aperture
|
|
&& lowMeanErr > meanErr
|
|
)
|
|
|| (
|
|
aper >= aperture
|
|
&& aper < pLow->aperture
|
|
&& pLow->aperture > aperture
|
|
)
|
|
|| (
|
|
aper <= aperture
|
|
&& (
|
|
pLow->aperture > aperture
|
|
|| fabs(aperture - aper) < fabs(aperture - pLow->aperture)
|
|
)
|
|
)
|
|
)
|
|
) {
|
|
pLow = aPersModel[pm];
|
|
}
|
|
|
|
if (
|
|
aPersModel[pm]->focLen == bestFocLenHigh
|
|
&& (
|
|
(
|
|
aper == aperture
|
|
&& highMeanErr > meanErr
|
|
)
|
|
|| (
|
|
aper <= aperture
|
|
&& aper > pHigh->aperture
|
|
&& pHigh->aperture < aperture
|
|
)
|
|
|| (
|
|
aper >= aperture
|
|
&& (
|
|
pHigh->aperture < aperture
|
|
|| fabs(aperture - aper) < fabs(aperture - pHigh->aperture)
|
|
)
|
|
)
|
|
)
|
|
) {
|
|
pHigh = aPersModel[pm];
|
|
}
|
|
}
|
|
else if (focusDist > 0 && mode != LCPCorrectionMode::VIGNETTE) {
|
|
// by focus distance
|
|
if (
|
|
aPersModel[pm]->focLen == bestFocLenLow
|
|
&& (
|
|
(
|
|
focDist == focusDist
|
|
&& lowMeanErr > meanErr
|
|
)
|
|
|| (
|
|
focDist >= focusDist
|
|
&& focDist < pLow->focDist
|
|
&& pLow->focDist > focusDist
|
|
)
|
|
|| (
|
|
focDist <= focusDist
|
|
&& (
|
|
pLow->focDist > focusDist
|
|
|| fabs(focusDistLog - focDistLog) < fabs(focusDistLog - (std::log(pLow->focDist) + euler))
|
|
)
|
|
)
|
|
)
|
|
) {
|
|
pLow = aPersModel[pm];
|
|
}
|
|
|
|
if (
|
|
aPersModel[pm]->focLen == bestFocLenHigh
|
|
&& (
|
|
(
|
|
focDist == focusDist
|
|
&& highMeanErr > meanErr
|
|
)
|
|
|| (
|
|
focDist <= focusDist
|
|
&& focDist > pHigh->focDist
|
|
&& pHigh->focDist < focusDist
|
|
)
|
|
|| (
|
|
focDist >= focusDist
|
|
&& (
|
|
pHigh->focDist < focusDist
|
|
|| fabs(focusDistLog - focDistLog) < fabs(focusDistLog - (std::log(pHigh->focDist) + euler))
|
|
)
|
|
)
|
|
)
|
|
) {
|
|
pHigh = aPersModel[pm];
|
|
}
|
|
}
|
|
else {
|
|
// no focus distance available, just error
|
|
if (aPersModel[pm]->focLen == bestFocLenLow && lowMeanErr > meanErr) {
|
|
pLow = aPersModel[pm];
|
|
}
|
|
|
|
if (aPersModel[pm]->focLen == bestFocLenHigh && highMeanErr > meanErr) {
|
|
pHigh = aPersModel[pm];
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pLow != nullptr && pHigh != nullptr) {
|
|
// average out the factors, linear interpolation in logarithmic scale
|
|
float facLow = 0.5f;
|
|
bool focLenOnSpot = false; // pretty often, since max/min are often as frames in LCP
|
|
|
|
// There is as foclen range, take that as basis
|
|
if (pLow->focLen < pHigh->focLen) {
|
|
facLow = (std::log(pHigh->focLen) - focalLengthLog) / (std::log(pHigh->focLen) - std::log(pLow->focLen));
|
|
} else {
|
|
focLenOnSpot = pLow->focLen == pHigh->focLen && pLow->focLen == focalLength;
|
|
}
|
|
|
|
// and average the other factor if available
|
|
if (
|
|
mode == LCPCorrectionMode::VIGNETTE
|
|
&& pLow->aperture < aperture
|
|
&& pHigh->aperture > aperture
|
|
) {
|
|
// Mix in aperture
|
|
const float facAperLow = (pHigh->aperture - aperture) / (pHigh->aperture - pLow->aperture);
|
|
facLow = focLenOnSpot ? facAperLow : (0.5f * (facLow + facAperLow));
|
|
}
|
|
else if (
|
|
mode != LCPCorrectionMode::VIGNETTE
|
|
&& focusDist > 0
|
|
&& pLow->focDist < focusDist
|
|
&& pHigh->focDist > focusDist
|
|
) {
|
|
// focus distance for all else (if focus distance is given)
|
|
const float facDistLow = (std::log(pHigh->focDist) + euler - focusDistLog) / (std::log(pHigh->focDist) - std::log(pLow->focDist));
|
|
facLow = focLenOnSpot ? facDistLow : (0.8f * facLow + 0.2f * facDistLow);
|
|
}
|
|
|
|
switch (mode) {
|
|
case LCPCorrectionMode::VIGNETTE: {
|
|
pCorr1->merge(pLow->vignette, pHigh->vignette, facLow);
|
|
break;
|
|
}
|
|
|
|
case LCPCorrectionMode::DISTORTION: {
|
|
pCorr1->merge(pLow->base, pHigh->base, facLow);
|
|
break;
|
|
}
|
|
|
|
case LCPCorrectionMode::CA: {
|
|
pCorr1->merge(pLow->chromRG, pHigh->chromRG, facLow);
|
|
pCorr2->merge(pLow->chromG, pHigh->chromG, facLow);
|
|
pCorr3->merge(pLow->chromBG, pHigh->chromBG, facLow);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (settings->verbose) {
|
|
std::printf("LCP mode=%i, dist: %g found frames: Fno %g-%g; FocLen %g-%g; Dist %g-%g with weight %g\n",
|
|
toUnderlying(mode),
|
|
static_cast<double>(focusDist),
|
|
static_cast<double>(pLow->aperture),
|
|
static_cast<double>(pHigh->aperture),
|
|
static_cast<double>(pLow->focLen),
|
|
static_cast<double>(pHigh->focLen),
|
|
static_cast<double>(pLow->focDist),
|
|
static_cast<double>(pHigh->focDist),
|
|
static_cast<double>(facLow));
|
|
}
|
|
} else {
|
|
if (settings->verbose) {
|
|
std::printf("Error: LCP file contained no %s parameters\n", mode == LCPCorrectionMode::VIGNETTE ? "vignette" : mode == LCPCorrectionMode::DISTORTION ? "distortion" : "CA" );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// from all frames not marked as bad already, take average and filter out frames with higher deviation than this if there are enough values
|
|
int rtengine::LCPProfile::filterBadFrames(LCPCorrectionMode mode, double maxAvgDevFac, int minFramesLeft)
|
|
{
|
|
// take average error, then calculated the maximum deviation allowed
|
|
double err = 0.0;
|
|
int count = 0;
|
|
|
|
for (int pm = 0; pm < MaxPersModelCount && aPersModel[pm]; ++pm) {
|
|
if (aPersModel[pm]->hasModeData(mode)) {
|
|
++count;
|
|
switch (mode) {
|
|
case LCPCorrectionMode::VIGNETTE: {
|
|
err += aPersModel[pm]->vignette.mean_error;
|
|
break;
|
|
}
|
|
|
|
case LCPCorrectionMode::DISTORTION: {
|
|
err += aPersModel[pm]->base.mean_error;
|
|
break;
|
|
}
|
|
|
|
case LCPCorrectionMode::CA: {
|
|
err += rtengine::max(aPersModel[pm]->chromRG.mean_error, aPersModel[pm]->chromG.mean_error, aPersModel[pm]->chromBG.mean_error);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only if we have enough frames, filter out errors
|
|
int filtered = 0;
|
|
|
|
if (count >= minFramesLeft) {
|
|
if (count > 0) {
|
|
err /= count;
|
|
}
|
|
|
|
// Now mark all the bad ones as bad, and hasModeData will return false;
|
|
for (int pm = 0; pm < MaxPersModelCount && aPersModel[pm]; ++pm) {
|
|
if (aPersModel[pm]->hasModeData(mode)) {
|
|
switch (mode) {
|
|
case LCPCorrectionMode::VIGNETTE: {
|
|
if (aPersModel[pm]->vignette.mean_error > maxAvgDevFac * err) {
|
|
aPersModel[pm]->vignette.bad_error = true;
|
|
filtered++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LCPCorrectionMode::DISTORTION: {
|
|
if (aPersModel[pm]->base.mean_error > maxAvgDevFac * err) {
|
|
aPersModel[pm]->base.bad_error = true;
|
|
filtered++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LCPCorrectionMode::CA: {
|
|
if (
|
|
aPersModel[pm]->chromRG.mean_error > maxAvgDevFac * err
|
|
|| aPersModel[pm]->chromG.mean_error > maxAvgDevFac * err
|
|
|| aPersModel[pm]->chromBG.mean_error > maxAvgDevFac * err
|
|
) {
|
|
aPersModel[pm]->chromRG.bad_error = true;
|
|
aPersModel[pm]->chromG.bad_error = true;
|
|
aPersModel[pm]->chromBG.bad_error = true;
|
|
++filtered;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (settings->verbose && count) {
|
|
std::printf("Filtered %.1f%% frames for maxAvgDevFac %g leaving %i\n", filtered * 100.0 / count, maxAvgDevFac, count - filtered);
|
|
}
|
|
}
|
|
|
|
return filtered;
|
|
}
|
|
|
|
void rtengine::LCPProfile::handle_text(const std::string& text)
|
|
{
|
|
// Check if it contains non-whitespaces (there are several calls to this for one tag unfortunately)
|
|
bool onlyWhiteSpace = true;
|
|
for (auto c : text) {
|
|
if (!std::isspace(c)) {
|
|
onlyWhiteSpace = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (onlyWhiteSpace) {
|
|
return;
|
|
}
|
|
|
|
LCPProfile* const pProf = this;
|
|
|
|
// convert to null terminated
|
|
const std::string tag = pProf->lastTag;
|
|
|
|
// Common data section
|
|
if (!pProf->firstLIDone) {
|
|
// Generic tags are the same for all
|
|
if (tag == "ProfileName") {
|
|
pProf->profileName = text;
|
|
} else if (tag == "Model") {
|
|
pProf->camera = text;
|
|
} else if (tag == "Lens") {
|
|
pProf->lens = text;
|
|
} else if (tag == "CameraPrettyName") {
|
|
pProf->cameraPrettyName = text;
|
|
} else if (tag == "LensPrettyName") {
|
|
pProf->lensPrettyName = text;
|
|
} else if (tag == "CameraRawProfile") {
|
|
pProf->isRaw = text == "True";
|
|
}
|
|
}
|
|
|
|
// Locale should be already set
|
|
assert(std::atof("1.2345") == 1.2345);
|
|
|
|
if (!pProf->firstLIDone) {
|
|
if (tag == "SensorFormatFactor") {
|
|
pProf->sensorFormatFactor = std::atof(text.c_str());
|
|
}
|
|
}
|
|
|
|
// Perspective model base data
|
|
if (tag == "FocalLength") {
|
|
pProf->pCurPersModel->focLen = std::atof(text.c_str());
|
|
} else if (tag == "FocusDistance") {
|
|
double focDist = std::atof(text.c_str());
|
|
pProf->pCurPersModel->focDist = focDist < 10000 ? focDist : 10000;
|
|
} else if (tag == "ApertureValue") {
|
|
pProf->pCurPersModel->aperture = std::atof(text.c_str());
|
|
}
|
|
|
|
// Section depended
|
|
if (tag == "FocalLengthX") {
|
|
pProf->pCurCommon->foc_len_x = std::atof(text.c_str());
|
|
} else if (tag == "FocalLengthY") {
|
|
pProf->pCurCommon->foc_len_y = std::atof(text.c_str());
|
|
} else if (tag == "ImageXCenter") {
|
|
pProf->pCurCommon->img_center_x = std::atof(text.c_str());
|
|
} else if (tag == "ImageYCenter") {
|
|
pProf->pCurCommon->img_center_y = std::atof(text.c_str());
|
|
} else if (tag == "ScaleFactor") {
|
|
pProf->pCurCommon->scale_factor = std::atof(text.c_str());
|
|
} else if (tag == "ResidualMeanError") {
|
|
pProf->pCurCommon->mean_error = std::atof(text.c_str());
|
|
} else if (tag == "RadialDistortParam1" || tag == "VignetteModelParam1") {
|
|
pProf->pCurCommon->param[0] = std::atof(text.c_str());
|
|
} else if (tag == "RadialDistortParam2" || tag == "VignetteModelParam2") {
|
|
pProf->pCurCommon->param[1] = std::atof(text.c_str());
|
|
} else if (tag == "RadialDistortParam3" || tag == "VignetteModelParam3") {
|
|
pProf->pCurCommon->param[2] = std::atof(text.c_str());
|
|
} else if (tag == "RadialDistortParam4" || tag == "TangentialDistortParam1") {
|
|
pProf->pCurCommon->param[3] = std::atof(text.c_str());
|
|
} else if (tag == "RadialDistortParam5" || tag == "TangentialDistortParam2") {
|
|
pProf->pCurCommon->param[4] = std::atof(text.c_str());
|
|
}
|
|
}
|
|
|
|
void XMLCALL rtengine::LCPProfile::XmlStartHandler(void* pLCPProfile, const char* el, const char** attr)
|
|
{
|
|
LCPProfile* const pProf = static_cast<LCPProfile*>(pLCPProfile);
|
|
|
|
bool parseAttr = false;
|
|
|
|
if (*pProf->inInvalidTag) {
|
|
return; // We ignore everything in dirty tag till it's gone
|
|
}
|
|
|
|
// clean up tagname
|
|
const char* src = strrchr(el, ':');
|
|
|
|
if (src == nullptr) {
|
|
src = el;
|
|
} else {
|
|
++src;
|
|
}
|
|
|
|
strncpy(pProf->lastTag, src, sizeof(pProf->lastTag) - 1);
|
|
pProf->lastTag[sizeof(pProf->lastTag) - 1] = 0;
|
|
const std::string src_str = src;
|
|
|
|
if (src_str == "VignetteModelPiecewiseParam") {
|
|
strncpy(pProf->inInvalidTag, src, sizeof(pProf->inInvalidTag) - 1);
|
|
pProf->inInvalidTag[sizeof(pProf->inInvalidTag) - 1] = 0;
|
|
}
|
|
|
|
if (src_str == "CameraProfiles") {
|
|
pProf->inCamProfiles = true;
|
|
}
|
|
|
|
if (src_str == "AlternateLensIDs") {
|
|
pProf->inAlternateLensID = true;
|
|
}
|
|
|
|
if (src_str == "AlternateLensNames") {
|
|
pProf->inAlternateLensNames = true;
|
|
}
|
|
|
|
if (
|
|
!pProf->inCamProfiles
|
|
|| pProf->inAlternateLensID
|
|
|| pProf->inAlternateLensNames
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (src_str == "li") {
|
|
pProf->pCurPersModel = new LCPPersModel();
|
|
pProf->pCurCommon = &pProf->pCurPersModel->base; // iterated to next tags within persModel
|
|
return;
|
|
}
|
|
|
|
if (src_str == "PerspectiveModel") {
|
|
pProf->firstLIDone = true;
|
|
pProf->inPerspect = true;
|
|
parseAttr = true;
|
|
} else if (src_str == "FisheyeModel") {
|
|
pProf->firstLIDone = true;
|
|
pProf->inPerspect = true;
|
|
pProf->isFisheye = true; // just misses third param, and different path, rest is the same
|
|
parseAttr = true;
|
|
} else if (src_str == "Description") {
|
|
parseAttr = true;
|
|
}
|
|
|
|
// Move pointer to general section
|
|
if (pProf->inPerspect) {
|
|
if (src_str == "ChromaticRedGreenModel") {
|
|
pProf->pCurCommon = &pProf->pCurPersModel->chromRG;
|
|
parseAttr = true;
|
|
} else if (src_str == "ChromaticGreenModel") {
|
|
pProf->pCurCommon = &pProf->pCurPersModel->chromG;
|
|
parseAttr = true;
|
|
} else if (src_str == "ChromaticBlueGreenModel") {
|
|
pProf->pCurCommon = &pProf->pCurPersModel->chromBG;
|
|
parseAttr = true;
|
|
} else if (src_str == "VignetteModel") {
|
|
pProf->pCurCommon = &pProf->pCurPersModel->vignette;
|
|
parseAttr = true;
|
|
}
|
|
}
|
|
|
|
// some profiles (espc. Pentax) have a different structure that is attributes based
|
|
// simulate tags by feeding them in
|
|
if (parseAttr && attr != nullptr) {
|
|
for (int i = 0; attr[i]; i += 2) {
|
|
const char* nameStart = strrchr(attr[i], ':');
|
|
|
|
if (nameStart == nullptr) {
|
|
nameStart = attr[i];
|
|
} else {
|
|
++nameStart;
|
|
}
|
|
|
|
strncpy(pProf->lastTag, nameStart, 255);
|
|
|
|
pProf->handle_text(attr[i + 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLCALL rtengine::LCPProfile::XmlTextHandler(void* pLCPProfile, const XML_Char* s, int len)
|
|
{
|
|
LCPProfile* const pProf = static_cast<LCPProfile*>(pLCPProfile);
|
|
|
|
if (
|
|
!pProf->inCamProfiles
|
|
|| pProf->inAlternateLensID
|
|
|| pProf->inAlternateLensNames
|
|
|| *pProf->inInvalidTag
|
|
) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < len; ++i) {
|
|
pProf->textbuf << s[i];
|
|
}
|
|
}
|
|
|
|
void XMLCALL rtengine::LCPProfile::XmlEndHandler(void* pLCPProfile, const char* el)
|
|
{
|
|
LCPProfile* const pProf = static_cast<LCPProfile*>(pLCPProfile);
|
|
|
|
pProf->handle_text(pProf->textbuf.str());
|
|
pProf->textbuf.str("");
|
|
|
|
// We ignore everything in dirty tag till it's gone
|
|
if (*pProf->inInvalidTag) {
|
|
if (std::strstr(el, pProf->inInvalidTag)) {
|
|
*pProf->inInvalidTag = 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (std::strstr(el, ":CameraProfiles")) {
|
|
pProf->inCamProfiles = false;
|
|
}
|
|
|
|
if (std::strstr(el, ":AlternateLensIDs")) {
|
|
pProf->inAlternateLensID = false;
|
|
}
|
|
|
|
if (std::strstr(el, ":AlternateLensNames")) {
|
|
pProf->inAlternateLensNames = false;
|
|
}
|
|
|
|
if (
|
|
!pProf->inCamProfiles
|
|
|| pProf->inAlternateLensID
|
|
|| pProf->inAlternateLensNames
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (std::strstr(el, ":PerspectiveModel") || std::strstr(el, ":FisheyeModel")) {
|
|
pProf->inPerspect = false;
|
|
} else if (std::strstr(el, ":li")) {
|
|
pProf->aPersModel[pProf->persModelCount] = pProf->pCurPersModel;
|
|
pProf->pCurPersModel = nullptr;
|
|
++pProf->persModelCount;
|
|
}
|
|
}
|
|
|
|
// Generates as singleton
|
|
rtengine::LCPStore* rtengine::LCPStore::getInstance()
|
|
{
|
|
static LCPStore instance_;
|
|
return &instance_;
|
|
}
|
|
|
|
bool rtengine::LCPStore::isValidLCPFileName(const Glib::ustring& filename) const
|
|
{
|
|
if (!Glib::file_test(filename, Glib::FILE_TEST_EXISTS) || Glib::file_test (filename, Glib::FILE_TEST_IS_DIR)) {
|
|
return false;
|
|
}
|
|
|
|
const size_t pos = filename.find_last_of ('.');
|
|
return pos > 0 && !filename.casefold().compare(pos, 4, ".lcp");
|
|
}
|
|
|
|
std::shared_ptr<rtengine::LCPProfile> rtengine::LCPStore::getProfile(const Glib::ustring& filename) const
|
|
{
|
|
if (filename.length() == 0 || !isValidLCPFileName(filename)) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<LCPProfile> res;
|
|
if (!cache.get(filename, res)) {
|
|
try {
|
|
res.reset(new LCPProfile(filename));
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
|
|
cache.set(filename, res);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Glib::ustring rtengine::LCPStore::getDefaultCommonDirectory() const
|
|
{
|
|
Glib::ustring dir;
|
|
|
|
#ifdef WIN32
|
|
WCHAR pathW[MAX_PATH] = {0};
|
|
|
|
if (SHGetSpecialFolderPathW(NULL, pathW, CSIDL_COMMON_APPDATA, false)) {
|
|
char pathA[MAX_PATH];
|
|
WideCharToMultiByte(CP_UTF8, 0, pathW, -1, pathA, MAX_PATH, 0, 0);
|
|
Glib::ustring fullDir = Glib::ustring(pathA) + Glib::ustring("\\Adobe\\CameraRaw\\LensProfiles\\1.0");
|
|
|
|
if (Glib::file_test (fullDir, Glib::FILE_TEST_IS_DIR)) {
|
|
dir = fullDir;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
// TODO: Add Mac paths here
|
|
|
|
return dir;
|
|
}
|
|
|
|
rtengine::LCPStore::LCPStore(unsigned int _cache_size) :
|
|
cache(_cache_size)
|
|
{
|
|
}
|
|
|
|
// if !vignette then geometric and CA
|
|
rtengine::LCPMapper::LCPMapper(
|
|
const std::shared_ptr<LCPProfile>& pProf,
|
|
float focalLength,
|
|
float focalLength35mm,
|
|
float focusDist,
|
|
float aperture,
|
|
bool vignette,
|
|
bool useCADistP,
|
|
int fullWidth,
|
|
int fullHeight,
|
|
const procparams::CoarseTransformParams& coarse,
|
|
int rawRotationDeg
|
|
) :
|
|
enableCA(false),
|
|
useCADist(useCADistP),
|
|
swapXY(false),
|
|
isFisheye(false)
|
|
{
|
|
if (!pProf) {
|
|
return;
|
|
}
|
|
|
|
// determine in what the image with the RAW landscape in comparison (calibration target)
|
|
// in vignetting, the rotation has not taken place yet
|
|
int rot = 0;
|
|
|
|
if (rawRotationDeg >= 0) {
|
|
rot = (coarse.rotate + rawRotationDeg) % 360;
|
|
}
|
|
|
|
swapXY = (rot == 90 || rot == 270);
|
|
|
|
const bool mirrorX = (rot == 90 || rot == 180);
|
|
const bool mirrorY = (rot == 180 || rot == 270);
|
|
if (settings->verbose) {
|
|
std::printf("Vign: %i, fullWidth: %i/%i, focLen %g SwapXY: %i / MirX/Y %i / %i on rot:%i from %i\n",vignette, fullWidth, fullHeight, static_cast<double>(focalLength), swapXY, mirrorX, mirrorY, rot, rawRotationDeg);
|
|
}
|
|
|
|
pProf->calcParams(vignette ? LCPCorrectionMode::VIGNETTE : LCPCorrectionMode::DISTORTION, focalLength, focusDist, aperture, &mc, nullptr, nullptr);
|
|
mc.prepareParams(fullWidth, fullHeight, focalLength, focalLength35mm, pProf->sensorFormatFactor, swapXY, mirrorX, mirrorY);
|
|
|
|
if (!vignette) {
|
|
pProf->calcParams(LCPCorrectionMode::CA, focalLength, focusDist, aperture, &chrom[0], &chrom[1], &chrom[2]);
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
chrom[i].prepareParams(fullWidth, fullHeight, focalLength, focalLength35mm, pProf->sensorFormatFactor, swapXY, mirrorX, mirrorY);
|
|
}
|
|
}
|
|
|
|
enableCA = !vignette && focusDist > 0.f;
|
|
isFisheye = pProf->isFisheye;
|
|
}
|
|
|
|
bool rtengine::LCPMapper::isCACorrectionAvailable() const
|
|
{
|
|
return enableCA;
|
|
}
|
|
|
|
void rtengine::LCPMapper::correctDistortion(double &x, double &y, int cx, int cy) const
|
|
{
|
|
x += cx;
|
|
y += cy;
|
|
|
|
if (isFisheye) {
|
|
const double u = x;
|
|
const double v = y;
|
|
const double u0 = static_cast<double>(mc.x0);
|
|
const double v0 = static_cast<double>(mc.y0);
|
|
const double du = (u - u0);
|
|
const double dv = (v - v0);
|
|
const double fx = mc.fx;
|
|
const double fy = mc.fy;
|
|
const double k1 = mc.param[0];
|
|
const double k2 = mc.param[1];
|
|
const double r = sqrt(du * du + dv * dv);
|
|
const double f = sqrt(fx*fy);
|
|
const double th = atan2(r, f);
|
|
const double th2 = th * th;
|
|
const double cfact = (((k2 * th2 + k1) * th2 + 1) * th) / r;
|
|
const double ud = cfact * fx * du + u0;
|
|
const double vd = cfact * fy * dv + v0;
|
|
|
|
x = ud;
|
|
y = vd;
|
|
} else {
|
|
const double x0 = static_cast<double>(mc.x0);
|
|
const double y0 = static_cast<double>(mc.y0);
|
|
const double xd = (x - x0) / static_cast<double>(mc.fx), yd = (y - y0) / static_cast<double>(mc.fy);
|
|
|
|
const auto& aDist = mc.param;
|
|
const double rsqr = xd * xd + yd * yd;
|
|
const double xfac = static_cast<double>(aDist[swapXY ? 3 : 4]), yfac = static_cast<double>(aDist[swapXY ? 4 : 3]);
|
|
|
|
const double commonFac = (((static_cast<double>(aDist[2]) * rsqr + static_cast<double>(aDist[1])) * rsqr + static_cast<double>(aDist[0])) * rsqr + 1.)
|
|
+ 2. * (yfac * yd + xfac * xd);
|
|
|
|
const double xnew = xd * commonFac + xfac * rsqr;
|
|
const double ynew = yd * commonFac + yfac * rsqr;
|
|
|
|
x = xnew * static_cast<double>(mc.fx) + x0;
|
|
y = ynew * static_cast<double>(mc.fy) + y0;
|
|
}
|
|
|
|
x -= cx;
|
|
y -= cy;
|
|
}
|
|
|
|
void rtengine::LCPMapper::correctCA(double& x, double& y, int cx, int cy, int channel) const
|
|
{
|
|
if (!enableCA) {
|
|
return;
|
|
}
|
|
|
|
x += cx;
|
|
y += cy;
|
|
|
|
double xgreen, ygreen;
|
|
|
|
// First calc the green channel like normal distortion
|
|
// the other are just deviations from it
|
|
double xd = (x - static_cast<double>(chrom[1].x0)) / static_cast<double>(chrom[1].fx);
|
|
double yd = (y - static_cast<double>(chrom[1].y0)) / static_cast<double>(chrom[1].fy);
|
|
|
|
// Green contains main distortion, just like base
|
|
if (useCADist) {
|
|
const auto& aDist = chrom[1].param;
|
|
double rsqr = xd * xd + yd * yd;
|
|
double xfac = static_cast<double>(aDist[swapXY ? 3 : 4]), yfac = static_cast<double>(aDist[swapXY ? 4 : 3]);
|
|
|
|
double commonFac = (((static_cast<double>(aDist[2]) * rsqr + static_cast<double>(aDist[1])) * rsqr + static_cast<double>(aDist[0])) * rsqr + 1.)
|
|
+ 2. * (yfac * yd + xfac * xd);
|
|
|
|
xgreen = xd * commonFac + static_cast<double>(aDist[4]) * rsqr;
|
|
ygreen = yd * commonFac + static_cast<double>(aDist[3]) * rsqr;
|
|
} else {
|
|
xgreen = xd;
|
|
ygreen = yd;
|
|
}
|
|
|
|
if (channel == 1) {
|
|
// green goes directly
|
|
x = xgreen * static_cast<double>(chrom[1].fx) + static_cast<double>(chrom[1].x0);
|
|
y = ygreen * static_cast<double>(chrom[1].fy) + static_cast<double>(chrom[1].y0);
|
|
} else {
|
|
// others are diffs from green
|
|
xd = xgreen;
|
|
yd = ygreen;
|
|
const double rsqr = xd * xd + yd * yd;
|
|
|
|
const auto& aCA = chrom[channel].param;
|
|
const double xfac = static_cast<double>(aCA[swapXY ? 3 : 4]), yfac = static_cast<double>(aCA[swapXY ? 4 : 3]);
|
|
const double commonSum = 1. + rsqr * (static_cast<double>(aCA[0]) + rsqr * (static_cast<double>(aCA[1]) + static_cast<double>(aCA[2] )* rsqr)) + 2. * (yfac * yd + xfac * xd);
|
|
|
|
x = (static_cast<double>(chrom[channel].scale_factor) * ( xd * commonSum + xfac * rsqr )) * static_cast<double>(chrom[channel].fx) + static_cast<double>(chrom[channel].x0);
|
|
y = (static_cast<double>(chrom[channel].scale_factor) * ( yd * commonSum + yfac * rsqr )) * static_cast<double>(chrom[channel].fy) + static_cast<double>(chrom[channel].y0);
|
|
}
|
|
|
|
x -= cx;
|
|
y -= cy;
|
|
}
|
|
|
|
void rtengine::LCPMapper::processVignette(int width, int height, float** rawData) const
|
|
{
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel for schedule(dynamic,16)
|
|
#endif
|
|
|
|
for (int y = 0; y < height; ++y) {
|
|
processVignetteLine(width, y, rawData[y]);
|
|
}
|
|
}
|
|
|
|
void rtengine::LCPMapper::processVignetteLine(int width, int y, float* line) const
|
|
{
|
|
// No need for swapXY, since vignette is in RAW and always before rotation
|
|
float yd = ((float)y - mc.y0) * mc.rfy;
|
|
yd *= yd;
|
|
int x = 0;
|
|
#ifdef __SSE2__
|
|
const vfloat fourv = F2V(4.f);
|
|
const vfloat zerov = F2V(0.f);
|
|
const vfloat ydv = F2V(yd);
|
|
const vfloat p0 = F2V(mc.vign_param[0]);
|
|
const vfloat p1 = F2V(mc.vign_param[1]);
|
|
const vfloat p2 = F2V(mc.vign_param[2]);
|
|
const vfloat p3 = F2V(mc.vign_param[3]);
|
|
const vfloat x0v = F2V(mc.x0);
|
|
const vfloat rfxv = F2V(mc.rfx);
|
|
|
|
vfloat xv = _mm_setr_ps(0.f, 1.f, 2.f, 3.f);
|
|
for (; x < width-3; x+=4) {
|
|
const vfloat xdv = (xv - x0v) * rfxv;
|
|
const vfloat rsqr = xdv * xdv + ydv;
|
|
const vfloat vignFactorv = rsqr * (p0 + rsqr * (p1 - p2 * rsqr + p3 * rsqr * rsqr));
|
|
vfloat valv = LVFU(line[x]);
|
|
valv += valv * vselfzero(vmaskf_gt(valv, zerov), vignFactorv);
|
|
STVFU(line[x], valv);
|
|
xv += fourv;
|
|
}
|
|
#endif // __SSE2__
|
|
for (; x < width; x++) {
|
|
if (line[x] > 0) {
|
|
const float xd = ((float)x - mc.x0) * mc.rfx;
|
|
const LCPModelCommon::VignParam vignParam = mc.vign_param;
|
|
const float rsqr = xd * xd + yd;
|
|
line[x] += line[x] * rsqr * (vignParam[0] + rsqr * ((vignParam[1]) - (vignParam[2]) * rsqr + (vignParam[3]) * rsqr * rsqr));
|
|
}
|
|
}
|
|
}
|
|
|
|
void rtengine::LCPMapper::processVignette3Channels(int width, int height, float** rawData) const
|
|
{
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel for schedule(dynamic,16)
|
|
#endif
|
|
|
|
for (int y = 0; y < height; ++y) {
|
|
processVignetteLine3Channels(width, y, rawData[y]);
|
|
}
|
|
}
|
|
|
|
void rtengine::LCPMapper::processVignetteLine3Channels(int width, int y, float* line) const
|
|
{
|
|
// No need for swapXY, since vignette is in RAW and always before rotation
|
|
float yd = ((float)y - mc.y0) * mc.rfy;
|
|
yd *= yd;
|
|
const LCPModelCommon::VignParam vignParam = mc.vign_param;
|
|
for (int x = 0; x < width; x++) {
|
|
const float xd = ((float)x - mc.x0) * mc.rfx;
|
|
const float rsqr = xd * xd + yd;
|
|
const float vignetteFactor = rsqr * (vignParam[0] + rsqr * ((vignParam[1]) - (vignParam[2]) * rsqr + (vignParam[3]) * rsqr * rsqr));
|
|
for(int c = 0;c < 3; ++c) {
|
|
if (line[3*x+c] > 0) {
|
|
line[3*x+c] += line[3*x+c] * vignetteFactor;
|
|
}
|
|
}
|
|
}
|
|
}
|