diff --git a/rtdata/languages/default b/rtdata/languages/default index da4d5c804..376eb6072 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1803,9 +1803,11 @@ TP_PCVIGNETTE_ROUNDNESS_TOOLTIP;Roundness:\n0 = rectangle,\n50 = fitted ellipse, TP_PCVIGNETTE_STRENGTH;Strength TP_PCVIGNETTE_STRENGTH_TOOLTIP;Filter strength in stops (reached in corners). TP_PDSHARPENING_LABEL;Capture Sharpening +TP_PERSPECTIVE_FOV;Field of view TP_PERSPECTIVE_HORIZONTAL;Horizontal TP_PERSPECTIVE_LABEL;Perspective TP_PERSPECTIVE_VERTICAL;Vertical +TP_PERSPECTIVE_VERTICAL_BIAS;Vertical bias TP_PFCURVE_CURVEEDITOR_CH;Hue TP_PFCURVE_CURVEEDITOR_CH_TOOLTIP;Controls defringe strength by color.\nHigher = more,\nLower = less. TP_PREPROCESS_DEADPIXFILT;Dead pixel filter diff --git a/rtengine/iptransform.cc b/rtengine/iptransform.cc index af513536e..7e509d584 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -236,20 +236,38 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, 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.0 - 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.0 - 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 = ascaleDef > 0 ? ascaleDef : (params->commonTrans.autofill ? getTransformAutoFill (oW, oH, pLCPMap) : 1.0); + // auxiliary variables for perspective correction + const double f = maxRadius / tan(params->perspective.fov / 360.0 * rtengine::RT_PI); + const double phtheta = params->perspective.horizontal / -180.0 * rtengine::RT_PI; + const double pvtheta = params->perspective.vertical / -180.0 * rtengine::RT_PI; + const double pbtheta = params->perspective.vBias / -180.0 * rtengine::RT_PI; + const double phcos = cos(phtheta); + const double pvcos = cos(pvtheta); + const double pbcos = cos(pbtheta); + const double phsin = sin(phtheta); + const double pvsin = sin(pvtheta); + const double pbsin = sin(pbtheta); + // Coordinates of distorted image center. + const double pxoffset = f * phsin * pvcos; + const double pyoffset = -f * (phcos * pvcos * pbsin + pvsin * pbcos); + const double pz = f * (phcos * pvcos * pbcos - pvsin * pbsin); + // Inverse transformation matrix. + const double p_xx = f * phcos; + const double p_xy = f * phsin * pbsin; + const double p_xz = f * phsin * pbcos; + const double p_yx = f * phsin * pvsin; + const double p_yy = f * (pvcos * pbcos - phcos * pvsin * pbsin); + const double p_yz = f * (-pvcos * pbsin - phcos * pvsin * pbcos); + const double p_zx = -phsin * pvcos; + const double p_zy = phcos * pvcos * pbsin + pvsin * pbcos; + const double p_zz = phcos * pvcos * pbcos - pvsin * pbsin; + // z is known, can calculate these in advance. + const double pz_xz = pz * p_xz; + const double pz_yz = pz * p_yz; + const double pz_zz = pz * p_zz; + for (size_t i = 0; i < src.size(); i++) { double x_d = src[i].x, y_d = src[i].y; @@ -264,13 +282,13 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, y_d += ascale * (0 - 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); + x_d -= pxoffset; + y_d -= pyoffset; + const double normalizer = p_zx * x_d + p_zy * y_d + pz_zz; + const double x_d_new = p_xx * x_d + p_xy * y_d + pz_xz; + y_d = p_yx * x_d + p_yy * y_d + pz_yz; + x_d = x_d_new / normalizer; + y_d /= normalizer; } // rotate @@ -903,28 +921,42 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I const double cost = cos(params->rotate.degree * rtengine::RT_PI / 180.0); const double sint = sin(params->rotate.degree * rtengine::RT_PI / 180.0); - // auxiliary variables for vertical perspective correction - const double vpdeg = params->perspective.vertical / 100.0 * 45.0; - const double vpalpha = (90.0 - vpdeg) / 180.0 * rtengine::RT_PI; - const double vpteta = fabs(vpalpha - rtengine::RT_PI / 2) < 3e-4 ? 0.0 : acos((vpdeg > 0 ? 1.0 : -1.0) * sqrt((-SQR(oW * tan(vpalpha)) + (vpdeg > 0 ? 1.0 : -1.0) * - oW * tan(vpalpha) * sqrt(SQR(4 * maxRadius) + SQR(oW * tan(vpalpha)))) / (SQR(maxRadius) * 8))); - const double vpcospt = (vpdeg >= 0 ? 1.0 : -1.0) * cos(vpteta); - const double vptanpt = tan(vpteta); - - // auxiliary variables for horizontal perspective correction - const double hpdeg = params->perspective.horizontal / 100.0 * 45.0; - const double hpalpha = (90.0 - hpdeg) / 180.0 * rtengine::RT_PI; - const double hpteta = fabs(hpalpha - rtengine::RT_PI / 2) < 3e-4 ? 0.0 : acos((hpdeg > 0 ? 1.0 : -1.0) * sqrt((-SQR(oH * tan(hpalpha)) + (hpdeg > 0 ? 1.0 : -1.0) * - oH * tan(hpalpha) * sqrt(SQR(4 * maxRadius) + SQR(oH * tan(hpalpha)))) / (SQR(maxRadius) * 8))); - const double hpcospt = (hpdeg >= 0 ? 1.0 : -1.0) * cos(hpteta); - const double hptanpt = tan(hpteta); - const double ascale = params->commonTrans.autofill ? getTransformAutoFill(oW, oH, pLCPMap) : 1.0; const bool darkening = (params->vignetting.amount <= 0.0); const double centerFactorx = cx - w2; const double centerFactory = cy - h2; + // auxiliary variables for perspective correction + const double f = maxRadius / tan(params->perspective.fov / 360.0 * rtengine::RT_PI); + const double phtheta = params->perspective.horizontal / -180.0 * rtengine::RT_PI; + const double pvtheta = params->perspective.vertical / -180.0 * rtengine::RT_PI; + const double pbtheta = params->perspective.vBias / -180.0 * rtengine::RT_PI; + const double phcos = cos(phtheta); + const double pvcos = cos(pvtheta); + const double pbcos = cos(pbtheta); + const double phsin = sin(phtheta); + const double pvsin = sin(pvtheta); + const double pbsin = sin(pbtheta); + // Coordinates of distorted image center. + const double pxoffset = f * phsin * pvcos; + const double pyoffset = -f * (phcos * pvcos * pbsin + pvsin * pbcos); + const double pz = f * (phcos * pvcos * pbcos - pvsin * pbsin); + // Inverse transformation matrix. + const double p_xx = f * phcos; + const double p_xy = f * phsin * pbsin; + const double p_xz = f * phsin * pbcos; + const double p_yx = f * phsin * pvsin; + const double p_yy = f * (pvcos * pbcos - phcos * pvsin * pbsin); + const double p_yz = f * (-pvcos * pbsin - phcos * pvsin * pbcos); + const double p_zx = -phsin * pvcos; + const double p_zy = phcos * pvcos * pbsin + pvsin * pbcos; + const double p_zz = phcos * pvcos * pbcos - pvsin * pbsin; + // z is known, can calculate these in advance. + const double pz_xz = pz * p_xz; + const double pz_yz = pz * p_yz; + const double pz_zz = pz * p_zz; + // main cycle #ifdef _OPENMP #pragma omp parallel for schedule(dynamic, 16) if(multiThread) @@ -946,13 +978,13 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I y_d += ascale * centerFactory; // centering y coord & scale if (enablePerspective) { - // 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); + x_d -= pxoffset; + y_d -= pyoffset; + const double normalizer = p_zx * x_d + p_zy * y_d + pz_zz; + const double x_d_new = p_xx * x_d + p_xy * y_d + pz_xz; + y_d = p_yx * x_d + p_yy * y_d + pz_yz; + x_d = x_d_new / normalizer; + y_d /= normalizer; } // rotate @@ -1150,7 +1182,7 @@ bool ImProcFunctions::needsRotation () const bool ImProcFunctions::needsPerspective () const { - return params->perspective.horizontal || params->perspective.vertical; + return params->perspective.horizontal || params->perspective.vertical || params->perspective.vBias; } bool ImProcFunctions::needsGradient () const diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index ba6fc237b..16f06b71f 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1851,7 +1851,9 @@ LensProfParams::LcMode LensProfParams::getMethodNumber(const Glib::ustring& mode PerspectiveParams::PerspectiveParams() : horizontal(0.0), - vertical(0.0) + vertical(0.0), + vBias(0.0), + fov(65.0) { } @@ -1859,7 +1861,9 @@ bool PerspectiveParams::operator ==(const PerspectiveParams& other) const { return horizontal == other.horizontal - && vertical == other.vertical; + && vertical == other.vertical + && vBias == other.vBias + && fov == other.fov; } bool PerspectiveParams::operator !=(const PerspectiveParams& other) const @@ -3343,6 +3347,8 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo // Perspective correction saveToKeyfile(!pedited || pedited->perspective.horizontal, "Perspective", "Horizontal", perspective.horizontal, keyFile); saveToKeyfile(!pedited || pedited->perspective.vertical, "Perspective", "Vertical", perspective.vertical, keyFile); + saveToKeyfile(!pedited || pedited->perspective.vBias, "Perspective", "VerticalBias", perspective.vBias, keyFile); + saveToKeyfile(!pedited || pedited->perspective.fov, "Perspective", "FOV", perspective.fov, keyFile); // Gradient saveToKeyfile(!pedited || pedited->gradient.enabled, "Gradient", "Enabled", gradient.enabled, keyFile); @@ -4420,6 +4426,8 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) if (keyFile.has_group("Perspective")) { assignFromKeyfile(keyFile, "Perspective", "Horizontal", pedited, perspective.horizontal, pedited->perspective.horizontal); assignFromKeyfile(keyFile, "Perspective", "Vertical", pedited, perspective.vertical, pedited->perspective.vertical); + assignFromKeyfile(keyFile, "Perspective", "VerticalBias", pedited, perspective.vBias, pedited->perspective.vBias); + assignFromKeyfile(keyFile, "Perspective", "FOV", pedited, perspective.fov, pedited->perspective.fov); } if (keyFile.has_group("Gradient")) { diff --git a/rtengine/procparams.h b/rtengine/procparams.h index c41e55872..956a0ac0c 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -907,6 +907,8 @@ struct LensProfParams { struct PerspectiveParams { double horizontal; double vertical; + double vBias; + double fov; PerspectiveParams(); diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 82132008a..ce7f3400c 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -336,6 +336,8 @@ void ParamsEdited::set(bool v) lensProf.lfLens = v; perspective.horizontal = v; perspective.vertical = v; + perspective.vBias = v; + perspective.fov = v; gradient.enabled = v; gradient.degree = v; gradient.feather = v; @@ -919,6 +921,8 @@ void ParamsEdited::initFrom(const std::vector& 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; + perspective.vBias = perspective.vBias && p.perspective.vBias == other.perspective.vBias; + perspective.fov = perspective.fov && p.perspective.fov == other.perspective.fov; gradient.enabled = gradient.enabled && p.gradient.enabled == other.gradient.enabled; gradient.degree = gradient.degree && p.gradient.degree == other.gradient.degree; gradient.feather = gradient.feather && p.gradient.feather == other.gradient.feather; @@ -2317,6 +2321,14 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.perspective.vertical = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.vertical + mods.perspective.vertical : mods.perspective.vertical; } + if (perspective.vBias) { + toEdit.perspective.vBias = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.vBias + mods.perspective.vBias : mods.perspective.vBias; + } + + if (perspective.fov) { + toEdit.perspective.fov = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.fov + mods.perspective.fov : mods.perspective.fov; + } + if (gradient.enabled) { toEdit.gradient.enabled = mods.gradient.enabled; } @@ -3308,4 +3320,4 @@ bool FilmNegativeParamsEdited::isUnchanged() const bool CaptureSharpeningParamsEdited::isUnchanged() const { return enabled && contrast && autoContrast && autoRadius && deconvradius && deconvradiusOffset && deconviter && deconvitercheck; -} \ No newline at end of file +} diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 01a3e4efe..aa1e10698 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -396,6 +396,8 @@ struct LensProfParamsEdited { struct PerspectiveParamsEdited { bool horizontal; bool vertical; + bool vBias; + bool fov; }; struct GradientParamsEdited { diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index b86da6a52..19bef6de6 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -32,18 +32,30 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" Gtk::Image* ipersHR = Gtk::manage (new RTImage ("perspective-horizontal-right-small.png")); Gtk::Image* ipersVL = Gtk::manage (new RTImage ("perspective-vertical-bottom-small.png")); Gtk::Image* ipersVR = Gtk::manage (new RTImage ("perspective-vertical-top-small.png")); + Gtk::Image* ipersBL = Gtk::manage (new RTImage ("perspective-vertical-bottom-small.png")); + Gtk::Image* ipersBR = Gtk::manage (new RTImage ("perspective-vertical-top-small.png")); - horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_HORIZONTAL"), -100, 100, 0.1, 0, ipersHL, ipersHR)); - horiz->setAdjusterListener (this); - - vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL"), -100, 100, 0.1, 0, ipersVL, ipersVR)); + vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL"), -85, 85, 0.1, 0, ipersVL, ipersVR)); vert->setAdjusterListener (this); - pack_start (*horiz); + horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_HORIZONTAL"), -85, 85, 0.1, 0, ipersHL, ipersHR)); + horiz->setAdjusterListener (this); + + vBias = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL_BIAS"), -85, 85, 0.1, 0, ipersBL, ipersBR)); + vBias->setAdjusterListener (this); + + fov = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_FOV"), 0.1, 150, 0.1, 65)); + fov->setAdjusterListener (this); + pack_start (*vert); + pack_start (*horiz); + pack_start (*vBias); + pack_start (*fov); horiz->setLogScale(2, 0); vert->setLogScale(2, 0); + vBias->setLogScale(2, 0); + fov->setLogScale(2, 0); show_all(); } @@ -56,10 +68,14 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) if (pedited) { horiz->setEditedState (pedited->perspective.horizontal ? Edited : UnEdited); vert->setEditedState (pedited->perspective.vertical ? Edited : UnEdited); + vBias->setEditedState (pedited->perspective.vBias ? Edited : UnEdited); + fov->setEditedState (pedited->perspective.fov ? Edited : UnEdited); } horiz->setValue (pp->perspective.horizontal); vert->setValue (pp->perspective.vertical); + vBias->setValue (pp->perspective.vBias); + fov->setValue (pp->perspective.fov); enableListener (); } @@ -69,10 +85,14 @@ void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited) pp->perspective.horizontal = horiz->getValue (); pp->perspective.vertical = vert->getValue (); + pp->perspective.vBias = vBias->getValue (); + pp->perspective.fov = fov->getValue (); if (pedited) { pedited->perspective.horizontal = horiz->getEditedState (); pedited->perspective.vertical = vert->getEditedState (); + pedited->perspective.vBias = vBias->getEditedState (); + pedited->perspective.fov = fov->getEditedState (); } } @@ -81,20 +101,26 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit horiz->setDefault (defParams->perspective.horizontal); vert->setDefault (defParams->perspective.vertical); + vBias->setDefault (defParams->perspective.vBias); + fov->setDefault (defParams->perspective.fov); if (pedited) { horiz->setDefaultEditedState (pedited->perspective.horizontal ? Edited : UnEdited); vert->setDefaultEditedState (pedited->perspective.vertical ? Edited : UnEdited); + vBias->setDefaultEditedState (pedited->perspective.vBias ? Edited : UnEdited); + fov->setDefaultEditedState (pedited->perspective.fov ? Edited : UnEdited); } else { horiz->setDefaultEditedState (Irrelevant); vert->setDefaultEditedState (Irrelevant); + vBias->setDefaultEditedState (Irrelevant); + fov->setDefaultEditedState (Irrelevant); } } void PerspCorrection::adjusterChanged(Adjuster* a, double newval) { if (listener) { - listener->panelChanged (EvPerspCorr, Glib::ustring::compose ("%1=%3\n%2=%4", M("TP_PERSPECTIVE_HORIZONTAL"), M("TP_PERSPECTIVE_VERTICAL"), horiz->getValue(), vert->getValue())); + listener->panelChanged (EvPerspCorr, Glib::ustring::compose ("%1=%5\n%2=%6\n%3=%7\n%4=%8", M("TP_PERSPECTIVE_HORIZONTAL"), M("TP_PERSPECTIVE_VERTICAL"), M("TP_PERSPECTIVE_VERTICAL_BIAS"), M("TP_PERSPECTIVE_FOV"), horiz->getValue(), vert->getValue(), vBias->getValue(), fov->getValue())); } } @@ -103,6 +129,8 @@ void PerspCorrection::setAdjusterBehavior (bool badd) horiz->setAddMode(badd); vert->setAddMode(badd); + vBias->setAddMode(badd); + fov->setAddMode(badd); } void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp) @@ -110,6 +138,8 @@ void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp) horiz->trimValue(pp->perspective.horizontal); vert->trimValue(pp->perspective.vertical); + vBias->trimValue(pp->perspective.vBias); + fov->trimValue(pp->perspective.fov); } void PerspCorrection::setBatchMode (bool batchMode) @@ -118,4 +148,6 @@ void PerspCorrection::setBatchMode (bool batchMode) ToolPanel::setBatchMode (batchMode); horiz->showEditedCB (); vert->showEditedCB (); + vBias->showEditedCB (); + fov->showEditedCB (); } diff --git a/rtgui/perspective.h b/rtgui/perspective.h index 0564479de..2b56f2727 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -32,6 +32,8 @@ class PerspCorrection final : protected: Adjuster* horiz; Adjuster* vert; + Adjuster* vBias; + Adjuster* fov; public: diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 68ef3b9ce..4bbeb345d 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -343,7 +343,7 @@ Gtk::Widget* Preferences::getBatchProcPanel () mi = behModel->append (); mi->set_value (behavColumns.label, M ("TP_PERSPECTIVE_LABEL")); - appendBehavList (mi, M ("TP_PERSPECTIVE_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL"), ADDSET_PERSPECTIVE, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL_BIAS") + ", " + M ("TP_PERSPECTIVE_FOV"), ADDSET_PERSPECTIVE, false); mi = behModel->append (); mi->set_value (behavColumns.label, M ("TP_GRADIENT_LABEL"));