682 lines
28 KiB
C++
682 lines
28 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 <cstring>
|
|
|
|
#include "lcp.h"
|
|
#include "safegtk.h"
|
|
#include "iccmatrices.h"
|
|
#include "iccstore.h"
|
|
#include "rawimagesource.h"
|
|
#include "improcfun.h"
|
|
#include "rt_math.h"
|
|
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
// for GCC32
|
|
#ifndef _WIN32_IE
|
|
#define _WIN32_IE 0x0600
|
|
#endif
|
|
#include <shlobj.h>
|
|
#endif
|
|
|
|
|
|
using namespace std;
|
|
using namespace rtengine;
|
|
using namespace rtexif;
|
|
|
|
|
|
LCPModelCommon::LCPModelCommon() {
|
|
focLenX=focLenY=-1; imgXCenter=imgYCenter=0.5;
|
|
x0=y0=fx=fy=meanErr=0; badErr=false;
|
|
for (int i=0;i<5;i++) param[i]=0;
|
|
scaleFac=1;
|
|
}
|
|
|
|
bool LCPModelCommon::empty() const {
|
|
return param[0]==0 && param[1]==0 && param[2]==0;
|
|
}
|
|
|
|
void LCPModelCommon::print() const {
|
|
printf("focLen %g/%g; imgCenter %g/%g; scale %g; err %g\n",focLenX,focLenY,imgXCenter,imgYCenter,scaleFac,meanErr);
|
|
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]);
|
|
}
|
|
|
|
// weightened merge two parameters
|
|
void LCPModelCommon::merge(const LCPModelCommon& a, const LCPModelCommon& b, float facA) {
|
|
float facB=1-facA;
|
|
|
|
focLenX = facA * a.focLenX + facB * b.focLenX;
|
|
focLenY = facA * a.focLenY + facB * b.focLenY;
|
|
imgXCenter = facA * a.imgXCenter + facB * b.imgXCenter;
|
|
imgYCenter = facA * a.imgYCenter + facB * b.imgYCenter;
|
|
scaleFac = facA * a.scaleFac + facB * b.scaleFac;
|
|
meanErr = facA * a.meanErr + facB * b.meanErr;
|
|
|
|
for (int i=0;i<5;i++) param[i]= facA * a.param[i] + facB * b.param[i];
|
|
}
|
|
|
|
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
|
|
int Dmax=fullWidth; if (fullHeight>fullWidth) Dmax=fullHeight;
|
|
|
|
// correct focLens
|
|
if (focLenX<0) { // they may not be given
|
|
// and 35mm may not be given either
|
|
if (focalLength35mm<1) focalLength35mm = focalLength*sensorFormatFactor;
|
|
|
|
focLenX=focLenY=focalLength / ( 35*focalLength/focalLength35mm); // focLen must be calculated in pixels
|
|
}
|
|
|
|
if (swapXY) {
|
|
x0 = (mirrorX ? 1.-imgYCenter : imgYCenter) * fullWidth;
|
|
y0 = (mirrorY ? 1.-imgXCenter : imgXCenter) * fullHeight;
|
|
fx = focLenY * Dmax;
|
|
fy = focLenX * Dmax;
|
|
} else {
|
|
x0 = (mirrorX ? 1.-imgXCenter : imgXCenter) * fullWidth;
|
|
y0 = (mirrorY ? 1.-imgYCenter : imgYCenter) * fullHeight;
|
|
fx = focLenX * Dmax;
|
|
fy = focLenY * Dmax;
|
|
}
|
|
//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.badErr) || (mode==1 && !base.empty() && !base.badErr)
|
|
|| (mode==2 && !chromRG.empty() && !chromG.empty() && !chromBG.empty() &&
|
|
!chromRG.badErr && !chromG.badErr && !chromBG.badErr);
|
|
}
|
|
|
|
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==NULL) 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, NULL, NULL);
|
|
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;
|
|
}
|
|
|
|
void LCPMapper::correctDistortion(double& x, double& y) const {
|
|
double xd=(x-mc.x0)/mc.fx, yd=(y-mc.y0)/mc.fy;
|
|
|
|
const float* 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 + mc.x0;
|
|
y = ynew * mc.fy + mc.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 float* 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 float* 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].scaleFac * ( xd * commonSum + xfac*rsqr )) * chrom[channel].fx + chrom[channel].x0;
|
|
y = (chrom[channel].scaleFac * ( yd * commonSum + yfac*rsqr )) * chrom[channel].fy + chrom[channel].y0;
|
|
}
|
|
}
|
|
|
|
float LCPMapper::calcVignetteFac(int x, int y) const {
|
|
// No need for swapXY, since vignette is in RAW and always before rotation
|
|
double xd=((double)x-mc.x0)/mc.fx, yd=((double)y-mc.y0)/mc.fy;
|
|
|
|
const float* aVig= mc.param;
|
|
double rsqr = xd*xd+yd*yd;
|
|
double param0Sqr = aVig[0]*aVig[0];
|
|
|
|
return 1. + rsqr * (-aVig[0] + rsqr * ((param0Sqr - aVig[1])
|
|
- (param0Sqr*aVig[0] - 2.*aVig[0]*aVig[1] + aVig[2]) *rsqr
|
|
+ (param0Sqr*param0Sqr + aVig[1]*aVig[1]
|
|
+ 2.*aVig[0]*aVig[2] - 3.*param0Sqr*aVig[1]) *rsqr*rsqr));
|
|
}
|
|
|
|
LCPProfile::LCPProfile(Glib::ustring fname) {
|
|
const int BufferSize=8192;
|
|
char buf[BufferSize];
|
|
|
|
XML_Parser parser = XML_ParserCreate(NULL);
|
|
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]=NULL;
|
|
persModelCount=0; *inInvalidTag=0;
|
|
|
|
FILE *pFile = safe_g_fopen(fname, "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.meanErr;
|
|
vignetteCount++;
|
|
}
|
|
|
|
if (aPersModel[pm]->hasModeData(1)) {
|
|
errBase+=aPersModel[pm]->base.meanErr;
|
|
baseCount++;
|
|
}
|
|
|
|
if (aPersModel[pm]->hasModeData(2)) {
|
|
errChrom+=std::max(std::max(aPersModel[pm]->chromRG.meanErr,aPersModel[pm]->chromG.meanErr),aPersModel[pm]->chromBG.meanErr);
|
|
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.meanErr > maxAvgDevFac * errVignette) {
|
|
aPersModel[pm]->vignette.badErr=true;
|
|
filtered++;
|
|
}
|
|
|
|
if (aPersModel[pm]->hasModeData(1) && aPersModel[pm]->base.meanErr > maxAvgDevFac * errBase) {
|
|
aPersModel[pm]->base.badErr=true;
|
|
filtered++;
|
|
}
|
|
|
|
if (aPersModel[pm]->hasModeData(2) &&
|
|
(aPersModel[pm]->chromRG.meanErr > maxAvgDevFac * errChrom || aPersModel[pm]->chromG.meanErr > maxAvgDevFac * errChrom
|
|
|| aPersModel[pm]->chromBG.meanErr > maxAvgDevFac * errChrom)) {
|
|
aPersModel[pm]->chromRG.badErr=aPersModel[pm]->chromG.badErr=aPersModel[pm]->chromBG.badErr=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=NULL, *pHigh=NULL;
|
|
|
|
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==NULL || f > pLow->focLen || (focusDist==0 && f==pLow->focLen && pLow->focDist>aPersModel[pm]->focDist))) {
|
|
pLow=aPersModel[pm];
|
|
}
|
|
if (f >= focalLength && (pHigh==NULL || 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.meanErr;
|
|
|
|
// 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.meanErr>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.meanErr>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.meanErr : aPersModel[pm]->chromG.meanErr);
|
|
|
|
if (focusDist>0) {
|
|
// by focus distance
|
|
if (aPersModel[pm]->focLen==bestFocLenLow && (
|
|
(focDist==focusDist && (mode==1 ? pLow->base.meanErr : pLow->chromG.meanErr)>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.meanErr : pHigh->chromG.meanErr)>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.meanErr : pLow->chromG.meanErr)>meanErr) {
|
|
pLow=aPersModel[pm];
|
|
}
|
|
if (aPersModel[pm]->focLen==bestFocLenHigh && (mode==1 ? pHigh->base.meanErr : pHigh->chromG.meanErr)>meanErr) {
|
|
pHigh=aPersModel[pm];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pLow!=NULL && pHigh!=NULL) {
|
|
// 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==NULL) 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!=NULL) {
|
|
for (int i = 0; attr[i]; i += 2) {
|
|
const char* nameStart=strrchr(attr[i],':');
|
|
if (nameStart==NULL) nameStart=const_cast<char*>(attr[i]); else nameStart++;
|
|
|
|
strcpy(pProf->lastTag, nameStart);
|
|
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;
|
|
|
|
// Check if it contains non-whitespaces (there are several calls to this for one tag unfortunately)
|
|
bool onlyWhiteSpace=true;
|
|
int i=0;
|
|
while (i<len && onlyWhiteSpace) { onlyWhiteSpace=isspace(s[i]); i++; }
|
|
if (onlyWhiteSpace) return;
|
|
|
|
// convert to null terminated
|
|
char raw[len+1]; memcpy(raw,s,len); raw[len]=0;
|
|
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) {
|
|
char* p=raw;
|
|
while (*p) {
|
|
if (*p=='.') *p=',';
|
|
p++;
|
|
}
|
|
}
|
|
|
|
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->focLenX=atof(raw);
|
|
else if (!strcmp("FocalLengthY",tag))
|
|
pProf->pCurCommon->focLenY=atof(raw);
|
|
else if (!strcmp("ImageXCenter",tag))
|
|
pProf->pCurCommon->imgXCenter=atof(raw);
|
|
else if (!strcmp("ImageYCenter",tag))
|
|
pProf->pCurCommon->imgYCenter=atof(raw);
|
|
else if (!strcmp("ScaleFactor",tag))
|
|
pProf->pCurCommon->scaleFac=atof(raw);
|
|
else if (!strcmp("ResidualMeanError",tag))
|
|
pProf->pCurCommon->meanErr=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);
|
|
|
|
// 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=NULL;
|
|
pProf->persModelCount++;
|
|
}
|
|
}
|
|
|
|
// Generates as singleton
|
|
LCPStore* LCPStore::getInstance()
|
|
{
|
|
static LCPStore* instance_ = 0;
|
|
if ( instance_ == 0 )
|
|
{
|
|
static MyMutex smutex_;
|
|
MyMutex::MyLock lock(smutex_);
|
|
if ( instance_ == 0 )
|
|
{
|
|
instance_ = new LCPStore();
|
|
}
|
|
}
|
|
return instance_;
|
|
}
|
|
|
|
LCPProfile* LCPStore::getProfile (Glib::ustring filename) {
|
|
if (filename.length()==0 || !isValidLCPFileName(filename)) return NULL;
|
|
|
|
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 (!safe_file_test (filename, Glib::FILE_TEST_EXISTS) || safe_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}; char pathA[MAX_PATH];
|
|
|
|
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 (safe_file_test(fullDir, Glib::FILE_TEST_IS_DIR)) dir=fullDir;
|
|
}
|
|
#endif
|
|
|
|
// TODO: Add Mac paths here
|
|
|
|
return dir;
|
|
}
|