Files
rawTherapee/rtengine/lcp.cc
Alberto Griggio 871c75e494 Correctly apply LCP "distortion" correction for Fisheye lenses
Note: as discussed e.g. at http://lensfun.sourceforge.net/manual/corrections.html, this is really a combination of distortion correction and change of projection (from fisheye to rectilinear), but "distortion correction" is how the Adobe camera model calls it, and this is how it appears in the RT gui
2017-04-08 10:13:53 +02:00

925 lines
32 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 <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <cstring>
#include "lcp.h"
#include <glib/gstdio.h>
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#endif
using namespace std;
using namespace 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 LCPModelCommon::empty() const
{
return param[0] == 0.0f && param[1] == 0.0f && param[2] == 0.0f;
}
void LCPModelCommon::print() const
{
printf("focLen %g/%g; imgCenter %g/%g; scale %g; err %g\n", foc_len_x, foc_len_y, img_center_x, img_center_y, scale_factor, mean_error);
printf("xy0 %g/%g fxy %g/%g\n", x0, y0, fx, fy);
printf("param: %g/%g/%g/%g/%g\n", param[0], param[1], param[2], param[3], param[4]);
}
// weighted merge two parameters
void LCPModelCommon::merge(const LCPModelCommon& a, const LCPModelCommon& b, float facA)
{
const float facB = 1.0f - facA;
foc_len_x = facA * a.foc_len_x + facB * b.foc_len_x;
foc_len_y = facA * a.foc_len_y + facB * b.foc_len_y;
img_center_x = facA * a.img_center_x + facB * b.img_center_x;
img_center_y = facA * a.img_center_y + facB * b.img_center_y;
scale_factor = facA * a.scale_factor + facB * b.scale_factor;
mean_error = facA * a.mean_error + facB * b.mean_error;
for (int i = 0; i < 5; i++) {
param[i] = facA * a.param[i] + facB * 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 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;
//printf("FW %i /X0 %g FH %i /Y0 %g %g\n",fullWidth,x0,fullHeight,y0, imgYCenter);
}
LCPPersModel::LCPPersModel()
{
focLen = focDist = aperture = 0;
}
// mode: 0=distortion, 1=vignette, 2=CA
bool LCPPersModel::hasModeData(int mode) const
{
return (mode == 0 && !vignette.empty() && !vignette.bad_error) || (mode == 1 && !base.empty() && !base.bad_error)
|| (mode == 2 && !chromRG.empty() && !chromG.empty() && !chromBG.empty() &&
!chromRG.bad_error && !chromG.bad_error && !chromBG.bad_error);
}
void LCPPersModel::print() const
{
printf("--- PersModel focLen %g; focDist %g; aperture %g\n", focLen, focDist, aperture);
printf("Base:\n");
base.print();
if (!chromRG.empty()) {
printf("ChromRG:\n");
chromRG.print();
}
if (!chromG.empty()) {
printf("ChromG:\n");
chromG.print();
}
if (!chromBG.empty()) {
printf("ChromBG:\n");
chromBG.print();
}
if (!vignette.empty()) {
printf("Vignette:\n");
vignette.print();
}
printf("\n");
}
// if !vignette then geometric and CA
LCPMapper::LCPMapper(LCPProfile* pProf, float focalLength, float focalLength35mm, float focusDist, float aperture, bool vignette, bool useCADistP,
int fullWidth, int fullHeight, const CoarseTransformParams& coarse, int rawRotationDeg)
{
if (pProf == nullptr) {
return;
}
useCADist = useCADistP;
// 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);
bool mirrorX = (rot == 90 || rot == 180);
bool mirrorY = (rot == 180 || rot == 270);
//printf("Vign: %i, fullWidth: %i/%i, focLen %g SwapXY: %i / MirX/Y %i / %i on rot:%i from %i\n",vignette, fullWidth, fullHeight, focalLength, swapXY, mirrorX, mirrorY, rot, rawRotationDeg);
pProf->calcParams(vignette ? 0 : 1, focalLength, focusDist, aperture, &mc, nullptr, nullptr);
mc.prepareParams(fullWidth, fullHeight, focalLength, focalLength35mm, pProf->sensorFormatFactor, swapXY, mirrorX, mirrorY);
if (!vignette) {
pProf->calcParams(2, 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;
isFisheye = pProf->isFisheye;
}
void LCPMapper::correctDistortion(double& x, double& y, double scale) const
{
if (isFisheye) {
double u = x * scale;
double v = y * scale;
double u0 = mc.x0 * scale;
double v0 = mc.y0 * scale;
double du = (u - u0);
double dv = (v - v0);
double fx = mc.fx;
double fy = mc.fy;
double k1 = mc.param[0];
double k2 = mc.param[1];
double r = sqrt(du * du + dv * dv);
double f = sqrt(fx*fy / (scale * scale));
double th = atan2(r, f);
double th2 = th * th;
double cfact = (((k2 * th2 + k1) * th2 + 1) * th) / r;
double ud = cfact * fx * du + u0;
double vd = cfact * fy * dv + v0;
x = ud;
y = vd;
} else {
x *= scale;
y *= scale;
double x0 = mc.x0 * scale;
double y0 = mc.y0 * scale;
double xd = (x - x0) / mc.fx, yd = (y - y0) / mc.fy;
const LCPModelCommon::Param aDist = mc.param;
double rsqr = xd * xd + yd * yd;
double xfac = aDist[swapXY ? 3 : 4], yfac = aDist[swapXY ? 4 : 3];
double commonFac = (((aDist[2] * rsqr + aDist[1]) * rsqr + aDist[0]) * rsqr + 1.)
+ 2. * (yfac * yd + xfac * xd);
double xnew = xd * commonFac + xfac * rsqr;
double ynew = yd * commonFac + yfac * rsqr;
x = xnew * mc.fx + x0;
y = ynew * mc.fy + y0;
}
}
void LCPMapper::correctCA(double& x, double& y, int channel) const
{
if (!enableCA) {
return;
}
double rsqr, xgreen, ygreen;
// First calc the green channel like normal distortion
// the other are just deviations from it
double xd = (x - chrom[1].x0) / chrom[1].fx, yd = (y - chrom[1].y0) / chrom[1].fy;
// Green contains main distortion, just like base
if (useCADist) {
const LCPModelCommon::Param aDist = chrom[1].param;
double rsqr = xd * xd + yd * yd;
double xfac = aDist[swapXY ? 3 : 4], yfac = aDist[swapXY ? 4 : 3];
double commonFac = (((aDist[2] * rsqr + aDist[1]) * rsqr + aDist[0]) * rsqr + 1.)
+ 2. * (yfac * yd + xfac * xd);
xgreen = xd * commonFac + aDist[4] * rsqr;
ygreen = yd * commonFac + aDist[3] * rsqr;
} else {
xgreen = xd;
ygreen = yd;
}
if (channel == 1) {
// green goes directly
x = xgreen * chrom[1].fx + chrom[1].x0;
y = ygreen * chrom[1].fy + chrom[1].y0;
} else {
// others are diffs from green
xd = xgreen;
yd = ygreen;
rsqr = xd * xd + yd * yd;
const LCPModelCommon::Param aCA = chrom[channel].param;
double xfac = aCA[swapXY ? 3 : 4], yfac = aCA[swapXY ? 4 : 3];
double commonSum = 1. + rsqr * (aCA[0] + rsqr * (aCA[1] + aCA[2] * rsqr)) + 2. * (yfac * yd + xfac * xd);
x = (chrom[channel].scale_factor * ( xd * commonSum + xfac * rsqr )) * chrom[channel].fx + chrom[channel].x0;
y = (chrom[channel].scale_factor * ( yd * commonSum + yfac * rsqr )) * chrom[channel].fy + chrom[channel].y0;
}
}
SSEFUNCTION void 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) {
vfloat xdv = (xv - x0v) * rfxv;
vfloat rsqr = xdv * xdv + ydv;
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) {
float xd = ((float)x - mc.x0) * mc.rfx;
const LCPModelCommon::VignParam vignParam = mc.vign_param;
float rsqr = xd * xd + yd;
line[x] += line[x] * rsqr * (vignParam[0] + rsqr * ((vignParam[1]) - (vignParam[2]) * rsqr + (vignParam[3]) * rsqr * rsqr));
}
}
}
SSEFUNCTION void 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++) {
float xd = ((float)x - mc.x0) * mc.rfx;
float rsqr = xd * xd + yd;
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;
}
}
}
}
LCPProfile::LCPProfile(const Glib::ustring &fname)
{
const int BufferSize = 8192;
char buf[BufferSize];
XML_Parser parser = XML_ParserCreate(nullptr);
if (!parser) {
throw "Couldn't allocate memory for XML parser";
}
XML_SetElementHandler(parser, XmlStartHandler, XmlEndHandler);
XML_SetCharacterDataHandler(parser, XmlTextHandler);
XML_SetUserData(parser, (void *)this);
isFisheye = inCamProfiles = firstLIDone = inPerspect = inAlternateLensID = inAlternateLensNames = false;
sensorFormatFactor = 1;
for (int i = 0; i < MaxPersModelCount; i++) {
aPersModel[i] = nullptr;
}
persModelCount = 0;
*inInvalidTag = 0;
FILE *pFile = g_fopen(fname.c_str (), "rb");
bool done;
do {
int bytesRead = (int)fread(buf, 1, BufferSize, pFile);
done = feof(pFile);
if (XML_Parse(parser, buf, bytesRead, done) == XML_STATUS_ERROR) {
throw "Invalid XML in LCP file";
}
} while (!done);
fclose(pFile);
XML_ParserFree(parser);
//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(2.0, 0);
// from the non-distorded, filter again on new average basis, but only if there are enough frames left
filterBadFrames(1.5, 100);
}
// 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 LCPProfile::filterBadFrames(double maxAvgDevFac, int minFramesLeft)
{
// take average error per type, then calculated the maximum deviation allowed
double errBase = 0, errChrom = 0, errVignette = 0;
int baseCount = 0, chromCount = 0, vignetteCount = 0;
for (int pm = 0; pm < MaxPersModelCount && aPersModel[pm]; pm++) {
if (aPersModel[pm]->hasModeData(0)) {
errVignette += aPersModel[pm]->vignette.mean_error;
vignetteCount++;
}
if (aPersModel[pm]->hasModeData(1)) {
errBase += aPersModel[pm]->base.mean_error;
baseCount++;
}
if (aPersModel[pm]->hasModeData(2)) {
errChrom += std::max(std::max(aPersModel[pm]->chromRG.mean_error, aPersModel[pm]->chromG.mean_error), aPersModel[pm]->chromBG.mean_error);
chromCount++;
}
}
// Only if we have enough frames, filter out errors
int filtered = 0;
if (baseCount + chromCount + vignetteCount >= minFramesLeft) {
if (baseCount > 0) {
errBase /= (double)baseCount;
}
if (chromCount > 0) {
errChrom /= (double)chromCount;
}
if (vignetteCount > 0) {
errVignette /= (double)vignetteCount;
}
// 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(0) && aPersModel[pm]->vignette.mean_error > maxAvgDevFac * errVignette) {
aPersModel[pm]->vignette.bad_error = true;
filtered++;
}
if (aPersModel[pm]->hasModeData(1) && aPersModel[pm]->base.mean_error > maxAvgDevFac * errBase) {
aPersModel[pm]->base.bad_error = true;
filtered++;
}
if (aPersModel[pm]->hasModeData(2) &&
(aPersModel[pm]->chromRG.mean_error > maxAvgDevFac * errChrom || aPersModel[pm]->chromG.mean_error > maxAvgDevFac * errChrom
|| aPersModel[pm]->chromBG.mean_error > maxAvgDevFac * errChrom)) {
aPersModel[pm]->chromRG.bad_error = aPersModel[pm]->chromG.bad_error = aPersModel[pm]->chromBG.bad_error = true;
filtered++;
}
}
//printf("Filtered %.1f%% frames for maxAvgDevFac %g leaving %i\n", filtered*100./(baseCount+chromCount+vignetteCount), maxAvgDevFac, baseCount+chromCount+vignetteCount-filtered);
}
return filtered;
}
// mode: 0=vignette, 1=distortion, 2=CA
void LCPProfile::calcParams(int mode, float focalLength, float focusDist, float aperture, LCPModelCommon *pCorr1, LCPModelCommon *pCorr2, LCPModelCommon *pCorr3) const
{
float euler = exp(1.0);
// find the frames with the least distance, focal length wise
LCPPersModel *pLow = nullptr, *pHigh = nullptr;
float focalLengthLog = log(focalLength); //, apertureLog=aperture>0 ? log(aperture) : 0;
float focusDistLog = focusDist > 0 ? 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++) {
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
float bestFocLenLow = pLow->focLen, bestFocLenHigh = pHigh->focLen;
for (int pm = 0; pm < persModelCount; pm++) {
float aper = aPersModel[pm]->aperture; // float aperLog=log(aper);
float focDist = aPersModel[pm]->focDist;
float focDistLog = log(focDist) + euler;
double meanErr;
if (aPersModel[pm]->hasModeData(mode)) {
if (mode == 0) {
meanErr = aPersModel[pm]->vignette.mean_error;
// by aperture (vignette), and max out focus distance
// tests showed doing this by log(aperture) is not as advisable
if (aPersModel[pm]->focLen == bestFocLenLow && (
(aper == aperture && pLow->vignette.mean_error > 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 && pHigh->vignette.mean_error > 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 {
meanErr = (mode == 1 ? aPersModel[pm]->base.mean_error : aPersModel[pm]->chromG.mean_error);
if (focusDist > 0) {
// by focus distance
if (aPersModel[pm]->focLen == bestFocLenLow && (
(focDist == focusDist && (mode == 1 ? pLow->base.mean_error : pLow->chromG.mean_error) > meanErr)
|| (focDist >= focusDist && focDist < pLow->focDist && pLow->focDist > focusDist)
|| (focDist <= focusDist && (pLow->focDist > focusDist || fabs(focusDistLog - focDistLog) < fabs(focusDistLog - (log(pLow->focDist) + euler)))))) {
pLow = aPersModel[pm];
}
if (aPersModel[pm]->focLen == bestFocLenHigh && (
(focDist == focusDist && (mode == 1 ? pHigh->base.mean_error : pHigh->chromG.mean_error) > meanErr)
|| (focDist <= focusDist && focDist > pHigh->focDist && pHigh->focDist < focusDist)
|| (focDist >= focusDist && (pHigh->focDist < focusDist || fabs(focusDistLog - focDistLog) < fabs(focusDistLog - (log(pHigh->focDist) + euler)))))) {
pHigh = aPersModel[pm];
}
} else {
// no focus distance available, just error
if (aPersModel[pm]->focLen == bestFocLenLow && (mode == 1 ? pLow->base.mean_error : pLow->chromG.mean_error) > meanErr) {
pLow = aPersModel[pm];
}
if (aPersModel[pm]->focLen == bestFocLenHigh && (mode == 1 ? pHigh->base.mean_error : pHigh->chromG.mean_error) > meanErr) {
pHigh = aPersModel[pm];
}
}
}
}
}
}
if (pLow != nullptr && pHigh != nullptr) {
// average out the factors, linear interpolation in logarithmic scale
float facLow = 0.5;
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 = (log(pHigh->focLen) - focalLengthLog) / (log(pHigh->focLen) - log(pLow->focLen));
} else {
focLenOnSpot = pLow->focLen == pHigh->focLen && pLow->focLen == focalLength;
}
// and average the other factor if available
if (mode == 0 && pLow->aperture < aperture && pHigh->aperture > aperture) {
// Mix in aperture
float facAperLow = (pHigh->aperture - aperture) / (pHigh->aperture - pLow->aperture);
facLow = focLenOnSpot ? facAperLow : (0.5 * facLow + 0.5 * facAperLow);
} else if (mode != 0 && focusDist > 0 && pLow->focDist < focusDist && pHigh->focDist > focusDist) {
// focus distance for all else (if focus distance is given)
float facDistLow = (log(pHigh->focDist) + euler - focusDistLog) / (log(pHigh->focDist) - log(pLow->focDist));
facLow = focLenOnSpot ? facDistLow : (0.8 * facLow + 0.2 * facDistLow);
}
switch (mode) {
case 0: // vignette
pCorr1->merge(pLow->vignette, pHigh->vignette, facLow);
break;
case 1: // distortion
pCorr1->merge(pLow->base, pHigh->base, facLow);
break;
case 2: // CA
pCorr1->merge(pLow->chromRG, pHigh->chromRG, facLow);
pCorr2->merge(pLow->chromG, pHigh->chromG, facLow);
pCorr3->merge(pLow->chromBG, pHigh->chromBG, facLow);
break;
}
//printf("LCP mode=%i, dist: %g found frames: Fno %g-%g; FocLen %g-%g; Dist %g-%g with weight %g\n", mode, focusDist, pLow->aperture, pHigh->aperture, pLow->focLen, pHigh->focLen, pLow->focDist, pHigh->focDist, facLow);
} else {
printf("Error: LCP file contained no %s parameters\n", mode == 0 ? "vignette" : mode == 1 ? "distortion" : "CA" );
}
}
void LCPProfile::print() const
{
printf("=== Profile %s\n", profileName.c_str());
printf("Frames: %i, RAW: %i; Fisheye: %i; Sensorformat: %f\n", persModelCount, isRaw, isFisheye, sensorFormatFactor);
for (int pm = 0; pm < persModelCount; pm++) {
aPersModel[pm]->print();
}
}
void XMLCALL LCPProfile::XmlStartHandler(void *pLCPProfile, const char *el, const char **attr)
{
LCPProfile *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 = const_cast<char*>(el);
} else {
src++;
}
strcpy(pProf->lastTag, src);
if (!strcmp("VignetteModelPiecewiseParam", src)) {
strcpy(pProf->inInvalidTag, src);
}
if (!strcmp("CameraProfiles", src)) {
pProf->inCamProfiles = true;
}
if (!strcmp("AlternateLensIDs", src)) {
pProf->inAlternateLensID = true;
}
if (!strcmp("AlternateLensNames", src)) {
pProf->inAlternateLensNames = true;
}
if (!pProf->inCamProfiles || pProf->inAlternateLensID || pProf->inAlternateLensNames) {
return;
}
if (!strcmp("li", src)) {
pProf->pCurPersModel = new LCPPersModel();
pProf->pCurCommon = &pProf->pCurPersModel->base; // iterated to next tags within persModel
return;
}
if (!strcmp("PerspectiveModel", src)) {
pProf->firstLIDone = true;
pProf->inPerspect = true;
return;
} else if (!strcmp("FisheyeModel", src)) {
pProf->firstLIDone = true;
pProf->inPerspect = true;
pProf->isFisheye = true; // just misses third param, and different path, rest is the same
return;
} else if (!strcmp("Description", src)) {
parseAttr = true;
}
// Move pointer to general section
if (pProf->inPerspect) {
if (!strcmp("ChromaticRedGreenModel", src)) {
pProf->pCurCommon = &pProf->pCurPersModel->chromRG;
parseAttr = true;
} else if (!strcmp("ChromaticGreenModel", src)) {
pProf->pCurCommon = &pProf->pCurPersModel->chromG;
parseAttr = true;
} else if (!strcmp("ChromaticBlueGreenModel", src)) {
pProf->pCurCommon = &pProf->pCurPersModel->chromBG;
parseAttr = true;
} else if (!strcmp("VignetteModel", src)) {
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 = const_cast<char*>(attr[i]);
} else {
nameStart++;
}
strcpy(pProf->lastTag, nameStart);
pProf->handle_text(attr[i+1]);
//XmlTextHandler(pLCPProfile, attr[i + 1], strlen(attr[i + 1]));
}
}
}
void XMLCALL LCPProfile::XmlTextHandler(void *pLCPProfile, const XML_Char *s, int len)
{
LCPProfile *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 LCPProfile::handle_text(std::string text)
{
const char *raw = text.c_str();
// Check if it contains non-whitespaces (there are several calls to this for one tag unfortunately)
bool onlyWhiteSpace = true;
for (size_t i = 0; i < text.size(); ++i) {
if (!isspace(text[i])) {
onlyWhiteSpace = false;
break;
}
}
if (onlyWhiteSpace) {
return;
}
LCPProfile *pProf = this;
// convert to null terminated
char* tag = pProf->lastTag;
// Common data section
if (!pProf->firstLIDone) {
// Generic tags are the same for all
if (!strcmp("ProfileName", tag)) {
pProf->profileName = Glib::ustring(raw);
} else if (!strcmp("Model", tag)) {
pProf->camera = Glib::ustring(raw);
} else if (!strcmp("Lens", tag)) {
pProf->lens = Glib::ustring(raw);
} else if (!strcmp("CameraPrettyName", tag)) {
pProf->cameraPrettyName = Glib::ustring(raw);
} else if (!strcmp("LensPrettyName", tag)) {
pProf->lensPrettyName = Glib::ustring(raw);
} else if (!strcmp("CameraRawProfile", tag)) {
pProf->isRaw = !strcmp("True", raw);
}
}
// --- Now all floating points. Must replace local dot characters
// WARNING: called by different threads, that may run on different local settings,
// so don't use system params
if (atof("1,2345") == 1.2345) {
for (size_t i = 0; i < text.size(); ++i) {
if (text[i] == '.') {
text[i] = ',';
}
}
raw = text.c_str();
}
if (!pProf->firstLIDone) {
if (!strcmp("SensorFormatFactor", tag)) {
pProf->sensorFormatFactor = atof(raw);
}
}
// Perspective model base data
if (!strcmp("FocalLength", tag)) {
pProf->pCurPersModel->focLen = atof(raw);
} else if (!strcmp("FocusDistance", tag)) {
double focDist = atof(raw);
pProf->pCurPersModel->focDist = focDist < 10000 ? focDist : 10000;
} else if (!strcmp("ApertureValue", tag)) {
pProf->pCurPersModel->aperture = atof(raw);
}
// Section depended
if (!strcmp("FocalLengthX", tag)) {
pProf->pCurCommon->foc_len_x = atof(raw);
} else if (!strcmp("FocalLengthY", tag)) {
pProf->pCurCommon->foc_len_y = atof(raw);
} else if (!strcmp("ImageXCenter", tag)) {
pProf->pCurCommon->img_center_x = atof(raw);
} else if (!strcmp("ImageYCenter", tag)) {
pProf->pCurCommon->img_center_y = atof(raw);
} else if (!strcmp("ScaleFactor", tag)) {
pProf->pCurCommon->scale_factor = atof(raw);
} else if (!strcmp("ResidualMeanError", tag)) {
pProf->pCurCommon->mean_error = atof(raw);
} else if (!strcmp("RadialDistortParam1", tag) || !strcmp("VignetteModelParam1", tag)) {
pProf->pCurCommon->param[0] = atof(raw);
} else if (!strcmp("RadialDistortParam2", tag) || !strcmp("VignetteModelParam2", tag)) {
pProf->pCurCommon->param[1] = atof(raw);
} else if (!strcmp("RadialDistortParam3", tag) || !strcmp("VignetteModelParam3", tag)) {
pProf->pCurCommon->param[2] = atof(raw);
} else if (!strcmp("RadialDistortParam4", tag) || !strcmp("TangentialDistortParam1", tag)) {
pProf->pCurCommon->param[3] = atof(raw);
} else if (!strcmp("RadialDistortParam5", tag) || !strcmp("TangentialDistortParam2", tag)) {
pProf->pCurCommon->param[4] = atof(raw);
}
}
void XMLCALL LCPProfile::XmlEndHandler(void *pLCPProfile, const char *el)
{
LCPProfile *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 (strstr(el, pProf->inInvalidTag)) {
*pProf->inInvalidTag = 0;
}
return;
}
if (strstr(el, ":CameraProfiles")) {
pProf->inCamProfiles = false;
}
if (strstr(el, ":AlternateLensIDs")) {
pProf->inAlternateLensID = false;
}
if (strstr(el, ":AlternateLensNames")) {
pProf->inAlternateLensNames = false;
}
if (!pProf->inCamProfiles || pProf->inAlternateLensID || pProf->inAlternateLensNames) {
return;
}
if (strstr(el, ":PerspectiveModel") || strstr(el, ":FisheyeModel")) {
pProf->inPerspect = false;
} else if (strstr(el, ":li")) {
pProf->aPersModel[pProf->persModelCount] = pProf->pCurPersModel;
pProf->pCurPersModel = nullptr;
pProf->persModelCount++;
}
}
// Generates as singleton
LCPStore* LCPStore::getInstance()
{
static LCPStore instance_;
return &instance_;
}
LCPProfile* LCPStore::getProfile (Glib::ustring filename)
{
if (filename.length() == 0 || !isValidLCPFileName(filename)) {
return nullptr;
}
MyMutex::MyLock lock(mtx);
std::map<Glib::ustring, LCPProfile*>::iterator r = profileCache.find (filename);
if (r != profileCache.end()) {
return r->second;
}
// Add profile (if exists)
profileCache[filename] = new LCPProfile(filename);
//profileCache[filename]->print();
return profileCache[filename];
}
bool LCPStore::isValidLCPFileName(Glib::ustring filename) const
{
if (!Glib::file_test (filename, Glib::FILE_TEST_EXISTS) || Glib::file_test (filename, Glib::FILE_TEST_IS_DIR)) {
return false;
}
size_t pos = filename.find_last_of ('.');
return pos > 0 && !filename.casefold().compare (pos, 4, ".lcp");
}
Glib::ustring 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;
}