diff --git a/AboutThisBuild.txt.in b/AboutThisBuild.txt.in index ea3269c09..f48d39b50 100644 --- a/AboutThisBuild.txt.in +++ b/AboutThisBuild.txt.in @@ -7,6 +7,7 @@ Processor: ${PROC_LABEL} System: ${SYSTEM} Bit depth: ${PROC_BIT_DEPTH} Gtkmm: V${GTKMM_VERSION} +Lensfun: V${LENSFUN_VERSION} Build type: ${BUILD_TYPE} Build flags: ${CXX_FLAGS} Link flags: ${LFLAGS} diff --git a/CMakeLists.txt b/CMakeLists.txt index 685b418bf..333e292f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,6 +272,7 @@ pkg_check_modules (GIOMM REQUIRED giomm-2.4>=2.44) pkg_check_modules (GTHREAD REQUIRED gthread-2.0>=2.44) pkg_check_modules (GOBJECT REQUIRED gobject-2.0>=2.44) pkg_check_modules (SIGC REQUIRED sigc++-2.0>=2.3.1) +pkg_check_modules (LENSFUN REQUIRED lensfun>=0.2) if(WIN32) add_definitions(-DWIN32) @@ -389,7 +390,8 @@ set(ABOUT_COMMAND_WITH_ARGS ${CMAKE_COMMAND} -DBUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DGTKMM_VERSION:STRING=${GTKMM_VERSION} -DOPTION_OMP:STRING=${OPTION_OMP} - -DWITH_MYFILE_MMAP:STRING=${WITH_MYFILE_MMAP}) + -DWITH_MYFILE_MMAP:STRING=${WITH_MYFILE_MMAP} + -DLENSFUN_VERSION:STRING=${LENSFUN_VERSION}) if(WIN32) list(APPEND ABOUT_COMMAND_WITH_ARGS -DSYSTEM:STRING=Windows diff --git a/rtdata/languages/default b/rtdata/languages/default index 91eb5e2bf..412aad101 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -281,9 +281,9 @@ HISTORY_MSG_30;RLD - Radius HISTORY_MSG_31;RLD - Amount HISTORY_MSG_32;RLD - Damping HISTORY_MSG_33;RLD - Iterations -HISTORY_MSG_34;LCP distortion correction -HISTORY_MSG_35;LCP vignetting correction -HISTORY_MSG_36;LCP CA correction +HISTORY_MSG_34;Lens Correction - Distortion +HISTORY_MSG_35;Lens Correction - Vignetting +HISTORY_MSG_36;Lens Correction - CA HISTORY_MSG_37;Exposure - Auto levels HISTORY_MSG_38;White Balance - Method HISTORY_MSG_39;WB - Temperature @@ -332,7 +332,7 @@ HISTORY_MSG_81;Resize HISTORY_MSG_82;Profile changed HISTORY_MSG_83;S/H - Sharp mask HISTORY_MSG_84;Perspective correction -HISTORY_MSG_85;LCP +HISTORY_MSG_85;Lens Correction - LCP file HISTORY_MSG_86;RGB Curves - Luminosity mode HISTORY_MSG_87;Impulse Noise Reduction HISTORY_MSG_88;Impulse NR threshold @@ -715,6 +715,9 @@ HISTORY_MSG_481;CAM02 - Temp scene HISTORY_MSG_482;CAM02 - Green scene HISTORY_MSG_483;CAM02 - Yb scene HISTORY_MSG_484;CAM02 - Auto Yb scene +HISTORY_MSG_485;Lens Correction +HISTORY_MSG_486;Lens Correction - Camera +HISTORY_MSG_487;Lens Correction - Lens HISTORY_NEWSNAPSHOT;Add HISTORY_NEWSNAPSHOT_TOOLTIP;Shortcut: Alt-s HISTORY_SNAPSHOT;Snapshot @@ -887,7 +890,7 @@ PARTIALPASTE_IMPULSEDENOISE;Impulse noise reduction PARTIALPASTE_IPTCINFO;IPTC PARTIALPASTE_LABCURVE;L*a*b* adjustments PARTIALPASTE_LENSGROUP;Lens Related Settings -PARTIALPASTE_LENSPROFILE;Lens correction profile +PARTIALPASTE_LENSPROFILE;Profiled lens correction PARTIALPASTE_METAGROUP;Metadata PARTIALPASTE_PCVIGNETTE;Vignette filter PARTIALPASTE_PERSPECTIVE;Perspective @@ -1654,7 +1657,7 @@ TP_LABCURVE_RSTPRO_TOOLTIP;Works on the Chromaticity slider and the CC curve. TP_LENSGEOM_AUTOCROP;Auto-Crop TP_LENSGEOM_FILL;Auto-fill TP_LENSGEOM_LABEL;Lens / Geometry -TP_LENSPROFILE_LABEL;Lens Correction Profile +TP_LENSPROFILE_LABEL;Profiled Lens Correction TP_LENSPROFILE_USECA;Chromatic aberration correction TP_LENSPROFILE_USEDIST;Distortion correction TP_LENSPROFILE_USEVIGN;Vignetting correction @@ -2152,3 +2155,7 @@ ZOOMPANEL_ZOOMFITCROPSCREEN;Fit crop to screen\nShortcut: Alt-f ZOOMPANEL_ZOOMFITSCREEN;Fit whole image to screen\nShortcut: f ZOOMPANEL_ZOOMIN;Zoom In\nShortcut: + ZOOMPANEL_ZOOMOUT;Zoom Out\nShortcut: - +LENSPROFILE_CORRECTION_AUTOMATCH;Auto-matched correction parameters +LENSPROFILE_CORRECTION_MANUAL;Manual correction parameters +LENSPROFILE_CORRECTION_LCPFILE;LCP File +LENSPROFILE_LENS_WARNING;Warning: the crop factor used for lens profiling is larger than the crop factor of the camera, the results might be wrong. \ No newline at end of file diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index a813d4156..32d99cb8f 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -9,6 +9,7 @@ include_directories(${EXTRA_INCDIR} ${GTK_INCLUDE_DIRS} ${IPTCDATA_INCLUDE_DIRS} ${LCMS_INCLUDE_DIRS} + ${LENSFUN_INCLUDE_DIRS} ) link_directories("${PROJECT_SOURCE_DIR}/rtexif" @@ -109,6 +110,7 @@ set(RTENGINESOURCEFILES slicer.cc stdimagesource.cc utils.cc + rtlensfun.cc ) if(NOT WITH_SYSTEM_KLT) @@ -153,6 +155,7 @@ target_link_libraries(rtengine rtexif ${PNG_LIBRARIES} ${TIFF_LIBRARIES} ${ZLIB_LIBRARIES} + ${LENSFUN_LIBRARIES} ) install(FILES ${CAMCONSTSFILE} DESTINATION "${DATADIR}" PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) 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/dcrop.cc b/rtengine/dcrop.cc index df75e7a0e..25710205f 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -698,9 +698,7 @@ void Crop::update (int todo) if (needstransform) parent->ipf.transform (baseCrop, transCrop, cropx / skip, cropy / skip, trafx / skip, trafy / skip, skips (parent->fw, skip), skips (parent->fh, skip), parent->getFullWidth(), parent->getFullHeight(), - parent->imgsrc->getMetaData()->getFocalLen(), parent->imgsrc->getMetaData()->getFocalLen35mm(), - parent->imgsrc->getMetaData()->getFocusDist(), - parent->imgsrc->getMetaData()->getFNumber(), + parent->imgsrc->getMetaData(), parent->imgsrc->getRotateDegree(), false); else { baseCrop->copyData (transCrop); @@ -1085,8 +1083,9 @@ bool check_need_larger_crop_for_lcp_distortion (int fw, int fh, int x, int y, in return false; } - return (params.lensProf.lcpFile.length() > 0 && - params.lensProf.useDist); + return (params.lensProf.useDist && + (params.lensProf.useLensfun || + params.lensProf.lcpFile.length() > 0)); } } // namespace diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index d5032cd46..2b9edc3d6 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -400,8 +400,8 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) } if (needstransform) - ipf.transform (orig_prev, oprevi, 0, 0, 0, 0, pW, pH, fw, fh, imgsrc->getMetaData()->getFocalLen(), - imgsrc->getMetaData()->getFocalLen35mm(), imgsrc->getMetaData()->getFocusDist(), imgsrc->getMetaData()->getFNumber(), imgsrc->getRotateDegree(), false); + ipf.transform (orig_prev, oprevi, 0, 0, 0, 0, pW, pH, fw, fh, + imgsrc->getMetaData(), imgsrc->getRotateDegree(), false); else { orig_prev->copyData (oprevi); } @@ -1118,10 +1118,10 @@ void ImProcCoordinator::getAutoCrop (double ratio, int &x, int &y, int &w, int & MyMutex::MyLock lock (mProcessing); - LCPMapper *pLCPMap = nullptr; + LensCorrection *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()); @@ -1219,8 +1219,8 @@ void ImProcCoordinator::saveInputICCReference (const Glib::ustring& fname, bool if (ipf.needsTransform()) { Imagefloat* trImg = new Imagefloat (fW, fH); - ipf.transform (im, trImg, 0, 0, 0, 0, fW, fH, fW, fH, imgsrc->getMetaData()->getFocalLen(), imgsrc->getMetaData()->getFocalLen35mm(), - imgsrc->getMetaData()->getFocusDist(), imgsrc->getMetaData()->getFNumber(), imgsrc->getRotateDegree(), true); + ipf.transform (im, trImg, 0, 0, 0, 0, fW, fH, fW, fH, + imgsrc->getMetaData(), imgsrc->getRotateDegree(), true); delete im; im = trImg; } diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 309e5c029..e48ec3c7f 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -54,9 +54,13 @@ class ImProcFunctions void calcVignettingParams (int oW, int oH, const VignettingParams& vignetting, double &w2, double &h2, double& maxRadius, double &v, double &b, double &mul); - void transformPreview (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const LCPMapper *pLCPMap); + enum TransformMode { + TRANSFORM_PREVIEW, + TRANSFORM_HIGH_QUALITY, + TRANSFORM_HIGH_QUALITY_FULLIMAGE + }; void transformLuminanceOnly (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int oW, int oH, int fW, int fH); - void transformHighQuality (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const LCPMapper *pLCPMap, bool fullImage); + void transformGeneral(TransformMode mode, Imagefloat *original, Imagefloat *transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const LensCorrection *pLCPMap); void sharpenHaloCtrl (float** luminance, float** blurmap, float** base, int W, int H, const SharpeningParams &sharpenParam); void sharpenHaloCtrl (LabImage* lab, float** blurmap, float** base, int W, int H, SharpeningParams &sharpenParam); @@ -70,6 +74,7 @@ class ImProcFunctions bool needsGradient (); bool needsVignetting (); bool needsLCP (); + bool needsLensfun(); // static cmsUInt8Number* Mempro = NULL; inline void interpolateTransformCubic (Imagefloat* src, int xs, int ys, double Dx, double Dy, float *r, float *g, float *b, double mul) @@ -235,8 +240,7 @@ public: void colorCurve (LabImage* lold, LabImage* lnew); void sharpening (LabImage* lab, float** buffer, SharpeningParams &sharpenParam); void sharpeningcam (CieImage* ncie, float** buffer); - void transform (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, - double focalLen, double focalLen35mm, float focusDist, double fNumber, int rawRotationDeg, bool fullImage); + void transform (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const ImageMetaData *metadata, int rawRotationDeg, bool fullImage); float resizeScale (const ProcParams* params, int fw, int fh, int &imw, int &imh); void lab2monitorRgb (LabImage* lab, Image8* image); void resize (Image16* src, Image16* dst, float dScale); @@ -347,11 +351,11 @@ public: Image16* lab2rgb16 (LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm, bool bw, GammaValues *ga = nullptr); // CieImage *ciec; - bool transCoord (int W, int H, int x, int y, int w, int h, int& xv, int& yv, int& wv, int& hv, double ascaleDef = -1, const LCPMapper *pLCPMap = nullptr); - bool transCoord (int W, int H, const std::vector &src, std::vector &red, std::vector &green, std::vector &blue, double ascaleDef = -1, const LCPMapper *pLCPMap = nullptr); + bool transCoord (int W, int H, int x, int y, int w, int h, int& xv, int& yv, int& wv, int& hv, double ascaleDef = -1, const LensCorrection *pLCPMap = nullptr); + bool transCoord (int W, int H, const std::vector &src, std::vector &red, std::vector &green, std::vector &blue, double ascaleDef = -1, const LensCorrection *pLCPMap = nullptr); static void getAutoExp (const LUTu & histogram, int histcompr, double defgain, double clip, double& expcomp, int& bright, int& contr, int& black, int& hlcompr, int& hlcomprthresh); static double getAutoDistor (const Glib::ustring& fname, int thumb_size); - double getTransformAutoFill (int oW, int oH, const LCPMapper *pLCPMap = nullptr); + double getTransformAutoFill (int oW, int oH, const LensCorrection *pLCPMap = nullptr); void rgb2lab (const Imagefloat &src, LabImage &dst, const Glib::ustring &workingSpace); void lab2rgb (const LabImage &src, Imagefloat &dst, const Glib::ustring &workingSpace); }; diff --git a/rtengine/init.cc b/rtengine/init.cc index 2d157c762..7ef40f43a 100644 --- a/rtengine/init.cc +++ b/rtengine/init.cc @@ -30,6 +30,7 @@ #include "rtthumbnail.h" #include "profilestore.h" #include "../rtgui/threadutils.h" +#include "rtlensfun.h" namespace rtengine { @@ -50,6 +51,7 @@ int init (const Settings* s, Glib::ustring baseDir, Glib::ustring userSettingsDi Color::init (); PerceptualToneCurve::init (); RawImageSource::init (); + LFDatabase::init(); delete lcmsMutex; lcmsMutex = new MyMutex; dfm.init( s->darkFramesPath ); diff --git a/rtengine/iptransform.cc b/rtengine/iptransform.cc index ee14c80c2..3f7472f6f 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -24,6 +24,7 @@ #include "mytime.h" #include "rt_math.h" #include "sleef.c" +#include "rtlensfun.h" using namespace std; @@ -86,18 +87,6 @@ float normn (float a, float b, int n) } -void correct_distortion(const rtengine::LCPMapper *lcp, double &x, double &y, - int cx, int cy, double scale) -{ - assert (lcp); - - x += cx; - y += cy; - lcp->correctDistortion(x, y, scale); - x -= (cx * scale); - y -= (cy * scale); -} - } namespace rtengine @@ -107,7 +96,7 @@ namespace rtengine #define CLIPTOC(a,b,c,d) ((a)>=(b)?((a)<=(c)?(a):(d=true,(c))):(d=true,(b))) bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, std::vector &red, std::vector &green, std::vector &blue, double ascaleDef, - const LCPMapper *pLCPMap) + const LensCorrection *pLCPMap) { bool clipped = false; @@ -157,7 +146,7 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, double x_d = src[i].x, y_d = src[i].y; if (pLCPMap && params->lensProf.useDist) { - correct_distortion (pLCPMap, x_d, y_d, 0, 0, ascale); + pLCPMap->correctDistortion(x_d, y_d, 0, 0, ascale); } else { x_d *= ascale; y_d *= ascale; @@ -209,7 +198,7 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, } // Transform all corners and critical sidelines of an image -bool ImProcFunctions::transCoord (int W, int H, int x, int y, int w, int h, int& xv, int& yv, int& wv, int& hv, double ascaleDef, const LCPMapper *pLCPMap) +bool ImProcFunctions::transCoord (int W, int H, int x, int y, int w, int h, int& xv, int& yv, int& wv, int& hv, double ascaleDef, const LensCorrection *pLCPMap) { const int DivisionsPerBorder = 32; @@ -307,32 +296,44 @@ bool ImProcFunctions::transCoord (int W, int H, int x, int y, int w, int h, int& } void ImProcFunctions::transform (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, - double focalLen, double focalLen35mm, float focusDist, double fNumber, int rawRotationDeg, bool fullImage) + const ImageMetaData *metadata, + int rawRotationDeg, bool fullImage) { + double focalLen = metadata->getFocalLen(); + double focalLen35mm = metadata->getFocalLen35mm(); + float focusDist = metadata->getFocusDist(); + double fNumber = metadata->getFNumber(); - LCPMapper *pLCPMap = nullptr; + std::unique_ptr pLCPMap; - if (needsLCP()) { // don't check focal length to allow distortion correction for lenses without chip - LCPProfile *pLCPProf = lcpStore->getProfile (params->lensProf.lcpFile); + if (needsLensfun()) { + pLCPMap = std::move(LFDatabase::findModifier(params->lensProf, metadata, oW, oH, params->coarse, rawRotationDeg)); + } else if (needsLCP()) { // don't check focal length to allow distortion correction for lenses without chip + const std::shared_ptr pLCPProf = LCPStore::getInstance()->getProfile (params->lensProf.lcpFile); if (pLCPProf) { - pLCPMap = new LCPMapper (pLCPProf, focalLen, focalLen35mm, - focusDist, fNumber, false, - params->lensProf.useDist, - oW, oH, params->coarse, rawRotationDeg); + pLCPMap.reset( + new LCPMapper (pLCPProf, focalLen, focalLen35mm, + focusDist, fNumber, false, + params->lensProf.useDist, + oW, oH, params->coarse, rawRotationDeg + ) + ); } } - if (! (needsCA() || needsDistortion() || needsRotation() || needsPerspective() || needsLCP()) && (needsVignetting() || needsPCVignetting() || needsGradient())) { + if (! (needsCA() || needsDistortion() || needsRotation() || needsPerspective() || needsLCP() || needsLensfun()) && (needsVignetting() || needsPCVignetting() || needsGradient())) { transformLuminanceOnly (original, transformed, cx, cy, oW, oH, fW, fH); - } else if (!needsCA() && scale != 1) { - transformPreview (original, transformed, cx, cy, sx, sy, oW, oH, fW, fH, pLCPMap); } else { - transformHighQuality (original, transformed, cx, cy, sx, sy, oW, oH, fW, fH, pLCPMap, fullImage); - } - - if (pLCPMap) { - delete pLCPMap; + TransformMode mode; + if (!needsCA() && scale != 1) { + mode = TRANSFORM_PREVIEW; + } else if (!fullImage) { + mode = TRANSFORM_HIGH_QUALITY; + } else { + mode = TRANSFORM_HIGH_QUALITY_FULLIMAGE; + } + transformGeneral(mode, original, transformed, cx, cy, sx, sy, oW, oH, fW, fH, pLCPMap.get()); } } @@ -721,9 +722,8 @@ void ImProcFunctions::transformLuminanceOnly (Imagefloat* original, Imagefloat* } } -// Transform WITH scaling (opt.) and CA, cubic interpolation -void ImProcFunctions::transformHighQuality (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, - const LCPMapper *pLCPMap, bool fullImage) + +void ImProcFunctions::transformGeneral(ImProcFunctions::TransformMode mode, Imagefloat *original, Imagefloat *transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const LensCorrection *pLCPMap) { double w2 = (double) oW / 2.0 - 0.5; double h2 = (double) oH / 2.0 - 0.5; @@ -781,17 +781,31 @@ void ImProcFunctions::transformHighQuality (Imagefloat* original, Imagefloat* tr oH * tan (hpalpha) * sqrt (SQR (4 * maxRadius) + SQR (oH * tan (hpalpha)))) / (SQR (maxRadius) * 8))); double hpcospt = (hpdeg >= 0 ? 1.0 : -1.0) * cos (hpteta), hptanpt = tan (hpteta); - double ascale = params->commonTrans.autofill ? getTransformAutoFill (oW, oH, true /*fullImage*/ ? pLCPMap : nullptr) : 1.0; + double ascale = params->commonTrans.autofill ? getTransformAutoFill (oW, oH, pLCPMap) : 1.0; // smaller crop images are a problem, so only when processing fully - bool enableLCPCA = pLCPMap && params->lensProf.useCA && fullImage && pLCPMap->enableCA; - bool enableLCPDist = pLCPMap && params->lensProf.useDist; // && fullImage; + bool enableLCPCA = false; + bool enableLCPDist = false; + bool enableCA = false; - if (enableLCPCA) { - enableLCPDist = false; + switch (mode) { + case ImProcFunctions::TRANSFORM_HIGH_QUALITY_FULLIMAGE: + enableLCPCA = pLCPMap && params->lensProf.useCA && pLCPMap->isCACorrectionAvailable(); + // no break on purpose + case ImProcFunctions::TRANSFORM_HIGH_QUALITY: + enableLCPDist = pLCPMap && params->lensProf.useDist; + if (enableLCPCA) { + enableLCPDist = false; + } + enableCA = enableLCPCA || needsCA(); + default: // ImProcFunctions::TRANSFORM_PREVIEW + enableLCPDist = pLCPMap && params->lensProf.useDist; + break; } - bool enableCA = enableLCPCA || needsCA(); + if (!enableCA) { + chDist[0] = 0.0; + } // main cycle bool darkening = (params->vignetting.amount <= 0.0); @@ -802,7 +816,7 @@ void ImProcFunctions::transformHighQuality (Imagefloat* original, Imagefloat* tr double x_d = x, y_d = y; if (enableLCPDist) { - correct_distortion(pLCPMap, x_d, y_d, cx, cy, ascale); // must be first transform + pLCPMap->correctDistortion(x_d, y_d, cx, cy, ascale); // must be first transform } else { x_d *= ascale; y_d *= ascale; @@ -895,6 +909,10 @@ void ImProcFunctions::transformHighQuality (Imagefloat* original, Imagefloat* tr // all interpolation pixels inside image if (enableCA) { interpolateTransformChannelsCubic (chOrig[c], xc - 1, yc - 1, Dx, Dy, & (chTrans[c][y][x]), vignmul); + } else if (mode == ImProcFunctions::TRANSFORM_PREVIEW) { + transformed->r (y, x) = vignmul * (original->r (yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->r (yc, xc + 1) * Dx * (1.0 - Dy) + original->r (yc + 1, xc) * (1.0 - Dx) * Dy + original->r (yc + 1, xc + 1) * Dx * Dy); + transformed->g (y, x) = vignmul * (original->g (yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->g (yc, xc + 1) * Dx * (1.0 - Dy) + original->g (yc + 1, xc) * (1.0 - Dx) * Dy + original->g (yc + 1, xc + 1) * Dx * Dy); + transformed->b (y, x) = vignmul * (original->b (yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->b (yc, xc + 1) * Dx * (1.0 - Dy) + original->b (yc + 1, xc) * (1.0 - Dx) * Dy + original->b (yc + 1, xc + 1) * Dx * Dy); } else { interpolateTransformCubic (original, xc - 1, yc - 1, Dx, Dy, & (transformed->r (y, x)), & (transformed->g (y, x)), & (transformed->b (y, x)), vignmul); } @@ -928,168 +946,8 @@ void ImProcFunctions::transformHighQuality (Imagefloat* original, Imagefloat* tr } } -// Transform WITH scaling, WITHOUT CA, simple (and fast) interpolation. Used for preview -void ImProcFunctions::transformPreview (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const LCPMapper *pLCPMap) -{ - double w2 = (double) oW / 2.0 - 0.5; - double h2 = (double) oH / 2.0 - 0.5; - - double vig_w2, vig_h2, maxRadius, v, b, mul; - calcVignettingParams (oW, oH, params->vignetting, vig_w2, vig_h2, maxRadius, v, b, mul); - - struct grad_params gp; - - if (needsGradient()) { - calcGradientParams (oW, oH, params->gradient, gp); - } - - struct pcv_params pcv; - - if (needsPCVignetting()) { - calcPCVignetteParams (fW, fH, oW, oH, params->pcvignette, params->crop, pcv); - } - - // auxiliary variables for distortion correction - bool needsDist = needsDistortion(); // for performance - double distAmount = params->distortion.amount; - - // auxiliary variables for rotation - double cost = cos (params->rotate.degree * rtengine::RT_PI / 180.0); - double sint = sin (params->rotate.degree * rtengine::RT_PI / 180.0); - - // auxiliary variables for vertical perspective correction - double vpdeg = params->perspective.vertical / 100.0 * 45.0; - double vpalpha = (90 - vpdeg) / 180.0 * rtengine::RT_PI; - double vpteta = fabs (vpalpha - rtengine::RT_PI / 2) < 3e-4 ? 0.0 : acos ((vpdeg > 0 ? 1.0 : -1.0) * sqrt ((-oW * oW * tan (vpalpha) * tan (vpalpha) + (vpdeg > 0 ? 1.0 : -1.0) * oW * tan (vpalpha) * sqrt (16 * maxRadius * maxRadius + oW * oW * tan (vpalpha) * tan (vpalpha))) / (maxRadius * maxRadius * 8))); - double vpcospt = (vpdeg >= 0 ? 1.0 : -1.0) * cos (vpteta), vptanpt = tan (vpteta); - - // auxiliary variables for horizontal perspective correction - double hpdeg = params->perspective.horizontal / 100.0 * 45.0; - double hpalpha = (90 - hpdeg) / 180.0 * rtengine::RT_PI; - double hpteta = fabs (hpalpha - rtengine::RT_PI / 2) < 3e-4 ? 0.0 : acos ((hpdeg > 0 ? 1.0 : -1.0) * sqrt ((-oH * oH * tan (hpalpha) * tan (hpalpha) + (hpdeg > 0 ? 1.0 : -1.0) * oH * tan (hpalpha) * sqrt (16 * maxRadius * maxRadius + oH * oH * tan (hpalpha) * tan (hpalpha))) / (maxRadius * maxRadius * 8))); - double hpcospt = (hpdeg >= 0 ? 1.0 : -1.0) * cos (hpteta), hptanpt = tan (hpteta); - - double ascale = params->commonTrans.autofill ? getTransformAutoFill (oW, oH, pLCPMap) : 1.0; - - bool darkening = (params->vignetting.amount <= 0.0); - - // main cycle - #pragma omp parallel for if (multiThread) - - for (int y = 0; y < transformed->getHeight(); y++) { - for (int x = 0; x < transformed->getWidth(); x++) { - double x_d = x, y_d = y; - - if (pLCPMap && params->lensProf.useDist) { - correct_distortion(pLCPMap, x_d, y_d, cx, cy, ascale); // must be first transform - } else { - x_d *= ascale; - y_d *= ascale; - } - - x_d += ascale * (cx - w2); // centering x coord & scale - y_d += ascale * (cy - h2); // centering y coord & scale - - double vig_x_d = 0., vig_y_d = 0.; - - if (needsVignetting()) { - vig_x_d = ascale * (x + cx - vig_w2); // centering x coord & scale - vig_y_d = ascale * (y + cy - vig_h2); // centering y coord & scale - } - - if (needsPerspective()) { - // horizontal perspective transformation - y_d *= maxRadius / (maxRadius + x_d * hptanpt); - x_d *= maxRadius * hpcospt / (maxRadius + x_d * hptanpt); - - // vertical perspective transformation - x_d *= maxRadius / (maxRadius - y_d * vptanpt); - y_d *= maxRadius * vpcospt / (maxRadius - y_d * vptanpt); - } - - // rotate - double Dx = x_d * cost - y_d * sint; - double Dy = x_d * sint + y_d * cost; - - // distortion correction - double s = 1; - - if (needsDist) { - double r = sqrt (Dx * Dx + Dy * Dy) / maxRadius; // sqrt is slow - s = 1.0 - distAmount + distAmount * r ; - Dx *= s; - Dy *= s; - } - - double r2 = 0.; - - if (needsVignetting()) { - double vig_Dx = vig_x_d * cost - vig_y_d * sint; - double vig_Dy = vig_x_d * sint + vig_y_d * cost; - r2 = sqrt (vig_Dx * vig_Dx + vig_Dy * vig_Dy); - } - - // de-center - Dx += w2; - Dy += h2; - - // Extract integer and fractions of source screen coordinates - int xc = (int)Dx; - Dx -= (double)xc; - xc -= sx; - int yc = (int)Dy; - Dy -= (double)yc; - yc -= sy; - - // Convert only valid pixels - if (yc >= 0 && yc < original->getHeight() && xc >= 0 && xc < original->getWidth()) { - - // multiplier for vignetting correction - double vignmul = 1.0; - - if (needsVignetting()) { - if (darkening) { - vignmul /= std::max (v + mul * tanh (b * (maxRadius - s * r2) / maxRadius), 0.001); - } else { - vignmul = v + mul * tanh (b * (maxRadius - s * r2) / maxRadius); - } - } - - if (needsGradient()) { - vignmul *= calcGradientFactor (gp, cx + x, cy + y); - } - - if (needsPCVignetting()) { - vignmul *= calcPCVignetteFactor (pcv, cx + x, cy + y); - } - - if (yc < original->getHeight() - 1 && xc < original->getWidth() - 1) { - // all interpolation pixels inside image - transformed->r (y, x) = vignmul * (original->r (yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->r (yc, xc + 1) * Dx * (1.0 - Dy) + original->r (yc + 1, xc) * (1.0 - Dx) * Dy + original->r (yc + 1, xc + 1) * Dx * Dy); - transformed->g (y, x) = vignmul * (original->g (yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->g (yc, xc + 1) * Dx * (1.0 - Dy) + original->g (yc + 1, xc) * (1.0 - Dx) * Dy + original->g (yc + 1, xc + 1) * Dx * Dy); - transformed->b (y, x) = vignmul * (original->b (yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->b (yc, xc + 1) * Dx * (1.0 - Dy) + original->b (yc + 1, xc) * (1.0 - Dx) * Dy + original->b (yc + 1, xc + 1) * Dx * Dy); - } else { - // edge pixels - int y1 = LIM (yc, 0, original->getHeight() - 1); - int y2 = LIM (yc + 1, 0, original->getHeight() - 1); - int x1 = LIM (xc, 0, original->getWidth() - 1); - int x2 = LIM (xc + 1, 0, original->getWidth() - 1); - transformed->r (y, x) = vignmul * (original->r (y1, x1) * (1.0 - Dx) * (1.0 - Dy) + original->r (y1, x2) * Dx * (1.0 - Dy) + original->r (y2, x1) * (1.0 - Dx) * Dy + original->r (y2, x2) * Dx * Dy); - transformed->g (y, x) = vignmul * (original->g (y1, x1) * (1.0 - Dx) * (1.0 - Dy) + original->g (y1, x2) * Dx * (1.0 - Dy) + original->g (y2, x1) * (1.0 - Dx) * Dy + original->g (y2, x2) * Dx * Dy); - transformed->b (y, x) = vignmul * (original->b (y1, x1) * (1.0 - Dx) * (1.0 - Dy) + original->b (y1, x2) * Dx * (1.0 - Dy) + original->b (y2, x1) * (1.0 - Dx) * Dy + original->b (y2, x2) * Dx * Dy); - } - } else { - // not valid (source pixel x,y not inside source image, etc.) - transformed->r (y, x) = 0; - transformed->g (y, x) = 0; - transformed->b (y, x) = 0; - } - } - } -} - -double ImProcFunctions::getTransformAutoFill (int oW, int oH, const LCPMapper *pLCPMap) +double ImProcFunctions::getTransformAutoFill (int oW, int oH, const LensCorrection *pLCPMap) { if (!needsCA() && !needsDistortion() && !needsRotation() && !needsPerspective() && (!params->lensProf.useDist || pLCPMap == nullptr)) { return 1; @@ -1150,12 +1008,17 @@ bool ImProcFunctions::needsVignetting () bool ImProcFunctions::needsLCP () { - return params->lensProf.lcpFile.length() > 0; + return params->lensProf.lcpFile.length() > 0 && !needsLensfun(); +} + +bool ImProcFunctions::needsLensfun() +{ + return params->lensProf.useLensfun; } bool ImProcFunctions::needsTransform () { - return needsCA () || needsDistortion () || needsRotation () || needsPerspective () || needsGradient () || needsPCVignetting () || needsVignetting () || needsLCP(); + return needsCA () || needsDistortion () || needsRotation () || needsPerspective () || needsGradient () || needsPCVignetting () || needsVignetting () || needsLCP() || needsLensfun(); } diff --git a/rtengine/lcp.cc b/rtengine/lcp.cc index c09d2d9be..3a7c44000 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,60 +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(int mode) const +bool rtengine::LCPProfile::LCPPersModel::hasModeData(LCPCorrectionMode mode) const { - return (mode == 0 && !vignette.empty() && !vignette.bad_error) || (mode == 1 && !base.empty() && !base.bad_error) - || (mode == 2 && !chromRG.empty() && !chromG.empty() && !chromBG.empty() && - !chromRG.bad_error && !chromG.bad_error && !chromBG.bad_error); + 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; } -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 @@ -188,84 +998,97 @@ 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 ? 0 : 1, 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(2, 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, int cx, int cy, double scale) const +{ + x += cx; + y += cy; + 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; } + + x -= cx * scale; + y -= cy * scale; } -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) { @@ -291,18 +1114,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; @@ -321,9 +1144,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); @@ -332,24 +1155,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; @@ -357,623 +1180,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(2.0, 0); - // from the non-distorded, filter again on new average basis, but only if there are enough frames left -// filterBadFrames(1.5, 100); -} - - -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(double maxAvgDevFac, int minFramesLeft) -{ - // take average error per type, then calculated the maximum deviation allowed - double errBase = 0, errChrom = 0, errVignette = 0; - int baseCount = 0, chromCount = 0, vignetteCount = 0; - - for (int pm = 0; pm < MaxPersModelCount && aPersModel[pm]; pm++) { - if (aPersModel[pm]->hasModeData(0)) { - errVignette += aPersModel[pm]->vignette.mean_error; - vignetteCount++; - } - - if (aPersModel[pm]->hasModeData(1)) { - errBase += aPersModel[pm]->base.mean_error; - baseCount++; - } - - if (aPersModel[pm]->hasModeData(2)) { - errChrom += rtengine::max(aPersModel[pm]->chromRG.mean_error, aPersModel[pm]->chromG.mean_error, aPersModel[pm]->chromBG.mean_error); - chromCount++; - } - } - - // Only if we have enough frames, filter out errors - int filtered = 0; - - if (baseCount + chromCount + vignetteCount >= minFramesLeft) { - if (baseCount > 0) { - errBase /= (double)baseCount; - } - - if (chromCount > 0) { - errChrom /= (double)chromCount; - } - - if (vignetteCount > 0) { - errVignette /= (double)vignetteCount; - } - - // Now mark all the bad ones as bad, and hasModeData will return false; - for (int pm = 0; pm < MaxPersModelCount && aPersModel[pm]; pm++) { - if (aPersModel[pm]->hasModeData(0) && aPersModel[pm]->vignette.mean_error > maxAvgDevFac * errVignette) { - aPersModel[pm]->vignette.bad_error = true; - filtered++; - } - - if (aPersModel[pm]->hasModeData(1) && aPersModel[pm]->base.mean_error > maxAvgDevFac * errBase) { - aPersModel[pm]->base.bad_error = true; - filtered++; - } - - if (aPersModel[pm]->hasModeData(2) && - (aPersModel[pm]->chromRG.mean_error > maxAvgDevFac * errChrom || aPersModel[pm]->chromG.mean_error > maxAvgDevFac * errChrom - || aPersModel[pm]->chromBG.mean_error > maxAvgDevFac * errChrom)) { - aPersModel[pm]->chromRG.bad_error = aPersModel[pm]->chromG.bad_error = aPersModel[pm]->chromBG.bad_error = true; - filtered++; - } - } - - if (settings->verbose) { - printf("Filtered %.1f%% frames for maxAvgDevFac %g leaving %i\n", filtered*100./(baseCount+chromCount+vignetteCount), maxAvgDevFac, baseCount+chromCount+vignetteCount-filtered); - } - } - - return filtered; -} - - -// mode: 0=vignette, 1=distortion, 2=CA -void LCPProfile::calcParams(int mode, float focalLength, float focusDist, float aperture, LCPModelCommon *pCorr1, LCPModelCommon *pCorr2, LCPModelCommon *pCorr3) const -{ - float euler = exp(1.0); - - // find the frames with the least distance, focal length wise - LCPPersModel *pLow = nullptr, *pHigh = nullptr; - - float focalLengthLog = log(focalLength); //, apertureLog=aperture>0 ? log(aperture) : 0; - float focusDistLog = focusDist > 0 ? log(focusDist) + euler : 0; - - // Pass 1: determining best focal length, if possible different focusDistances (for the focDist is not given case) - for (int pm = 0; pm < persModelCount; pm++) { - float f = aPersModel[pm]->focLen; - - if (aPersModel[pm]->hasModeData(mode)) { - if (f <= focalLength && (pLow == nullptr || f > pLow->focLen || (focusDist == 0 && f == pLow->focLen && pLow->focDist > aPersModel[pm]->focDist))) { - pLow = aPersModel[pm]; - } - - if (f >= focalLength && (pHigh == nullptr || f < pHigh->focLen || (focusDist == 0 && f == pHigh->focLen && pHigh->focDist < aPersModel[pm]->focDist))) { - pHigh = aPersModel[pm]; - } - } - } - - if (!pLow) { - pLow = pHigh; - } else if (!pHigh) { - pHigh = pLow; - } else { - // Pass 2: We have some, so take the best aperture for vignette and best focus for CA and distortion - // there are usually several frame per focal length. In the end pLow will have both flen and apterure/focdis below the target, - // and vice versa pHigh - float bestFocLenLow = pLow->focLen, bestFocLenHigh = pHigh->focLen; - - for (int pm = 0; pm < persModelCount; pm++) { - float aper = aPersModel[pm]->aperture; // float aperLog=log(aper); - float focDist = aPersModel[pm]->focDist; - float focDistLog = log(focDist) + euler; - double meanErr; - if (aPersModel[pm]->hasModeData(mode)) { - double lowMeanErr, highMeanErr; - switch (mode) { - case 0: - meanErr = aPersModel[pm]->vignette.mean_error; - lowMeanErr = pLow->vignette.mean_error; - highMeanErr = pHigh->vignette.mean_error; - break; - case 1: - meanErr = aPersModel[pm]->base.mean_error; - lowMeanErr = pLow->base.mean_error; - highMeanErr = pHigh->base.mean_error; - break; - default: //case 2: - meanErr = aPersModel[pm]->chromG.mean_error; - lowMeanErr = pLow->chromG.mean_error; - highMeanErr = pHigh->chromG.mean_error; - break; - } - - if (aperture > 0 && mode != 2) { - 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 != 0) { - // 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 == 0 && pLow->aperture < aperture && pHigh->aperture > aperture) { - // Mix in aperture - float facAperLow = (pHigh->aperture - aperture) / (pHigh->aperture - pLow->aperture); - facLow = focLenOnSpot ? facAperLow : (0.5 * facLow + 0.5 * facAperLow); - } else if (mode != 0 && focusDist > 0 && pLow->focDist < focusDist && pHigh->focDist > focusDist) { - // focus distance for all else (if focus distance is given) - float facDistLow = (log(pHigh->focDist) + euler - focusDistLog) / (log(pHigh->focDist) - log(pLow->focDist)); - facLow = focLenOnSpot ? facDistLow : (0.8 * facLow + 0.2 * facDistLow); - } - - switch (mode) { - case 0: // vignette - pCorr1->merge(pLow->vignette, pHigh->vignette, facLow); - break; - - case 1: // distortion - pCorr1->merge(pLow->base, pHigh->base, facLow); - break; - - case 2: // CA - pCorr1->merge(pLow->chromRG, pHigh->chromRG, facLow); - pCorr2->merge(pLow->chromG, pHigh->chromG, facLow); - pCorr3->merge(pLow->chromBG, pHigh->chromBG, facLow); - break; - } - - 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 == 0 ? "vignette" : mode == 1 ? "distortion" : "CA" ); - } - } -} - -void LCPProfile::print() const -{ - printf("=== Profile %s\n", profileName.c_str()); - printf("Frames: %i, RAW: %i; Fisheye: %i; Sensorformat: %f\n", persModelCount, isRaw, isFisheye, sensorFormatFactor); - - for (int pm = 0; pm < persModelCount; pm++) { - aPersModel[pm]->print(); - } -} - -void XMLCALL LCPProfile::XmlStartHandler(void *pLCPProfile, const char *el, const char **attr) -{ - LCPProfile *pProf = static_cast(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 f7164117f..17eb892c8 100644 --- a/rtengine/lcp.h +++ b/rtengine/lcp.h @@ -21,27 +21,45 @@ #include #include +#include #include #include #include #include +#include "cache.h" #include "imagefloat.h" #include "opthelper.h" namespace rtengine { +enum class LCPCorrectionMode { + VIGNETTE, + DISTORTION, + CA +}; + // Perspective model common data, also used for Vignette and Fisheye 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; @@ -66,97 +84,125 @@ 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(int 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(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(int 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() +class LensCorrection { +public: + virtual ~LensCorrection() {} + virtual void correctDistortion(double &x, double &y, int cx, int cy, double scale) const = 0; + virtual bool isCACorrectionAvailable() const = 0; + virtual void correctCA(double &x, double &y, int channel) const = 0; + virtual void processVignetteLine(int width, int y, float *line) const = 0; + virtual void processVignetteLine3Channels(int width, int y, float *line) const = 0; +}; // Once precalculated class to correct a point -class LCPMapper +class LCPMapper: public LensCorrection { +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 + ); + + void correctDistortion(double &x, double &y, int cx, int cy, double scale) const; // MUST be the first stage + bool isCACorrectionAvailable() const; + 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/procevents.h b/rtengine/procevents.h index cf7f50704..5f30374ee 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -511,6 +511,10 @@ enum ProcEvent { EvCATgreensc = 481, EvCATybscen = 482, EvCATAutoyb = 483, + // profiled lens correction new events + EvLensCorrMode = 484, + EvLensCorrLensfunCamera = 485, + EvLensCorrLensfunLens = 486, NUMOFEVENTS diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index e1a3002e2..29b2cce84 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -922,6 +922,11 @@ void LensProfParams::setDefaults() lcpFile = ""; useDist = useVign = true; useCA = false; + useLensfun = false; + lfAutoMatch = true; + lfCameraMake = ""; + lfCameraModel = ""; + lfLens = ""; } void CoarseTransformParams::setDefaults() @@ -2565,6 +2570,22 @@ int ProcParams::save (const Glib::ustring &fname, const Glib::ustring &fname2, b keyFile.set_boolean ("LensProfile", "UseCA", lensProf.useCA); } + if (!pedited || pedited->lensProf.useLensfun) { + keyFile.set_boolean("LensProfile", "UseLensfun", lensProf.useLensfun); + } + if (!pedited || pedited->lensProf.lfAutoMatch) { + keyFile.set_boolean("LensProfile", "LFAutoMatch", lensProf.lfAutoMatch); + } + if (!pedited || pedited->lensProf.lfCameraMake) { + keyFile.set_string("LensProfile", "LFCameraMake", lensProf.lfCameraMake); + } + if (!pedited || pedited->lensProf.lfCameraModel) { + keyFile.set_string("LensProfile", "LFCameraModel", lensProf.lfCameraModel); + } + if (!pedited || pedited->lensProf.lfLens) { + keyFile.set_string("LensProfile", "LFLens", lensProf.lfLens); + } + // save perspective correction if (!pedited || pedited->perspective.horizontal) { keyFile.set_double ("Perspective", "Horizontal", perspective.horizontal); @@ -5832,6 +5853,41 @@ int ProcParams::load (const Glib::ustring &fname, ParamsEdited* pedited) pedited->lensProf.useCA = true; } } + + if (keyFile.has_key("LensProfile", "UseLensfun")) { + lensProf.useLensfun = keyFile.get_boolean("LensProfile", "UseLensfun"); + if (pedited) { + pedited->lensProf.useLensfun = true; + } + } + + if (keyFile.has_key("LensProfile", "LFAutoMatch")) { + lensProf.lfAutoMatch = keyFile.get_boolean("LensProfile", "LFAutoMatch"); + if (pedited) { + pedited->lensProf.lfAutoMatch = true; + } + } + + if (keyFile.has_key("LensProfile", "LFCameraMake")) { + lensProf.lfCameraMake = keyFile.get_string("LensProfile", "LFCameraMake"); + if (pedited) { + pedited->lensProf.lfCameraMake = true; + } + } + + if (keyFile.has_key("LensProfile", "LFCameraModel")) { + lensProf.lfCameraModel = keyFile.get_string("LensProfile", "LFCameraModel"); + if (pedited) { + pedited->lensProf.lfCameraModel = true; + } + } + + if (keyFile.has_key("LensProfile", "LFLens")) { + lensProf.lfLens = keyFile.get_string("LensProfile", "LFLens"); + if (pedited) { + pedited->lensProf.lfLens = true; + } + } } // load perspective correction @@ -8432,6 +8488,11 @@ bool ProcParams::operator== (const ProcParams& other) && lensProf.useDist == other.lensProf.useDist && lensProf.useVign == other.lensProf.useVign && lensProf.useCA == other.lensProf.useCA + && lensProf.useLensfun == other.lensProf.useLensfun + && lensProf.lfAutoMatch == other.lensProf.lfAutoMatch + && lensProf.lfCameraMake == other.lensProf.lfCameraMake + && lensProf.lfCameraModel == other.lensProf.lfCameraModel + && lensProf.lfLens == other.lensProf.lfLens && perspective.horizontal == other.perspective.horizontal && perspective.vertical == other.perspective.vertical && gradient.enabled == other.gradient.enabled diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 53561ce16..c7bedf611 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -831,6 +831,11 @@ class LensProfParams public: Glib::ustring lcpFile; bool useDist, useVign, useCA; + bool useLensfun; + bool lfAutoMatch; + Glib::ustring lfCameraMake; + Glib::ustring lfCameraModel; + Glib::ustring lfLens; LensProfParams() { @@ -839,6 +844,7 @@ public: void setDefaults(); }; + /** * Parameters of the perspective correction */ diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index 77710229a..fdf5b6bc8 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -33,6 +33,7 @@ #include "dcp.h" #include "rt_math.h" #include "improcfun.h" +#include "rtlensfun.h" #ifdef _OPENMP #include #endif @@ -1855,11 +1856,19 @@ void RawImageSource::preprocess (const RAWParams &raw, const LensProfParams &le // Correct vignetting of lens profile if (!hasFlatField && lensProf.useVign) { - LCPProfile *pLCPProf = lcpStore->getProfile(lensProf.lcpFile); + std::unique_ptr pmap; + if (lensProf.useLensfun) { + pmap = std::move(LFDatabase::findModifier(lensProf, idata, W, H, coarse, -1)); + } else { + 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); + 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 + pmap.reset(new LCPMapper(pLCPProf, max(idata->getFocalLen(), 1.0), idata->getFocalLen35mm(), idata->getFocusDist(), idata->getFNumber(), true, false, W, H, coarse, -1)); + } + } + if (pmap) { + LensCorrection &map = *pmap; if (ri->getSensorType() == ST_BAYER || ri->getSensorType() == ST_FUJI_XTRANS || ri->get_colors() == 1) { if(numFrames == 4) { for(int i = 0; i < 4; ++i) { diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index 5400a2e47..74eda6110 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -510,8 +510,10 @@ int refreshmap[rtengine::NUMOFEVENTS] = { LUMINANCECURVE, // EvCATtempsc LUMINANCECURVE, // EvCATgreensc LUMINANCECURVE, // EvCATybscen - LUMINANCECURVE // EvCATAutoyb - + LUMINANCECURVE, // EvCATAutoyb + DARKFRAME, // EvLensCorrMode + DARKFRAME, // EvLensCorrLensfunCamera + DARKFRAME // EvLensCorrLensfunLens }; diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index 9ba794a1f..c78d8ef40 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -48,6 +48,7 @@ class IImage8; class IImage16; class IImagefloat; + /** * This class represents provides functions to obtain exif and IPTC metadata information * from the image file diff --git a/rtengine/rtlensfun.cc b/rtengine/rtlensfun.cc new file mode 100644 index 000000000..87a7272e9 --- /dev/null +++ b/rtengine/rtlensfun.cc @@ -0,0 +1,424 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2017 Alberto Griggio + * + * 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 "rtlensfun.h" +#include "settings.h" +#include + +namespace rtengine { + +extern const Settings *settings; + +//----------------------------------------------------------------------------- +// LFModifier +//----------------------------------------------------------------------------- + +LFModifier::~LFModifier() +{ + if (data_) { + data_->Destroy(); + } +} + + +LFModifier::operator bool() const +{ + return data_; +} + + +void LFModifier::correctDistortion(double &x, double &y, int cx, int cy, double scale) const +{ + if (!data_) { + return; + } + + float pos[2]; + float xx = x + cx; + float yy = y + cy; + if (swap_xy_) { + std::swap(xx, yy); + } + if (data_->ApplyGeometryDistortion(xx, yy, 1, 1, pos)) { + x = pos[0]; + y = pos[1]; + if (swap_xy_) { + std::swap(x, y); + } + x -= cx; + y -= cy; + } + x *= scale; + y *= scale; +} + + +bool LFModifier::isCACorrectionAvailable() const +{ + return false; +} + + +void LFModifier::correctCA(double &x, double &y, int channel) const +{ +} + + +void LFModifier::processVignetteLine(int width, int y, float *line) const +{ + data_->ApplyColorModification(line, 0, y, width, 1, LF_CR_1(INTENSITY), 0); +} + + +void LFModifier::processVignetteLine3Channels(int width, int y, float *line) const +{ + data_->ApplyColorModification(line, 0, y, width, 1, LF_CR_3(RED, GREEN, BLUE), 0); +} + + +Glib::ustring LFModifier::getDisplayString() const +{ + if (!data_) { + return "NONE"; + } else { + Glib::ustring ret; + Glib::ustring sep = ""; + if (flags_ & LF_MODIFY_DISTORTION) { + ret += "distortion"; + sep = ", "; + } + if (flags_ & LF_MODIFY_VIGNETTING) { + ret += sep; + ret += "vignetting"; + sep = ", "; + } + if (flags_ & LF_MODIFY_SCALE) { + ret += sep; + ret += "autoscaling"; + } + return ret; + } +} + + +LFModifier::LFModifier(lfModifier *m, bool swap_xy, int flags): + data_(m), + swap_xy_(swap_xy), + flags_(flags) +{ +} + + +//----------------------------------------------------------------------------- +// LFCamera +//----------------------------------------------------------------------------- + +LFCamera::LFCamera(): + data_(nullptr) +{ +} + + +LFCamera::operator bool() const +{ + return data_; +} + + +Glib::ustring LFCamera::getMake() const +{ + if (data_) { + return data_->Maker; + } else { + return ""; + } +} + + +Glib::ustring LFCamera::getModel() const +{ + if (data_) { + return data_->Model; + } else { + return ""; + } +} + + +float LFCamera::getCropFactor() const +{ + if (data_) { + return data_->CropFactor; + } else { + return 0; + } +} + + +Glib::ustring LFCamera::getDisplayString() const +{ + if (data_) { + return Glib::ustring::compose("%1 %2", getMake(), getModel()); + } else { + return "---"; + } +} + + +//----------------------------------------------------------------------------- +// LFLens +//----------------------------------------------------------------------------- + +LFLens::LFLens(): + data_(nullptr) +{ +} + + +LFLens::operator bool() const +{ + return data_; +} + + +Glib::ustring LFLens::getMake() const +{ + if (data_) { + return data_->Maker; + } else { + return ""; + } +} + + +Glib::ustring LFLens::getLens() const +{ + if (data_) { + return Glib::ustring::compose("%1 %2", data_->Maker, data_->Model); + } else { + return "---"; + } +} + + +float LFLens::getCropFactor() const +{ + if (data_) { + return data_->CropFactor; + } else { + return 0; + } +} + +bool LFLens::hasVignettingCorrection() const +{ + if (data_) { + return data_->CalibVignetting; + } else { + return false; + } +} + +bool LFLens::hasDistortionCorrection() const +{ + if (data_) { + return data_->CalibDistortion; + } else { + return false; + } +} + + +//----------------------------------------------------------------------------- +// LFDatabase +//----------------------------------------------------------------------------- + +LFDatabase LFDatabase::instance_; + + +bool LFDatabase::init() +{ + instance_.data_ = lfDatabase::Create(); + return instance_.data_->Load() != LF_NO_ERROR; +} + + +LFDatabase::LFDatabase(): + data_(nullptr) +{ +} + + +LFDatabase::~LFDatabase() +{ + if (data_) { + data_->Destroy(); + } +} + + +const LFDatabase *LFDatabase::getInstance() +{ + return &instance_; +} + + +std::vector LFDatabase::getCameras() const +{ + std::vector ret; + if (data_) { + auto cams = data_->GetCameras(); + while (*cams) { + ret.emplace_back(); + ret.back().data_ = *cams; + ++cams; + } + } + return ret; +} + + +std::vector LFDatabase::getLenses() const +{ + std::vector ret; + if (data_) { + auto lenses = data_->GetLenses(); + while (*lenses) { + ret.emplace_back(); + ret.back().data_ = *lenses; + ++lenses; + } + } + return ret; +} + + +LFCamera LFDatabase::findCamera(const Glib::ustring &make, const Glib::ustring &model) const +{ + LFCamera ret; + if (data_) { + auto found = data_->FindCamerasExt(make.c_str(), model.c_str()); + if (found) { + ret.data_ = found[0]; + lf_free(found); + } + } + return ret; +} + + +LFLens LFDatabase::findLens(const LFCamera &camera, const Glib::ustring &name) const +{ + LFLens ret; + if (data_) { + Glib::ustring lname = name; + bool stdlens = camera && (name.empty() || name.find("Unknown") == 0); + if (stdlens) { + lname = camera.getModel(); // "Standard" + } + auto found = data_->FindLenses(camera.data_, nullptr, lname.c_str()); + if (!found) { + // try to split the maker from the model of the lens + Glib::ustring make, model; + auto i = name.find_first_of(' '); + if (i != Glib::ustring::npos) { + make = name.substr(0, i); + model = name.substr(i+1); + found = data_->FindLenses(camera.data_, make.c_str(), model.c_str()); + } + } + if (found) { + ret.data_ = found[0]; + lf_free(found); + } + } + return ret; +} + + +std::unique_ptr LFDatabase::getModifier(const LFCamera &camera, const LFLens &lens, + float focalLen, float aperture, float focusDist, + int width, int height, bool swap_xy) const +{ + std::unique_ptr ret; + if (data_) { + if (camera && lens) { + lfModifier *mod = lfModifier::Create(lens.data_, camera.getCropFactor(), width, height); + int flags = LF_MODIFY_DISTORTION | LF_MODIFY_SCALE; + if (aperture > 0) { + flags |= LF_MODIFY_VIGNETTING; + } + flags = mod->Initialize(lens.data_, LF_PF_F32, focalLen, aperture, focusDist > 0 ? focusDist : 1000, 0.0, LF_RECTILINEAR, flags, false); + ret.reset(new LFModifier(mod, swap_xy, flags)); + } + } + return ret; +} + + +std::unique_ptr LFDatabase::findModifier(const LensProfParams &lensProf, const ImageMetaData *idata, int width, int height, const CoarseTransformParams &coarse, int rawRotationDeg) +{ + const LFDatabase *db = getInstance(); + Glib::ustring make, model, lens; + float focallen = idata->getFocalLen(); + if (lensProf.lfAutoMatch) { + if (focallen <= 0) { + return nullptr; + } + make = idata->getMake(); + model = idata->getModel(); + lens = idata->getLens(); + } else { + make = lensProf.lfCameraMake; + model = lensProf.lfCameraModel; + lens = lensProf.lfLens; + } + LFCamera c = db->findCamera(make, model); + LFLens l = db->findLens(lensProf.lfAutoMatch ? c : LFCamera(), lens); + if (focallen <= 0 && l.data_ && l.data_->MinFocal == l.data_->MaxFocal) { + focallen = l.data_->MinFocal; + } + if (focallen <= 0) { + return nullptr; + } + bool swap_xy = false; + if (rawRotationDeg >= 0) { + int rot = (coarse.rotate + rawRotationDeg) % 360; + swap_xy = (rot == 90 || rot == 270); + if (swap_xy) { + std::swap(width, height); + } + } + + std::unique_ptr ret = db->getModifier(c, l, idata->getFocalLen(), idata->getFNumber(), idata->getFocusDist(), width, height, swap_xy); + + if (settings->verbose) { + std::cout << "LENSFUN:\n" + << " camera: " << c.getDisplayString() << "\n" + << " lens: " << l.getDisplayString() << "\n" + << " correction: " + << (ret ? ret->getDisplayString() : "NONE") << std::endl; + } + + return ret; +} + + +} // namespace rtengine diff --git a/rtengine/rtlensfun.h b/rtengine/rtlensfun.h new file mode 100644 index 000000000..5774968ee --- /dev/null +++ b/rtengine/rtlensfun.h @@ -0,0 +1,124 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2017 Alberto Griggio + * + * 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 . + */ + +#pragma once + +#include +#include + +#include + +#include + +#include "lcp.h" +#include "noncopyable.h" +#include "procparams.h" + +namespace rtengine { + +class LFModifier final : + public LensCorrection, + public NonCopyable +{ +public: + ~LFModifier(); + + explicit operator bool() const; + + void correctDistortion(double &x, double &y, int cx, int cy, double scale) const override; + bool isCACorrectionAvailable() const override; + void correctCA(double &x, double &y, int channel) const override; + void processVignetteLine(int width, int y, float *line) const override; + void processVignetteLine3Channels(int width, int y, float *line) const; + + Glib::ustring getDisplayString() const; + +private: + LFModifier(lfModifier *m, bool swap_xy, int flags); + + friend class LFDatabase; + lfModifier *data_; + bool swap_xy_; + int flags_; +}; + +class LFCamera final +{ +public: + LFCamera(); + + explicit operator bool() const; + + Glib::ustring getMake() const; + Glib::ustring getModel() const; + float getCropFactor() const; + + Glib::ustring getDisplayString() const; + +private: + friend class LFDatabase; + const lfCamera *data_; +}; + +class LFLens final +{ +public: + LFLens(); + + explicit operator bool() const; + + Glib::ustring getMake() const; + Glib::ustring getLens() const; + Glib::ustring getDisplayString() const { return getLens(); } + float getCropFactor() const; + bool hasVignettingCorrection() const; + bool hasDistortionCorrection() const; + +private: + friend class LFDatabase; + const lfLens *data_; +}; + +class LFDatabase final : + public NonCopyable +{ +public: + static bool init(); + static const LFDatabase *getInstance(); + + ~LFDatabase(); + + std::vector getCameras() const; + std::vector getLenses() const; + LFCamera findCamera(const Glib::ustring &make, const Glib::ustring &model) const; + LFLens findLens(const LFCamera &camera, const Glib::ustring &name) const; + + static std::unique_ptr findModifier(const LensProfParams &lensProf, const ImageMetaData *idata, int width, int height, const CoarseTransformParams &coarse, int rawRotationDeg); + +private: + std::unique_ptr getModifier(const LFCamera &camera, const LFLens &lens, + float focalLen, float aperture, float focusDist, + int width, int height, bool swap_xy) const; + LFDatabase(); + static LFDatabase instance_; + lfDatabase *data_; +}; + +} // namespace rtengine diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index 511b17f9f..065d4640c 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -954,9 +954,14 @@ IImage8* Thumbnail::quickProcessImage (const procparams::ProcParams& params, int } // Full thumbnail processing, second stage if complete profile exists -IImage8* Thumbnail::processImage (const procparams::ProcParams& params, int rheight, TypeInterpolation interp, std::string camName, - double focalLen, double focalLen35mm, float focusDist, float shutter, float fnumber, float iso, std::string expcomp_, double& myscale) +IImage8* Thumbnail::processImage (const procparams::ProcParams& params, int rheight, TypeInterpolation interp, const ImageMetaData *metadata, double& myscale) { + std::string camName = metadata->getCamera(); + float shutter = metadata->getShutterSpeed(); + float fnumber = metadata->getFNumber(); + float iso = metadata->getISOSpeed(); + float fcomp = metadata->getExpComp(); + // check if the WB's equalizer value has changed if (wbEqual < (params.wb.equal - 5e-4) || wbEqual > (params.wb.equal + 5e-4) || wbTempBias < (params.wb.tempBias - 5e-4) || wbTempBias > (params.wb.tempBias + 5e-4)) { wbEqual = params.wb.equal; @@ -1079,7 +1084,7 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, int rhei int origFH; double tscale = 0.0; getDimensions (origFW, origFH, tscale); - ipf.transform (baseImg, trImg, 0, 0, 0, 0, fw, fh, origFW * tscale + 0.5, origFH * tscale + 0.5, focalLen, focalLen35mm, focusDist, fnumber, 0, true); // Raw rotate degree not detectable here + ipf.transform (baseImg, trImg, 0, 0, 0, 0, fw, fh, origFW * tscale + 0.5, origFH * tscale + 0.5, metadata, 0, true); // Raw rotate degree not detectable here delete baseImg; baseImg = trImg; } @@ -1278,11 +1283,6 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, int rhei float fnum = fnumber;// F number float fiso = iso;// ISO float fspeed = shutter;//speed - char * writ = new char[expcomp_.size() + 1];//convert expcomp_ to char - std::copy (expcomp_.begin(), expcomp_.end(), writ); - writ[expcomp_.size()] = '\0'; - float fcomp = atof (writ); //compensation + - - delete[] writ; float adap; if (fnum < 0.3f || fiso < 5.f || fspeed < 0.00001f) diff --git a/rtengine/rtthumbnail.h b/rtengine/rtthumbnail.h index c40a226c4..3610a530f 100644 --- a/rtengine/rtthumbnail.h +++ b/rtengine/rtthumbnail.h @@ -71,8 +71,7 @@ public: void init (); - IImage8* processImage (const procparams::ProcParams& pparams, int rheight, TypeInterpolation interp, std::string camName, - double focalLen, double focalLen35mm, float focusDist, float shutter, float fnumber, float iso, std::string expcomp_, double& scale); + IImage8* processImage (const procparams::ProcParams& pparams, int rheight, TypeInterpolation interp, const ImageMetaData *metadata, double& scale); IImage8* quickProcessImage (const procparams::ProcParams& pparams, int rheight, TypeInterpolation interp, double& scale); int getImageWidth (const procparams::ProcParams& pparams, int rheight, float &ratio); void getDimensions (int& w, int& h, double& scaleFac); diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 14ad333e4..fd0a67411 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -813,10 +813,8 @@ private: // perform transform (excepted resizing) if (ipf.needsTransform()) { Imagefloat* trImg = new Imagefloat (fw, fh); - ipf.transform (baseImg, trImg, 0, 0, 0, 0, fw, fh, fw, fh, imgsrc->getMetaData()->getFocalLen(), imgsrc->getMetaData()->getFocalLen35mm(), - imgsrc->getMetaData()->getFocusDist(), - imgsrc->getMetaData()->getFNumber(), - imgsrc->getRotateDegree(), true); + ipf.transform (baseImg, trImg, 0, 0, 0, 0, fw, fh, fw, fh, + imgsrc->getMetaData(), imgsrc->getRotateDegree(), true); delete baseImg; baseImg = trImg; } diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index 05afd9af5..377b1bcbe 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -169,6 +169,7 @@ if(WIN32) ${GLIBMM_INCLUDE_DIRS} ${GTKMM_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS} + ${LENSFUN_INCLUDE_DIRS} ) link_directories(. "${PROJECT_SOURCE_DIR}/rtexif" ${EXTRA_LIBDIR} @@ -194,6 +195,7 @@ else() ${GTK_INCLUDE_DIRS} ${IPTCDATA_INCLUDE_DIRS} ${LCMS_INCLUDE_DIRS} + ${LENSFUN_INCLUDE_DIRS} ) link_directories(${EXTRA_LIBDIR} ${CANBERRA-GTK_LIBRARY_DIRS} @@ -251,6 +253,7 @@ target_link_libraries(rth rtengine ${PNG_LIBRARIES} ${TIFF_LIBRARIES} ${ZLIB_LIBRARIES} + ${LENSFUN_LIBRARIES} ) target_link_libraries(rth-cli rtengine @@ -270,6 +273,7 @@ target_link_libraries(rth-cli rtengine ${PNG_LIBRARIES} ${TIFF_LIBRARIES} ${ZLIB_LIBRARIES} + ${LENSFUN_LIBRARIES} ) # Install executables diff --git a/rtgui/cacheimagedata.h b/rtgui/cacheimagedata.h index f655bd88b..e1c317508 100644 --- a/rtgui/cacheimagedata.h +++ b/rtgui/cacheimagedata.h @@ -21,8 +21,9 @@ #include #include "options.h" +#include "../rtengine/rtengine.h" -class CacheImageData +class CacheImageData: public rtengine::ImageMetaData { public: @@ -76,9 +77,26 @@ public: int load (const Glib::ustring& fname); int save (const Glib::ustring& fname); - Glib::ustring getCamera() const - { - return Glib::ustring(camMake + " " + camModel); - } + //------------------------------------------------------------------------- + // ImageMetaData interface + //------------------------------------------------------------------------- + + bool hasExif() const { return false; } + const rtexif::TagDirectory *getExifData() const { return NULL; } + bool hasIPTC() const { return false; } + const rtengine::procparams::IPTCPairs getIPTCData () const { return rtengine::procparams::IPTCPairs(); } + struct tm getDateTime () const { struct tm ret; return ret; } + time_t getDateTimeAsTS() const { return time_t(-1); } + int getISOSpeed() const { return iso; } + double getFNumber() const { return fnumber; } + double getFocalLen() const { return focalLen; } + double getFocalLen35mm() const { return focalLen35mm; } + float getFocusDist() const { return focusDist; } + double getShutterSpeed() const { return shutter; } + double getExpComp() const { return atof(expcomp.c_str()); } + std::string getMake() const { return camMake; } + std::string getModel() const { return camModel; } + std::string getLens() const { return lens; } + std::string getOrientation() const { return ""; } // TODO }; #endif diff --git a/rtgui/lensprofile.cc b/rtgui/lensprofile.cc index 0855ef03f..bb9138461 100644 --- a/rtgui/lensprofile.cc +++ b/rtgui/lensprofile.cc @@ -22,15 +22,82 @@ #include "../rtengine/lcp.h" #include #include "rtimage.h" +#include "../rtengine/rtlensfun.h" +#include +#include using namespace rtengine; using namespace rtengine::procparams; -LensProfilePanel::LensProfilePanel () : FoldableToolPanel(this, "lensprof", M("TP_LENSPROFILE_LABEL")), lcpFileChanged(false), useDistChanged(false), useVignChanged(false), useCAChanged(false), isRaw(true), lensgeomLcpFill(nullptr) -{ - hbLCPFile = Gtk::manage(new Gtk::HBox()); +LensProfilePanel::LFDbHelper *LensProfilePanel::lf(nullptr); - lLCPFileHead = Gtk::manage(new Gtk::Label(M("GENERAL_FILE"))); +LensProfilePanel::LensProfilePanel () : + FoldableToolPanel(this, "lensprof", M("TP_LENSPROFILE_LABEL")), + lcpFileChanged(false), + useDistChanged(false), + useVignChanged(false), + useCAChanged(false), + isRaw(true), + metadata(nullptr), + useLensfunChanged(false), + lensfunAutoChanged(false), + lensfunCameraChanged(false), + lensfunLensChanged(false) +{ + if (!lf) { + lf = new LFDbHelper(); + } + + corrUnchanged = Gtk::manage(new Gtk::RadioButton(M("GENERAL_UNCHANGED"))); + pack_start(*corrUnchanged); + + corrGroup = corrUnchanged->get_group(); + + corrOff = Gtk::manage(new Gtk::RadioButton(corrGroup, M("GENERAL_NONE"))); + pack_start(*corrOff); + + corrLensfunAuto = Gtk::manage(new Gtk::RadioButton(corrGroup, M("LENSPROFILE_CORRECTION_AUTOMATCH"))); + pack_start(*corrLensfunAuto); + + corrLensfunManual = Gtk::manage(new Gtk::RadioButton(corrGroup, M("LENSPROFILE_CORRECTION_MANUAL"))); + pack_start(*corrLensfunManual); + + lensfunCameras = Gtk::manage(new MyComboBox()); + lensfunCameras->set_model(lf->lensfunCameraModel); + lensfunCameras->pack_start(lf->lensfunModelCam.model); + Gtk::CellRendererText* cellRenderer = dynamic_cast(lensfunCameras->get_first_cell()); + cellRenderer->property_ellipsize() = Pango::ELLIPSIZE_MIDDLE; + cellRenderer->property_ellipsize_set() = true; + lensfunCameras->setPreferredWidth(50, 120); + + lensfunLenses = Gtk::manage(new MyComboBox()); + lensfunLenses->set_model(lf->lensfunLensModel); + lensfunLenses->pack_start(lf->lensfunModelLens.prettylens); + cellRenderer = dynamic_cast(lensfunLenses->get_first_cell()); + cellRenderer->property_ellipsize() = Pango::ELLIPSIZE_MIDDLE; + cellRenderer->property_ellipsize_set() = true; + lensfunLenses->setPreferredWidth(50, 120); + + Gtk::HBox *hb = Gtk::manage(new Gtk::HBox()); + hb->pack_start(*Gtk::manage(new Gtk::Label(M("EXIFFILTER_CAMERA"))), Gtk::PACK_SHRINK, 4); + hb->pack_start(*lensfunCameras); + pack_start(*hb); + + hb = Gtk::manage(new Gtk::HBox()); + hb->pack_start(*Gtk::manage(new Gtk::Label(M("EXIFFILTER_LENS"))), Gtk::PACK_SHRINK, 4); + hb->pack_start(*lensfunLenses); + warning = Gtk::manage(new Gtk::Image()); + warning->set_from_icon_name("dialog-warning", Gtk::ICON_SIZE_LARGE_TOOLBAR); + warning->set_tooltip_text(M("LENSPROFILE_LENS_WARNING")); + warning->hide(); + hb->pack_start(*warning, Gtk::PACK_SHRINK, 4); + pack_start(*hb); + + corrLcpFile = Gtk::manage(new Gtk::RadioButton(corrGroup)); + hbLCPFile = Gtk::manage(new Gtk::HBox()); + hbLCPFile->pack_start(*corrLcpFile, Gtk::PACK_SHRINK); + + lLCPFileHead = Gtk::manage(new Gtk::Label(M("LENSPROFILE_CORRECTION_LCPFILE"))); hbLCPFile->pack_start(*lLCPFileHead, Gtk::PACK_SHRINK, 4); fcbLCPFile = Gtk::manage(new MyFileChooserButton(M("TP_LENSPROFILE_LABEL"), Gtk::FILE_CHOOSER_ACTION_OPEN)); @@ -41,7 +108,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 @@ -74,6 +141,15 @@ LensProfilePanel::LensProfilePanel () : FoldableToolPanel(this, "lensprof", M("T ckbUseVign->signal_toggled().connect( sigc::mem_fun(*this, &LensProfilePanel::onUseVignChanged) ); ckbUseCA->signal_toggled().connect( sigc::mem_fun(*this, &LensProfilePanel::onUseCAChanged) ); + lensfunCameras->signal_changed().connect(sigc::mem_fun(*this, &LensProfilePanel::onLensfunCameraChanged)); + lensfunLenses->signal_changed().connect(sigc::mem_fun(*this, &LensProfilePanel::onLensfunLensChanged)); + corrOff->signal_toggled().connect(sigc::mem_fun(*this, &LensProfilePanel::onCorrModeChanged)); + corrLensfunAuto->signal_toggled().connect(sigc::mem_fun(*this, &LensProfilePanel::onCorrModeChanged)); + corrLensfunManual->signal_toggled().connect(sigc::mem_fun(*this, &LensProfilePanel::onCorrModeChanged)); + corrLcpFile->signal_toggled().connect(sigc::mem_fun(*this, &LensProfilePanel::onCorrModeChanged)); + + corrUnchanged->hide(); + allowFocusDep = true; } @@ -82,7 +158,20 @@ 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 (!batchMode) { + corrUnchanged->hide(); + } + + corrLensfunAuto->set_sensitive(true); + + if (pp->lensProf.useLensfun) { + if (pp->lensProf.lfAutoMatch) { + corrLensfunAuto->set_active(true); + } else { + corrLensfunManual->set_active(true); + } + } else if (!pp->lensProf.lcpFile.empty() && LCPStore::getInstance()->isValidLCPFileName(pp->lensProf.lcpFile)) { + corrLcpFile->set_active(true); fcbLCPFile->set_filename (pp->lensProf.lcpFile); updateDisabled(true); } else { @@ -98,18 +187,76 @@ void LensProfilePanel::read(const rtengine::procparams::ProcParams* pp, const Pa } updateDisabled(false); + + corrOff->set_active(true); } ckbUseDist->set_active (pp->lensProf.useDist); ckbUseVign->set_active (pp->lensProf.useVign && isRaw); ckbUseCA->set_active (pp->lensProf.useCA && isRaw); + const LFDatabase *db = LFDatabase::getInstance(); + LFCamera c; + LFLens l; + if (metadata) { + c = db->findCamera(metadata->getMake(), metadata->getModel()); + l = db->findLens(c, metadata->getLens()); + } + + if (!setLensfunCamera(pp->lensProf.lfCameraMake, pp->lensProf.lfCameraModel) && pp->lensProf.lfAutoMatch) { + setLensfunCamera(c.getMake(), c.getModel()); + } + if (!setLensfunLens(pp->lensProf.lfLens) && pp->lensProf.lfAutoMatch) { + setLensfunLens(l.getLens()); + } + lcpFileChanged = useDistChanged = useVignChanged = useCAChanged = false; + useLensfunChanged = lensfunAutoChanged = lensfunCameraChanged = lensfunLensChanged = false; + + if (!batchMode && !checkLensfunCanCorrect(true)) { + if (corrLensfunAuto->get_active()) { + corrOff->set_active(true); + } + corrLensfunAuto->set_sensitive(false); + } + + if (corrLensfunManual->get_active() && !checkLensfunCanCorrect(false)) { + corrOff->set_active(true); + } + + updateLensfunWarning(); enableListener (); conUseDist.block(false); } + +void LensProfilePanel::updateLensfunWarning() +{ + warning->hide(); + if (corrLensfunManual->get_active()) { + const LFDatabase *db = LFDatabase::getInstance(); + + auto itc = lensfunCameras->get_active(); + if (!itc) { + return; + } + LFCamera c = db->findCamera((*itc)[lf->lensfunModelCam.make], (*itc)[lf->lensfunModelCam.model]); + auto itl = lensfunLenses->get_active(); + if (!itl) { + return; + } + LFLens l = db->findLens(LFCamera(), (*itl)[lf->lensfunModelLens.lens]); + float lenscrop = l.getCropFactor(); + float camcrop = c.getCropFactor(); + if (lenscrop <= 0 || camcrop <= 0 || lenscrop / camcrop >= 1.01f) { + warning->show(); + } + ckbUseVign->set_sensitive(l.hasVignettingCorrection()); + ckbUseDist->set_sensitive(l.hasDistortionCorrection()); + } +} + void LensProfilePanel::setRawMeta(bool raw, const rtengine::ImageMetaData* pMeta) { if (!raw || pMeta->getFocusDist() <= 0) { @@ -124,11 +271,12 @@ void LensProfilePanel::setRawMeta(bool raw, const rtengine::ImageMetaData* pMeta } isRaw = raw; + metadata = pMeta; } void LensProfilePanel::write( rtengine::procparams::ProcParams* pp, ParamsEdited* pedited) { - if (lcpStore->isValidLCPFileName(fcbLCPFile->get_filename())) { + if (corrLcpFile->get_active() && LCPStore::getInstance()->isValidLCPFileName(fcbLCPFile->get_filename())) { pp->lensProf.lcpFile = fcbLCPFile->get_filename(); } else { pp->lensProf.lcpFile = ""; @@ -138,20 +286,48 @@ void LensProfilePanel::write( rtengine::procparams::ProcParams* pp, ParamsEdited pp->lensProf.useVign = ckbUseVign->get_active(); pp->lensProf.useCA = ckbUseCA->get_active(); + pp->lensProf.useLensfun = corrLensfunAuto->get_active() || corrLensfunManual->get_active(); + pp->lensProf.lfAutoMatch = corrLensfunAuto->get_active(); + auto itc = lensfunCameras->get_active(); + if (itc) { + pp->lensProf.lfCameraMake = (*itc)[lf->lensfunModelCam.make]; + pp->lensProf.lfCameraModel = (*itc)[lf->lensfunModelCam.model]; + } else { + pp->lensProf.lfCameraMake = ""; + pp->lensProf.lfCameraModel = ""; + } + auto itl = lensfunLenses->get_active(); + if (itl) { + pp->lensProf.lfLens = (*itl)[lf->lensfunModelLens.lens]; + } else { + pp->lensProf.lfLens = ""; + } + if (pedited) { pedited->lensProf.lcpFile = lcpFileChanged; pedited->lensProf.useDist = useDistChanged; pedited->lensProf.useVign = useVignChanged; pedited->lensProf.useCA = useCAChanged; + pedited->lensProf.useLensfun = useLensfunChanged; + pedited->lensProf.lfAutoMatch = lensfunAutoChanged; + pedited->lensProf.lfCameraMake = lensfunCameraChanged; + pedited->lensProf.lfCameraModel = lensfunCameraChanged; + pedited->lensProf.lfLens = lensfunLensChanged; } } void LensProfilePanel::onLCPFileChanged() { lcpFileChanged = true; - updateDisabled(lcpStore->isValidLCPFileName(fcbLCPFile->get_filename())); + bool valid = LCPStore::getInstance()->isValidLCPFileName(fcbLCPFile->get_filename()); + updateDisabled(valid); if (listener) { + if (valid) { + disableListener(); + corrLcpFile->set_active(true); + enableListener(); + } listener->panelChanged (EvLCPFile, Glib::path_get_basename(fcbLCPFile->get_filename())); } } @@ -163,7 +339,11 @@ void LensProfilePanel::onLCPFileReset() fcbLCPFile->unselect_filename(fcbLCPFile->get_filename()); updateDisabled(false); + if (listener) { + disableListener(); + corrOff->set_active(true); + enableListener(); listener->panelChanged (EvLCPFile, M("GENERAL_NONE")); } } @@ -199,3 +379,280 @@ void LensProfilePanel::updateDisabled(bool enable) ckbUseVign->set_sensitive(enable && isRaw); ckbUseCA->set_sensitive(enable && allowFocusDep); } + +void LensProfilePanel::setBatchMode(bool yes) +{ + FoldableToolPanel::setBatchMode(yes); + if (yes) { + corrUnchanged->show(); + corrUnchanged->set_active(true); + } else { + corrUnchanged->hide(); + } +} + + +bool LensProfilePanel::setLensfunCamera(const Glib::ustring &make, const Glib::ustring &model) +{ + if (!make.empty() && !model.empty()) { + auto it = lensfunCameras->get_active(); + if (it && (*it)[lf->lensfunModelCam.make] == make && (*it)[lf->lensfunModelCam.model] == model) { + return true; + } + + // search for the active row + for (auto row : lf->lensfunCameraModel->children()) { + if (row[lf->lensfunModelCam.make] == make) { + auto &c = row.children(); + for (auto it = c.begin(), end = c.end(); it != end; ++it) { + auto &childrow = *it; + if (childrow[lf->lensfunModelCam.model] == model) { + lensfunCameras->set_active(it); + return true; + } + } + break; + } + } + } + lensfunCameras->set_active(-1); + return false; +} + + +bool LensProfilePanel::setLensfunLens(const Glib::ustring &lens) +{ + if (!lens.empty()) { + auto it = lensfunLenses->get_active(); + if (it && (*it)[lf->lensfunModelLens.lens] == lens) { + return true; + } + + for (auto row : lf->lensfunLensModel->children()) { + if (lens.find(row[lf->lensfunModelLens.lens]) == 0) { + auto &c = row.children(); + for (auto it = c.begin(), end = c.end(); it != end; ++it) { + auto &childrow = *it; + if (childrow[lf->lensfunModelLens.lens] == lens) { + lensfunLenses->set_active(it); + return true; + } + } + break; + } + } + } + lensfunLenses->set_active(-1); + return false; +} + + + +void LensProfilePanel::onLensfunCameraChanged() +{ + auto iter = lensfunCameras->get_active(); + + if (iter) { + lensfunCameraChanged = true; + + if (listener) { + disableListener(); + corrLensfunManual->set_active(true); + enableListener(); + + Glib::ustring name = (*iter)[lf->lensfunModelCam.model]; + listener->panelChanged(EvLensCorrLensfunCamera, name); + } + } + + updateLensfunWarning(); +} + + +void LensProfilePanel::onLensfunLensChanged() +{ + auto iter = lensfunLenses->get_active(); + + if (iter) { + lensfunLensChanged = true; + + if (listener) { + disableListener(); + corrLensfunManual->set_active(true); + enableListener(); + + Glib::ustring name = (*iter)[lf->lensfunModelLens.prettylens]; + listener->panelChanged(EvLensCorrLensfunLens, name); + } + } + + updateLensfunWarning(); +} + + +void LensProfilePanel::onCorrModeChanged() +{ + Glib::ustring mode; + + if (corrOff->get_active()) { + useLensfunChanged = true; + lensfunAutoChanged = true; + lcpFileChanged = true; + + ckbUseDist->set_sensitive(false); + ckbUseVign->set_sensitive(false); + ckbUseCA->set_sensitive(false); + + mode = M("GENERAL_NONE"); + } else if (corrLensfunAuto->get_active()) { + useLensfunChanged = true; + lensfunAutoChanged = true; + lcpFileChanged = true; + useDistChanged = true; + useVignChanged = true; + + ckbUseDist->set_sensitive(true); + ckbUseVign->set_sensitive(true); + ckbUseCA->set_sensitive(false); + + if (metadata) { + bool b = disableListener(); + const LFDatabase *db = LFDatabase::getInstance(); + LFCamera c = db->findCamera(metadata->getMake(), metadata->getModel()); + LFLens l = db->findLens(c, metadata->getLens()); + setLensfunCamera(c.getMake(), c.getModel()); + setLensfunLens(l.getLens()); + if (b) { + enableListener(); + } + } + + mode = M("LENSPROFILE_CORRECTION_AUTOMATCH"); + } else if (corrLensfunManual->get_active()) { + useLensfunChanged = true; + lensfunAutoChanged = true; + lcpFileChanged = true; + useDistChanged = true; + useVignChanged = true; + + ckbUseDist->set_sensitive(true); + ckbUseVign->set_sensitive(true); + ckbUseCA->set_sensitive(false); + + mode = M("LENSPROFILE_CORRECTION_MANUAL"); + } else if (corrLcpFile->get_active()) { + useLensfunChanged = true; + lensfunAutoChanged = true; + lcpFileChanged = true; + useDistChanged = true; + useVignChanged = true; + + updateDisabled(true); + + mode = M("LENSPROFILE_CORRECTION_LCPFILE"); + } else if (corrUnchanged->get_active()) { + useLensfunChanged = false; + lensfunAutoChanged = false; + lcpFileChanged = false; + lensfunCameraChanged = false; + lensfunLensChanged = false; + + ckbUseDist->set_sensitive(true); + ckbUseVign->set_sensitive(true); + ckbUseCA->set_sensitive(true); + + mode = M("GENERAL_UNCHANGED"); + } + + updateLensfunWarning(); + + if (listener) { + listener->panelChanged(EvLensCorrMode, mode); + } +} + + +bool LensProfilePanel::checkLensfunCanCorrect(bool automatch) +{ + if (!metadata) { + return false; + } + rtengine::procparams::ProcParams lpp; + write(&lpp); + lpp.lensProf.lfAutoMatch = automatch; + std::unique_ptr mod(LFDatabase::findModifier(lpp.lensProf, metadata, 100, 100, lpp.coarse, -1)); + return mod.get() != nullptr; +} + + +//----------------------------------------------------------------------------- +// LFDbHelper +//----------------------------------------------------------------------------- + +LensProfilePanel::LFDbHelper::LFDbHelper() +{ + lensfunCameraModel = Gtk::TreeStore::create(lensfunModelCam); + lensfunLensModel = Gtk::TreeStore::create(lensfunModelLens); + + fillLensfunCameras(); + fillLensfunLenses(); +} + +void LensProfilePanel::LFDbHelper::fillLensfunCameras() +{ + if (options.rtSettings.verbose) { + std::cout << "LENSFUN, scanning cameras:" << std::endl; + } + std::map> camnames; + auto camlist = LFDatabase::getInstance()->getCameras(); + for (auto &c : camlist) { + camnames[c.getMake()].insert(c.getModel()); + + if (options.rtSettings.verbose) { + std::cout << " found: " << c.getDisplayString() << std::endl; + } + } + for (auto &p : camnames) { + Gtk::TreeModel::Row row = *(lensfunCameraModel->append()); + row[lensfunModelCam.make] = p.first; + row[lensfunModelCam.model] = p.first; + for (auto &c : p.second) { + Gtk::TreeModel::Row child = *(lensfunCameraModel->append(row.children())); + child[lensfunModelCam.make] = p.first; + child[lensfunModelCam.model] = c; + } + } +} + + +void LensProfilePanel::LFDbHelper::fillLensfunLenses() +{ + if (options.rtSettings.verbose) { + std::cout << "LENSFUN, scanning lenses:" << std::endl; + } + std::map> lenses; + auto lenslist = LFDatabase::getInstance()->getLenses(); + for (auto &l : lenslist) { + auto name = l.getLens(); + auto make = l.getMake(); + lenses[make].insert(name); + + if (options.rtSettings.verbose) { + std::cout << " found: " << l.getDisplayString() << std::endl; + } + } + for (auto &p : lenses) { + Gtk::TreeModel::Row row = *(lensfunLensModel->append()); + row[lensfunModelLens.lens] = p.first; + row[lensfunModelLens.prettylens] = p.first; + for (auto &c : p.second) { + Gtk::TreeModel::Row child = *(lensfunLensModel->append(row.children())); + child[lensfunModelLens.lens] = c; + if (c.find(p.first, p.first.size()+1) == p.first.size()+1) { + child[lensfunModelLens.prettylens] = c.substr(p.first.size()+1); + } else { + child[lensfunModelLens.prettylens] = c; + } + } + } +} diff --git a/rtgui/lensprofile.h b/rtgui/lensprofile.h index 9543721a0..aca8f16ef 100644 --- a/rtgui/lensprofile.h +++ b/rtgui/lensprofile.h @@ -39,8 +39,56 @@ protected: void updateDisabled(bool enable); bool allowFocusDep; bool isRaw; - LensGeometry *lensgeomLcpFill; + const rtengine::ImageMetaData* metadata; + Gtk::RadioButton::Group corrGroup; + Gtk::RadioButton *corrOff; + Gtk::RadioButton *corrLensfunAuto; + Gtk::RadioButton *corrLensfunManual; + Gtk::RadioButton *corrLcpFile; + Gtk::RadioButton *corrUnchanged; + MyComboBox *lensfunCameras; + MyComboBox *lensfunLenses; + Gtk::Image *warning; + + class LFDbHelper { + public: + class LFModelCam: public Gtk::TreeModel::ColumnRecord { + public: + LFModelCam() { add(make); add(model); } + Gtk::TreeModelColumn make; + Gtk::TreeModelColumn model; + }; + + class LFModelLens: public Gtk::TreeModel::ColumnRecord { + public: + LFModelLens() { add(lens); add(prettylens); } + Gtk::TreeModelColumn lens; + Gtk::TreeModelColumn prettylens; + }; + + LFModelCam lensfunModelCam; + LFModelLens lensfunModelLens; + + Glib::RefPtr lensfunCameraModel; + Glib::RefPtr lensfunLensModel; + + LFDbHelper(); + void fillLensfunCameras(); + void fillLensfunLenses(); + }; + static LFDbHelper *lf; + + bool useLensfunChanged; + bool lensfunAutoChanged; + bool lensfunCameraChanged; + bool lensfunLensChanged; + + bool setLensfunCamera(const Glib::ustring &make, const Glib::ustring &model); + bool setLensfunLens(const Glib::ustring &lens); + bool checkLensfunCanCorrect(bool automatch); + void updateLensfunWarning(); + public: LensProfilePanel (); @@ -54,10 +102,12 @@ public: void onUseDistChanged(); void onUseVignChanged(); void onUseCAChanged(); - void setLensGeomRef( LensGeometry *foo) - { - lensgeomLcpFill = foo ; - }; + + void setBatchMode(bool yes); + + void onLensfunCameraChanged(); + void onLensfunLensChanged(); + void onCorrModeChanged(); }; #endif diff --git a/rtgui/main.cc b/rtgui/main.cc index 3ebd894df..f5e134b5a 100644 --- a/rtgui/main.cc +++ b/rtgui/main.cc @@ -33,6 +33,7 @@ #include #include #include +#include #include "options.h" #include "soundman.h" #include "rtimage.h" @@ -138,6 +139,7 @@ int processLineParams ( int argc, char **argv ) #endif case 'v': + std::cout << "Using lensfun " << LF_VERSION_MAJOR << "." << LF_VERSION_MINOR << "." << LF_VERSION_MICRO << "." << LF_VERSION_BUGFIX << std::endl; return 0; #ifndef __APPLE__ // TODO agriggio - there seems to be already some "single instance app" support for OSX in rtwindow. Disabling it here until I understand how to merge the two diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index adff63e45..7c32265d9 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -291,6 +291,11 @@ void ParamsEdited::set (bool v) lensProf.useDist = v; lensProf.useVign = v; lensProf.useCA = v; + lensProf.useLensfun = v; + lensProf.lfAutoMatch = v; + lensProf.lfCameraMake = v; + lensProf.lfCameraModel = v; + lensProf.lfLens = v; perspective.horizontal = v; perspective.vertical = v; gradient.enabled = v; @@ -825,6 +830,11 @@ void ParamsEdited::initFrom (const std::vector lensProf.useDist = lensProf.useDist && p.lensProf.useDist == other.lensProf.useDist; lensProf.useVign = lensProf.useVign && p.lensProf.useVign == other.lensProf.useVign; lensProf.useCA = lensProf.useCA && p.lensProf.useCA == other.lensProf.useCA; + lensProf.useLensfun = lensProf.useLensfun && p.lensProf.useLensfun == other.lensProf.useLensfun; + lensProf.lfAutoMatch = lensProf.lfAutoMatch && p.lensProf.lfAutoMatch == other.lensProf.lfAutoMatch; + lensProf.lfCameraMake = lensProf.lfCameraMake && p.lensProf.lfCameraMake == other.lensProf.lfCameraMake; + lensProf.lfCameraModel = lensProf.lfCameraModel && p.lensProf.lfCameraModel == other.lensProf.lfCameraModel; + lensProf.lfLens = lensProf.lfLens && p.lensProf.lfLens == other.lensProf.lfLens; perspective.horizontal = perspective.horizontal && p.perspective.horizontal == other.perspective.horizontal; perspective.vertical = perspective.vertical && p.perspective.vertical == other.perspective.vertical; gradient.enabled = gradient.enabled && p.gradient.enabled == other.gradient.enabled; @@ -2068,6 +2078,26 @@ void ParamsEdited::combine (rtengine::procparams::ProcParams& toEdit, const rten toEdit.lensProf.useCA = mods.lensProf.useCA; } + if (lensProf.useLensfun) { + toEdit.lensProf.useLensfun = mods.lensProf.useLensfun; + } + + if (lensProf.lfAutoMatch) { + toEdit.lensProf.lfAutoMatch = mods.lensProf.lfAutoMatch; + } + + if (lensProf.lfCameraMake) { + toEdit.lensProf.lfCameraMake = mods.lensProf.lfCameraMake; + } + + if (lensProf.lfCameraModel) { + toEdit.lensProf.lfCameraModel = mods.lensProf.lfCameraModel; + } + + if (lensProf.lfLens) { + toEdit.lensProf.lfLens = mods.lensProf.lfLens; + } + if (perspective.horizontal) { toEdit.perspective.horizontal = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.horizontal + mods.perspective.horizontal : mods.perspective.horizontal; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 35e3c80b8..c30134f86 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -428,6 +428,7 @@ class LensProfParamsEdited { public: bool lcpFile, useDist, useVign, useCA; + bool useLensfun, lfAutoMatch, lfCameraMake, lfCameraModel, lfLens; bool isUnchanged() const; }; diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index 9db7c69b6..2d67a4a03 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -602,7 +602,8 @@ rtengine::IImage8* Thumbnail::processThumbImage (const rtengine::procparams::Pro image = tpp->quickProcessImage (pparams, h, rtengine::TI_Nearest, scale); } else { // Full thumbnail: apply profile - image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, cfs.getCamera(), cfs.focalLen, cfs.focalLen35mm, cfs.focusDist, cfs.shutter, cfs.fnumber, cfs.iso, cfs.expcomp, scale ); + // image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, cfs.getCamera(), cfs.focalLen, cfs.focalLen35mm, cfs.focusDist, cfs.shutter, cfs.fnumber, cfs.iso, cfs.expcomp, scale ); + image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, &cfs, scale ); } tpp->getDimensions(lastW, lastH, lastScale); @@ -627,7 +628,8 @@ rtengine::IImage8* Thumbnail::upgradeThumbImage (const rtengine::procparams::Pro return nullptr; } - rtengine::IImage8* image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, cfs.getCamera(), cfs.focalLen, cfs.focalLen35mm, cfs.focusDist, cfs.shutter, cfs.fnumber, cfs.iso, cfs.expcomp, scale ); + // rtengine::IImage8* image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, cfs.getCamera(), cfs.focalLen, cfs.focalLen35mm, cfs.focusDist, cfs.shutter, cfs.fnumber, cfs.iso, cfs.expcomp, scale ); + rtengine::IImage8* image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, &cfs, scale ); tpp->getDimensions(lastW, lastH, lastScale); delete tpp; diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index d2c0fba2d..ad0bd09bc 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -53,7 +53,6 @@ ToolPanelCoordinator::ToolPanelCoordinator () : ipc (nullptr), hasChanged (false colortoning = Gtk::manage (new ColorToning ()); lensgeom = Gtk::manage (new LensGeometry ()); lensProf = Gtk::manage (new LensProfilePanel ()); - lensProf->setLensGeomRef (lensgeom); distortion = Gtk::manage (new Distortion ()); rotate = Gtk::manage (new Rotate ()); vibrance = Gtk::manage (new Vibrance ());