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