diff --git a/rtengine/clutstore.cc b/rtengine/clutstore.cc index ba117a2fd..5731773a4 100644 --- a/rtengine/clutstore.cc +++ b/rtengine/clutstore.cc @@ -305,7 +305,7 @@ rtengine::CLUTStore& rtengine::CLUTStore::getInstance() return instance; } -std::shared_ptr rtengine::CLUTStore::getClut(const Glib::ustring& filename) +std::shared_ptr rtengine::CLUTStore::getClut(const Glib::ustring& filename) const { std::shared_ptr result; diff --git a/rtengine/clutstore.h b/rtengine/clutstore.h index 5e4930fa1..a43526f78 100644 --- a/rtengine/clutstore.h +++ b/rtengine/clutstore.h @@ -57,14 +57,14 @@ class CLUTStore final : public: static CLUTStore& getInstance(); - std::shared_ptr getClut(const Glib::ustring& filename); + std::shared_ptr getClut(const Glib::ustring& filename) const; void clearCache(); private: CLUTStore(); - Cache> cache; + mutable Cache> cache; }; } diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index d5032cd46..9bbd9b48b 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -1121,7 +1121,7 @@ void ImProcCoordinator::getAutoCrop (double ratio, int &x, int &y, int &w, int & LCPMapper *pLCPMap = nullptr; if (params.lensProf.lcpFile.length() && imgsrc->getMetaData()->getFocalLen() > 0) { - LCPProfile *pLCPProf = lcpStore->getProfile (params.lensProf.lcpFile); + const std::shared_ptr pLCPProf = LCPStore::getInstance()->getProfile (params.lensProf.lcpFile); if (pLCPProf) pLCPMap = new LCPMapper (pLCPProf, imgsrc->getMetaData()->getFocalLen(), imgsrc->getMetaData()->getFocalLen35mm(), imgsrc->getMetaData()->getFocusDist(), 0, false, params.lensProf.useDist, fullw, fullh, params.coarse, imgsrc->getRotateDegree()); diff --git a/rtengine/iptransform.cc b/rtengine/iptransform.cc index ee14c80c2..c46176365 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -313,7 +313,7 @@ void ImProcFunctions::transform (Imagefloat* original, Imagefloat* transformed, LCPMapper *pLCPMap = nullptr; if (needsLCP()) { // don't check focal length to allow distortion correction for lenses without chip - LCPProfile *pLCPProf = lcpStore->getProfile (params->lensProf.lcpFile); + const std::shared_ptr pLCPProf = LCPStore::getInstance()->getProfile (params->lensProf.lcpFile); if (pLCPProf) { pLCPMap = new LCPMapper (pLCPProf, focalLen, focalLen35mm, @@ -784,7 +784,7 @@ void ImProcFunctions::transformHighQuality (Imagefloat* original, Imagefloat* tr double ascale = params->commonTrans.autofill ? getTransformAutoFill (oW, oH, true /*fullImage*/ ? pLCPMap : nullptr) : 1.0; // smaller crop images are a problem, so only when processing fully - bool enableLCPCA = pLCPMap && params->lensProf.useCA && fullImage && pLCPMap->enableCA; + bool enableLCPCA = pLCPMap && params->lensProf.useCA && fullImage && pLCPMap->isCACorrectionAvailable(); bool enableLCPDist = pLCPMap && params->lensProf.useDist; // && fullImage; if (enableLCPCA) { diff --git a/rtengine/lcp.cc b/rtengine/lcp.cc index 048ba9dcf..d95d3a32e 100644 --- a/rtengine/lcp.cc +++ b/rtengine/lcp.cc @@ -20,27 +20,43 @@ #include #include -#include "lcp.h" #include #ifdef WIN32 -#include #include +#include #endif +#include "lcp.h" + #include "settings.h" -using namespace std; -using namespace rtengine; - - -namespace rtengine { +namespace rtengine +{ extern const Settings* settings; } -LCPModelCommon::LCPModelCommon() : +class rtengine::LCPProfile::LCPPersModel +{ +public: + LCPPersModel(); + bool hasModeData(LCPCorrectionMode mode) const; + void print() 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), @@ -59,20 +75,23 @@ LCPModelCommon::LCPModelCommon() : { } -bool LCPModelCommon::empty() const +bool rtengine::LCPModelCommon::empty() const { - return param[0] == 0.0f && param[1] == 0.0f && param[2] == 0.0f; + return + param[0] == 0.0f + && param[1] == 0.0f + && param[2] == 0.0f; } -void LCPModelCommon::print() const +void rtengine::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]); + std::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); + std::printf("xy0 %g/%g fxy %g/%g\n", x0, y0, fx, fy); + std::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) +void rtengine::LCPModelCommon::merge(const LCPModelCommon& a, const LCPModelCommon& b, float facA) { const float facB = 1.0f - facA; @@ -83,7 +102,7 @@ void LCPModelCommon::merge(const LCPModelCommon& a, const LCPModelCommon& b, flo 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++) { + for (int i = 0; i < 5; ++i) { param[i] = facA * a.param[i] + facB * b.param[i]; } @@ -96,7 +115,16 @@ void LCPModelCommon::merge(const LCPModelCommon& a, const LCPModelCommon& b, flo } -void LCPModelCommon::prepareParams(int fullWidth, int fullHeight, float focalLength, float focalLength35mm, float sensorFormatFactor, bool swapXY, bool mirrorX, bool mirrorY) +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); @@ -125,69 +153,842 @@ void LCPModelCommon::prepareParams(int fullWidth, int fullHeight, float focalLen rfx = 1.0f / fx; rfy = 1.0f / fy; - //printf("FW %i /X0 %g FH %i /Y0 %g %g\n",fullWidth,x0,fullHeight,y0, imgYCenter); + //std::printf("FW %i /X0 %g FH %i /Y0 %g %g\n",fullWidth,x0,fullHeight,y0, imgYCenter); } -LCPPersModel::LCPPersModel() +rtengine::LCPProfile::LCPPersModel::LCPPersModel() : + focLen(0.f), + focDist(0.f), + aperture(0.f) { - focLen = focDist = aperture = 0; } -// mode: 0=distortion, 1=vignette, 2=CA -bool LCPPersModel::hasModeData(LCPCorrectionMode mode) const +bool rtengine::LCPProfile::LCPPersModel::hasModeData(LCPCorrectionMode mode) const { switch (mode) { - case LCP_MODE_VIGNETTE: - return !vignette.empty() && !vignette.bad_error; - case LCP_MODE_DISTORTION: - return !base.empty() && !base.bad_error; - case LCP_MODE_CA: - return !chromRG.empty() && !chromG.empty() && !chromBG.empty() && - !chromRG.bad_error && !chromG.bad_error && !chromBG.bad_error; - default: - assert(false); - return false; + 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; } -void LCPPersModel::print() const +void rtengine::LCPProfile::LCPPersModel::print() const { - printf("--- PersModel focLen %g; focDist %g; aperture %g\n", focLen, focDist, aperture); - printf("Base:\n"); + std::printf("--- PersModel focLen %g; focDist %g; aperture %g\n", focLen, focDist, aperture); + std::printf("Base:\n"); base.print(); if (!chromRG.empty()) { - printf("ChromRG:\n"); + std::printf("ChromRG:\n"); chromRG.print(); } if (!chromG.empty()) { - printf("ChromG:\n"); + std::printf("ChromG:\n"); chromG.print(); } if (!chromBG.empty()) { - printf("ChromBG:\n"); + std::printf("ChromBG:\n"); chromBG.print(); } if (!vignette.empty()) { - printf("Vignette:\n"); + std::printf("Vignette:\n"); vignette.print(); } - printf("\n"); + std::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) :useCADist(false), swapXY(false), isFisheye(false), enableCA(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{} { - if (pProf == nullptr) { + 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, static_cast(this)); + + FILE* const pFile = g_fopen(fname.c_str (), "rb"); + + if (pFile) { + 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 "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; + + double meanErr; + + if (aPersModel[pm]->hasModeData(mode)) { + 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.5 * facLow + 0.5 * 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.8 * facLow + 0.2 * 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), focusDist, pLow->aperture, pHigh->aperture, pLow->focLen, pHigh->focLen, pLow->focDist, pHigh->focDist, facLow); + } + } else { + if (settings->verbose) { + std::printf("Error: LCP file contained no %s parameters\n", mode == LCPCorrectionMode::VIGNETTE ? "vignette" : mode == LCPCorrectionMode::DISTORTION ? "distortion" : "CA" ); + } + } +} + +void rtengine::LCPProfile::print() const +{ + std::printf("=== Profile %s\n", profileName.c_str()); + std::printf("Frames: %i, RAW: %i; Fisheye: %i; Sensorformat: %f\n", persModelCount, isRaw, isFisheye, sensorFormatFactor); + + for (int pm = 0; pm < persModelCount; ++pm) { + aPersModel[pm]->print(); + } +} + +// 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) { + std::printf("Filtered %.1f%% frames for maxAvgDevFac %g leaving %i\n", filtered *100.f / 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; } - useCADist = useCADistP; + 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(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; + } + + strcpy(pProf->lastTag, src); + + const std::string src_str = src; + + if (src_str == "VignetteModelPiecewiseParam") { + strcpy(pProf->inInvalidTag, src); + } + + 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; + return; + } 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 + return; + } 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(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(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::LCPStore::getProfile(const Glib::ustring& filename) const +{ + if (filename.length() == 0 || !isValidLCPFileName(filename)) { + return nullptr; + } + + std::shared_ptr res; + if (!cache.get(filename, res)) { + res.reset(new LCPProfile(filename)); + 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& pProf, + float focalLength, + float focalLength35mm, + float focusDist, + float aperture, + bool vignette, + bool useCADistP, + int fullWidth, + int fullHeight, + const 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 @@ -197,84 +998,91 @@ LCPMapper::LCPMapper(LCPProfile* pProf, float focalLength, float focalLength35mm rot = (coarse.rotate + rawRotationDeg) % 360; } - swapXY = (rot == 90 || rot == 270); - bool mirrorX = (rot == 90 || rot == 180); - bool mirrorY = (rot == 180 || rot == 270); + swapXY = (rot == 90 || rot == 270); + + const bool mirrorX = (rot == 90 || rot == 180); + const bool mirrorY = (rot == 180 || rot == 270); if (settings->verbose) { - 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); + std::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 ? LCP_MODE_VIGNETTE : LCP_MODE_DISTORTION, focalLength, focusDist, aperture, &mc, nullptr, nullptr); + 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(LCP_MODE_CA, focalLength, focusDist, aperture, &chrom[0], &chrom[1], &chrom[2]); + pProf->calcParams(LCPCorrectionMode::CA, focalLength, focusDist, aperture, &chrom[0], &chrom[1], &chrom[2]); - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 3; ++i) { chrom[i].prepareParams(fullWidth, fullHeight, focalLength, focalLength35mm, pProf->sensorFormatFactor, swapXY, mirrorX, mirrorY); } } - enableCA = !vignette && focusDist > 0; + enableCA = !vignette && focusDist > 0.f; isFisheye = pProf->isFisheye; } -void LCPMapper::correctDistortion(double& x, double& y, double scale) const +bool rtengine::LCPMapper::isCACorrectionAvailable() const +{ + return enableCA; +} + +void rtengine::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; + const double u = x * scale; + const double v = y * scale; + const double u0 = mc.x0 * scale; + const double v0 = mc.y0 * scale; + 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 / (scale * scale)); + 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 { 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 double x0 = mc.x0 * scale; + const double y0 = mc.y0 * scale; + const 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]; + const double rsqr = xd * xd + yd * yd; + const double xfac = aDist[swapXY ? 3 : 4], yfac = aDist[swapXY ? 4 : 3]; - double commonFac = (((aDist[2] * rsqr + aDist[1]) * rsqr + aDist[0]) * rsqr + 1.) + const 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; + const double xnew = xd * commonFac + xfac * rsqr; + const 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 +void rtengine::LCPMapper::correctCA(double& x, double& y, int channel) const { if (!enableCA) { return; } - double rsqr, xgreen, ygreen; + double 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; + double xd = (x - chrom[1].x0) / chrom[1].fx; + double yd = (y - chrom[1].y0) / chrom[1].fy; // Green contains main distortion, just like base if (useCADist) { @@ -300,18 +1108,18 @@ void LCPMapper::correctCA(double& x, double& y, int channel) const // others are diffs from green xd = xgreen; yd = ygreen; - rsqr = xd * xd + yd * yd; + const double 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); + const double xfac = aCA[swapXY ? 3 : 4], yfac = aCA[swapXY ? 4 : 3]; + const 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 +SSEFUNCTION 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; @@ -330,9 +1138,9 @@ SSEFUNCTION void LCPMapper::processVignetteLine(int width, int y, float *line) c 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)); + 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); @@ -341,24 +1149,24 @@ SSEFUNCTION void LCPMapper::processVignetteLine(int width, int y, float *line) c #endif // __SSE2__ for (; x < width; x++) { if (line[x] > 0) { - float xd = ((float)x - mc.x0) * mc.rfx; + const float xd = ((float)x - mc.x0) * mc.rfx; const LCPModelCommon::VignParam vignParam = mc.vign_param; - float rsqr = xd * xd + yd; + const 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 +SSEFUNCTION 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++) { - 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)); + 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; @@ -366,623 +1174,3 @@ SSEFUNCTION void LCPMapper::processVignetteLine3Channels(int width, int y, float } } } - - -LCPProfile::LCPProfile(const Glib::ustring &fname) -{ - for (int i = 0; i < MaxPersModelCount; i++) { - aPersModel[i] = nullptr; - } - pCurPersModel = nullptr; - - 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; - - persModelCount = 0; - *inInvalidTag = 0; - - FILE *pFile = g_fopen(fname.c_str (), "rb"); - - if(pFile) { - 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); - - if (settings->verbose) { - 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(LCP_MODE_VIGNETTE, 2.0, 0); - filterBadFrames(LCP_MODE_CA, 2.0, 0); - // from the non-distorded, filter again on new average basis, but only if there are enough frames left - filterBadFrames(LCP_MODE_VIGNETTE, 1.5, 50); - filterBadFrames(LCP_MODE_CA, 1.5, 50); -} - - -LCPProfile::~LCPProfile() -{ - if (pCurPersModel) { - delete pCurPersModel; - } - for (int i = 0; i < MaxPersModelCount; i++) { - if (aPersModel[i]) { - delete aPersModel[i]; - } - } -} - -// 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(LCPCorrectionMode mode, double maxAvgDevFac, int minFramesLeft) -{ - // take average error, then calculated the maximum deviation allowed - double err = 0; - int count = 0; - - for (int pm = 0; pm < MaxPersModelCount && aPersModel[pm]; pm++) { - if (aPersModel[pm]->hasModeData(mode)) { - count++; - switch (mode) { - case LCP_MODE_VIGNETTE: - err += aPersModel[pm]->vignette.mean_error; - break; - case LCP_MODE_DISTORTION: - err += aPersModel[pm]->base.mean_error; - break; - case LCP_MODE_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 /= (double)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 LCP_MODE_VIGNETTE: - if (aPersModel[pm]->vignette.mean_error > maxAvgDevFac * err) { - aPersModel[pm]->vignette.bad_error = true; - filtered++; - } - break; - case LCP_MODE_DISTORTION: - if (aPersModel[pm]->base.mean_error > maxAvgDevFac * err) { - aPersModel[pm]->base.bad_error = true; - filtered++; - } - break; - case LCP_MODE_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 = aPersModel[pm]->chromG.bad_error = aPersModel[pm]->chromBG.bad_error = true; - filtered++; - } - break; - } - } - } - - if (settings->verbose) { - printf("Filtered %.1f%% frames for maxAvgDevFac %g leaving %i\n", filtered*100./count, maxAvgDevFac, count-filtered); - } - } - - return filtered; -} - - -void LCPProfile::calcParams(LCPCorrectionMode 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)) { - double lowMeanErr, highMeanErr; - switch (mode) { - case LCP_MODE_VIGNETTE: - meanErr = aPersModel[pm]->vignette.mean_error; - lowMeanErr = pLow->vignette.mean_error; - highMeanErr = pHigh->vignette.mean_error; - break; - case LCP_MODE_DISTORTION: - meanErr = aPersModel[pm]->base.mean_error; - lowMeanErr = pLow->base.mean_error; - highMeanErr = pHigh->base.mean_error; - break; - default: // LCP_MODE_CA - meanErr = aPersModel[pm]->chromG.mean_error; - lowMeanErr = pLow->chromG.mean_error; - highMeanErr = pHigh->chromG.mean_error; - break; - } - - if (aperture > 0 && mode != LCP_MODE_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 != LCP_MODE_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 - (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 - (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.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 == LCP_MODE_VIGNETTE && 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 != LCP_MODE_VIGNETTE && 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 LCP_MODE_VIGNETTE: - pCorr1->merge(pLow->vignette, pHigh->vignette, facLow); - break; - - case LCP_MODE_DISTORTION: - pCorr1->merge(pLow->base, pHigh->base, facLow); - break; - - case LCP_MODE_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) { - 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 { - if (settings->verbose) { - printf("Error: LCP file contained no %s parameters\n", mode == LCP_MODE_VIGNETTE ? "vignette" : mode == LCP_MODE_DISTORTION ? "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(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(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(attr[i]); - } else { - nameStart++; - } - - strncpy(pProf->lastTag, nameStart, 255); - - 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(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) -{ - // 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; - - const char* raw = text.c_str(); - - // 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(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_; -} - - -LCPStore::~LCPStore() -{ - for (auto &p : profileCache) { - delete p.second; - } -} - - -LCPProfile* LCPStore::getProfile (Glib::ustring filename) -{ - if (filename.length() == 0 || !isValidLCPFileName(filename)) { - return nullptr; - } - - MyMutex::MyLock lock(mtx); - - std::map::iterator r = profileCache.find (filename); - - if (r != profileCache.end()) { - return r->second; - } - - // Add profile (if exists) - profileCache[filename] = new LCPProfile(filename); - if (settings->verbose) { - 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; -} diff --git a/rtengine/lcp.h b/rtengine/lcp.h index 291710c5f..cbeff3259 100644 --- a/rtengine/lcp.h +++ b/rtengine/lcp.h @@ -21,22 +21,24 @@ #include #include +#include #include #include #include #include +#include "cache.h" #include "imagefloat.h" #include "opthelper.h" namespace rtengine { -enum LCPCorrectionMode { - LCP_MODE_VIGNETTE = 0, - LCP_MODE_DISTORTION = 1, - LCP_MODE_CA = 2 +enum class LCPCorrectionMode { + VIGNETTE, + DISTORTION, + CA }; // Perspective model common data, also used for Vignette and Fisheye @@ -44,10 +46,20 @@ class LCPModelCommon final { public: LCPModelCommon(); + bool empty() const; // is it empty void print() const; // printf all values void merge(const LCPModelCommon& a, const LCPModelCommon& b, float facA); - void prepareParams(int fullWidth, int fullHeight, float focalLength, float focalLength35mm, float sensorFormatFactor, bool swapXY, bool mirrorX, bool mirrorY); + void prepareParams( + int fullWidth, + int fullHeight, + float focalLength, + float focalLength35mm, + float sensorFormatFactor, + bool swapXY, + bool mirrorX, + bool mirrorY + ); //private: using Param = std::array; @@ -72,97 +84,114 @@ public: VignParam vign_param; }; -class LCPPersModel -{ -public: - float focLen, focDist, aperture; // this is what it refers to - - LCPModelCommon base; // base perspective correction - LCPModelCommon chromRG, chromG, chromBG; // red/green, green, blue/green (may be empty) - LCPModelCommon vignette; // vignette (may be empty) - - LCPPersModel(); - bool hasModeData(LCPCorrectionMode mode) const; - void print() const; -}; - - class LCPProfile { - // Temporary data for parsing - bool inCamProfiles, firstLIDone, inPerspect, inAlternateLensID, inAlternateLensNames; - char lastTag[256], inInvalidTag[256]; - LCPPersModel* pCurPersModel; - LCPModelCommon* pCurCommon; - - static void XMLCALL XmlStartHandler(void *pLCPProfile, const char *el, const char **attr); - static void XMLCALL XmlTextHandler (void *pLCPProfile, const XML_Char *s, int len); - static void XMLCALL XmlEndHandler (void *pLCPProfile, const char *el); - - int filterBadFrames(LCPCorrectionMode mode, double maxAvgDevFac, int minFramesLeft); - - void handle_text(std::string text); - std::ostringstream textbuf; - public: + explicit LCPProfile(const Glib::ustring& fname); + ~LCPProfile(); + + void calcParams( + LCPCorrectionMode mode, + float focalLength, + float focusDist, + float aperture, + LCPModelCommon* pCorr1, + LCPModelCommon* pCorr2, + LCPModelCommon *pCorr3 + ) const; // Interpolates between the persModels frames + + void print() const; + +//private: // Common data - Glib::ustring profileName, lensPrettyName, cameraPrettyName, lens, camera; // lens/camera(=model) can be auto-matched with DNG - bool isRaw, isFisheye; + Glib::ustring profileName; + Glib::ustring lensPrettyName; + Glib::ustring cameraPrettyName; + Glib::ustring lens; + Glib::ustring camera; // lens/camera(=model) can be auto-matched with DNG + bool isRaw; + bool isFisheye; float sensorFormatFactor; int persModelCount; +private: + class LCPPersModel; + + int filterBadFrames(LCPCorrectionMode mode, double maxAvgDevFac, int minFramesLeft); + + void handle_text(const std::string& text); + + static void XMLCALL XmlStartHandler(void* pLCPProfile, const char* el, const char** attr); + static void XMLCALL XmlTextHandler(void* pLCPProfile, const XML_Char* s, int len); + static void XMLCALL XmlEndHandler(void* pLCPProfile, const char* el); + + // Temporary data for parsing + bool inCamProfiles; + bool firstLIDone; + bool inPerspect; + bool inAlternateLensID; + bool inAlternateLensNames; + char lastTag[256]; + char inInvalidTag[256]; + LCPPersModel* pCurPersModel; + LCPModelCommon* pCurCommon; + + std::ostringstream textbuf; + // The correction frames - static const int MaxPersModelCount = 3000; + static constexpr int MaxPersModelCount = 3000; LCPPersModel* aPersModel[MaxPersModelCount]; // Do NOT use std::list or something, it's buggy in GCC! - - explicit LCPProfile(const Glib::ustring &fname); - ~LCPProfile(); - - void calcParams(LCPCorrectionMode mode, float focalLength, float focusDist, float aperture, LCPModelCommon *pCorr1, LCPModelCommon *pCorr2, LCPModelCommon *pCorr3) const; // Interpolates between the persModels frames - - void print() const; }; class LCPStore { - MyMutex mtx; +public: + static LCPStore* getInstance(); + + bool isValidLCPFileName(const Glib::ustring& filename) const; + std::shared_ptr getProfile(const Glib::ustring& filename) const; + Glib::ustring getDefaultCommonDirectory() const; + +private: + LCPStore(unsigned int _cache_size = 32); // Maps file name to profile as cache - std::map profileCache; - -public: - ~LCPStore(); - Glib::ustring getDefaultCommonDirectory() const; - bool isValidLCPFileName(Glib::ustring filename) const; - LCPProfile* getProfile(Glib::ustring filename); - - static LCPStore* getInstance(); + mutable Cache> cache; }; -#define lcpStore LCPStore::getInstance() - - // Once precalculated class to correct a point class LCPMapper { +public: + // Precalculates the mapper + LCPMapper( + const std::shared_ptr& pProf, + float focalLength, + float focalLength35mm, + float focusDist, + float aperture, + bool vignette, + bool useCADistP, + int fullWidth, + int fullHeight, + const CoarseTransformParams& coarse, + int rawRotationDeg + ); + bool isCACorrectionAvailable() const; + + void correctDistortion(double& x, double& y, double scale) const; // MUST be the first stage + void correctCA(double& x, double& y, int channel) const; + void processVignetteLine(int width, int y, float* line) const; + void processVignetteLine3Channels(int width, int y, float* line) const; + +private: + bool enableCA; // is the mapper capable if CA correction? bool useCADist; // should the distortion in the CA info be used? bool swapXY; LCPModelCommon mc; LCPModelCommon chrom[3]; // in order RedGreen/Green/BlueGreen bool isFisheye; - -public: - bool enableCA; // is the mapper capable if CA correction? - - // precalculates the mapper. - LCPMapper(LCPProfile* pProf, float focalLength, float focalLength35mm, float focusDist, float aperture, bool vignette, bool useCADistP, int fullWidth, int fullHeight, - const CoarseTransformParams& coarse, int rawRotationDeg); - - void correctDistortion(double& x, double& y, double scale) const; // MUST be the first stage - void correctCA(double& x, double& y, int channel) const; - void processVignetteLine(int width, int y, float *line) const; - void processVignetteLine3Channels(int width, int y, float *line) const; }; } diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index 148d17ee9..718b518ae 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -1855,7 +1855,7 @@ void RawImageSource::preprocess (const RAWParams &raw, const LensProfParams &le // Correct vignetting of lens profile if (!hasFlatField && lensProf.useVign) { - LCPProfile *pLCPProf = lcpStore->getProfile(lensProf.lcpFile); + const std::shared_ptr pLCPProf = LCPStore::getInstance()->getProfile(lensProf.lcpFile); if (pLCPProf) { // don't check focal length to allow distortion correction for lenses without chip, also pass dummy focal length 1 in case of 0 LCPMapper map(pLCPProf, max(idata->getFocalLen(), 1.0), idata->getFocalLen35mm(), idata->getFocusDist(), idata->getFNumber(), true, false, W, H, coarse, -1); diff --git a/rtgui/lensprofile.cc b/rtgui/lensprofile.cc index 0855ef03f..91fc55687 100644 --- a/rtgui/lensprofile.cc +++ b/rtgui/lensprofile.cc @@ -41,7 +41,7 @@ LensProfilePanel::LensProfilePanel () : FoldableToolPanel(this, "lensprof", M("T filterLCP->add_pattern("*.LCP"); fcbLCPFile->add_filter(filterLCP); - Glib::ustring defDir = lcpStore->getDefaultCommonDirectory(); + Glib::ustring defDir = LCPStore::getInstance()->getDefaultCommonDirectory(); if (!defDir.empty()) { #ifdef WIN32 @@ -82,7 +82,7 @@ void LensProfilePanel::read(const rtengine::procparams::ProcParams* pp, const Pa disableListener (); conUseDist.block(true); - if (!pp->lensProf.lcpFile.empty() && lcpStore->isValidLCPFileName(pp->lensProf.lcpFile)) { + if (!pp->lensProf.lcpFile.empty() && LCPStore::getInstance()->isValidLCPFileName(pp->lensProf.lcpFile)) { fcbLCPFile->set_filename (pp->lensProf.lcpFile); updateDisabled(true); } else { @@ -128,7 +128,7 @@ void LensProfilePanel::setRawMeta(bool raw, const rtengine::ImageMetaData* pMeta void LensProfilePanel::write( rtengine::procparams::ProcParams* pp, ParamsEdited* pedited) { - if (lcpStore->isValidLCPFileName(fcbLCPFile->get_filename())) { + if (LCPStore::getInstance()->isValidLCPFileName(fcbLCPFile->get_filename())) { pp->lensProf.lcpFile = fcbLCPFile->get_filename(); } else { pp->lensProf.lcpFile = ""; @@ -149,7 +149,7 @@ void LensProfilePanel::write( rtengine::procparams::ProcParams* pp, ParamsEdited void LensProfilePanel::onLCPFileChanged() { lcpFileChanged = true; - updateDisabled(lcpStore->isValidLCPFileName(fcbLCPFile->get_filename())); + updateDisabled(LCPStore::getInstance()->isValidLCPFileName(fcbLCPFile->get_filename())); if (listener) { listener->panelChanged (EvLCPFile, Glib::path_get_basename(fcbLCPFile->get_filename()));