From f4c37598ee68db549170a3aa97a9b537fd9ef6e6 Mon Sep 17 00:00:00 2001 From: Lawrence Date: Wed, 18 Dec 2019 10:22:05 -0800 Subject: [PATCH 01/20] Generalize perspective correction Revise perspective transformation to remove hard-coded angular field of view and horizontal perspective axis of rotation. Add vertical bias parameter to retain ability to perform vertical perspective transformation independent of the horizontal perspective axis of rotation. Add field of view parameter as a tentative method for specifying angular field of view. The current implementation of perspective transformation applies horizontal perspective transformation in such a way that preserves the orientation of a horizontal line going through the center of the image. In common use cases, horizontal lines such as the horizon do not go through the center of the image. In such cases, the horizontal perspective axis of rotation should not be parallel to the image's y-axis. This commit makes the axis of rotation dependent on the vertical parameter. The two axes of rotation should be placed at the appropriate distance from the image in order to prevent stretched or compressed proportions. In the current implementation, the axes are at a fixed relative distance from the image. This commit adds the ability to specify the distance in the form of the diagonal angular field of view. --- rtdata/languages/default | 2 + rtengine/iptransform.cc | 118 +++++++++++++++++++++++++-------------- rtengine/procparams.cc | 12 +++- rtengine/procparams.h | 2 + rtgui/paramsedited.cc | 14 ++++- rtgui/paramsedited.h | 2 + rtgui/perspective.cc | 44 +++++++++++++-- rtgui/perspective.h | 2 + rtgui/preferences.cc | 2 +- 9 files changed, 145 insertions(+), 53 deletions(-) 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")); From 1c6a635497397c25f68b9cb9521ca9f1a57ccc4a Mon Sep 17 00:00:00 2001 From: Lawrence Date: Tue, 24 Dec 2019 15:18:15 -0800 Subject: [PATCH 02/20] Add homogeneous coordinate functions Add functions for transformations in 3 dimensional homogeneous space. These include functions that generate transformation matrices and perform matrix multiplication. --- rtengine/homogeneouscoordinates.cc | 174 +++++++++++++++++++++++++++++ rtengine/homogeneouscoordinates.h | 75 +++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 rtengine/homogeneouscoordinates.cc create mode 100644 rtengine/homogeneouscoordinates.h diff --git a/rtengine/homogeneouscoordinates.cc b/rtengine/homogeneouscoordinates.cc new file mode 100644 index 000000000..f134abc53 --- /dev/null +++ b/rtengine/homogeneouscoordinates.cc @@ -0,0 +1,174 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Lawrence Lee + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#include + +#include "homogeneouscoordinates.h" + +namespace rtengine +{ + +template +homogeneous::Vector operator*(const homogeneous::Matrix& a, const homogeneous::Vector& b) +{ + homogeneous::Vector prod; + + prod.fill(0); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + prod[i] += a[i][j] * b[j]; + } + } + + return prod; +} + +template +homogeneous::Matrix operator*(const homogeneous::Matrix& a, const homogeneous::Matrix& b) +{ + homogeneous::Matrix prod; + + for (int i = 0; i < 4; i++) { + prod[i].fill(0); + + for (int j = 0; j < 4; j++) { + for (int k = 0; k < 4; k++) { + prod[i][j] += a[i][k] * b[k][j]; + } + } + } + + return prod; +} + +namespace homogeneous +{ + +template +Matrix projectionMatrix(T location, Axis normal) +{ + Matrix matrix; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + matrix[i][j] = 0; + } + } + + matrix[0][0] = location; + matrix[1][1] = location; + matrix[2][2] = location; + matrix[3][3] = 0; + + switch (normal) { + case X: + matrix[3][0] = 1; + break; + + case Y: + matrix[3][1] = 1; + break; + + case Z: + matrix[3][2] = 1; + break; + } + + return matrix; +} + +template +Matrix rotationMatrix(double radians, Axis axis) +{ + Matrix matrix; + + switch (axis) { + case X: + matrix[0][0] = 1; + matrix[0][1] = 0; + matrix[0][2] = 0; + matrix[1][0] = 0; + matrix[1][1] = cos(radians); + matrix[1][2] = -sin(radians); + matrix[2][0] = 0; + matrix[2][1] = sin(radians); + matrix[2][2] = cos(radians); + break; + + case Y: + matrix[0][0] = cos(radians); + matrix[0][1] = 0; + matrix[0][2] = sin(radians); + matrix[1][0] = 0; + matrix[1][1] = 1; + matrix[1][2] = 0; + matrix[2][0] = -sin(radians); + matrix[2][1] = 0; + matrix[2][2] = cos(radians); + break; + + case Z: + matrix[0][0] = cos(radians); + matrix[0][1] = -sin(radians); + matrix[0][2] = 0; + matrix[1][0] = sin(radians); + matrix[1][1] = cos(radians); + matrix[1][2] = 0; + matrix[2][0] = 0; + matrix[2][1] = 0; + matrix[2][2] = 1; + break; + } + + matrix[0][3] = 0; + matrix[1][3] = 0; + matrix[2][3] = 0; + matrix[3][0] = 0; + matrix[3][1] = 0; + matrix[3][2] = 0; + matrix[3][3] = 1; + + return matrix; +} + +template +Matrix translationMatrix(T x, T y, T z) +{ + Matrix matrix; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 3; j++) { + matrix[i][j] = 0; + } + } + + matrix[0][0] = 1; + matrix[1][1] = 1; + matrix[2][2] = 1; + matrix[0][3] = x; + matrix[1][3] = y; + matrix[2][3] = z; + matrix[3][3] = 1; + + return matrix; +} + +} + +} diff --git a/rtengine/homogeneouscoordinates.h b/rtengine/homogeneouscoordinates.h new file mode 100644 index 000000000..c24b73f05 --- /dev/null +++ b/rtengine/homogeneouscoordinates.h @@ -0,0 +1,75 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Lawrence Lee + * + * 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 + +namespace rtengine +{ + +namespace homogeneous +{ + +enum Axis {X, Y, Z}; + +template +using Matrix = std::array, 4>; + +/** + * 3 dimensional homogeneous vector. + */ +template +using Vector = std::array; + +/** + * Creates a 3 dimensional transformation matrix for projection onto a plane. + * @param location Distance from the origin to the plane. + * @param normal Direction of the plane's normal. + */ +template +Matrix projectionMatrix(T location, Axis normal); + +/** + * Creates a 3 dimensional transformation matrix for rotation. + * @param radians Rotation angle. + * @param axis Axis of rotation. + */ +template +Matrix rotationMatrix(T radians, Axis axis); + +/** + * Creates a 3 dimensional transformation matrix for translation. + * @param x Translation in the the x-direction. + * @param y Translation in the the y-direction. + * @param z Translation in the the z-direction. + */ +template +Matrix translationMatrix(T x, T y, T z); + +} + +template +homogeneous::Vector operator*(const homogeneous::Matrix& a, const homogeneous::Vector& b); + +template +homogeneous::Matrix operator*(const homogeneous::Matrix& a, const homogeneous::Matrix& b); + +} + +#include "homogeneouscoordinates.cc" From 253da17bc775bc49789b4f0793970968d72ed440 Mon Sep 17 00:00:00 2001 From: Lawrence Date: Wed, 25 Dec 2019 14:45:47 -0800 Subject: [PATCH 03/20] Add homogeneous scale transformation Add function to generate homogeneous 3 dimensional scale transformation matrices. --- rtengine/homogeneouscoordinates.cc | 17 +++++++++++++++++ rtengine/homogeneouscoordinates.h | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/rtengine/homogeneouscoordinates.cc b/rtengine/homogeneouscoordinates.cc index f134abc53..85b189b9f 100644 --- a/rtengine/homogeneouscoordinates.cc +++ b/rtengine/homogeneouscoordinates.cc @@ -147,6 +147,23 @@ Matrix rotationMatrix(double radians, Axis axis) return matrix; } +template +Matrix scaleMatrix(T x, T y, T z) +{ + Matrix matrix; + + for (int i = 0; i < 4; i++) { + matrix[i].fill(0); + } + + matrix[0][0] = x; + matrix[1][1] = y; + matrix[2][2] = z; + matrix[3][3] = 1; + + return matrix; +} + template Matrix translationMatrix(T x, T y, T z) { diff --git a/rtengine/homogeneouscoordinates.h b/rtengine/homogeneouscoordinates.h index c24b73f05..7c5924d1e 100644 --- a/rtengine/homogeneouscoordinates.h +++ b/rtengine/homogeneouscoordinates.h @@ -53,6 +53,15 @@ Matrix projectionMatrix(T location, Axis normal); template Matrix rotationMatrix(T radians, Axis axis); +/** + * Creates a 3 dimensional transformation matrix for scaling. + * @param x Scale in x-direction + * @param y Scale in y-direction + * @param z Scale in z-direction + */ +template +Matrix scaleMatrix(T x, T y, T z); + /** * Creates a 3 dimensional transformation matrix for translation. * @param x Translation in the the x-direction. From f9a8875bb5a15465aaf48c57fd74d70dd13c2f8b Mon Sep 17 00:00:00 2001 From: Lawrence Date: Fri, 27 Dec 2019 16:40:41 -0800 Subject: [PATCH 04/20] Add more perspective correction adjusters Add camera lens/sensor shift support with horizontal/vertical shift adjusters. Add shifting and rotation of corrected image. This allows post-correction adjustments to be made more easily given the fixed image canvas size. Add scaling of final result. This also helps reduce frustrations with the fixed image canvas size. Replace field of view with focal length and crop factor. Use of focal length and crop factor is more common than diagonal angular field of view. The new adjusters should be more intuitive for most photographers. The implementation of perspective correction uses a focal length relative to the image dimensions. The existing code calculates that focal length with trigonometry. The new code does it by multiplying by a ratio. Replace vertical bias with horizontal and vertical perspective distortion recovery. Vertical bias is not intuitive as it causes vertical lines to converge off-center if horizontal correction is applied. The new adjusters perform perspective distortion on the projection of the corrected image, allowing vertical/horizontal lines to converge towards the center lines of the image. Refactor perspective transformation math to use dynamically computed homogeneous coordinate matrices instead of pre-calculated formulas. This should add some overhead, but results in more maintainable code and possible improved performance due to the reduced number of arithmetic and assignments needed for each pixel. Integrate new adjusters in the GUI. This includes fine granularity for batch processing add/set modes and history. --- rtdata/languages/default | 18 ++- rtengine/iptransform.cc | 205 +++++++++++++++++++++----------- rtengine/procevents.h | 6 + rtengine/procparams.cc | 48 ++++++-- rtengine/procparams.h | 12 +- rtengine/refreshmap.cc | 8 +- rtgui/addsetids.h | 6 + rtgui/batchtoolpanelcoord.cc | 10 +- rtgui/paramsedited.cc | 64 ++++++++-- rtgui/paramsedited.h | 12 +- rtgui/perspective.cc | 223 ++++++++++++++++++++++++++++++----- rtgui/perspective.h | 14 ++- rtgui/preferences.cc | 8 +- 13 files changed, 502 insertions(+), 132 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 376eb6072..776074d5e 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -727,6 +727,12 @@ HISTORY_MSG_491;White Balance HISTORY_MSG_492;RGB Curves HISTORY_MSG_493;L*a*b* Adjustments HISTORY_MSG_494;Capture Sharpening +HISTORY_MSG_496;Perspective - Camera +HISTORY_MSG_497;Perspective - Camera +HISTORY_MSG_498;Perspective - Recovery +HISTORY_MSG_499;Perspective - PCA - Rotation +HISTORY_MSG_500;Perspective - PCA - Scale +HISTORY_MSG_501;Perspective - PCA - Shift HISTORY_MSG_CLAMPOOG;Clip out-of-gamut colors HISTORY_MSG_COLORTONING_LABGRID_VALUE;CT - Color correction HISTORY_MSG_COLORTONING_LABREGION_AB;CT - Color correction @@ -1803,11 +1809,19 @@ 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_CAMERA_CROP_FACTOR;Crop factor +TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH;Focal length +TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL;Horizontal shift +TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL;Vertical shift TP_PERSPECTIVE_HORIZONTAL;Horizontal TP_PERSPECTIVE_LABEL;Perspective +TP_PERSPECTIVE_PROJECTION_PITCH;Vertical +TP_PERSPECTIVE_PROJECTION_ROTATE;Rotation +TP_PERSPECTIVE_PROJECTION_SCALE;Scale +TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL;Horizontal shift +TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL;Vertical shift +TP_PERSPECTIVE_PROJECTION_YAW;Horizontal 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 7e509d584..46c0601e0 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -21,6 +21,7 @@ #include "imagefloat.h" #include "improcfun.h" +#include "homogeneouscoordinates.h" #include "procparams.h" #include "rt_math.h" #include "rtengine.h" @@ -203,6 +204,84 @@ namespace rtengine #define CLIPTOC(a,b,c,d) ((a)>=(b)?((a)<=(c)?(a):(d=true,(c))):(d=true,(b))) +/** + * Creates an inverse transformation matrix for camera-geometry-based + * perspective correction. Unless otherwise specified, units are the same as the + * units of the vectors which the matrix will transform. The projection_* + * parameters are applied in the order they appear. + * @param camera_focal_length Camera's focal length. + * @param camera_shift_horiz Camera lens's shift to the right. + * @param camera_shift_vert Camera lens's shift upwards. + * @param camera_pitch Camera's pitch in radians. Up is positive. + * @param camera_yaw Camera's yaw in radians. Right is positive. + * Up is positive. + * @param projection_shift_horiz Shift of perspective-corrected image to the + * right. + * @param projection_shift_vert Shift of perspective-corrected image upwards. + * @param projection_rotate Rotation of perspective-corrected image + * counter-clockwise in radians. + * @param projection_yaw Yaw in radians of simulated perspective distortion. + * Right is positive. + * @param projection_pitch Pitch in radians of simulated perspective distortion. + * Up is positive. + * @param projection_scale Scale factor of perspective-corrected image. + */ +homogeneous::Matrix perspectiveMatrix(double camera_focal_length, double + camera_shift_horiz, double camera_shift_vert, double camera_pitch, + double camera_yaw, double projection_yaw, double projection_pitch, + double projection_rotate, double projection_shift_horiz, double + projection_shift_vert, double projection_scale) +{ + const double projection_scale_inverse = 1.0 / projection_scale; + homogeneous::Vector center; + center[0] = 0; + center[1] = 0; + center[2] = camera_focal_length; + center[3] = 1; + + // Locations of image center after rotations. + const homogeneous::Vector camera_center_yaw_pitch = + homogeneous::rotationMatrix(camera_yaw, homogeneous::Axis::Y) * + homogeneous::rotationMatrix(camera_pitch, homogeneous::Axis::X) * + center; + const homogeneous::Vector projection_center_yaw_pitch = + homogeneous::rotationMatrix(-projection_yaw, homogeneous::Axis::Y) * + homogeneous::rotationMatrix(-projection_pitch, homogeneous::Axis::X) * + center; + + // The following comments refer to the forward transformation. + const homogeneous::Matrix matrix = + // Lens/sensor shift and move to z == camera_focal_length. + homogeneous::translationMatrix(-camera_shift_horiz, + -camera_shift_vert, -camera_focal_length) * + // Perspective correction. + homogeneous::projectionMatrix(camera_focal_length, homogeneous::Axis::Z) * + homogeneous::rotationMatrix(-camera_pitch, homogeneous::Axis::X) * + homogeneous::rotationMatrix(-camera_yaw, homogeneous::Axis::Y) * + // Re-center after perspective rotation. + homogeneous::translationMatrix(camera_center_yaw_pitch[0], + camera_center_yaw_pitch[1], camera_center_yaw_pitch[2] - camera_focal_length) * + // Translate corrected image. + homogeneous::translationMatrix(-projection_shift_horiz, + -projection_shift_vert, 0) * + // Rotate corrected image. + homogeneous::rotationMatrix(projection_rotate, homogeneous::Axis::Z) * + // Un-center for perspective rotation. + homogeneous::translationMatrix(projection_center_yaw_pitch[0], + projection_center_yaw_pitch[1], camera_focal_length - projection_center_yaw_pitch[2]) * + // Simulate perspective transformation. + homogeneous::projectionMatrix(projection_center_yaw_pitch[2], homogeneous::Axis::Z) * + homogeneous::rotationMatrix(projection_yaw, homogeneous::Axis::Y) * + homogeneous::rotationMatrix(projection_pitch, homogeneous::Axis::X) * + // Move to z == 0. + homogeneous::translationMatrix(0, 0, camera_focal_length) * + // Scale corrected image. + homogeneous::scaleMatrix(projection_scale_inverse, + projection_scale_inverse, 1); + + return matrix; +} + bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, std::vector &red, std::vector &green, std::vector &blue, double ascaleDef, const LensCorrection *pLCPMap) const { @@ -239,34 +318,24 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, 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; + const double f = params->perspective.camera_focal_length * + params->perspective.camera_crop_factor * (maxRadius / sqrt(18.0*18.0 + + 12.0*12.0)); + const double p_camera_yaw = params->perspective.horizontal / 180.0 * rtengine::RT_PI; + const double p_camera_pitch = params->perspective.vertical / 180.0 * rtengine::RT_PI; + const double p_camera_shift_horiz = oW / 100.0 * params->perspective.camera_shift_horiz; + const double p_camera_shift_vert = oH / -100.0 * params->perspective.camera_shift_vert; + const double p_projection_shift_horiz = oW / 100.0 * params->perspective.projection_shift_horiz; + const double p_projection_shift_vert = oH / -100.0 * params->perspective.projection_shift_vert; + const double p_projection_rotate = params->perspective.projection_rotate * rtengine::RT_PI_180; + const double p_projection_yaw = -params->perspective.projection_yaw * rtengine::RT_PI_180; + const double p_projection_pitch = -params->perspective.projection_pitch * rtengine::RT_PI_180; + const double p_projection_scale = params->perspective.projection_scale; + const homogeneous::Matrix p_matrix = perspectiveMatrix(f, + p_camera_shift_horiz, p_camera_shift_vert, p_camera_pitch, + p_camera_yaw, p_projection_yaw, p_projection_pitch, + p_projection_rotate, p_projection_shift_horiz, + p_projection_shift_vert, p_projection_scale); for (size_t i = 0; i < src.size(); i++) { double x_d = src[i].x, y_d = src[i].y; @@ -282,13 +351,11 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, y_d += ascale * (0 - h2); // centering y coord & scale if (needsPerspective()) { - 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; + const double w = p_matrix[3][0] * x_d + p_matrix[3][1] * y_d + p_matrix[3][3]; + const double xw = p_matrix[0][0] * x_d + p_matrix[0][1] * y_d + p_matrix[0][3]; + const double yw = p_matrix[1][0] * x_d + p_matrix[1][1] * y_d + p_matrix[1][3]; + x_d = xw / w; + y_d = yw / w; } // rotate @@ -928,34 +995,24 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I 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; + const double f = params->perspective.camera_focal_length * + params->perspective.camera_crop_factor * (maxRadius / sqrt(18.0*18.0 + + 12.0*12.0)); + const double p_camera_yaw = params->perspective.horizontal / 180.0 * rtengine::RT_PI; + const double p_camera_pitch = params->perspective.vertical / 180.0 * rtengine::RT_PI; + const double p_camera_shift_horiz = oW / 100.0 * params->perspective.camera_shift_horiz; + const double p_camera_shift_vert = oH / -100.0 * params->perspective.camera_shift_vert; + const double p_projection_shift_horiz = oW / 100.0 * params->perspective.projection_shift_horiz; + const double p_projection_shift_vert = oH / -100.0 * params->perspective.projection_shift_vert; + const double p_projection_rotate = params->perspective.projection_rotate * rtengine::RT_PI_180; + const double p_projection_yaw = -params->perspective.projection_yaw * rtengine::RT_PI_180; + const double p_projection_pitch = -params->perspective.projection_pitch * rtengine::RT_PI_180; + const double p_projection_scale = params->perspective.projection_scale; + const homogeneous::Matrix p_matrix = perspectiveMatrix(f, + p_camera_shift_horiz, p_camera_shift_vert, p_camera_pitch, + p_camera_yaw, p_projection_yaw, p_projection_pitch, + p_projection_rotate, p_projection_shift_horiz, + p_projection_shift_vert, p_projection_scale); // main cycle #ifdef _OPENMP @@ -978,13 +1035,11 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I y_d += ascale * centerFactory; // centering y coord & scale if (enablePerspective) { - 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; + const double w = p_matrix[3][0] * x_d + p_matrix[3][1] * y_d + p_matrix[3][3]; + const double xw = p_matrix[0][0] * x_d + p_matrix[0][1] * y_d + p_matrix[0][3]; + const double yw = p_matrix[1][0] * x_d + p_matrix[1][1] * y_d + p_matrix[1][3]; + x_d = xw / w; + y_d = yw / w; } // rotate @@ -1182,7 +1237,15 @@ bool ImProcFunctions::needsRotation () const bool ImProcFunctions::needsPerspective () const { - return params->perspective.horizontal || params->perspective.vertical || params->perspective.vBias; + return params->perspective.horizontal || params->perspective.vertical || + params->perspective.camera_shift_horiz || + params->perspective.camera_shift_vert || + params->perspective.projection_pitch || + params->perspective.projection_rotate || + params->perspective.projection_scale != 1.0 || + params->perspective.projection_shift_horiz || + params->perspective.projection_shift_vert || + params->perspective.projection_yaw; } bool ImProcFunctions::needsGradient () const diff --git a/rtengine/procevents.h b/rtengine/procevents.h index 1b00d4218..dddfdea30 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -518,6 +518,12 @@ enum ProcEventCode { EvLEnabled = 492, EvPdShrEnabled = 493, EvPdShrMaskToggled = 494, + EvPerspCamFocalLength = 495, + EvPerspCamShift = 496, + EvPerspProjAngle = 497, + EvPerspProjRotate = 498, + EvPerspProjScale = 499, + EvPerspProjShift = 500, NUMOFEVENTS diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 16f06b71f..dbabc9165 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1852,8 +1852,16 @@ LensProfParams::LcMode LensProfParams::getMethodNumber(const Glib::ustring& mode PerspectiveParams::PerspectiveParams() : horizontal(0.0), vertical(0.0), - vBias(0.0), - fov(65.0) + camera_crop_factor(1.0), + camera_focal_length(24.0), + camera_shift_horiz(0.0), + camera_shift_vert(0.0), + projection_pitch(0.0), + projection_rotate(0.0), + projection_scale(1.0), + projection_shift_horiz(0.0), + projection_shift_vert(0.0), + projection_yaw(0.0) { } @@ -1862,8 +1870,16 @@ bool PerspectiveParams::operator ==(const PerspectiveParams& other) const return horizontal == other.horizontal && vertical == other.vertical - && vBias == other.vBias - && fov == other.fov; + && camera_focal_length == other.camera_focal_length + && camera_crop_factor == other.camera_crop_factor + && camera_shift_horiz == other.camera_shift_horiz + && camera_shift_vert == other.camera_shift_vert + && projection_shift_horiz == other.projection_shift_horiz + && projection_shift_vert == other.projection_shift_vert + && projection_scale == other.projection_scale + && projection_rotate == other.projection_rotate + && projection_pitch == other.projection_pitch + && projection_yaw == other.projection_yaw; } bool PerspectiveParams::operator !=(const PerspectiveParams& other) const @@ -3347,8 +3363,16 @@ 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); + saveToKeyfile(!pedited || pedited->perspective.camera_crop_factor, "Perspective", "CameraCropFactor", perspective.camera_crop_factor, keyFile); + saveToKeyfile(!pedited || pedited->perspective.camera_focal_length, "Perspective", "CameraFocalLength", perspective.camera_focal_length, keyFile); + saveToKeyfile(!pedited || pedited->perspective.camera_shift_horiz, "Perspective", "CameraShiftHorizontal", perspective.camera_shift_horiz, keyFile); + saveToKeyfile(!pedited || pedited->perspective.camera_shift_vert, "Perspective", "CameraShiftVertical", perspective.camera_shift_vert, keyFile); + saveToKeyfile(!pedited || pedited->perspective.projection_pitch, "Perspective", "ProjectionPitch", perspective.projection_pitch, keyFile); + saveToKeyfile(!pedited || pedited->perspective.projection_rotate, "Perspective", "ProjectionRotate", perspective.projection_rotate, keyFile); + saveToKeyfile(!pedited || pedited->perspective.projection_scale, "Perspective", "ProjectionScale", perspective.projection_scale, keyFile); + saveToKeyfile(!pedited || pedited->perspective.projection_shift_horiz, "Perspective", "ProjectionShiftHorizontal", perspective.projection_shift_horiz, keyFile); + saveToKeyfile(!pedited || pedited->perspective.projection_shift_vert, "Perspective", "ProjectionShiftVertical", perspective.projection_shift_vert, keyFile); + saveToKeyfile(!pedited || pedited->perspective.projection_yaw, "Perspective", "ProjectionYaw", perspective.projection_yaw, keyFile); // Gradient saveToKeyfile(!pedited || pedited->gradient.enabled, "Gradient", "Enabled", gradient.enabled, keyFile); @@ -4426,8 +4450,16 @@ 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); + assignFromKeyfile(keyFile, "Perspective", "CameraShiftHorizontal", pedited, perspective.camera_shift_horiz, pedited->perspective.camera_shift_horiz); + assignFromKeyfile(keyFile, "Perspective", "CameraShiftVertical", pedited, perspective.camera_shift_vert, pedited->perspective.camera_shift_vert); + assignFromKeyfile(keyFile, "Perspective", "CameraCropFactor", pedited, perspective.camera_crop_factor, pedited->perspective.camera_crop_factor); + assignFromKeyfile(keyFile, "Perspective", "CameraFocalLength", pedited, perspective.camera_focal_length, pedited->perspective.camera_focal_length); + assignFromKeyfile(keyFile, "Perspective", "ProjectionPitch", pedited, perspective.projection_pitch, pedited->perspective.projection_pitch); + assignFromKeyfile(keyFile, "Perspective", "ProjectionRotate", pedited, perspective.projection_rotate, pedited->perspective.projection_rotate); + assignFromKeyfile(keyFile, "Perspective", "ProjectionScale", pedited, perspective.projection_scale, pedited->perspective.projection_scale); + assignFromKeyfile(keyFile, "Perspective", "ProjectionShiftHorizontal", pedited, perspective.projection_shift_horiz, pedited->perspective.projection_shift_horiz); + assignFromKeyfile(keyFile, "Perspective", "ProjectionShiftVertical", pedited, perspective.projection_shift_vert, pedited->perspective.projection_shift_vert); + assignFromKeyfile(keyFile, "Perspective", "ProjectionYaw", pedited, perspective.projection_yaw, pedited->perspective.projection_yaw); } if (keyFile.has_group("Gradient")) { diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 956a0ac0c..eb251a9d0 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -907,8 +907,16 @@ struct LensProfParams { struct PerspectiveParams { double horizontal; double vertical; - double vBias; - double fov; + double camera_crop_factor; + double camera_focal_length; + double camera_shift_horiz; + double camera_shift_vert; + double projection_pitch; + double projection_rotate; + double projection_scale; + double projection_shift_horiz; + double projection_shift_vert; + double projection_yaw; PerspectiveParams(); diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index b77eac29c..aeb783ae5 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -521,7 +521,13 @@ int refreshmap[rtengine::NUMOFEVENTS] = { RGBCURVE, // EvRGBEnabled LUMINANCECURVE, // EvLEnabled DEMOSAIC, // EvPdShrEnabled - CAPTURESHARPEN // EvPdShrMaskToggled + CAPTURESHARPEN, // EvPdShrMaskToggled + TRANSFORM, // EvPerspCamFocalLength + TRANSFORM, // EvPerspCamShift + TRANSFORM, // EvPerspProjAngle + TRANSFORM, // EvPerspProjRotate + TRANSFORM, // EvPerspProjScale + TRANSFORM // EvPerspProjShift }; diff --git a/rtgui/addsetids.h b/rtgui/addsetids.h index 05150517f..dd21f9a89 100644 --- a/rtgui/addsetids.h +++ b/rtgui/addsetids.h @@ -18,6 +18,12 @@ enum { ADDSET_ROTATE_DEGREE, ADDSET_DIST_AMOUNT, ADDSET_PERSPECTIVE, + ADDSET_PERSP_CAM_FOCAL_LENGTH, + ADDSET_PERSP_CAM_SHIFT, + ADDSET_PERSP_PROJ_ANGLE, + ADDSET_PERSP_PROJ_ROTATE, + ADDSET_PERSP_PROJ_SCALE, + ADDSET_PERSP_PROJ_SHIFT, ADDSET_CA, ADDSET_VIGN_AMOUNT, ADDSET_VIGN_RADIUS, diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index 2824a285c..da7073b71 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -151,7 +151,7 @@ void BatchToolPanelCoordinator::initSession () rotate->setAdjusterBehavior (false); resize->setAdjusterBehavior (false); distortion->setAdjusterBehavior (false); - perspective->setAdjusterBehavior (false); + perspective->setAdjusterBehavior (false, false, false, false, false, false, false); gradient->setAdjusterBehavior (false, false, false, false); pcvignette->setAdjusterBehavior (false, false, false); cacorrection->setAdjusterBehavior (false); @@ -196,7 +196,7 @@ void BatchToolPanelCoordinator::initSession () rotate->setAdjusterBehavior (options.baBehav[ADDSET_ROTATE_DEGREE]); resize->setAdjusterBehavior (options.baBehav[ADDSET_RESIZE_SCALE]); distortion->setAdjusterBehavior (options.baBehav[ADDSET_DIST_AMOUNT]); - perspective->setAdjusterBehavior (options.baBehav[ADDSET_PERSPECTIVE]); + perspective->setAdjusterBehavior (options.baBehav[ADDSET_PERSPECTIVE], options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH], options.baBehav[ADDSET_PERSP_CAM_SHIFT], options.baBehav[ADDSET_PERSP_PROJ_ANGLE], options.baBehav[ADDSET_PERSP_PROJ_SHIFT], options.baBehav[ADDSET_PERSP_PROJ_ROTATE], options.baBehav[ADDSET_PERSP_PROJ_SCALE]); gradient->setAdjusterBehavior (options.baBehav[ADDSET_GRADIENT_DEGREE], options.baBehav[ADDSET_GRADIENT_FEATHER], options.baBehav[ADDSET_GRADIENT_STRENGTH], options.baBehav[ADDSET_GRADIENT_CENTER]); pcvignette->setAdjusterBehavior (options.baBehav[ADDSET_PCVIGNETTE_STRENGTH], options.baBehav[ADDSET_PCVIGNETTE_FEATHER], options.baBehav[ADDSET_PCVIGNETTE_ROUNDNESS]); cacorrection->setAdjusterBehavior (options.baBehav[ADDSET_CA]); @@ -308,6 +308,12 @@ void BatchToolPanelCoordinator::initSession () if (options.baBehav[ADDSET_RESIZE_SCALE]) { pparams.resize.scale = 0; } if (options.baBehav[ADDSET_DIST_AMOUNT]) { pparams.distortion.amount = 0; } if (options.baBehav[ADDSET_PERSPECTIVE]) { pparams.perspective.horizontal = pparams.perspective.vertical = 0; } + if (options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH]) { pparams.perspective.camera_focal_length = pparams.perspective.camera_crop_factor = 0; } + if (options.baBehav[ADDSET_PERSP_CAM_SHIFT]) { pparams.perspective.camera_shift_horiz = pparams.perspective.camera_shift_vert = 0; } + if (options.baBehav[ADDSET_PERSP_PROJ_ANGLE]) { pparams.perspective.projection_yaw = pparams.perspective.projection_pitch = 0; } + if (options.baBehav[ADDSET_PERSP_PROJ_SHIFT]) { pparams.perspective.projection_shift_horiz = pparams.perspective.projection_shift_vert = 0; } + if (options.baBehav[ADDSET_PERSP_PROJ_ROTATE]) { pparams.perspective.projection_rotate = 0; } + if (options.baBehav[ADDSET_PERSP_PROJ_SCALE]) { pparams.perspective.projection_scale = 0; } if (options.baBehav[ADDSET_GRADIENT_DEGREE]) { pparams.gradient.degree = 0; } if (options.baBehav[ADDSET_GRADIENT_FEATHER]) { pparams.gradient.feather = 0; } if (options.baBehav[ADDSET_GRADIENT_STRENGTH]) { pparams.gradient.strength = 0; } diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index ce7f3400c..ddd6146a3 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -336,8 +336,16 @@ void ParamsEdited::set(bool v) lensProf.lfLens = v; perspective.horizontal = v; perspective.vertical = v; - perspective.vBias = v; - perspective.fov = v; + perspective.camera_crop_factor = v; + perspective.camera_focal_length = v; + perspective.camera_shift_horiz = v; + perspective.camera_shift_vert = v; + perspective.projection_pitch = v; + perspective.projection_rotate = v; + perspective.projection_scale = v; + perspective.projection_shift_horiz = v; + perspective.projection_shift_vert = v; + perspective.projection_yaw = v; gradient.enabled = v; gradient.degree = v; gradient.feather = v; @@ -921,8 +929,16 @@ 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; + perspective.camera_crop_factor = perspective.camera_crop_factor && p.perspective.camera_crop_factor == other.perspective.camera_crop_factor; + perspective.camera_focal_length = perspective.camera_focal_length && p.perspective.camera_focal_length == other.perspective.camera_focal_length; + perspective.camera_shift_horiz = perspective.camera_shift_horiz && p.perspective.camera_shift_horiz == other.perspective.camera_shift_horiz; + perspective.camera_shift_vert = perspective.camera_shift_vert && p.perspective.camera_shift_vert == other.perspective.camera_shift_vert; + perspective.projection_pitch = perspective.projection_pitch && p.perspective.projection_pitch == other.perspective.projection_pitch; + perspective.projection_rotate = perspective.projection_rotate && p.perspective.projection_rotate == other.perspective.projection_rotate; + perspective.projection_scale = perspective.projection_scale && p.perspective.projection_scale == other.perspective.projection_scale; + perspective.projection_shift_horiz = perspective.projection_shift_horiz && p.perspective.projection_shift_horiz == other.perspective.projection_shift_horiz; + perspective.projection_shift_vert = perspective.projection_shift_vert && p.perspective.projection_shift_vert == other.perspective.projection_shift_vert; + perspective.projection_yaw = perspective.projection_yaw && p.perspective.projection_yaw == other.perspective.projection_yaw; 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; @@ -2321,12 +2337,44 @@ 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.camera_crop_factor) { + toEdit.perspective.camera_crop_factor = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH] ? toEdit.perspective.camera_crop_factor + mods.perspective.camera_crop_factor : mods.perspective.camera_crop_factor; } - if (perspective.fov) { - toEdit.perspective.fov = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.fov + mods.perspective.fov : mods.perspective.fov; + if (perspective.camera_focal_length) { + toEdit.perspective.camera_focal_length = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH] ? toEdit.perspective.camera_focal_length + mods.perspective.camera_focal_length : mods.perspective.camera_focal_length; + } + + if (perspective.camera_shift_horiz) { + toEdit.perspective.camera_shift_horiz = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_SHIFT] ? toEdit.perspective.camera_shift_horiz + mods.perspective.camera_shift_horiz : mods.perspective.camera_shift_horiz; + } + + if (perspective.camera_shift_vert) { + toEdit.perspective.camera_shift_vert = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_SHIFT] ? toEdit.perspective.camera_shift_vert + mods.perspective.camera_shift_vert : mods.perspective.camera_shift_vert; + } + + if (perspective.projection_pitch) { + toEdit.perspective.projection_pitch = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_ANGLE] ? toEdit.perspective.projection_pitch + mods.perspective.projection_pitch : mods.perspective.projection_pitch; + } + + if (perspective.projection_rotate) { + toEdit.perspective.projection_rotate = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_ROTATE] ? toEdit.perspective.projection_rotate + mods.perspective.projection_rotate : mods.perspective.projection_rotate; + } + + if (perspective.projection_scale) { + toEdit.perspective.projection_scale = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_SCALE] ? toEdit.perspective.projection_scale + mods.perspective.projection_scale : mods.perspective.projection_scale; + } + + if (perspective.projection_shift_horiz) { + toEdit.perspective.projection_shift_horiz = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_SHIFT] ? toEdit.perspective.projection_shift_horiz + mods.perspective.projection_shift_horiz : mods.perspective.projection_shift_horiz; + } + + if (perspective.projection_shift_vert) { + toEdit.perspective.projection_shift_vert = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_SHIFT] ? toEdit.perspective.projection_shift_vert + mods.perspective.projection_shift_vert : mods.perspective.projection_shift_vert; + } + + if (perspective.projection_yaw) { + toEdit.perspective.projection_yaw = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_ANGLE] ? toEdit.perspective.projection_yaw + mods.perspective.projection_yaw : mods.perspective.projection_yaw; } if (gradient.enabled) { diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index aa1e10698..6403eba3b 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -396,8 +396,16 @@ struct LensProfParamsEdited { struct PerspectiveParamsEdited { bool horizontal; bool vertical; - bool vBias; - bool fov; + bool camera_crop_factor; + bool camera_focal_length; + bool camera_shift_horiz; + bool camera_shift_vert; + bool projection_pitch; + bool projection_rotate; + bool projection_scale; + bool projection_shift_horiz; + bool projection_shift_vert; + bool projection_yaw; }; struct GradientParamsEdited { diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index 19bef6de6..a7c3900ad 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -32,8 +32,13 @@ 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")); + + Gtk::Image* ipers_proj_yaw_left = Gtk::manage (new RTImage ("perspective-horizontal-left-small.png")); + Gtk::Image* ipers_proj_yaw_right = Gtk::manage (new RTImage ("perspective-horizontal-right-small.png")); + Gtk::Image* ipers_proj_pitch_left = Gtk::manage (new RTImage ("perspective-vertical-bottom-small.png")); + Gtk::Image* ipers_proj_pitch_right = Gtk::manage (new RTImage ("perspective-vertical-top-small.png")); + Gtk::Image* ipers_rotate_left = Gtk::manage(new RTImage("rotate-right-small.png")); + Gtk::Image* ipers_rotate_right = Gtk::manage(new RTImage("rotate-left-small.png")); vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL"), -85, 85, 0.1, 0, ipersVL, ipersVR)); vert->setAdjusterListener (this); @@ -41,21 +46,54 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" 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); + camera_focal_length = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH"), 0.5, 2000, 0.01, 24)); + camera_focal_length->setAdjusterListener (this); - fov = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_FOV"), 0.1, 150, 0.1, 65)); - fov->setAdjusterListener (this); + camera_crop_factor = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_CROP_FACTOR"), 0.1, 30, 0.01, 1)); + camera_crop_factor->setAdjusterListener (this); + + camera_shift_horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL"), -100, 100, 0.01, 0)); + camera_shift_horiz->setAdjusterListener (this); + + camera_shift_vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), -100, 100, 0.01, 0)); + camera_shift_vert->setAdjusterListener (this); + + projection_shift_horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL"), -100, 100, 0.01, 0)); + projection_shift_horiz->setAdjusterListener (this); + + projection_shift_vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL"), -100, 100, 0.01, 0)); + projection_shift_vert->setAdjusterListener (this); + + projection_rotate = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_ROTATE"), -180, 180, 0.01, 0, ipers_rotate_left, ipers_rotate_right)); + projection_rotate->setAdjusterListener (this); + + projection_scale = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_SCALE"), 0.5, 2, 0.01, 1)); + projection_scale->setAdjusterListener (this); + + projection_pitch = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_PITCH"), -85, 85, 0.1, 0, ipers_proj_pitch_left, ipers_proj_pitch_right)); + projection_pitch->setAdjusterListener (this); + + projection_yaw = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_YAW"), -85, 85, 0.1, 0, ipers_proj_yaw_left, ipers_proj_yaw_right)); + projection_yaw->setAdjusterListener (this); pack_start (*vert); pack_start (*horiz); - pack_start (*vBias); - pack_start (*fov); + pack_start (*camera_focal_length); + pack_start (*camera_crop_factor); + pack_start (*camera_shift_horiz); + pack_start (*camera_shift_vert); + pack_start (*projection_shift_horiz); + pack_start (*projection_shift_vert); + pack_start (*projection_rotate); + pack_start (*projection_scale); + pack_start (*projection_yaw); + pack_start (*projection_pitch); horiz->setLogScale(2, 0); vert->setLogScale(2, 0); - vBias->setLogScale(2, 0); - fov->setLogScale(2, 0); + camera_focal_length->setLogScale(4000, 0.5); + camera_crop_factor->setLogScale(300, 0.1); + projection_scale->setLogScale(4, 0.5); show_all(); } @@ -68,14 +106,30 @@ 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); + camera_crop_factor->setEditedState (pedited->perspective.camera_crop_factor ? Edited : UnEdited); + camera_focal_length->setEditedState (pedited->perspective.camera_focal_length ? Edited : UnEdited); + camera_shift_horiz->setEditedState (pedited->perspective.camera_shift_horiz ? Edited : UnEdited); + camera_shift_vert->setEditedState (pedited->perspective.camera_shift_vert ? Edited : UnEdited); + projection_pitch->setEditedState (pedited->perspective.projection_pitch ? Edited : UnEdited); + projection_rotate->setEditedState (pedited->perspective.projection_rotate ? Edited : UnEdited); + projection_scale->setEditedState (pedited->perspective.projection_scale ? Edited : UnEdited); + projection_shift_horiz->setEditedState (pedited->perspective.projection_shift_horiz ? Edited : UnEdited); + projection_shift_vert->setEditedState (pedited->perspective.projection_shift_vert ? Edited : UnEdited); + projection_yaw->setEditedState (pedited->perspective.projection_yaw ? Edited : UnEdited); } horiz->setValue (pp->perspective.horizontal); vert->setValue (pp->perspective.vertical); - vBias->setValue (pp->perspective.vBias); - fov->setValue (pp->perspective.fov); + camera_crop_factor->setValue (pp->perspective.camera_crop_factor); + camera_focal_length->setValue (pp->perspective.camera_focal_length); + camera_shift_horiz->setValue (pp->perspective.camera_shift_horiz); + camera_shift_vert->setValue (pp->perspective.camera_shift_vert); + projection_pitch->setValue (pp->perspective.projection_pitch); + projection_rotate->setValue (pp->perspective.projection_rotate); + projection_scale->setValue (pp->perspective.projection_scale); + projection_shift_horiz->setValue (pp->perspective.projection_shift_horiz); + projection_shift_vert->setValue (pp->perspective.projection_shift_vert); + projection_yaw->setValue (pp->perspective.projection_yaw); enableListener (); } @@ -85,14 +139,30 @@ 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 (); + pp->perspective.camera_crop_factor= camera_crop_factor->getValue (); + pp->perspective.camera_focal_length = camera_focal_length->getValue (); + pp->perspective.camera_shift_horiz = camera_shift_horiz->getValue (); + pp->perspective.camera_shift_vert = camera_shift_vert->getValue (); + pp->perspective.projection_pitch = projection_pitch->getValue (); + pp->perspective.projection_rotate = projection_rotate->getValue (); + pp->perspective.projection_scale = projection_scale->getValue (); + pp->perspective.projection_shift_horiz = projection_shift_horiz->getValue (); + pp->perspective.projection_shift_vert = projection_shift_vert->getValue (); + pp->perspective.projection_yaw = projection_yaw->getValue (); if (pedited) { pedited->perspective.horizontal = horiz->getEditedState (); pedited->perspective.vertical = vert->getEditedState (); - pedited->perspective.vBias = vBias->getEditedState (); - pedited->perspective.fov = fov->getEditedState (); + pedited->perspective.camera_crop_factor= camera_crop_factor->getEditedState (); + pedited->perspective.camera_focal_length = camera_focal_length->getEditedState (); + pedited->perspective.camera_shift_horiz = camera_shift_horiz->getEditedState(); + pedited->perspective.camera_shift_vert = camera_shift_vert->getEditedState(); + pedited->perspective.projection_pitch = projection_pitch->getEditedState(); + pedited->perspective.projection_rotate = projection_rotate->getEditedState(); + pedited->perspective.projection_scale = projection_scale->getEditedState(); + pedited->perspective.projection_shift_horiz = projection_shift_horiz->getEditedState(); + pedited->perspective.projection_shift_vert = projection_shift_vert->getEditedState(); + pedited->perspective.projection_yaw = projection_yaw->getEditedState(); } } @@ -101,36 +171,109 @@ 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); + camera_crop_factor->setDefault (defParams->perspective.camera_crop_factor); + camera_focal_length->setDefault (defParams->perspective.camera_focal_length); + camera_shift_horiz->setDefault (defParams->perspective.camera_shift_horiz); + camera_shift_vert->setDefault (defParams->perspective.camera_shift_vert); + projection_pitch->setDefault (defParams->perspective.projection_pitch); + projection_rotate->setDefault (defParams->perspective.projection_rotate); + projection_scale->setDefault (defParams->perspective.projection_scale); + projection_shift_horiz->setDefault (defParams->perspective.projection_shift_horiz); + projection_shift_vert->setDefault (defParams->perspective.projection_shift_vert); + projection_yaw->setDefault (defParams->perspective.projection_yaw); 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); + camera_crop_factor->setDefaultEditedState (pedited->perspective.camera_crop_factor ? Edited : UnEdited); + camera_focal_length->setDefaultEditedState (pedited->perspective.camera_focal_length ? Edited : UnEdited); + camera_shift_horiz->setDefaultEditedState (pedited->perspective.camera_shift_horiz ? Edited : UnEdited); + camera_shift_vert->setDefaultEditedState (pedited->perspective.camera_shift_vert ? Edited : UnEdited); + projection_pitch->setDefaultEditedState (pedited->perspective.projection_pitch ? Edited : UnEdited); + projection_rotate->setDefaultEditedState (pedited->perspective.projection_rotate ? Edited : UnEdited); + projection_scale->setDefaultEditedState (pedited->perspective.projection_scale ? Edited : UnEdited); + projection_shift_horiz->setDefaultEditedState (pedited->perspective.projection_shift_horiz ? Edited : UnEdited); + projection_shift_vert->setDefaultEditedState (pedited->perspective.projection_shift_vert ? Edited : UnEdited); + projection_yaw->setDefaultEditedState (pedited->perspective.projection_yaw ? Edited : UnEdited); } else { horiz->setDefaultEditedState (Irrelevant); vert->setDefaultEditedState (Irrelevant); - vBias->setDefaultEditedState (Irrelevant); - fov->setDefaultEditedState (Irrelevant); + camera_crop_factor->setDefaultEditedState (Irrelevant); + camera_focal_length->setDefaultEditedState (Irrelevant); + camera_shift_horiz->setDefaultEditedState (Irrelevant); + camera_shift_vert->setDefaultEditedState (Irrelevant); + projection_pitch->setDefaultEditedState (Irrelevant); + projection_rotate->setDefaultEditedState (Irrelevant); + projection_scale->setDefaultEditedState (Irrelevant); + projection_shift_horiz->setDefaultEditedState (Irrelevant); + projection_shift_vert->setDefaultEditedState (Irrelevant); + projection_yaw->setDefaultEditedState (Irrelevant); } } void PerspCorrection::adjusterChanged(Adjuster* a, double newval) { if (listener) { - 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())); + if (a == horiz || a == vert) { + listener->panelChanged (EvPerspCorr, + Glib::ustring::compose("%1=%2\n%3=%4", + M("TP_PERSPECTIVE_HORIZONTAL"), + horiz->getValue(), + M("TP_PERSPECTIVE_VERTICAL"), + vert->getValue())); + } else if (a == camera_focal_length || a == camera_crop_factor) { + listener->panelChanged (EvPerspCamFocalLength, + Glib::ustring::compose("%1=%2\n%3=%4", + M("TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH"), + camera_focal_length->getValue(), + M("TP_PERSPECTIVE_CAMERA_CROP_FACTOR"), + camera_crop_factor->getValue())); + } else if (a == camera_shift_horiz || a == camera_shift_vert) { + listener->panelChanged (EvPerspCamShift, + Glib::ustring::compose("%1=%2\n%3=%4", + M("TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL"), + camera_shift_horiz->getValue(), + M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), + camera_shift_vert->getValue())); + } else if (a == projection_shift_horiz || a == projection_shift_vert) { + listener->panelChanged (EvPerspProjShift, + Glib::ustring::compose("%1=%2\n%3=%4", + M("TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL"), + projection_shift_horiz->getValue(), + M("TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL"), + projection_shift_vert->getValue())); + } else if (a == projection_rotate) { + listener->panelChanged (EvPerspProjRotate, + Glib::ustring::format(projection_rotate->getValue())); + } else if (a == projection_scale) { + listener->panelChanged (EvPerspProjScale, + Glib::ustring::format(projection_scale->getValue())); + } else if (a == projection_pitch || a == projection_yaw) { + listener->panelChanged (EvPerspProjAngle, + Glib::ustring::compose("%1=%2\n%3=%4", + M("TP_PERSPECTIVE_PROJECTION_PITCH"), + projection_pitch->getValue(), + M("TP_PERSPECTIVE_PROJECTION_YAW"), + projection_yaw->getValue())); + } } } -void PerspCorrection::setAdjusterBehavior (bool badd) +void PerspCorrection::setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add) { horiz->setAddMode(badd); vert->setAddMode(badd); - vBias->setAddMode(badd); - fov->setAddMode(badd); + camera_crop_factor->setAddMode(camera_focal_length_add); + camera_focal_length->setAddMode(camera_focal_length_add); + camera_shift_horiz->setAddMode(camera_shift_add); + camera_shift_vert->setAddMode(camera_shift_add); + projection_pitch->setAddMode(projection_angle_add); + projection_rotate->setAddMode(projection_rotate_add); + projection_scale->setAddMode(projection_scale_add); + projection_shift_horiz->setAddMode(projection_shift_add); + projection_shift_vert->setAddMode(projection_shift_add); + projection_yaw->setAddMode(projection_angle_add); } void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp) @@ -138,8 +281,16 @@ 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); + camera_crop_factor->trimValue(pp->perspective.camera_crop_factor); + camera_focal_length->trimValue(pp->perspective.camera_focal_length); + camera_shift_horiz->trimValue(pp->perspective.camera_shift_horiz); + camera_shift_vert->trimValue(pp->perspective.camera_shift_vert); + projection_pitch->trimValue(pp->perspective.projection_pitch); + projection_rotate->trimValue(pp->perspective.projection_rotate); + projection_scale->trimValue(pp->perspective.projection_scale); + projection_shift_horiz->trimValue(pp->perspective.projection_shift_horiz); + projection_shift_vert->trimValue(pp->perspective.projection_shift_vert); + projection_yaw->trimValue(pp->perspective.projection_yaw); } void PerspCorrection::setBatchMode (bool batchMode) @@ -148,6 +299,14 @@ void PerspCorrection::setBatchMode (bool batchMode) ToolPanel::setBatchMode (batchMode); horiz->showEditedCB (); vert->showEditedCB (); - vBias->showEditedCB (); - fov->showEditedCB (); + camera_crop_factor->showEditedCB (); + camera_focal_length->showEditedCB (); + camera_shift_horiz->showEditedCB (); + camera_shift_vert->showEditedCB (); + projection_pitch->showEditedCB (); + projection_rotate->showEditedCB (); + projection_scale->showEditedCB (); + projection_shift_horiz->showEditedCB (); + projection_shift_vert->showEditedCB (); + projection_yaw->showEditedCB (); } diff --git a/rtgui/perspective.h b/rtgui/perspective.h index 2b56f2727..bf443f430 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -32,8 +32,16 @@ class PerspCorrection final : protected: Adjuster* horiz; Adjuster* vert; - Adjuster* vBias; - Adjuster* fov; + Adjuster* camera_crop_factor; + Adjuster* camera_focal_length; + Adjuster* camera_shift_horiz; + Adjuster* camera_shift_vert; + Adjuster* projection_pitch; + Adjuster* projection_rotate; + Adjuster* projection_scale; + Adjuster* projection_shift_horiz; + Adjuster* projection_shift_vert; + Adjuster* projection_yaw; public: @@ -45,6 +53,6 @@ public: void setBatchMode (bool batchMode) override; void adjusterChanged (Adjuster* a, double newval) override; - void setAdjusterBehavior (bool badd); + void setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add); void trimValues (rtengine::procparams::ProcParams* pp) override; }; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 4bbeb345d..a66cfd7d7 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -343,7 +343,13 @@ 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") + ", " + M ("TP_PERSPECTIVE_VERTICAL_BIAS") + ", " + M ("TP_PERSPECTIVE_FOV"), ADDSET_PERSPECTIVE, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL"), ADDSET_PERSPECTIVE, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH") + ", " + M ("TP_PERSPECTIVE_CAMERA_CROP_FACTOR"), ADDSET_PERSP_CAM_FOCAL_LENGTH, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), ADDSET_PERSP_CAM_SHIFT, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL"), ADDSET_PERSP_PROJ_SHIFT, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_ROTATE"), ADDSET_PERSP_PROJ_ROTATE, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_SCALE"), ADDSET_PERSP_PROJ_SCALE, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_YAW") + ", " + M ("TP_PERSPECTIVE_PROJECTION_PITCH"), ADDSET_PERSP_PROJ_ANGLE, false); mi = behModel->append (); mi->set_value (behavColumns.label, M ("TP_GRADIENT_LABEL")); From f514a691041fcb528d4b8e683038012f5b70ea2b Mon Sep 17 00:00:00 2001 From: Lawrence Date: Fri, 27 Dec 2019 20:50:18 -0800 Subject: [PATCH 05/20] Group perspective adjusters in frames Divide perspective correction adjusters into three groups: camera attributes, post-correction adjustments, and perspective distortion recovery. --- rtdata/languages/default | 3 +++ rtgui/perspective.cc | 50 ++++++++++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 776074d5e..98a11b164 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1810,17 +1810,20 @@ TP_PCVIGNETTE_STRENGTH;Strength TP_PCVIGNETTE_STRENGTH_TOOLTIP;Filter strength in stops (reached in corners). TP_PDSHARPENING_LABEL;Capture Sharpening TP_PERSPECTIVE_CAMERA_CROP_FACTOR;Crop factor +TP_PERSPECTIVE_CAMERA_FRAME;Correction TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH;Focal length TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL;Horizontal shift TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL;Vertical shift TP_PERSPECTIVE_HORIZONTAL;Horizontal TP_PERSPECTIVE_LABEL;Perspective +TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME;Post-correction adjustment TP_PERSPECTIVE_PROJECTION_PITCH;Vertical TP_PERSPECTIVE_PROJECTION_ROTATE;Rotation TP_PERSPECTIVE_PROJECTION_SCALE;Scale TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL;Horizontal shift TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL;Vertical shift TP_PERSPECTIVE_PROJECTION_YAW;Horizontal +TP_PERSPECTIVE_RECOVERY_FRAME;Recovery TP_PERSPECTIVE_VERTICAL;Vertical TP_PFCURVE_CURVEEDITOR_CH;Hue TP_PFCURVE_CURVEEDITOR_CH_TOOLTIP;Controls defringe strength by color.\nHigher = more,\nLower = less. diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index a7c3900ad..c9ccd794b 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -40,6 +40,12 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" Gtk::Image* ipers_rotate_left = Gtk::manage(new RTImage("rotate-right-small.png")); Gtk::Image* ipers_rotate_right = Gtk::manage(new RTImage("rotate-left-small.png")); + Gtk::Frame* camera_frame = Gtk::manage (new Gtk::Frame + (M("TP_PERSPECTIVE_CAMERA_FRAME"))); + camera_frame->set_label_align(0.025, 0.5); + + Gtk::VBox* camera_vbox = Gtk::manage (new Gtk::VBox()); + vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL"), -85, 85, 0.1, 0, ipersVL, ipersVR)); vert->setAdjusterListener (this); @@ -58,6 +64,12 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" camera_shift_vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), -100, 100, 0.01, 0)); camera_shift_vert->setAdjusterListener (this); + Gtk::Frame* pca_frame = Gtk::manage (new Gtk::Frame + (M("TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME"))); + pca_frame->set_label_align(0.025, 0.5); + + Gtk::VBox* pca_vbox = Gtk::manage (new Gtk::VBox()); + projection_shift_horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL"), -100, 100, 0.01, 0)); projection_shift_horiz->setAdjusterListener (this); @@ -70,24 +82,38 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" projection_scale = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_SCALE"), 0.5, 2, 0.01, 1)); projection_scale->setAdjusterListener (this); + Gtk::Frame* recovery_frame = Gtk::manage (new Gtk::Frame + (M("TP_PERSPECTIVE_RECOVERY_FRAME"))); + recovery_frame->set_label_align(0.025, 0.5); + + Gtk::VBox* recovery_vbox = Gtk::manage (new Gtk::VBox()); + projection_pitch = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_PITCH"), -85, 85, 0.1, 0, ipers_proj_pitch_left, ipers_proj_pitch_right)); projection_pitch->setAdjusterListener (this); projection_yaw = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_YAW"), -85, 85, 0.1, 0, ipers_proj_yaw_left, ipers_proj_yaw_right)); projection_yaw->setAdjusterListener (this); - pack_start (*vert); - pack_start (*horiz); - pack_start (*camera_focal_length); - pack_start (*camera_crop_factor); - pack_start (*camera_shift_horiz); - pack_start (*camera_shift_vert); - pack_start (*projection_shift_horiz); - pack_start (*projection_shift_vert); - pack_start (*projection_rotate); - pack_start (*projection_scale); - pack_start (*projection_yaw); - pack_start (*projection_pitch); + camera_vbox->pack_start (*vert); + camera_vbox->pack_start (*horiz); + camera_vbox->pack_start (*camera_focal_length); + camera_vbox->pack_start (*camera_crop_factor); + camera_vbox->pack_start (*camera_shift_horiz); + camera_vbox->pack_start (*camera_shift_vert); + camera_frame->add(*camera_vbox); + pack_start(*camera_frame); + + pca_vbox->pack_start (*projection_shift_horiz); + pca_vbox->pack_start (*projection_shift_vert); + pca_vbox->pack_start (*projection_rotate); + pca_vbox->pack_start (*projection_scale); + pca_frame->add(*pca_vbox); + pack_start(*pca_frame); + + recovery_vbox->pack_start (*projection_yaw); + recovery_vbox->pack_start (*projection_pitch); + recovery_frame->add(*recovery_vbox); + pack_start(*recovery_frame); horiz->setLogScale(2, 0); vert->setLogScale(2, 0); From f83a62be5bef3bfaed632219e2c0e88d0ee2002b Mon Sep 17 00:00:00 2001 From: Lawrence Date: Sat, 28 Dec 2019 17:18:59 -0800 Subject: [PATCH 06/20] Add back the old perspective tool Add perspective correction method chooser to allow choice between the original perspective tool (simple) and the new one (camera-based). --- rtdata/languages/default | 9 ++- rtengine/iptransform.cc | 113 ++++++++++++++++++++++++-------- rtengine/procevents.h | 2 + rtengine/procparams.cc | 15 ++++- rtengine/procparams.h | 3 + rtengine/refreshmap.cc | 4 +- rtgui/addsetids.h | 1 + rtgui/batchtoolpanelcoord.cc | 5 +- rtgui/paramsedited.cc | 18 ++++++ rtgui/paramsedited.h | 3 + rtgui/perspective.cc | 121 +++++++++++++++++++++++++++++++---- rtgui/perspective.h | 8 ++- rtgui/preferences.cc | 9 +-- 13 files changed, 263 insertions(+), 48 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 98a11b164..82431fb43 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -732,7 +732,9 @@ HISTORY_MSG_497;Perspective - Camera HISTORY_MSG_498;Perspective - Recovery HISTORY_MSG_499;Perspective - PCA - Rotation HISTORY_MSG_500;Perspective - PCA - Scale -HISTORY_MSG_501;Perspective - PCA - Shift +HISTORY_MSG_501;Perspective - PCA +HISTORY_MSG_502;Perspective - Camera +HISTORY_MSG_503;Perspective - Method HISTORY_MSG_CLAMPOOG;Clip out-of-gamut colors HISTORY_MSG_COLORTONING_LABGRID_VALUE;CT - Color correction HISTORY_MSG_COLORTONING_LABREGION_AB;CT - Color correction @@ -1812,10 +1814,15 @@ TP_PDSHARPENING_LABEL;Capture Sharpening TP_PERSPECTIVE_CAMERA_CROP_FACTOR;Crop factor TP_PERSPECTIVE_CAMERA_FRAME;Correction TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH;Focal length +TP_PERSPECTIVE_CAMERA_PITCH;Vertical TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL;Horizontal shift TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL;Vertical shift +TP_PERSPECTIVE_CAMERA_YAW;Horizontal TP_PERSPECTIVE_HORIZONTAL;Horizontal TP_PERSPECTIVE_LABEL;Perspective +TP_PERSPECTIVE_METHOD;Method +TP_PERSPECTIVE_METHOD_SIMPLE;Simple +TP_PERSPECTIVE_METHOD_CAMERA_BASED;Camera-based TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME;Post-correction adjustment TP_PERSPECTIVE_PROJECTION_PITCH;Vertical TP_PERSPECTIVE_PROJECTION_ROTATE;Rotation diff --git a/rtengine/iptransform.cc b/rtengine/iptransform.cc index 46c0601e0..b8083c1d6 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -286,6 +286,10 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, const LensCorrection *pLCPMap) const { + enum PerspType { NONE, SIMPLE, CAMERA_BASED }; + const PerspType perspectiveType = needsPerspective() ? ( + (params->perspective.method == "camera_based") ? + PerspType::CAMERA_BASED : PerspType::SIMPLE ) : PerspType::NONE; bool clipped = false; red.clear (); @@ -318,11 +322,21 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, double ascale = ascaleDef > 0 ? ascaleDef : (params->commonTrans.autofill ? getTransformAutoFill (oW, oH, pLCPMap) : 1.0); // auxiliary variables for perspective correction + // Simple. + 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); + 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); + // Camera-based. const double f = params->perspective.camera_focal_length * params->perspective.camera_crop_factor * (maxRadius / sqrt(18.0*18.0 + 12.0*12.0)); - const double p_camera_yaw = params->perspective.horizontal / 180.0 * rtengine::RT_PI; - const double p_camera_pitch = params->perspective.vertical / 180.0 * rtengine::RT_PI; + const double p_camera_yaw = params->perspective.camera_yaw / 180.0 * rtengine::RT_PI; + const double p_camera_pitch = params->perspective.camera_pitch / 180.0 * rtengine::RT_PI; const double p_camera_shift_horiz = oW / 100.0 * params->perspective.camera_shift_horiz; const double p_camera_shift_vert = oH / -100.0 * params->perspective.camera_shift_vert; const double p_projection_shift_horiz = oW / 100.0 * params->perspective.projection_shift_horiz; @@ -350,12 +364,25 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, x_d += ascale * (0 - w2); // centering x coord & scale y_d += ascale * (0 - h2); // centering y coord & scale - if (needsPerspective()) { - const double w = p_matrix[3][0] * x_d + p_matrix[3][1] * y_d + p_matrix[3][3]; - const double xw = p_matrix[0][0] * x_d + p_matrix[0][1] * y_d + p_matrix[0][3]; - const double yw = p_matrix[1][0] * x_d + p_matrix[1][1] * y_d + p_matrix[1][3]; - x_d = xw / w; - y_d = yw / w; + switch (perspectiveType) { + case PerspType::NONE: + break; + case PerspType::SIMPLE: + // 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); + break; + case PerspType::CAMERA_BASED: + const double w = p_matrix[3][0] * x_d + p_matrix[3][1] * y_d + p_matrix[3][3]; + const double xw = p_matrix[0][0] * x_d + p_matrix[0][1] * y_d + p_matrix[0][3]; + const double yw = p_matrix[1][0] * x_d + p_matrix[1][1] * y_d + p_matrix[1][3]; + x_d = xw / w; + y_d = yw / w; + break; } // rotate @@ -933,13 +960,16 @@ void ImProcFunctions::transformLuminanceOnly (Imagefloat* original, Imagefloat* void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, Imagefloat *transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const LensCorrection *pLCPMap) { // set up stuff, depending on the mode we are + enum PerspType { NONE, SIMPLE, CAMERA_BASED }; const bool enableLCPDist = pLCPMap && params->lensProf.useDist; const bool enableCA = highQuality && needsCA(); const bool enableGradient = needsGradient(); const bool enablePCVignetting = needsPCVignetting(); const bool enableVignetting = needsVignetting(); - const bool enablePerspective = needsPerspective(); const bool enableDistortion = needsDistortion(); + const PerspType perspectiveType = needsPerspective() ? ( + (params->perspective.method == "camera_based") ? + PerspType::CAMERA_BASED : PerspType::SIMPLE ) : PerspType::NONE; const double w2 = static_cast(oW) / 2.0 - 0.5; const double h2 = static_cast(oH) / 2.0 - 0.5; @@ -995,11 +1025,25 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I const double centerFactory = cy - h2; // auxiliary variables for perspective correction + // Simple. + 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); + 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); + // Camera-based. const double f = params->perspective.camera_focal_length * params->perspective.camera_crop_factor * (maxRadius / sqrt(18.0*18.0 + 12.0*12.0)); - const double p_camera_yaw = params->perspective.horizontal / 180.0 * rtengine::RT_PI; - const double p_camera_pitch = params->perspective.vertical / 180.0 * rtengine::RT_PI; + const double p_camera_yaw = params->perspective.camera_yaw / 180.0 * rtengine::RT_PI; + const double p_camera_pitch = params->perspective.camera_pitch / 180.0 * rtengine::RT_PI; const double p_camera_shift_horiz = oW / 100.0 * params->perspective.camera_shift_horiz; const double p_camera_shift_vert = oH / -100.0 * params->perspective.camera_shift_vert; const double p_projection_shift_horiz = oW / 100.0 * params->perspective.projection_shift_horiz; @@ -1034,12 +1078,25 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I x_d += ascale * centerFactorx; // centering x coord & scale y_d += ascale * centerFactory; // centering y coord & scale - if (enablePerspective) { - const double w = p_matrix[3][0] * x_d + p_matrix[3][1] * y_d + p_matrix[3][3]; - const double xw = p_matrix[0][0] * x_d + p_matrix[0][1] * y_d + p_matrix[0][3]; - const double yw = p_matrix[1][0] * x_d + p_matrix[1][1] * y_d + p_matrix[1][3]; - x_d = xw / w; - y_d = yw / w; + switch (perspectiveType) { + case PerspType::NONE: + break; + case PerspType::SIMPLE: + // 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); + break; + case PerspType::CAMERA_BASED: + const double w = p_matrix[3][0] * x_d + p_matrix[3][1] * y_d + p_matrix[3][3]; + const double xw = p_matrix[0][0] * x_d + p_matrix[0][1] * y_d + p_matrix[0][3]; + const double yw = p_matrix[1][0] * x_d + p_matrix[1][1] * y_d + p_matrix[1][3]; + x_d = xw / w; + y_d = yw / w; + break; } // rotate @@ -1237,15 +1294,19 @@ bool ImProcFunctions::needsRotation () const bool ImProcFunctions::needsPerspective () const { - return params->perspective.horizontal || params->perspective.vertical || - params->perspective.camera_shift_horiz || - params->perspective.camera_shift_vert || - params->perspective.projection_pitch || - params->perspective.projection_rotate || - params->perspective.projection_scale != 1.0 || - params->perspective.projection_shift_horiz || - params->perspective.projection_shift_vert || - params->perspective.projection_yaw; + return ( (params->perspective.method == "simple") && + (params->perspective.horizontal || params->perspective.vertical) ) + || ( (params->perspective.method == "camera_based") && ( + params->perspective.camera_pitch || + params->perspective.camera_shift_horiz || + params->perspective.camera_shift_vert || + params->perspective.camera_yaw || + params->perspective.projection_pitch || + params->perspective.projection_rotate || + params->perspective.projection_scale != 1.0 || + params->perspective.projection_shift_horiz || + params->perspective.projection_shift_vert || + params->perspective.projection_yaw) ); } bool ImProcFunctions::needsGradient () const diff --git a/rtengine/procevents.h b/rtengine/procevents.h index dddfdea30..9ab3e0395 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -524,6 +524,8 @@ enum ProcEventCode { EvPerspProjRotate = 498, EvPerspProjScale = 499, EvPerspProjShift = 500, + EvPerspCamAngle = 501, + EvPerspMethod = 502, NUMOFEVENTS diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index dbabc9165..18be5346b 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1850,12 +1850,15 @@ LensProfParams::LcMode LensProfParams::getMethodNumber(const Glib::ustring& mode } PerspectiveParams::PerspectiveParams() : + method("simple"), horizontal(0.0), vertical(0.0), camera_crop_factor(1.0), camera_focal_length(24.0), + camera_pitch(0.0), camera_shift_horiz(0.0), camera_shift_vert(0.0), + camera_yaw(0.0), projection_pitch(0.0), projection_rotate(0.0), projection_scale(1.0), @@ -1868,12 +1871,15 @@ PerspectiveParams::PerspectiveParams() : bool PerspectiveParams::operator ==(const PerspectiveParams& other) const { return - horizontal == other.horizontal + method == other.method + && horizontal == other.horizontal && vertical == other.vertical && camera_focal_length == other.camera_focal_length && camera_crop_factor == other.camera_crop_factor + && camera_pitch == other.camera_pitch && camera_shift_horiz == other.camera_shift_horiz && camera_shift_vert == other.camera_shift_vert + && camera_yaw == other.camera_yaw && projection_shift_horiz == other.projection_shift_horiz && projection_shift_vert == other.projection_shift_vert && projection_scale == other.projection_scale @@ -3361,12 +3367,16 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->lensProf.lfLens, "LensProfile", "LFLens", lensProf.lfLens, keyFile); // Perspective correction + saveToKeyfile(!pedited || pedited->perspective.method, "Perspective", "Method", perspective.method, keyFile); saveToKeyfile(!pedited || pedited->perspective.horizontal, "Perspective", "Horizontal", perspective.horizontal, keyFile); saveToKeyfile(!pedited || pedited->perspective.vertical, "Perspective", "Vertical", perspective.vertical, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_crop_factor, "Perspective", "CameraCropFactor", perspective.camera_crop_factor, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_focal_length, "Perspective", "CameraFocalLength", perspective.camera_focal_length, keyFile); + saveToKeyfile(!pedited || pedited->perspective.camera_pitch, "Perspective", "CameraPitch", perspective.camera_pitch, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_shift_horiz, "Perspective", "CameraShiftHorizontal", perspective.camera_shift_horiz, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_shift_vert, "Perspective", "CameraShiftVertical", perspective.camera_shift_vert, keyFile); + saveToKeyfile(!pedited || pedited->perspective.camera_yaw, "Perspective", "CameraYaw", perspective.camera_yaw, keyFile); + saveToKeyfile(!pedited || pedited->perspective.projection_shift_horiz, "Perspective", "ProjectionShiftHorizontal", perspective.projection_shift_horiz, keyFile); saveToKeyfile(!pedited || pedited->perspective.projection_pitch, "Perspective", "ProjectionPitch", perspective.projection_pitch, keyFile); saveToKeyfile(!pedited || pedited->perspective.projection_rotate, "Perspective", "ProjectionRotate", perspective.projection_rotate, keyFile); saveToKeyfile(!pedited || pedited->perspective.projection_scale, "Perspective", "ProjectionScale", perspective.projection_scale, keyFile); @@ -4448,12 +4458,15 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) } if (keyFile.has_group("Perspective")) { + assignFromKeyfile(keyFile, "Perspective", "Method", pedited, perspective.method, pedited->perspective.method); assignFromKeyfile(keyFile, "Perspective", "Horizontal", pedited, perspective.horizontal, pedited->perspective.horizontal); assignFromKeyfile(keyFile, "Perspective", "Vertical", pedited, perspective.vertical, pedited->perspective.vertical); assignFromKeyfile(keyFile, "Perspective", "CameraShiftHorizontal", pedited, perspective.camera_shift_horiz, pedited->perspective.camera_shift_horiz); assignFromKeyfile(keyFile, "Perspective", "CameraShiftVertical", pedited, perspective.camera_shift_vert, pedited->perspective.camera_shift_vert); + assignFromKeyfile(keyFile, "Perspective", "CameraPitch", pedited, perspective.camera_pitch, pedited->perspective.camera_pitch); assignFromKeyfile(keyFile, "Perspective", "CameraCropFactor", pedited, perspective.camera_crop_factor, pedited->perspective.camera_crop_factor); assignFromKeyfile(keyFile, "Perspective", "CameraFocalLength", pedited, perspective.camera_focal_length, pedited->perspective.camera_focal_length); + assignFromKeyfile(keyFile, "Perspective", "CameraYaw", pedited, perspective.camera_yaw, pedited->perspective.camera_yaw); assignFromKeyfile(keyFile, "Perspective", "ProjectionPitch", pedited, perspective.projection_pitch, pedited->perspective.projection_pitch); assignFromKeyfile(keyFile, "Perspective", "ProjectionRotate", pedited, perspective.projection_rotate, pedited->perspective.projection_rotate); assignFromKeyfile(keyFile, "Perspective", "ProjectionScale", pedited, perspective.projection_scale, pedited->perspective.projection_scale); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index eb251a9d0..3803f5a34 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -905,12 +905,15 @@ struct LensProfParams { * Parameters of the perspective correction */ struct PerspectiveParams { + Glib::ustring method; double horizontal; double vertical; double camera_crop_factor; double camera_focal_length; + double camera_pitch; double camera_shift_horiz; double camera_shift_vert; + double camera_yaw; double projection_pitch; double projection_rotate; double projection_scale; diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index aeb783ae5..e480f91ce 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -527,7 +527,9 @@ int refreshmap[rtengine::NUMOFEVENTS] = { TRANSFORM, // EvPerspProjAngle TRANSFORM, // EvPerspProjRotate TRANSFORM, // EvPerspProjScale - TRANSFORM // EvPerspProjShift + TRANSFORM, // EvPerspProjShift + TRANSFORM, // EvPerspCamAngle + TRANSFORM // EvPerspMethod }; diff --git a/rtgui/addsetids.h b/rtgui/addsetids.h index dd21f9a89..2a02274eb 100644 --- a/rtgui/addsetids.h +++ b/rtgui/addsetids.h @@ -18,6 +18,7 @@ enum { ADDSET_ROTATE_DEGREE, ADDSET_DIST_AMOUNT, ADDSET_PERSPECTIVE, + ADDSET_PERSP_CAM_ANGLE, ADDSET_PERSP_CAM_FOCAL_LENGTH, ADDSET_PERSP_CAM_SHIFT, ADDSET_PERSP_PROJ_ANGLE, diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index da7073b71..c6865a82d 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -151,7 +151,7 @@ void BatchToolPanelCoordinator::initSession () rotate->setAdjusterBehavior (false); resize->setAdjusterBehavior (false); distortion->setAdjusterBehavior (false); - perspective->setAdjusterBehavior (false, false, false, false, false, false, false); + perspective->setAdjusterBehavior (false, false, false, false, false, false, false, false); gradient->setAdjusterBehavior (false, false, false, false); pcvignette->setAdjusterBehavior (false, false, false); cacorrection->setAdjusterBehavior (false); @@ -196,7 +196,7 @@ void BatchToolPanelCoordinator::initSession () rotate->setAdjusterBehavior (options.baBehav[ADDSET_ROTATE_DEGREE]); resize->setAdjusterBehavior (options.baBehav[ADDSET_RESIZE_SCALE]); distortion->setAdjusterBehavior (options.baBehav[ADDSET_DIST_AMOUNT]); - perspective->setAdjusterBehavior (options.baBehav[ADDSET_PERSPECTIVE], options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH], options.baBehav[ADDSET_PERSP_CAM_SHIFT], options.baBehav[ADDSET_PERSP_PROJ_ANGLE], options.baBehav[ADDSET_PERSP_PROJ_SHIFT], options.baBehav[ADDSET_PERSP_PROJ_ROTATE], options.baBehav[ADDSET_PERSP_PROJ_SCALE]); + perspective->setAdjusterBehavior (options.baBehav[ADDSET_PERSPECTIVE], options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH], options.baBehav[ADDSET_PERSP_CAM_SHIFT], options.baBehav[ADDSET_PERSP_CAM_ANGLE], options.baBehav[ADDSET_PERSP_PROJ_ANGLE], options.baBehav[ADDSET_PERSP_PROJ_SHIFT], options.baBehav[ADDSET_PERSP_PROJ_ROTATE], options.baBehav[ADDSET_PERSP_PROJ_SCALE]); gradient->setAdjusterBehavior (options.baBehav[ADDSET_GRADIENT_DEGREE], options.baBehav[ADDSET_GRADIENT_FEATHER], options.baBehav[ADDSET_GRADIENT_STRENGTH], options.baBehav[ADDSET_GRADIENT_CENTER]); pcvignette->setAdjusterBehavior (options.baBehav[ADDSET_PCVIGNETTE_STRENGTH], options.baBehav[ADDSET_PCVIGNETTE_FEATHER], options.baBehav[ADDSET_PCVIGNETTE_ROUNDNESS]); cacorrection->setAdjusterBehavior (options.baBehav[ADDSET_CA]); @@ -310,6 +310,7 @@ void BatchToolPanelCoordinator::initSession () if (options.baBehav[ADDSET_PERSPECTIVE]) { pparams.perspective.horizontal = pparams.perspective.vertical = 0; } if (options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH]) { pparams.perspective.camera_focal_length = pparams.perspective.camera_crop_factor = 0; } if (options.baBehav[ADDSET_PERSP_CAM_SHIFT]) { pparams.perspective.camera_shift_horiz = pparams.perspective.camera_shift_vert = 0; } + if (options.baBehav[ADDSET_PERSP_CAM_ANGLE]) { pparams.perspective.camera_yaw = pparams.perspective.camera_pitch = 0; } if (options.baBehav[ADDSET_PERSP_PROJ_ANGLE]) { pparams.perspective.projection_yaw = pparams.perspective.projection_pitch = 0; } if (options.baBehav[ADDSET_PERSP_PROJ_SHIFT]) { pparams.perspective.projection_shift_horiz = pparams.perspective.projection_shift_vert = 0; } if (options.baBehav[ADDSET_PERSP_PROJ_ROTATE]) { pparams.perspective.projection_rotate = 0; } diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index ddd6146a3..d21df0741 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -334,12 +334,15 @@ void ParamsEdited::set(bool v) lensProf.lfCameraMake = v; lensProf.lfCameraModel = v; lensProf.lfLens = v; + perspective.method = v; perspective.horizontal = v; perspective.vertical = v; perspective.camera_crop_factor = v; perspective.camera_focal_length = v; + perspective.camera_pitch = v; perspective.camera_shift_horiz = v; perspective.camera_shift_vert = v; + perspective.camera_yaw = v; perspective.projection_pitch = v; perspective.projection_rotate = v; perspective.projection_scale = v; @@ -927,13 +930,16 @@ void ParamsEdited::initFrom(const std::vector& 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.method = perspective.method && p.perspective.method == other.perspective.method; perspective.horizontal = perspective.horizontal && p.perspective.horizontal == other.perspective.horizontal; perspective.vertical = perspective.vertical && p.perspective.vertical == other.perspective.vertical; perspective.camera_crop_factor = perspective.camera_crop_factor && p.perspective.camera_crop_factor == other.perspective.camera_crop_factor; perspective.camera_focal_length = perspective.camera_focal_length && p.perspective.camera_focal_length == other.perspective.camera_focal_length; + perspective.camera_pitch = perspective.camera_pitch && p.perspective.camera_pitch == other.perspective.camera_pitch; perspective.camera_shift_horiz = perspective.camera_shift_horiz && p.perspective.camera_shift_horiz == other.perspective.camera_shift_horiz; perspective.camera_shift_vert = perspective.camera_shift_vert && p.perspective.camera_shift_vert == other.perspective.camera_shift_vert; perspective.projection_pitch = perspective.projection_pitch && p.perspective.projection_pitch == other.perspective.projection_pitch; + perspective.camera_yaw = perspective.camera_yaw && p.perspective.camera_yaw == other.perspective.camera_yaw; perspective.projection_rotate = perspective.projection_rotate && p.perspective.projection_rotate == other.perspective.projection_rotate; perspective.projection_scale = perspective.projection_scale && p.perspective.projection_scale == other.perspective.projection_scale; perspective.projection_shift_horiz = perspective.projection_shift_horiz && p.perspective.projection_shift_horiz == other.perspective.projection_shift_horiz; @@ -2329,6 +2335,10 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.lensProf.lfLens = mods.lensProf.lfLens; } + if (perspective.method) { + toEdit.perspective.method = mods.perspective.method; + } + if (perspective.horizontal) { toEdit.perspective.horizontal = dontforceSet && options.baBehav[ADDSET_PERSPECTIVE] ? toEdit.perspective.horizontal + mods.perspective.horizontal : mods.perspective.horizontal; } @@ -2345,6 +2355,10 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.perspective.camera_focal_length = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH] ? toEdit.perspective.camera_focal_length + mods.perspective.camera_focal_length : mods.perspective.camera_focal_length; } + if (perspective.camera_pitch) { + toEdit.perspective.camera_pitch = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_ANGLE] ? toEdit.perspective.camera_pitch + mods.perspective.camera_pitch : mods.perspective.camera_pitch; + } + if (perspective.camera_shift_horiz) { toEdit.perspective.camera_shift_horiz = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_SHIFT] ? toEdit.perspective.camera_shift_horiz + mods.perspective.camera_shift_horiz : mods.perspective.camera_shift_horiz; } @@ -2353,6 +2367,10 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.perspective.camera_shift_vert = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_SHIFT] ? toEdit.perspective.camera_shift_vert + mods.perspective.camera_shift_vert : mods.perspective.camera_shift_vert; } + if (perspective.camera_yaw) { + toEdit.perspective.camera_yaw = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_ANGLE] ? toEdit.perspective.camera_yaw + mods.perspective.camera_yaw : mods.perspective.camera_yaw; + } + if (perspective.projection_pitch) { toEdit.perspective.projection_pitch = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_ANGLE] ? toEdit.perspective.projection_pitch + mods.perspective.projection_pitch : mods.perspective.projection_pitch; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 6403eba3b..29422b9d0 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -394,12 +394,15 @@ struct LensProfParamsEdited { }; struct PerspectiveParamsEdited { + bool method; bool horizontal; bool vertical; bool camera_crop_factor; bool camera_focal_length; + bool camera_pitch; bool camera_shift_horiz; bool camera_shift_vert; + bool camera_yaw; bool projection_pitch; bool projection_rotate; bool projection_scale; diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index c9ccd794b..e895c08c5 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -33,6 +33,10 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" 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* ipers_cam_yaw_left = Gtk::manage (new RTImage ("perspective-horizontal-left-small.png")); + Gtk::Image* ipers_cam_yaw_right = Gtk::manage (new RTImage ("perspective-horizontal-right-small.png")); + Gtk::Image* ipers_cam_pitch_left = Gtk::manage (new RTImage ("perspective-vertical-bottom-small.png")); + Gtk::Image* ipers_cam_pitch_right = Gtk::manage (new RTImage ("perspective-vertical-top-small.png")); Gtk::Image* ipers_proj_yaw_left = Gtk::manage (new RTImage ("perspective-horizontal-left-small.png")); Gtk::Image* ipers_proj_yaw_right = Gtk::manage (new RTImage ("perspective-horizontal-right-small.png")); Gtk::Image* ipers_proj_pitch_left = Gtk::manage (new RTImage ("perspective-vertical-bottom-small.png")); @@ -40,18 +44,31 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" Gtk::Image* ipers_rotate_left = Gtk::manage(new RTImage("rotate-right-small.png")); Gtk::Image* ipers_rotate_right = Gtk::manage(new RTImage("rotate-left-small.png")); + Gtk::HBox* method_hbox = Gtk::manage (new Gtk::HBox()); + Gtk::Label* method_label = Gtk::manage (new Gtk::Label (M("TP_PERSPECTIVE_METHOD") + ": ")); + method = Gtk::manage (new MyComboBoxText ()); + method->append (M("TP_PERSPECTIVE_METHOD_SIMPLE")); + method->append (M("TP_PERSPECTIVE_METHOD_CAMERA_BASED")); + method_hbox->pack_start(*method_label, Gtk::PACK_SHRINK); + method_hbox->pack_start(*method); + pack_start(*method_hbox); + + simple = Gtk::manage( new Gtk::VBox() ); + + vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL"), -100, 100, 0.1, 0, ipersVL, ipersVR)); + vert->setAdjusterListener (this); + + horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_HORIZONTAL"), -100, 100, 0.1, 0, ipersHL, ipersHR)); + horiz->setAdjusterListener (this); + + camera_based = Gtk::manage( new Gtk::VBox() ); + Gtk::Frame* camera_frame = Gtk::manage (new Gtk::Frame (M("TP_PERSPECTIVE_CAMERA_FRAME"))); camera_frame->set_label_align(0.025, 0.5); Gtk::VBox* camera_vbox = Gtk::manage (new Gtk::VBox()); - vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_VERTICAL"), -85, 85, 0.1, 0, ipersVL, ipersVR)); - vert->setAdjusterListener (this); - - horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_HORIZONTAL"), -85, 85, 0.1, 0, ipersHL, ipersHR)); - horiz->setAdjusterListener (this); - camera_focal_length = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH"), 0.5, 2000, 0.01, 24)); camera_focal_length->setAdjusterListener (this); @@ -64,6 +81,14 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" camera_shift_vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), -100, 100, 0.01, 0)); camera_shift_vert->setAdjusterListener (this); + camera_pitch = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_PITCH"), + -85, 85, 0.1, 0, ipers_cam_pitch_left, ipers_cam_pitch_right)); + camera_pitch->setAdjusterListener (this); + + camera_yaw = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_YAW"), + -85, 85, 0.1, 0, ipers_cam_yaw_left, ipers_cam_yaw_right)); + camera_yaw->setAdjusterListener (this); + Gtk::Frame* pca_frame = Gtk::manage (new Gtk::Frame (M("TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME"))); pca_frame->set_label_align(0.025, 0.5); @@ -94,26 +119,32 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" projection_yaw = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_YAW"), -85, 85, 0.1, 0, ipers_proj_yaw_left, ipers_proj_yaw_right)); projection_yaw->setAdjusterListener (this); - camera_vbox->pack_start (*vert); - camera_vbox->pack_start (*horiz); + simple->pack_start (*horiz); + simple->pack_start (*vert); + camera_vbox->pack_start (*camera_focal_length); camera_vbox->pack_start (*camera_crop_factor); camera_vbox->pack_start (*camera_shift_horiz); camera_vbox->pack_start (*camera_shift_vert); + camera_vbox->pack_start (*camera_pitch); + camera_vbox->pack_start (*camera_yaw); camera_frame->add(*camera_vbox); - pack_start(*camera_frame); + camera_based->pack_start(*camera_frame); pca_vbox->pack_start (*projection_shift_horiz); pca_vbox->pack_start (*projection_shift_vert); pca_vbox->pack_start (*projection_rotate); pca_vbox->pack_start (*projection_scale); pca_frame->add(*pca_vbox); - pack_start(*pca_frame); + camera_based->pack_start(*pca_frame); recovery_vbox->pack_start (*projection_yaw); recovery_vbox->pack_start (*projection_pitch); recovery_frame->add(*recovery_vbox); - pack_start(*recovery_frame); + camera_based->pack_start(*recovery_frame); + + pack_start(*simple); + pack_start(*camera_based); horiz->setLogScale(2, 0); vert->setLogScale(2, 0); @@ -121,6 +152,8 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" camera_crop_factor->setLogScale(300, 0.1); projection_scale->setLogScale(4, 0.5); + method->signal_changed().connect(sigc::mem_fun(*this, &PerspCorrection::methodChanged)); + show_all(); } @@ -134,8 +167,10 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) vert->setEditedState (pedited->perspective.vertical ? Edited : UnEdited); camera_crop_factor->setEditedState (pedited->perspective.camera_crop_factor ? Edited : UnEdited); camera_focal_length->setEditedState (pedited->perspective.camera_focal_length ? Edited : UnEdited); + camera_pitch->setEditedState (pedited->perspective.camera_pitch ? Edited : UnEdited); camera_shift_horiz->setEditedState (pedited->perspective.camera_shift_horiz ? Edited : UnEdited); camera_shift_vert->setEditedState (pedited->perspective.camera_shift_vert ? Edited : UnEdited); + camera_yaw->setEditedState (pedited->perspective.camera_yaw ? Edited : UnEdited); projection_pitch->setEditedState (pedited->perspective.projection_pitch ? Edited : UnEdited); projection_rotate->setEditedState (pedited->perspective.projection_rotate ? Edited : UnEdited); projection_scale->setEditedState (pedited->perspective.projection_scale ? Edited : UnEdited); @@ -148,8 +183,10 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) vert->setValue (pp->perspective.vertical); camera_crop_factor->setValue (pp->perspective.camera_crop_factor); camera_focal_length->setValue (pp->perspective.camera_focal_length); + camera_pitch->setValue (pp->perspective.camera_pitch); camera_shift_horiz->setValue (pp->perspective.camera_shift_horiz); camera_shift_vert->setValue (pp->perspective.camera_shift_vert); + camera_yaw->setValue (pp->perspective.camera_yaw); projection_pitch->setValue (pp->perspective.projection_pitch); projection_rotate->setValue (pp->perspective.projection_rotate); projection_scale->setValue (pp->perspective.projection_scale); @@ -157,6 +194,14 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) projection_shift_vert->setValue (pp->perspective.projection_shift_vert); projection_yaw->setValue (pp->perspective.projection_yaw); + if (pedited && !pedited->perspective.method) { + method->set_active (2); + } else if (pp->perspective.method == "simple") { + method->set_active (0); + } else if (pp->perspective.method == "camera_based") { + method->set_active (1); + } + enableListener (); } @@ -167,8 +212,10 @@ void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited) pp->perspective.vertical = vert->getValue (); pp->perspective.camera_crop_factor= camera_crop_factor->getValue (); pp->perspective.camera_focal_length = camera_focal_length->getValue (); + pp->perspective.camera_pitch = camera_pitch->getValue (); pp->perspective.camera_shift_horiz = camera_shift_horiz->getValue (); pp->perspective.camera_shift_vert = camera_shift_vert->getValue (); + pp->perspective.camera_yaw = camera_yaw->getValue (); pp->perspective.projection_pitch = projection_pitch->getValue (); pp->perspective.projection_rotate = projection_rotate->getValue (); pp->perspective.projection_scale = projection_scale->getValue (); @@ -176,13 +223,22 @@ void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited) pp->perspective.projection_shift_vert = projection_shift_vert->getValue (); pp->perspective.projection_yaw = projection_yaw->getValue (); + if (method->get_active_row_number() == 0) { + pp->perspective.method = "simple"; + } else if (method->get_active_row_number() == 1) { + pp->perspective.method = "camera_based"; + } + if (pedited) { + pedited->perspective.method = method->get_active_row_number() != 2; pedited->perspective.horizontal = horiz->getEditedState (); pedited->perspective.vertical = vert->getEditedState (); pedited->perspective.camera_crop_factor= camera_crop_factor->getEditedState (); pedited->perspective.camera_focal_length = camera_focal_length->getEditedState (); + pedited->perspective.camera_pitch = camera_pitch->getEditedState(); pedited->perspective.camera_shift_horiz = camera_shift_horiz->getEditedState(); pedited->perspective.camera_shift_vert = camera_shift_vert->getEditedState(); + pedited->perspective.camera_yaw = camera_yaw->getEditedState(); pedited->perspective.projection_pitch = projection_pitch->getEditedState(); pedited->perspective.projection_rotate = projection_rotate->getEditedState(); pedited->perspective.projection_scale = projection_scale->getEditedState(); @@ -199,8 +255,10 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit vert->setDefault (defParams->perspective.vertical); camera_crop_factor->setDefault (defParams->perspective.camera_crop_factor); camera_focal_length->setDefault (defParams->perspective.camera_focal_length); + camera_pitch->setDefault (defParams->perspective.camera_pitch); camera_shift_horiz->setDefault (defParams->perspective.camera_shift_horiz); camera_shift_vert->setDefault (defParams->perspective.camera_shift_vert); + camera_yaw->setDefault (defParams->perspective.camera_yaw); projection_pitch->setDefault (defParams->perspective.projection_pitch); projection_rotate->setDefault (defParams->perspective.projection_rotate); projection_scale->setDefault (defParams->perspective.projection_scale); @@ -213,8 +271,10 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit vert->setDefaultEditedState (pedited->perspective.vertical ? Edited : UnEdited); camera_crop_factor->setDefaultEditedState (pedited->perspective.camera_crop_factor ? Edited : UnEdited); camera_focal_length->setDefaultEditedState (pedited->perspective.camera_focal_length ? Edited : UnEdited); + camera_pitch->setDefaultEditedState (pedited->perspective.camera_pitch ? Edited : UnEdited); camera_shift_horiz->setDefaultEditedState (pedited->perspective.camera_shift_horiz ? Edited : UnEdited); camera_shift_vert->setDefaultEditedState (pedited->perspective.camera_shift_vert ? Edited : UnEdited); + camera_yaw->setDefaultEditedState (pedited->perspective.camera_yaw ? Edited : UnEdited); projection_pitch->setDefaultEditedState (pedited->perspective.projection_pitch ? Edited : UnEdited); projection_rotate->setDefaultEditedState (pedited->perspective.projection_rotate ? Edited : UnEdited); projection_scale->setDefaultEditedState (pedited->perspective.projection_scale ? Edited : UnEdited); @@ -226,8 +286,10 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit vert->setDefaultEditedState (Irrelevant); camera_crop_factor->setDefaultEditedState (Irrelevant); camera_focal_length->setDefaultEditedState (Irrelevant); + camera_pitch->setDefaultEditedState (Irrelevant); camera_shift_horiz->setDefaultEditedState (Irrelevant); camera_shift_vert->setDefaultEditedState (Irrelevant); + camera_yaw->setDefaultEditedState (Irrelevant); projection_pitch->setDefaultEditedState (Irrelevant); projection_rotate->setDefaultEditedState (Irrelevant); projection_scale->setDefaultEditedState (Irrelevant); @@ -261,6 +323,13 @@ void PerspCorrection::adjusterChanged(Adjuster* a, double newval) camera_shift_horiz->getValue(), M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), camera_shift_vert->getValue())); + } else if (a == camera_pitch || a == camera_yaw) { + listener->panelChanged (EvPerspCamAngle, + Glib::ustring::compose("%1=%2\n%3=%4", + M("TP_PERSPECTIVE_CAMERA_YAW"), + camera_yaw->getValue(), + M("TP_PERSPECTIVE_CAMERA_PITCH"), + camera_pitch->getValue())); } else if (a == projection_shift_horiz || a == projection_shift_vert) { listener->panelChanged (EvPerspProjShift, Glib::ustring::compose("%1=%2\n%3=%4", @@ -285,15 +354,37 @@ void PerspCorrection::adjusterChanged(Adjuster* a, double newval) } } -void PerspCorrection::setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add) +void PerspCorrection::methodChanged (void) +{ + + if (!batchMode) { + removeIfThere (this, simple, false); + removeIfThere (this, camera_based, false); + + if (method->get_active_row_number() == 0) { + pack_start (*simple); + } else if (method->get_active_row_number() == 1) { + pack_start (*camera_based); + } + } + + if (listener) { + listener->panelChanged (EvPerspMethod, method->get_active_text ()); + } + +} + +void PerspCorrection::setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool camera_angle_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add) { horiz->setAddMode(badd); vert->setAddMode(badd); camera_crop_factor->setAddMode(camera_focal_length_add); camera_focal_length->setAddMode(camera_focal_length_add); + camera_pitch->setAddMode(camera_angle_add); camera_shift_horiz->setAddMode(camera_shift_add); camera_shift_vert->setAddMode(camera_shift_add); + camera_yaw->setAddMode(camera_angle_add); projection_pitch->setAddMode(projection_angle_add); projection_rotate->setAddMode(projection_rotate_add); projection_scale->setAddMode(projection_scale_add); @@ -309,8 +400,10 @@ void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp) vert->trimValue(pp->perspective.vertical); camera_crop_factor->trimValue(pp->perspective.camera_crop_factor); camera_focal_length->trimValue(pp->perspective.camera_focal_length); + camera_pitch->trimValue(pp->perspective.camera_pitch); camera_shift_horiz->trimValue(pp->perspective.camera_shift_horiz); camera_shift_vert->trimValue(pp->perspective.camera_shift_vert); + camera_yaw->trimValue(pp->perspective.camera_yaw); projection_pitch->trimValue(pp->perspective.projection_pitch); projection_rotate->trimValue(pp->perspective.projection_rotate); projection_scale->trimValue(pp->perspective.projection_scale); @@ -327,12 +420,16 @@ void PerspCorrection::setBatchMode (bool batchMode) vert->showEditedCB (); camera_crop_factor->showEditedCB (); camera_focal_length->showEditedCB (); + camera_pitch->showEditedCB (); camera_shift_horiz->showEditedCB (); camera_shift_vert->showEditedCB (); + camera_yaw->showEditedCB (); projection_pitch->showEditedCB (); projection_rotate->showEditedCB (); projection_scale->showEditedCB (); projection_shift_horiz->showEditedCB (); projection_shift_vert->showEditedCB (); projection_yaw->showEditedCB (); + + method->append (M("GENERAL_UNCHANGED")); } diff --git a/rtgui/perspective.h b/rtgui/perspective.h index bf443f430..9d19c84b0 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -30,12 +30,17 @@ class PerspCorrection final : { protected: + MyComboBoxText* method; + Gtk::VBox* simple; Adjuster* horiz; Adjuster* vert; + Gtk::VBox* camera_based; Adjuster* camera_crop_factor; Adjuster* camera_focal_length; + Adjuster* camera_pitch; Adjuster* camera_shift_horiz; Adjuster* camera_shift_vert; + Adjuster* camera_yaw; Adjuster* projection_pitch; Adjuster* projection_rotate; Adjuster* projection_scale; @@ -53,6 +58,7 @@ public: void setBatchMode (bool batchMode) override; void adjusterChanged (Adjuster* a, double newval) override; - void setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add); + void methodChanged (void); + void setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool camera_angle_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add); void trimValues (rtengine::procparams::ProcParams* pp) override; }; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index a66cfd7d7..5e3b2e7af 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -343,13 +343,14 @@ 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_METHOD_SIMPLE") + " - " + M ("TP_PERSPECTIVE_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL"), ADDSET_PERSPECTIVE, false); appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH") + ", " + M ("TP_PERSPECTIVE_CAMERA_CROP_FACTOR"), ADDSET_PERSP_CAM_FOCAL_LENGTH, false); - appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), ADDSET_PERSP_CAM_SHIFT, false); - appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL"), ADDSET_PERSP_PROJ_SHIFT, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FRAME") + " - " + M ("TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), ADDSET_PERSP_CAM_SHIFT, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FRAME") + " - " + M ("TP_PERSPECTIVE_CAMERA_YAW") + ", " + M ("TP_PERSPECTIVE_CAMERA_PITCH"), ADDSET_PERSP_CAM_ANGLE, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME") + " - " + M ("TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL"), ADDSET_PERSP_PROJ_SHIFT, false); appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_ROTATE"), ADDSET_PERSP_PROJ_ROTATE, false); appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_SCALE"), ADDSET_PERSP_PROJ_SCALE, false); - appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_YAW") + ", " + M ("TP_PERSPECTIVE_PROJECTION_PITCH"), ADDSET_PERSP_PROJ_ANGLE, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_RECOVERY_FRAME") + " - " + M ("TP_PERSPECTIVE_PROJECTION_YAW") + ", " + M ("TP_PERSPECTIVE_PROJECTION_PITCH"), ADDSET_PERSP_PROJ_ANGLE, false); mi = behModel->append (); mi->set_value (behavColumns.label, M ("TP_GRADIENT_LABEL")); From d9886d58f94ffa7b1d863931c918dd87bb00234a Mon Sep 17 00:00:00 2001 From: Lawrence Date: Mon, 13 Jan 2020 17:10:25 -0800 Subject: [PATCH 07/20] Take auto perspective correction files from ART --- rtengine/ashift_dt.c | 4991 +++++++++++++++++++++++++++++ rtengine/ashift_lsd.c | 2335 ++++++++++++++ rtengine/ashift_nmsimplex.c | 425 +++ rtengine/perspectivecorrection.cc | 433 +++ rtengine/perspectivecorrection.h | 55 + 5 files changed, 8239 insertions(+) create mode 100644 rtengine/ashift_dt.c create mode 100644 rtengine/ashift_lsd.c create mode 100644 rtengine/ashift_nmsimplex.c create mode 100644 rtengine/perspectivecorrection.cc create mode 100644 rtengine/perspectivecorrection.h diff --git a/rtengine/ashift_dt.c b/rtengine/ashift_dt.c new file mode 100644 index 000000000..e25288589 --- /dev/null +++ b/rtengine/ashift_dt.c @@ -0,0 +1,4991 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2019 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 . + */ + +using namespace std; + +// taken from darktable (src/iop/ashift.c) +/* + This file is part of darktable, + copyright (c) 2016 Ulrich Pegelow. + + darktable 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. + + darktable 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 darktable. If not, see . +*/ + +// Inspiration to this module comes from the program ShiftN (http://www.shiftn.de) by +// Marcus Hebel. + +// Thanks to Marcus for his support when implementing part of the ShiftN functionality +// to darktable. + +#define ROTATION_RANGE 10 // allowed min/max default range for rotation parameter +#define ROTATION_RANGE_SOFT 20 // allowed min/max range for rotation parameter with manual adjustment +#define LENSSHIFT_RANGE 0.5 // allowed min/max default range for lensshift parameters +#define LENSSHIFT_RANGE_SOFT 1 // allowed min/max range for lensshift parameters with manual adjustment +#define SHEAR_RANGE 0.2 // allowed min/max range for shear parameter +#define SHEAR_RANGE_SOFT 0.5 // allowed min/max range for shear parameter with manual adjustment +#define MIN_LINE_LENGTH 5 // the minimum length of a line in pixels to be regarded as relevant +#define MAX_TANGENTIAL_DEVIATION 30 // by how many degrees a line may deviate from the +/-180 and +/-90 to be regarded as relevant +#define LSD_SCALE 0.99 // LSD: scaling factor for line detection +#define LSD_SIGMA_SCALE 0.6 // LSD: sigma for Gaussian filter is computed as sigma = sigma_scale/scale +#define LSD_QUANT 2.0 // LSD: bound to the quantization error on the gradient norm +#define LSD_ANG_TH 22.5 // LSD: gradient angle tolerance in degrees +#define LSD_LOG_EPS 0.0 // LSD: detection threshold: -log10(NFA) > log_eps +#define LSD_DENSITY_TH 0.7 // LSD: minimal density of region points in rectangle +#define LSD_N_BINS 1024 // LSD: number of bins in pseudo-ordering of gradient modulus +#define LSD_GAMMA 0.45 // gamma correction to apply on raw images prior to line detection +#define RANSAC_RUNS 400 // how many iterations to run in ransac +#define RANSAC_EPSILON 2 // starting value for ransac epsilon (in -log10 units) +#define RANSAC_EPSILON_STEP 1 // step size of epsilon optimization (log10 units) +#define RANSAC_ELIMINATION_RATIO 60 // percentage of lines we try to eliminate as outliers +#define RANSAC_OPTIMIZATION_STEPS 5 // home many steps to optimize epsilon +#define RANSAC_OPTIMIZATION_DRY_RUNS 50 // how man runs per optimization steps +#define RANSAC_HURDLE 5 // hurdle rate: the number of lines below which we do a complete permutation instead of random sampling +#define MINIMUM_FITLINES 4 // minimum number of lines needed for automatic parameter fit +#define NMS_EPSILON 1e-3 // break criterion for Nelder-Mead simplex +#define NMS_SCALE 1.0 // scaling factor for Nelder-Mead simplex +#define NMS_ITERATIONS 400 // number of iterations for Nelder-Mead simplex +#define NMS_CROP_EPSILON 100.0 // break criterion for Nelder-Mead simplex on crop fitting +#define NMS_CROP_SCALE 0.5 // scaling factor for Nelder-Mead simplex on crop fitting +#define NMS_CROP_ITERATIONS 100 // number of iterations for Nelder-Mead simplex on crop fitting +#define NMS_ALPHA 1.0 // reflection coefficient for Nelder-Mead simplex +#define NMS_BETA 0.5 // contraction coefficient for Nelder-Mead simplex +#define NMS_GAMMA 2.0 // expansion coefficient for Nelder-Mead simplex +#define DEFAULT_F_LENGTH 28.0 // focal length we assume if no exif data are available + +/* // define to get debugging output */ +/* #undef ASHIFT_DEBUG */ + +#define SQR(a) ((a) * (a)) + +// For line detection we use the LSD algorithm as published by Rafael Grompone: +// +// "LSD: a Line Segment Detector" by Rafael Grompone von Gioi, +// Jeremie Jakubowicz, Jean-Michel Morel, and Gregory Randall, +// Image Processing On Line, 2012. DOI:10.5201/ipol.2012.gjmr-lsd +// http://dx.doi.org/10.5201/ipol.2012.gjmr-lsd +#include "ashift_lsd.c" + +// For parameter optimization we are using the Nelder-Mead simplex method +// implemented by Michael F. Hutt. +#include "ashift_nmsimplex.c" + + +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 +DT_MODULE_INTROSPECTION(4, dt_iop_ashift_params_t) + + +const char *name() +{ + return _("perspective correction"); +} + +int flags() +{ + return IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_TILING_FULL_ROI | IOP_FLAGS_ONE_INSTANCE; +} + +int groups() +{ + return dt_iop_get_group("perspective correction", IOP_GROUP_CORRECT); +} + +int operation_tags() +{ + return IOP_TAG_DISTORT; +} + +int operation_tags_filter() +{ + // switch off clipping and decoration, we want to see the full image. + return IOP_TAG_DECORATION | IOP_TAG_CLIPPING; +} +#endif // if 0 +//----------------------------------------------------------------------------- + +typedef enum dt_iop_ashift_homodir_t +{ + ASHIFT_HOMOGRAPH_FORWARD, + ASHIFT_HOMOGRAPH_INVERTED +} dt_iop_ashift_homodir_t; + +//typedef enum dt_iop_ashift_linetype_t +enum +{ + ASHIFT_LINE_IRRELEVANT = 0, // the line is found to be not interesting + // eg. too short, or not horizontal or vertical + ASHIFT_LINE_RELEVANT = 1 << 0, // the line is relevant for us + ASHIFT_LINE_DIRVERT = 1 << 1, // the line is (mostly) vertical, else (mostly) horizontal + ASHIFT_LINE_SELECTED = 1 << 2, // the line is selected for fitting + ASHIFT_LINE_VERTICAL_NOT_SELECTED = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_DIRVERT, + ASHIFT_LINE_HORIZONTAL_NOT_SELECTED = ASHIFT_LINE_RELEVANT, + ASHIFT_LINE_VERTICAL_SELECTED = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_DIRVERT | ASHIFT_LINE_SELECTED, + ASHIFT_LINE_HORIZONTAL_SELECTED = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED, + ASHIFT_LINE_MASK = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_DIRVERT | ASHIFT_LINE_SELECTED +}; //dt_iop_ashift_linetype_t; +typedef unsigned int dt_iop_ashift_linetype_t; + +typedef enum dt_iop_ashift_linecolor_t +{ + ASHIFT_LINECOLOR_GREY = 0, + ASHIFT_LINECOLOR_GREEN = 1, + ASHIFT_LINECOLOR_RED = 2, + ASHIFT_LINECOLOR_BLUE = 3, + ASHIFT_LINECOLOR_YELLOW = 4 +} dt_iop_ashift_linecolor_t; + +//typedef enum dt_iop_ashift_fitaxis_t +enum +{ + ASHIFT_FIT_NONE = 0, // none + ASHIFT_FIT_ROTATION = 1 << 0, // flag indicates to fit rotation angle + ASHIFT_FIT_LENS_VERT = 1 << 1, // flag indicates to fit vertical lens shift + ASHIFT_FIT_LENS_HOR = 1 << 2, // flag indicates to fit horizontal lens shift + ASHIFT_FIT_SHEAR = 1 << 3, // flag indicates to fit shear parameter + ASHIFT_FIT_LINES_VERT = 1 << 4, // use vertical lines for fitting + ASHIFT_FIT_LINES_HOR = 1 << 5, // use horizontal lines for fitting + ASHIFT_FIT_LENS_BOTH = ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR, + ASHIFT_FIT_LINES_BOTH = ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR, + ASHIFT_FIT_VERTICALLY = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LINES_VERT, + ASHIFT_FIT_HORIZONTALLY = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LENS_HOR | ASHIFT_FIT_LINES_HOR, + ASHIFT_FIT_BOTH = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR | + ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR, + ASHIFT_FIT_VERTICALLY_NO_ROTATION = ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LINES_VERT, + ASHIFT_FIT_HORIZONTALLY_NO_ROTATION = ASHIFT_FIT_LENS_HOR | ASHIFT_FIT_LINES_HOR, + ASHIFT_FIT_BOTH_NO_ROTATION = ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR | + ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR, + ASHIFT_FIT_BOTH_SHEAR = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR | + ASHIFT_FIT_SHEAR | ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR, + ASHIFT_FIT_ROTATION_VERTICAL_LINES = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LINES_VERT, + ASHIFT_FIT_ROTATION_HORIZONTAL_LINES = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LINES_HOR, + ASHIFT_FIT_ROTATION_BOTH_LINES = ASHIFT_FIT_ROTATION | ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR, + ASHIFT_FIT_FLIP = ASHIFT_FIT_LENS_VERT | ASHIFT_FIT_LENS_HOR | ASHIFT_FIT_LINES_VERT | ASHIFT_FIT_LINES_HOR +}; //dt_iop_ashift_fitaxis_t; +typedef unsigned int dt_iop_ashift_fitaxis_t; + +typedef enum dt_iop_ashift_nmsresult_t +{ + NMS_SUCCESS = 0, + NMS_NOT_ENOUGH_LINES = 1, + NMS_DID_NOT_CONVERGE = 2, + NMS_INSANE = 3 +} dt_iop_ashift_nmsresult_t; + +typedef enum dt_iop_ashift_enhance_t +{ + ASHIFT_ENHANCE_NONE = 0, + ASHIFT_ENHANCE_EDGES = 1 << 0, + ASHIFT_ENHANCE_DETAIL = 1 << 1, + ASHIFT_ENHANCE_HORIZONTAL = 0x100, + ASHIFT_ENHANCE_VERTICAL = 0x200 +} dt_iop_ashift_enhance_t; + +typedef enum dt_iop_ashift_mode_t +{ + ASHIFT_MODE_GENERIC = 0, + ASHIFT_MODE_SPECIFIC = 1 +} dt_iop_ashift_mode_t; + +typedef enum dt_iop_ashift_crop_t +{ + ASHIFT_CROP_OFF = 0, + ASHIFT_CROP_LARGEST = 1, + ASHIFT_CROP_ASPECT = 2 +} dt_iop_ashift_crop_t; + +typedef enum dt_iop_ashift_bounding_t +{ + ASHIFT_BOUNDING_OFF = 0, + ASHIFT_BOUNDING_SELECT = 1, + ASHIFT_BOUNDING_DESELECT = 2 +} dt_iop_ashift_bounding_t; + +typedef enum dt_iop_ashift_jobcode_t +{ + ASHIFT_JOBCODE_NONE = 0, + ASHIFT_JOBCODE_GET_STRUCTURE = 1, + ASHIFT_JOBCODE_FIT = 2 +} dt_iop_ashift_jobcode_t; + +typedef struct dt_iop_ashift_params1_t +{ + float rotation; + float lensshift_v; + float lensshift_h; + int toggle; +} dt_iop_ashift_params1_t; + +typedef struct dt_iop_ashift_params2_t +{ + float rotation; + float lensshift_v; + float lensshift_h; + float f_length; + float crop_factor; + float orthocorr; + float aspect; + dt_iop_ashift_mode_t mode; + int toggle; +} dt_iop_ashift_params2_t; + +typedef struct dt_iop_ashift_params3_t +{ + float rotation; + float lensshift_v; + float lensshift_h; + float f_length; + float crop_factor; + float orthocorr; + float aspect; + dt_iop_ashift_mode_t mode; + int toggle; + dt_iop_ashift_crop_t cropmode; + float cl; + float cr; + float ct; + float cb; +} dt_iop_ashift_params3_t; + +typedef struct dt_iop_ashift_params_t +{ + float rotation; + float lensshift_v; + float lensshift_h; + float shear; + float f_length; + float crop_factor; + float orthocorr; + float aspect; + dt_iop_ashift_mode_t mode; + int toggle; + dt_iop_ashift_crop_t cropmode; + float cl; + float cr; + float ct; + float cb; +} dt_iop_ashift_params_t; + +typedef struct dt_iop_ashift_line_t +{ + float p1[3]; + float p2[3]; + float length; + float width; + float weight; + dt_iop_ashift_linetype_t type; + // homogeneous coordinates: + float L[3]; +} dt_iop_ashift_line_t; + +typedef struct dt_iop_ashift_points_idx_t +{ + size_t offset; + int length; + int near; + int bounded; + dt_iop_ashift_linetype_t type; + dt_iop_ashift_linecolor_t color; + // bounding box: + float bbx, bby, bbX, bbY; +} dt_iop_ashift_points_idx_t; + +typedef struct dt_iop_ashift_fit_params_t +{ + int params_count; + dt_iop_ashift_linetype_t linetype; + dt_iop_ashift_linetype_t linemask; + dt_iop_ashift_line_t *lines; + int lines_count; + int width; + int height; + float weight; + float f_length_kb; + float orthocorr; + float aspect; + float rotation; + float lensshift_v; + float lensshift_h; + float shear; + float rotation_range; + float lensshift_v_range; + float lensshift_h_range; + float shear_range; +} dt_iop_ashift_fit_params_t; + +typedef struct dt_iop_ashift_cropfit_params_t +{ + int width; + int height; + float x; + float y; + float alpha; + float homograph[3][3]; + float edges[4][3]; +} dt_iop_ashift_cropfit_params_t; + +typedef struct dt_iop_ashift_gui_data_t +{ + /* GtkWidget *rotation; */ + /* GtkWidget *lensshift_v; */ + /* GtkWidget *lensshift_h; */ + /* GtkWidget *shear; */ + /* GtkWidget *guide_lines; */ + /* GtkWidget *cropmode; */ + /* GtkWidget *mode; */ + /* GtkWidget *f_length; */ + /* GtkWidget *crop_factor; */ + /* GtkWidget *orthocorr; */ + /* GtkWidget *aspect; */ + /* GtkWidget *fit_v; */ + /* GtkWidget *fit_h; */ + /* GtkWidget *fit_both; */ + /* GtkWidget *structure; */ + /* GtkWidget *clean; */ + /* GtkWidget *eye; */ + int lines_suppressed; + int fitting; + int isflipped; + int show_guides; + int isselecting; + int isdeselecting; + dt_iop_ashift_bounding_t isbounding; + float near_delta; + int selecting_lines_version; + float rotation_range; + float lensshift_v_range; + float lensshift_h_range; + float shear_range; + dt_iop_ashift_line_t *lines; + int lines_in_width; + int lines_in_height; + int lines_x_off; + int lines_y_off; + int lines_count; + int vertical_count; + int horizontal_count; + int lines_version; + float vertical_weight; + float horizontal_weight; + float *points; + dt_iop_ashift_points_idx_t *points_idx; + int points_lines_count; + int points_version; + float *buf; + int buf_width; + int buf_height; + int buf_x_off; + int buf_y_off; + float buf_scale; + uint64_t lines_hash; + uint64_t grid_hash; + uint64_t buf_hash; + dt_iop_ashift_fitaxis_t lastfit; + float lastx; + float lasty; + float crop_cx; + float crop_cy; + dt_iop_ashift_jobcode_t jobcode; + int jobparams; + /* dt_pthread_mutex_t lock; */ + MyMutex lock; + gboolean adjust_crop; +} dt_iop_ashift_gui_data_t; + +typedef struct dt_iop_ashift_data_t +{ + float rotation; + float lensshift_v; + float lensshift_h; + float shear; + float f_length_kb; + float orthocorr; + float aspect; + float cl; + float cr; + float ct; + float cb; +} dt_iop_ashift_data_t; + +typedef struct dt_iop_ashift_global_data_t +{ + int kernel_ashift_bilinear; + int kernel_ashift_bicubic; + int kernel_ashift_lanczos2; + int kernel_ashift_lanczos3; +} dt_iop_ashift_global_data_t; + +typedef struct dt_iop_module_t +{ + dt_iop_ashift_gui_data_t *gui_data; + int is_raw; +} dt_iop_module_t; + +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 +int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, + void *new_params, const int new_version) +{ + if(old_version == 1 && new_version == 4) + { + const dt_iop_ashift_params1_t *old = old_params; + dt_iop_ashift_params_t *new = new_params; + new->rotation = old->rotation; + new->lensshift_v = old->lensshift_v; + new->lensshift_h = old->lensshift_h; + new->shear = 0.0f; + new->toggle = old->toggle; + new->f_length = DEFAULT_F_LENGTH; + new->crop_factor = 1.0f; + new->orthocorr = 100.0f; + new->aspect = 1.0f; + new->mode = ASHIFT_MODE_GENERIC; + new->cropmode = ASHIFT_CROP_OFF; + new->cl = 0.0f; + new->cr = 1.0f; + new->ct = 0.0f; + new->cb = 1.0f; + return 0; + } + if(old_version == 2 && new_version == 4) + { + const dt_iop_ashift_params2_t *old = old_params; + dt_iop_ashift_params_t *new = new_params; + new->rotation = old->rotation; + new->lensshift_v = old->lensshift_v; + new->lensshift_h = old->lensshift_h; + new->shear = 0.0f; + new->toggle = old->toggle; + new->f_length = old->f_length; + new->crop_factor = old->crop_factor; + new->orthocorr = old->orthocorr; + new->aspect = old->aspect; + new->mode = old->mode; + new->cropmode = ASHIFT_CROP_OFF; + new->cl = 0.0f; + new->cr = 1.0f; + new->ct = 0.0f; + new->cb = 1.0f; + return 0; + } + if(old_version == 3 && new_version == 4) + { + const dt_iop_ashift_params3_t *old = old_params; + dt_iop_ashift_params_t *new = new_params; + new->rotation = old->rotation; + new->lensshift_v = old->lensshift_v; + new->lensshift_h = old->lensshift_h; + new->shear = 0.0f; + new->toggle = old->toggle; + new->f_length = old->f_length; + new->crop_factor = old->crop_factor; + new->orthocorr = old->orthocorr; + new->aspect = old->aspect; + new->mode = old->mode; + new->cropmode = old->cropmode; + new->cl = old->cl; + new->cr = old->cr; + new->ct = old->ct; + new->cb = old->cb; + return 0; + } + + return 1; +} + +void init_key_accels(dt_iop_module_so_t *self) +{ + dt_accel_register_slider_iop(self, FALSE, NC_("accel", "rotation")); + dt_accel_register_slider_iop(self, FALSE, NC_("accel", "lens shift (v)")); + dt_accel_register_slider_iop(self, FALSE, NC_("accel", "lens shift (h)")); + dt_accel_register_slider_iop(self, FALSE, NC_("accel", "shear")); +} + +void connect_key_accels(dt_iop_module_t *self) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + dt_accel_connect_slider_iop(self, "rotation", GTK_WIDGET(g->rotation)); + dt_accel_connect_slider_iop(self, "lens shift (v)", GTK_WIDGET(g->lensshift_v)); + dt_accel_connect_slider_iop(self, "lens shift (h)", GTK_WIDGET(g->lensshift_h)); + dt_accel_connect_slider_iop(self, "shear", GTK_WIDGET(g->shear)); +} +#endif // if 0 +//----------------------------------------------------------------------------- + +// multiply 3x3 matrix with 3x1 vector +// dst needs to be different from v +static inline void mat3mulv(float *dst, const float *const mat, const float *const v) +{ + for(int k = 0; k < 3; k++) + { + float x = 0.0f; + for(int i = 0; i < 3; i++) x += mat[3 * k + i] * v[i]; + dst[k] = x; + } +} + +// multiply two 3x3 matrices +// dst needs to be different from m1 and m2 +static inline void mat3mul(float *dst, const float *const m1, const float *const m2) +{ + for(int k = 0; k < 3; k++) + { + for(int i = 0; i < 3; i++) + { + float x = 0.0f; + for(int j = 0; j < 3; j++) x += m1[3 * k + j] * m2[3 * j + i]; + dst[3 * k + i] = x; + } + } +} + +// normalized product of two 3x1 vectors +// dst needs to be different from v1 and v2 +static inline void vec3prodn(float *dst, const float *const v1, const float *const v2) +{ + const float l1 = v1[1] * v2[2] - v1[2] * v2[1]; + const float l2 = v1[2] * v2[0] - v1[0] * v2[2]; + const float l3 = v1[0] * v2[1] - v1[1] * v2[0]; + + // normalize so that l1^2 + l2^2 + l3^3 = 1 + const float sq = sqrt(l1 * l1 + l2 * l2 + l3 * l3); + + const float f = sq > 0.0f ? 1.0f / sq : 1.0f; + + dst[0] = l1 * f; + dst[1] = l2 * f; + dst[2] = l3 * f; +} + +// normalize a 3x1 vector so that x^2 + y^2 + z^2 = 1 +// dst and v may be the same +static inline void vec3norm(float *dst, const float *const v) +{ + const float sq = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + + // special handling for an all-zero vector + const float f = sq > 0.0f ? 1.0f / sq : 1.0f; + + dst[0] = v[0] * f; + dst[1] = v[1] * f; + dst[2] = v[2] * f; +} + +// normalize a 3x1 vector so that x^2 + y^2 = 1; a useful normalization for +// lines in homogeneous coordinates +// dst and v may be the same +static inline void vec3lnorm(float *dst, const float *const v) +{ + const float sq = sqrt(v[0] * v[0] + v[1] * v[1]); + + // special handling for a point vector of the image center + const float f = sq > 0.0f ? 1.0f / sq : 1.0f; + + dst[0] = v[0] * f; + dst[1] = v[1] * f; + dst[2] = v[2] * f; +} + + +// scalar product of two 3x1 vectors +static inline float vec3scalar(const float *const v1, const float *const v2) +{ + return (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]); +} + +// check if 3x1 vector is (very close to) null +static inline int vec3isnull(const float *const v) +{ + const float eps = 1e-10f; + return (fabs(v[0]) < eps && fabs(v[1]) < eps && fabs(v[2]) < eps); +} + +#ifdef ASHIFT_DEBUG +static void print_roi(const dt_iop_roi_t *roi, const char *label) +{ + printf("{ %5d %5d %5d %5d %.6f } %s\n", roi->x, roi->y, roi->width, roi->height, roi->scale, label); +} +#endif + +#define MAT3SWAP(a, b) { float (*tmp)[3] = (a); (a) = (b); (b) = tmp; } + +static void homography(float *homograph, const float angle, const float shift_v, const float shift_h, + const float shear, const float f_length_kb, const float orthocorr, const float aspect, + const int width, const int height, dt_iop_ashift_homodir_t dir) +{ + // calculate homograph that combines all translations, rotations + // and warping into one single matrix operation. + // this is heavily leaning on ShiftN where the homographic matrix expects + // input in (y : x : 1) format. in the darktable world we want to keep the + // (x : y : 1) convention. therefore we need to flip coordinates first and + // make sure that output is in correct format after corrections are applied. + + const float u = width; + const float v = height; + + const float phi = M_PI * angle / 180.0f; + const float cosi = cos(phi); + const float sini = sin(phi); + const float ascale = sqrt(aspect); + + // most of this comes from ShiftN + const float f_global = f_length_kb; + const float horifac = 1.0f - orthocorr / 100.0f; + const float exppa_v = exp(shift_v); + const float fdb_v = f_global / (14.4f + (v / u - 1) * 7.2f); + const float rad_v = fdb_v * (exppa_v - 1.0f) / (exppa_v + 1.0f); + const float alpha_v = CLAMP(atan(rad_v), -1.5f, 1.5f); + const float rt_v = sin(0.5f * alpha_v); + const float r_v = fmax(0.1f, 2.0f * (horifac - 1.0f) * rt_v * rt_v + 1.0f); + + const float vertifac = 1.0f - orthocorr / 100.0f; + const float exppa_h = exp(shift_h); + const float fdb_h = f_global / (14.4f + (u / v - 1) * 7.2f); + const float rad_h = fdb_h * (exppa_h - 1.0f) / (exppa_h + 1.0f); + const float alpha_h = CLAMP(atan(rad_h), -1.5f, 1.5f); + const float rt_h = sin(0.5f * alpha_h); + const float r_h = fmax(0.1f, 2.0f * (vertifac - 1.0f) * rt_h * rt_h + 1.0f); + + + // three intermediate buffers for matrix calculation ... + float m1[3][3], m2[3][3], m3[3][3]; + + // ... and some pointers to handle them more intuitively + float (*mwork)[3] = m1; + float (*minput)[3] = m2; + float (*moutput)[3] = m3; + + // Step 1: flip x and y coordinates (see above) + memset(minput, 0, 9 * sizeof(float)); + minput[0][1] = 1.0f; + minput[1][0] = 1.0f; + minput[2][2] = 1.0f; + + + // Step 2: rotation of image around its center + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = cosi; + mwork[0][1] = -sini; + mwork[1][0] = sini; + mwork[1][1] = cosi; + mwork[0][2] = -0.5f * v * cosi + 0.5f * u * sini + 0.5f * v; + mwork[1][2] = -0.5f * v * sini - 0.5f * u * cosi + 0.5f * u; + mwork[2][2] = 1.0f; + + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // Step 3: apply shearing + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = 1.0f; + mwork[0][1] = shear; + mwork[1][1] = 1.0f; + mwork[1][0] = shear; + mwork[2][2] = 1.0f; + + // moutput (of last calculation) -> minput + MAT3SWAP(minput, moutput); + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // Step 4: apply vertical lens shift effect + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = exppa_v; + mwork[1][0] = 0.5f * ((exppa_v - 1.0f) * u) / v; + mwork[1][1] = 2.0f * exppa_v / (exppa_v + 1.0f); + mwork[1][2] = -0.5f * ((exppa_v - 1.0f) * u) / (exppa_v + 1.0f); + mwork[2][0] = (exppa_v - 1.0f) / v; + mwork[2][2] = 1.0f; + + // moutput (of last calculation) -> minput + MAT3SWAP(minput, moutput); + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // Step 5: horizontal compression + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = 1.0f; + mwork[1][1] = r_v; + mwork[1][2] = 0.5f * u * (1.0f - r_v); + mwork[2][2] = 1.0f; + + // moutput (of last calculation) -> minput + MAT3SWAP(minput, moutput); + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // Step 6: flip x and y back again + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][1] = 1.0f; + mwork[1][0] = 1.0f; + mwork[2][2] = 1.0f; + + // moutput (of last calculation) -> minput + MAT3SWAP(minput, moutput); + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // from here output vectors would be in (x : y : 1) format + + // Step 7: now we can apply horizontal lens shift with the same matrix format as above + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = exppa_h; + mwork[1][0] = 0.5f * ((exppa_h - 1.0f) * v) / u; + mwork[1][1] = 2.0f * exppa_h / (exppa_h + 1.0f); + mwork[1][2] = -0.5f * ((exppa_h - 1.0f) * v) / (exppa_h + 1.0f); + mwork[2][0] = (exppa_h - 1.0f) / u; + mwork[2][2] = 1.0f; + + // moutput (of last calculation) -> minput + MAT3SWAP(minput, moutput); + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // Step 8: vertical compression + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = 1.0f; + mwork[1][1] = r_h; + mwork[1][2] = 0.5f * v * (1.0f - r_h); + mwork[2][2] = 1.0f; + + // moutput (of last calculation) -> minput + MAT3SWAP(minput, moutput); + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // Step 9: apply aspect ratio scaling + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = 1.0f * ascale; + mwork[1][1] = 1.0f / ascale; + mwork[2][2] = 1.0f; + + // moutput (of last calculation) -> minput + MAT3SWAP(minput, moutput); + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // Step 10: find x/y offsets and apply according correction so that + // no negative coordinates occur in output vector + float umin = FLT_MAX, vmin = FLT_MAX; + // visit all four corners + for(int y = 0; y < height; y += height - 1) + for(int x = 0; x < width; x += width - 1) + { + float pi[3], po[3]; + pi[0] = x; + pi[1] = y; + pi[2] = 1.0f; + // moutput expects input in (x:y:1) format and gives output as (x:y:1) + mat3mulv(po, (float *)moutput, pi); + umin = fmin(umin, po[0] / po[2]); + vmin = fmin(vmin, po[1] / po[2]); + } + + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = 1.0f; + mwork[1][1] = 1.0f; + mwork[2][2] = 1.0f; + mwork[0][2] = -umin; + mwork[1][2] = -vmin; + + // moutput (of last calculation) -> minput + MAT3SWAP(minput, moutput); + // multiply mwork * minput -> moutput + mat3mul((float *)moutput, (float *)mwork, (float *)minput); + + + // on request we either keep the final matrix for forward conversions + // or produce an inverted matrix for backward conversions + if(dir == ASHIFT_HOMOGRAPH_FORWARD) + { + // we have what we need -> copy it to the right place + memcpy(homograph, moutput, 9 * sizeof(float)); + } + else + { + // generate inverted homograph (mat3inv function defined in colorspaces.c) + if(mat3inv((float *)homograph, (float *)moutput)) + { + // in case of error we set to unity matrix + memset(mwork, 0, 9 * sizeof(float)); + mwork[0][0] = 1.0f; + mwork[1][1] = 1.0f; + mwork[2][2] = 1.0f; + memcpy(homograph, mwork, 9 * sizeof(float)); + } + } +} +#undef MAT3SWAP + + +// check if module parameters are set to all neutral values in which case the module's +// output is identical to its input +static inline int isneutral(dt_iop_ashift_data_t *data) +{ + // values lower than this have no visible effect + const float eps = 1.0e-4f; + + return(fabs(data->rotation) < eps && + fabs(data->lensshift_v) < eps && + fabs(data->lensshift_h) < eps && + fabs(data->shear) < eps && + fabs(data->aspect - 1.0f) < eps && + data->cl < eps && + 1.0f - data->cr < eps && + data->ct < eps && + 1.0f - data->cb < eps); +} + + +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 +int distort_transform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *points, size_t points_count) +{ + dt_iop_ashift_data_t *data = (dt_iop_ashift_data_t *)piece->data; + + // nothing to be done if parameters are set to neutral values + if(isneutral(data)) return 1; + + float homograph[3][3]; + homography((float *)homograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb, + data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_FORWARD); + + // clipping offset + const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl); + const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct); + const float cx = fullwidth * data->cl; + const float cy = fullheight * data->ct; + +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(points, points_count, homograph) +#endif + for(size_t i = 0; i < points_count * 2; i += 2) + { + float pi[3] = { points[i], points[i + 1], 1.0f }; + float po[3]; + mat3mulv(po, (float *)homograph, pi); + points[i] = po[0] / po[2] - cx; + points[i + 1] = po[1] / po[2] - cy; + } + + return 1; +} + + +int distort_backtransform(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *points, + size_t points_count) +{ + dt_iop_ashift_data_t *data = (dt_iop_ashift_data_t *)piece->data; + + // nothing to be done if parameters are set to neutral values + if(isneutral(data)) return 1; + + float ihomograph[3][3]; + homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb, + data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED); + + // clipping offset + const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl); + const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct); + const float cx = fullwidth * data->cl; + const float cy = fullheight * data->ct; + +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(points, points_count, ihomograph) +#endif + for(size_t i = 0; i < points_count * 2; i += 2) + { + float pi[3] = { points[i] + cx, points[i + 1] + cy, 1.0f }; + float po[3]; + mat3mulv(po, (float *)ihomograph, pi); + points[i] = po[0] / po[2]; + points[i + 1] = po[1] / po[2]; + } + + return 1; +} + +void modify_roi_out(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, + const dt_iop_roi_t *roi_in) +{ + dt_iop_ashift_data_t *data = (dt_iop_ashift_data_t *)piece->data; + *roi_out = *roi_in; + + // nothing more to be done if parameters are set to neutral values + if(isneutral(data)) return; + + float homograph[3][3]; + homography((float *)homograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb, + data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_FORWARD); + + float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX; + + // go through all four vertices of input roi and convert coordinates to output + for(int y = 0; y < roi_in->height; y += roi_in->height - 1) + { + for(int x = 0; x < roi_in->width; x += roi_in->width - 1) + { + float pin[3], pout[3]; + + // convert from input coordinates to original image coordinates + pin[0] = roi_in->x + x; + pin[1] = roi_in->y + y; + pin[0] /= roi_in->scale; + pin[1] /= roi_in->scale; + pin[2] = 1.0f; + + // apply homograph + mat3mulv(pout, (float *)homograph, pin); + + // convert to output image coordinates + pout[0] /= pout[2]; + pout[1] /= pout[2]; + pout[0] *= roi_out->scale; + pout[1] *= roi_out->scale; + xm = MIN(xm, pout[0]); + xM = MAX(xM, pout[0]); + ym = MIN(ym, pout[1]); + yM = MAX(yM, pout[1]); + } + } + float width = xM - xm + 1; + float height = yM - ym + 1; + + // clipping adjustments + width *= data->cr - data->cl; + height *= data->cb - data->ct; + + roi_out->width = floorf(width); + roi_out->height = floorf(height); + +#ifdef ASHIFT_DEBUG + print_roi(roi_in, "roi_in (going into modify_roi_out)"); + print_roi(roi_out, "roi_out (after modify_roi_out)"); +#endif +} + +void modify_roi_in(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece, + const dt_iop_roi_t *const roi_out, dt_iop_roi_t *roi_in) +{ + dt_iop_ashift_data_t *data = (dt_iop_ashift_data_t *)piece->data; + *roi_in = *roi_out; + + // nothing more to be done if parameters are set to neutral values + if(isneutral(data)) return; + + float ihomograph[3][3]; + homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb, + data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED); + + const float orig_w = roi_in->scale * piece->buf_in.width; + const float orig_h = roi_in->scale * piece->buf_in.height; + + // clipping offset + const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl); + const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct); + const float cx = roi_out->scale * fullwidth * data->cl; + const float cy = roi_out->scale * fullheight * data->ct; + + float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX; + + // go through all four vertices of output roi and convert coordinates to input + for(int y = 0; y < roi_out->height; y += roi_out->height - 1) + { + for(int x = 0; x < roi_out->width; x += roi_out->width - 1) + { + float pin[3], pout[3]; + + // convert from output image coordinates to original image coordinates + pout[0] = roi_out->x + x + cx; + pout[1] = roi_out->y + y + cy; + pout[0] /= roi_out->scale; + pout[1] /= roi_out->scale; + pout[2] = 1.0f; + + // apply homograph + mat3mulv(pin, (float *)ihomograph, pout); + + // convert to input image coordinates + pin[0] /= pin[2]; + pin[1] /= pin[2]; + pin[0] *= roi_in->scale; + pin[1] *= roi_in->scale; + xm = MIN(xm, pin[0]); + xM = MAX(xM, pin[0]); + ym = MIN(ym, pin[1]); + yM = MAX(yM, pin[1]); + } + } + + const struct dt_interpolation *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF); + roi_in->x = fmaxf(0.0f, xm - interpolation->width); + roi_in->y = fmaxf(0.0f, ym - interpolation->width); + roi_in->width = fminf(ceilf(orig_w) - roi_in->x, xM - roi_in->x + 1 + interpolation->width); + roi_in->height = fminf(ceilf(orig_h) - roi_in->y, yM - roi_in->y + 1 + interpolation->width); + + // sanity check. + roi_in->x = CLAMP(roi_in->x, 0, (int)floorf(orig_w)); + roi_in->y = CLAMP(roi_in->y, 0, (int)floorf(orig_h)); + roi_in->width = CLAMP(roi_in->width, 1, (int)floorf(orig_w) - roi_in->x); + roi_in->height = CLAMP(roi_in->height, 1, (int)floorf(orig_h) - roi_in->y); +#ifdef ASHIFT_DEBUG + print_roi(roi_out, "roi_out (going into modify_roi_in)"); + print_roi(roi_in, "roi_in (after modify_roi_in)"); +#endif +} +#endif // if 0 +//----------------------------------------------------------------------------- + +// simple conversion of rgb image into greyscale variant suitable for line segment detection +// the lsd routines expect input as *double, roughly in the range [0.0; 256.0] +static void rgb2grey256(const float *in, double *out, const int width, const int height) +{ + const int ch = 4; + +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(in, out) +#endif + for(int j = 0; j < height; j++) + { + const float *inp = in + (size_t)ch * j * width; + double *outp = out + (size_t)j * width; + for(int i = 0; i < width; i++, inp += ch, outp++) + { + *outp = (0.3f * inp[0] + 0.59f * inp[1] + 0.11f * inp[2]) * 256.0; + } + } +} + +// sobel edge enhancement in one direction +static void edge_enhance_1d(const double *in, double *out, const int width, const int height, + dt_iop_ashift_enhance_t dir) +{ + // Sobel kernels for both directions + const double hkernel[3][3] = { { 1.0, 0.0, -1.0 }, { 2.0, 0.0, -2.0 }, { 1.0, 0.0, -1.0 } }; + const double vkernel[3][3] = { { 1.0, 2.0, 1.0 }, { 0.0, 0.0, 0.0 }, { -1.0, -2.0, -1.0 } }; + const int kwidth = 3; + const int khwidth = kwidth / 2; + + // select kernel + const double *kernel = (dir == ASHIFT_ENHANCE_HORIZONTAL) ? (const double *)hkernel : (const double *)vkernel; + +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(in, out, kernel) +#endif + // loop over image pixels and perform sobel convolution + for(int j = khwidth; j < height - khwidth; j++) + { + const double *inp = in + (size_t)j * width + khwidth; + double *outp = out + (size_t)j * width + khwidth; + for(int i = khwidth; i < width - khwidth; i++, inp++, outp++) + { + double sum = 0.0f; + for(int jj = 0; jj < kwidth; jj++) + { + const int k = jj * kwidth; + const int l = (jj - khwidth) * width; + for(int ii = 0; ii < kwidth; ii++) + { + sum += inp[l + ii - khwidth] * kernel[k + ii]; + } + } + *outp = sum; + } + } + +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(out) +#endif + // border fill in output buffer, so we don't get pseudo lines at image frame + for(int j = 0; j < height; j++) + for(int i = 0; i < width; i++) + { + double val = out[j * width + i]; + + if(j < khwidth) + val = out[(khwidth - j) * width + i]; + else if(j >= height - khwidth) + val = out[(j - khwidth) * width + i]; + else if(i < khwidth) + val = out[j * width + (khwidth - i)]; + else if(i >= width - khwidth) + val = out[j * width + (i - khwidth)]; + + out[j * width + i] = val; + + // jump over center of image + if(i == khwidth && j >= khwidth && j < height - khwidth) i = width - khwidth; + } +} + +// edge enhancement in both directions +static int edge_enhance(const double *in, double *out, const int width, const int height) +{ + double *Gx = NULL; + double *Gy = NULL; + + Gx = (double *)malloc((size_t)width * height * sizeof(double)); + if(Gx == NULL) goto error; + + Gy = (double *)malloc((size_t)width * height * sizeof(double)); + if(Gy == NULL) goto error; + + // perform edge enhancement in both directions + edge_enhance_1d(in, Gx, width, height, ASHIFT_ENHANCE_HORIZONTAL); + edge_enhance_1d(in, Gy, width, height, ASHIFT_ENHANCE_VERTICAL); + +// calculate absolute values +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(Gx, Gy, out) +#endif + for(size_t k = 0; k < (size_t)width * height; k++) + { + out[k] = sqrt(Gx[k] * Gx[k] + Gy[k] * Gy[k]); + } + + free(Gx); + free(Gy); + return TRUE; + +error: + if(Gx) free(Gx); + if(Gy) free(Gy); + return FALSE; +} + +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 +// XYZ -> sRGB matrix +static void XYZ_to_sRGB(const float *XYZ, float *sRGB) +{ + sRGB[0] = 3.1338561f * XYZ[0] - 1.6168667f * XYZ[1] - 0.4906146f * XYZ[2]; + sRGB[1] = -0.9787684f * XYZ[0] + 1.9161415f * XYZ[1] + 0.0334540f * XYZ[2]; + sRGB[2] = 0.0719453f * XYZ[0] - 0.2289914f * XYZ[1] + 1.4052427f * XYZ[2]; +} + +// sRGB -> XYZ matrix +static void sRGB_to_XYZ(const float *sRGB, float *XYZ) +{ + XYZ[0] = 0.4360747f * sRGB[0] + 0.3850649f * sRGB[1] + 0.1430804f * sRGB[2]; + XYZ[1] = 0.2225045f * sRGB[0] + 0.7168786f * sRGB[1] + 0.0606169f * sRGB[2]; + XYZ[2] = 0.0139322f * sRGB[0] + 0.0971045f * sRGB[1] + 0.7141733f * sRGB[2]; +} +#endif // if 0 +//----------------------------------------------------------------------------- + +// detail enhancement via bilateral grid (function arguments in and out may represent identical buffers) +static int detail_enhance(const float *in, float *out, const int width, const int height) +{ + return TRUE; +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 + const float sigma_r = 5.0f; + const float sigma_s = fminf(width, height) * 0.02f; + const float detail = 10.0f; + + int success = TRUE; + + // we need to convert from RGB to Lab first; + // as colors don't matter we are safe to assume data to be sRGB + + // convert RGB input to Lab, use output buffer for intermediate storage +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(in, out) +#endif + for(int j = 0; j < height; j++) + { + const float *inp = in + (size_t)4 * j * width; + float *outp = out + (size_t)4 * j * width; + for(int i = 0; i < width; i++, inp += 4, outp += 4) + { + float XYZ[3]; + sRGB_to_XYZ(inp, XYZ); + dt_XYZ_to_Lab(XYZ, outp); + } + } + + // bilateral grid detail enhancement + dt_bilateral_t *b = dt_bilateral_init(width, height, sigma_s, sigma_r); + + if(b != NULL) + { + dt_bilateral_splat(b, out); + dt_bilateral_blur(b); + dt_bilateral_slice_to_output(b, out, out, detail); + dt_bilateral_free(b); + } + else + success = FALSE; + + // convert resulting Lab to RGB output +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(out) +#endif + for(int j = 0; j < height; j++) + { + float *outp = out + (size_t)4 * j * width; + for(int i = 0; i < width; i++, outp += 4) + { + float XYZ[3]; + dt_Lab_to_XYZ(outp, XYZ); + XYZ_to_sRGB(XYZ, outp); + } + } + + return success; +#endif // if 0 +//----------------------------------------------------------------------------- +} + +// apply gamma correction to RGB buffer (function arguments in and out may represent identical buffers) +static void gamma_correct(const float *in, float *out, const int width, const int height) +{ +#ifdef _OPENMP +#pragma omp parallel for schedule(static) shared(in, out) +#endif + for(int j = 0; j < height; j++) + { + const float *inp = in + (size_t)4 * j * width; + float *outp = out + (size_t)4 * j * width; + for(int i = 0; i < width; i++, inp += 4, outp += 4) + { + for(int c = 0; c < 3; c++) + outp[c] = powf(inp[c], LSD_GAMMA); + } + } +} + +// do actual line_detection based on LSD algorithm and return results according +// to this module's conventions +static int line_detect(float *in, const int width, const int height, const int x_off, const int y_off, + const float scale, dt_iop_ashift_line_t **alines, int *lcount, int *vcount, int *hcount, + float *vweight, float *hweight, dt_iop_ashift_enhance_t enhance, const int is_raw) +{ + double *greyscale = NULL; + double *lsd_lines = NULL; + dt_iop_ashift_line_t *ashift_lines = NULL; + + int vertical_count = 0; + int horizontal_count = 0; + float vertical_weight = 0.0f; + float horizontal_weight = 0.0f; + // + int lines_count; + // we count the lines that we really want to use + int lct = 0; + + // apply gamma correction if image is raw + if(is_raw) + { + gamma_correct(in, in, width, height); + } + + // if requested perform an additional detail enhancement step + if(enhance & ASHIFT_ENHANCE_DETAIL) + { + (void)detail_enhance(in, in, width, height); + } + + // allocate intermediate buffers + greyscale = (double *)malloc((size_t)width * height * sizeof(double)); + if(greyscale == NULL) goto error; + + // convert to greyscale image + rgb2grey256(in, greyscale, width, height); + + // if requested perform an additional edge enhancement step + if(enhance & ASHIFT_ENHANCE_EDGES) + { + (void)edge_enhance(greyscale, greyscale, width, height); + } + + // call the line segment detector LSD; + // LSD stores the number of found lines in lines_count. + // it returns structural details as vector 'double lines[7 * lines_count]' + lsd_lines = LineSegmentDetection(&lines_count, greyscale, width, height, + LSD_SCALE, LSD_SIGMA_SCALE, LSD_QUANT, + LSD_ANG_TH, LSD_LOG_EPS, LSD_DENSITY_TH, + LSD_N_BINS, NULL, NULL, NULL); + + if(lines_count > 0) + { + // aggregate lines data into our own structures + ashift_lines = (dt_iop_ashift_line_t *)malloc((size_t)lines_count * sizeof(dt_iop_ashift_line_t)); + if(ashift_lines == NULL) goto error; + + for(int n = 0; n < lines_count; n++) + { + float x1 = lsd_lines[n * 7 + 0]; + float y1 = lsd_lines[n * 7 + 1]; + float x2 = lsd_lines[n * 7 + 2]; + float y2 = lsd_lines[n * 7 + 3]; + + // check for lines running along image borders and skip them. + // these would likely be false-positives which could result + // from any kind of processing artifacts + if((fabs(x1 - x2) < 1 && fmax(x1, x2) < 2) || + (fabs(x1 - x2) < 1 && fmin(x1, x2) > width - 3) || + (fabs(y1 - y2) < 1 && fmax(y1, y2) < 2) || + (fabs(y1 - y2) < 1 && fmin(y1, y2) > height - 3)) + continue; + + // line position in absolute coordinates + float px1 = x_off + x1; + float py1 = y_off + y1; + float px2 = x_off + x2; + float py2 = y_off + y2; + + // scale back to input buffer + px1 /= scale; + py1 /= scale; + px2 /= scale; + py2 /= scale; + + // store as homogeneous coordinates + ashift_lines[lct].p1[0] = px1; + ashift_lines[lct].p1[1] = py1; + ashift_lines[lct].p1[2] = 1.0f; + ashift_lines[lct].p2[0] = px2; + ashift_lines[lct].p2[1] = py2; + ashift_lines[lct].p2[2] = 1.0f; + + // calculate homogeneous coordinates of connecting line (defined by the two points) + vec3prodn(ashift_lines[lct].L, ashift_lines[lct].p1, ashift_lines[lct].p2); + + // normalaze line coordinates so that x^2 + y^2 = 1 + // (this will always succeed as L is a real line connecting two real points) + vec3lnorm(ashift_lines[lct].L, ashift_lines[lct].L); + + // length and width of rectangle (see LSD) + ashift_lines[lct].length = sqrt((px2 - px1) * (px2 - px1) + (py2 - py1) * (py2 - py1)); + ashift_lines[lct].width = lsd_lines[n * 7 + 4] / scale; + + // ... and weight (= length * width * angle precision) + const float weight = ashift_lines[lct].length * ashift_lines[lct].width * lsd_lines[n * 7 + 5]; + ashift_lines[lct].weight = weight; + + + const float angle = atan2(py2 - py1, px2 - px1) / M_PI * 180.0f; + const int vertical = fabs(fabs(angle) - 90.0f) < MAX_TANGENTIAL_DEVIATION ? 1 : 0; + const int horizontal = fabs(fabs(fabs(angle) - 90.0f) - 90.0f) < MAX_TANGENTIAL_DEVIATION ? 1 : 0; + + const int relevant = ashift_lines[lct].length > MIN_LINE_LENGTH ? 1 : 0; + + // register type of line + dt_iop_ashift_linetype_t type = ASHIFT_LINE_IRRELEVANT; + if(vertical && relevant) + { + type = ASHIFT_LINE_VERTICAL_SELECTED; + vertical_count++; + vertical_weight += weight; + } + else if(horizontal && relevant) + { + type = ASHIFT_LINE_HORIZONTAL_SELECTED; + horizontal_count++; + horizontal_weight += weight; + } + ashift_lines[lct].type = type; + + // the next valid line + lct++; + } + } +#ifdef ASHIFT_DEBUG + printf("%d lines (vertical %d, horizontal %d, not relevant %d)\n", lines_count, vertical_count, + horizontal_count, lct - vertical_count - horizontal_count); + float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN; + for(int n = 0; n < lct; n++) + { + xmin = fmin(xmin, fmin(ashift_lines[n].p1[0], ashift_lines[n].p2[0])); + xmax = fmax(xmax, fmax(ashift_lines[n].p1[0], ashift_lines[n].p2[0])); + ymin = fmin(ymin, fmin(ashift_lines[n].p1[1], ashift_lines[n].p2[1])); + ymax = fmax(ymax, fmax(ashift_lines[n].p1[1], ashift_lines[n].p2[1])); + printf("x1 %.0f, y1 %.0f, x2 %.0f, y2 %.0f, length %.0f, width %f, X %f, Y %f, Z %f, type %d, scalars %f %f\n", + ashift_lines[n].p1[0], ashift_lines[n].p1[1], ashift_lines[n].p2[0], ashift_lines[n].p2[1], + ashift_lines[n].length, ashift_lines[n].width, + ashift_lines[n].L[0], ashift_lines[n].L[1], ashift_lines[n].L[2], ashift_lines[n].type, + vec3scalar(ashift_lines[n].p1, ashift_lines[n].L), + vec3scalar(ashift_lines[n].p2, ashift_lines[n].L)); + } + printf("xmin %.0f, xmax %.0f, ymin %.0f, ymax %.0f\n", xmin, xmax, ymin, ymax); +#endif + + // store results in provided locations + *lcount = lct; + *vcount = vertical_count; + *vweight = vertical_weight; + *hcount = horizontal_count; + *hweight = horizontal_weight; + *alines = ashift_lines; + + // free intermediate buffers + free(lsd_lines); + free(greyscale); + return lct > 0 ? TRUE : FALSE; + +error: + free(lsd_lines); + free(greyscale); + return FALSE; +} + +// get image from buffer, analyze for structure and save results +static int get_structure(dt_iop_module_t *module, dt_iop_ashift_enhance_t enhance) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + float *buffer = NULL; + int width = 0; + int height = 0; + int x_off = 0; + int y_off = 0; + float scale = 0.0f; + + { //dt_pthread_mutex_lock(&g->lock); + MyMutex::MyLock lock(g->lock); + + // read buffer data if they are available + if(g->buf != NULL) + { + width = g->buf_width; + height = g->buf_height; + x_off = g->buf_x_off; + y_off = g->buf_y_off; + scale = g->buf_scale; + + // create a temporary buffer to hold image data + buffer = (float *)malloc((size_t)width * height * 4 * sizeof(float)); + if(buffer != NULL) + memcpy(buffer, g->buf, (size_t)width * height * 4 * sizeof(float)); + } + } /* dt_pthread_mutex_unlock(&g->lock); */ + + if(buffer == NULL) goto error; + + // get rid of old structural data + g->lines_count = 0; + g->vertical_count = 0; + g->horizontal_count = 0; + free(g->lines); + g->lines = NULL; + + dt_iop_ashift_line_t *lines; + int lines_count; + int vertical_count; + int horizontal_count; + float vertical_weight; + float horizontal_weight; + + // get new structural data + if(!line_detect(buffer, width, height, x_off, y_off, scale, &lines, &lines_count, + &vertical_count, &horizontal_count, &vertical_weight, &horizontal_weight, + enhance, module->is_raw))//dt_image_is_raw(&module->dev->image_storage))) + goto error; + + // save new structural data + g->lines_in_width = width; + g->lines_in_height = height; + g->lines_x_off = x_off; + g->lines_y_off = y_off; + g->lines_count = lines_count; + g->vertical_count = vertical_count; + g->horizontal_count = horizontal_count; + g->vertical_weight = vertical_weight; + g->horizontal_weight = horizontal_weight; + g->lines_version++; + g->lines_suppressed = 0; + g->lines = lines; + + free(buffer); + return TRUE; + +error: + free(buffer); + return FALSE; +} + + +// swap two integer values +static inline void swap(int *a, int *b) +{ + int tmp = *a; + *a = *b; + *b = tmp; +} + +// do complete permutations +static int quickperm(int *a, int *p, const int N, int *i) +{ + if(*i >= N) return FALSE; + + p[*i]--; + int j = (*i % 2 == 1) ? p[*i] : 0; + swap(&a[j], &a[*i]); + *i = 1; + while(p[*i] == 0) + { + p[*i] = *i; + (*i)++; + } + return TRUE; +} + +// Fisher-Yates shuffle +static void shuffle(int *a, const int N) +{ + for(int i = 0; i < N; i++) + { + int j = i + rand() % (N - i); + swap(&a[j], &a[i]); + } +} + +// factorial function +static int fact(const int n) +{ + return (n == 1 ? 1 : n * fact(n - 1)); +} + +// We use a pseudo-RANSAC algorithm to elminiate ouliers from our set of lines. The +// original RANSAC works on linear optimization problems. Our model is nonlinear. We +// take advantage of the fact that lines interesting for our model are vantage lines +// that meet in one vantage point for each subset of lines (vertical/horizontal). +// Stragegy: we construct a model by (random) sampling within the subset of lines and +// calculate the vantage point. Then we check the "distance" of all other lines to the +// vantage point. The model that gives highest number of lines combined with the highest +// total weight and lowest overall "distance" wins. +// Disadvantage: compared to the original RANSAC we don't get any model parameters that +// we could use for the following NMS fit. +// Self-tuning: we optimize "epsilon", the hurdle rate to reject a line as an outlier, +// by a number of dry runs first. The target average percentage value of lines to eliminate as +// outliers (without judging on the quality of the model) is given by RANSAC_ELIMINATION_RATIO, +// note: the actual percentage of outliers removed in the final run will be lower because we +// will finally look for the best quality model with the optimized epsilon and that quality value also +// encloses the number of good lines +static void ransac(const dt_iop_ashift_line_t *lines, int *index_set, int *inout_set, + const int set_count, const float total_weight, const int xmin, const int xmax, + const int ymin, const int ymax) +{ + if(set_count < 3) return; + + const size_t set_size = set_count * sizeof(int); + int *best_set = (int *)malloc(set_size); + memcpy(best_set, index_set, set_size); + int *best_inout = (int *)calloc(1, set_size); + + float best_quality = 0.0f; + + // hurdle value epsilon for rejecting a line as an outlier will be self-tuning + // in a number of dry runs + float epsilon = pow(10.0f, -RANSAC_EPSILON); + float epsilon_step = RANSAC_EPSILON_STEP; + // some accounting variables for self-tuning + int lines_eliminated = 0; + int valid_runs = 0; + + // number of runs to optimize epsilon + const int optiruns = RANSAC_OPTIMIZATION_STEPS * RANSAC_OPTIMIZATION_DRY_RUNS; + // go for complete permutations on small set sizes, else for random sample consensus + const int riter = (set_count > RANSAC_HURDLE) ? RANSAC_RUNS : fact(set_count); + + // some data needed for quickperm + int *perm = (int *)malloc((set_count + 1) * sizeof(int)); + for(int n = 0; n < set_count + 1; n++) perm[n] = n; + int piter = 1; + + // inout holds good/bad qualification for each line + int *inout = (int *)malloc(set_size); + + for(int r = 0; r < optiruns + riter; r++) + { + // get random or systematic variation of index set + if(set_count > RANSAC_HURDLE || r < optiruns) + shuffle(index_set, set_count); + else + (void)quickperm(index_set, perm, set_count, &piter); + + // summed quality evaluation of this run + float quality = 0.0f; + + // we build a model ouf of the first two lines + const float *L1 = lines[index_set[0]].L; + const float *L2 = lines[index_set[1]].L; + + // get intersection point (ideally a vantage point) + float V[3]; + vec3prodn(V, L1, L2); + + // catch special cases: + // a) L1 and L2 are identical -> V is NULL -> no valid vantage point + // b) vantage point lies inside image frame (no chance to correct for this case) + if(vec3isnull(V) || + (fabs(V[2]) > 0.0f && + V[0]/V[2] >= xmin && + V[1]/V[2] >= ymin && + V[0]/V[2] <= xmax && + V[1]/V[2] <= ymax)) + { + // no valid model + quality = 0.0f; + } + else + { + // valid model + + // normalize V so that x^2 + y^2 + z^2 = 1 + vec3norm(V, V); + + // the two lines constituting the model are part of the set + inout[0] = 1; + inout[1] = 1; + + // go through all remaining lines, check if they are within the model, and + // mark that fact in inout[]. + // summarize a quality parameter for all lines within the model + for(int n = 2; n < set_count; n++) + { + // L is normalized so that x^2 + y^2 = 1 + const float *L3 = lines[index_set[n]].L; + + // we take the absolute value of the dot product of V and L as a measure + // of the "distance" between point and line. Note that this is not the real euclidian + // distance but - with the given normalization - just a pragmatically selected number + // that goes to zero if V lies on L and increases the more V and L are apart + const float d = fabs(vec3scalar(V, L3)); + + // depending on d we either include or exclude the point from the set + inout[n] = (d < epsilon) ? 1 : 0; + + float q; + + if(inout[n] == 1) + { + // a quality parameter that depends 1/3 on the number of lines within the model, + // 1/3 on their weight, and 1/3 on their weighted distance d to the vantage point + q = 0.33f / (float)set_count + + 0.33f * lines[index_set[n]].weight / total_weight + + 0.33f * (1.0f - d / epsilon) * (float)set_count * lines[index_set[n]].weight / total_weight; + } + else + { + q = 0.0f; + lines_eliminated++; + } + + quality += q; + } + valid_runs++; + } + + if(r < optiruns) + { + // on last run of each self-tuning step + if((r % RANSAC_OPTIMIZATION_DRY_RUNS) == (RANSAC_OPTIMIZATION_DRY_RUNS - 1) && (valid_runs > 0)) + { +#ifdef ASHIFT_DEBUG + printf("ransac self-tuning (run %d): epsilon %f", r, epsilon); +#endif + // average ratio of lines that we eliminated with the given epsilon + float ratio = 100.0f * (float)lines_eliminated / ((float)set_count * valid_runs); + // adjust epsilon accordingly + if(ratio < RANSAC_ELIMINATION_RATIO) + epsilon = pow(10.0f, log10(epsilon) - epsilon_step); + else if(ratio > RANSAC_ELIMINATION_RATIO) + epsilon = pow(10.0f, log10(epsilon) + epsilon_step); +#ifdef ASHIFT_DEBUG + printf(" (elimination ratio %f) -> %f\n", ratio, epsilon); +#endif + // reduce step-size for next optimization round + epsilon_step /= 2.0f; + lines_eliminated = 0; + valid_runs = 0; + } + } + else + { + // in the "real" runs check against the best model found so far + if(quality > best_quality) + { + memcpy(best_set, index_set, set_size); + memcpy(best_inout, inout, set_size); + best_quality = quality; + } + } + +#ifdef ASHIFT_DEBUG + // report some statistics + int count = 0, lastcount = 0; + for(int n = 0; n < set_count; n++) count += best_inout[n]; + for(int n = 0; n < set_count; n++) lastcount += inout[n]; + printf("ransac run %d: best qual %.6f, eps %.6f, line count %d of %d (this run: qual %.5f, count %d (%2f%%))\n", r, + best_quality, epsilon, count, set_count, quality, lastcount, 100.0f * lastcount / (float)set_count); +#endif + } + + // store back best set + memcpy(index_set, best_set, set_size); + memcpy(inout_set, best_inout, set_size); + + free(inout); + free(perm); + free(best_inout); + free(best_set); +} + + +// try to clean up structural data by eliminating outliers and thereby increasing +// the chance of a convergent fitting +static int remove_outliers(dt_iop_module_t *module) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + const int width = g->lines_in_width; + const int height = g->lines_in_height; + const int xmin = g->lines_x_off; + const int ymin = g->lines_y_off; + const int xmax = xmin + width; + const int ymax = ymin + height; + + // holds the index set of lines we want to work on + int *lines_set = (int *)malloc(g->lines_count * sizeof(int)); + // holds the result of ransac + int *inout_set = (int *)malloc(g->lines_count * sizeof(int)); + + // some accounting variables + int vnb = 0, vcount = 0; + int hnb = 0, hcount = 0; + + // just to be on the safe side + if(g->lines == NULL) goto error; + + // generate index list for the vertical lines + for(int n = 0; n < g->lines_count; n++) + { + // is this a selected vertical line? + if((g->lines[n].type & ASHIFT_LINE_MASK) != ASHIFT_LINE_VERTICAL_SELECTED) + continue; + + lines_set[vnb] = n; + inout_set[vnb] = 0; + vnb++; + } + + // it only makes sense to call ransac if we have more than two lines + if(vnb > 2) + ransac(g->lines, lines_set, inout_set, vnb, g->vertical_weight, + xmin, xmax, ymin, ymax); + + // adjust line selected flag according to the ransac results + for(int n = 0; n < vnb; n++) + { + const int m = lines_set[n]; + if(inout_set[n] == 1) + { + g->lines[m].type |= ASHIFT_LINE_SELECTED; + vcount++; + } + else + g->lines[m].type &= ~ASHIFT_LINE_SELECTED; + } + // update number of vertical lines + g->vertical_count = vcount; + g->lines_version++; + + // now generate index list for the horizontal lines + for(int n = 0; n < g->lines_count; n++) + { + // is this a selected horizontal line? + if((g->lines[n].type & ASHIFT_LINE_MASK) != ASHIFT_LINE_HORIZONTAL_SELECTED) + continue; + + lines_set[hnb] = n; + inout_set[hnb] = 0; + hnb++; + } + + // it only makes sense to call ransac if we have more than two lines + if(hnb > 2) + ransac(g->lines, lines_set, inout_set, hnb, g->horizontal_weight, + xmin, xmax, ymin, ymax); + + // adjust line selected flag according to the ransac results + for(int n = 0; n < hnb; n++) + { + const int m = lines_set[n]; + if(inout_set[n] == 1) + { + g->lines[m].type |= ASHIFT_LINE_SELECTED; + hcount++; + } + else + g->lines[m].type &= ~ASHIFT_LINE_SELECTED; + } + // update number of horizontal lines + g->horizontal_count = hcount; + g->lines_version++; + + free(inout_set); + free(lines_set); + + return TRUE; + +error: + free(inout_set); + free(lines_set); + return FALSE; +} + +// utility function to map a variable in [min; max] to [-INF; + INF] +static inline double logit(double x, double min, double max) +{ + const double eps = 1.0e-6; + // make sure p does not touch the borders of its definition area, + // not critical for data accuracy as logit() is only used on initial fit parameters + double p = CLAMP((x - min) / (max - min), eps, 1.0 - eps); + + return (2.0 * atanh(2.0 * p - 1.0)); +} + +// inverted function to logit() +static inline double ilogit(double L, double min, double max) +{ + double p = 0.5 * (1.0 + tanh(0.5 * L)); + + return (p * (max - min) + min); +} + +// helper function for simplex() return quality parameter for the given model +// strategy: +// * generate homography matrix out of fixed parameters and fitting parameters +// * apply homography to all end points of affected lines +// * generate new line out of transformed end points +// * calculate scalar product s of line with perpendicular axis +// * sum over weighted s^2 values +static double model_fitness(double *params, void *data) +{ + dt_iop_ashift_fit_params_t *fit = (dt_iop_ashift_fit_params_t *)data; + + // just for convenience: get shorter names + dt_iop_ashift_line_t *lines = fit->lines; + const int lines_count = fit->lines_count; + const int width = fit->width; + const int height = fit->height; + const float f_length_kb = fit->f_length_kb; + const float orthocorr = fit->orthocorr; + const float aspect = fit->aspect; + + float rotation = fit->rotation; + float lensshift_v = fit->lensshift_v; + float lensshift_h = fit->lensshift_h; + float shear = fit->shear; + float rotation_range = fit->rotation_range; + float lensshift_v_range = fit->lensshift_v_range; + float lensshift_h_range = fit->lensshift_h_range; + float shear_range = fit->shear_range; + + int pcount = 0; + + // fill in fit parameters from params[]. Attention: order matters!!! + if(isnan(rotation)) + { + rotation = ilogit(params[pcount], -rotation_range, rotation_range); + pcount++; + } + + if(isnan(lensshift_v)) + { + lensshift_v = ilogit(params[pcount], -lensshift_v_range, lensshift_v_range); + pcount++; + } + + if(isnan(lensshift_h)) + { + lensshift_h = ilogit(params[pcount], -lensshift_h_range, lensshift_h_range); + pcount++; + } + + if(isnan(shear)) + { + shear = ilogit(params[pcount], -shear_range, shear_range); + pcount++; + } + + assert(pcount == fit->params_count); + + // the possible reference axes + const float Av[3] = { 1.0f, 0.0f, 0.0f }; + const float Ah[3] = { 0.0f, 1.0f, 0.0f }; + + // generate homograph out of the parameters + float homograph[3][3]; + homography((float *)homograph, rotation, lensshift_v, lensshift_h, shear, f_length_kb, + orthocorr, aspect, width, height, ASHIFT_HOMOGRAPH_FORWARD); + + // accounting variables + double sumsq_v = 0.0; + double sumsq_h = 0.0; + double weight_v = 0.0; + double weight_h = 0.0; + int count_v = 0; + int count_h = 0; + int count = 0; + + // iterate over all lines + for(int n = 0; n < lines_count; n++) + { + // check if this is a line which we must skip + if((lines[n].type & fit->linemask) != fit->linetype) + continue; + + // the direction of this line (vertical?) + const int isvertical = lines[n].type & ASHIFT_LINE_DIRVERT; + + // select the perpendicular reference axis + const float *A = isvertical ? Ah : Av; + + // apply homographic transformation to the end points + float P1[3], P2[3]; + mat3mulv(P1, (float *)homograph, lines[n].p1); + mat3mulv(P2, (float *)homograph, lines[n].p2); + + // get line connecting the two points + float L[3]; + vec3prodn(L, P1, P2); + + // normalize L so that x^2 + y^2 = 1; makes sure that + // y^2 = 1 / (1 + m^2) and x^2 = m^2 / (1 + m^2) with m defining the slope of the line + vec3lnorm(L, L); + + // get scalar product of line L with orthogonal axis A -> gives 0 if line is perpendicular + float s = vec3scalar(L, A); + + // sum up weighted s^2 for both directions individually + sumsq_v += isvertical ? s * s * lines[n].weight : 0.0; + weight_v += isvertical ? lines[n].weight : 0.0; + count_v += isvertical ? 1 : 0; + sumsq_h += !isvertical ? s * s * lines[n].weight : 0.0; + weight_h += !isvertical ? lines[n].weight : 0.0; + count_h += !isvertical ? 1 : 0; + count++; + } + + const double v = weight_v > 0.0f && count > 0 ? sumsq_v / weight_v * (float)count_v / count : 0.0; + const double h = weight_h > 0.0f && count > 0 ? sumsq_h / weight_h * (float)count_h / count : 0.0; + + double sum = sqrt(1.0 - (1.0 - v) * (1.0 - h)) * 1.0e6; + //double sum = sqrt(v + h) * 1.0e6; + +#ifdef ASHIFT_DEBUG + printf("fitness with rotation %f, lensshift_v %f, lensshift_h %f, shear %f -> lines %d, quality %10f\n", + rotation, lensshift_v, lensshift_h, shear, count, sum); +#endif + + return sum; +} + +// setup all data structures for fitting and call NM simplex +static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_params_t *p, dt_iop_ashift_fitaxis_t dir) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + if(!g->lines) return NMS_NOT_ENOUGH_LINES; + if(dir == ASHIFT_FIT_NONE) return NMS_SUCCESS; + + double params[4]; + int pcount = 0; + int enough_lines = TRUE; + + // initialize fit parameters + dt_iop_ashift_fit_params_t fit; + fit.lines = g->lines; + fit.lines_count = g->lines_count; + fit.width = g->lines_in_width; + fit.height = g->lines_in_height; + fit.f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor; + fit.orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr; + fit.aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect; + fit.rotation = p->rotation; + fit.lensshift_v = p->lensshift_v; + fit.lensshift_h = p->lensshift_h; + fit.shear = p->shear; + fit.rotation_range = g->rotation_range; + fit.lensshift_v_range = g->lensshift_v_range; + fit.lensshift_h_range = g->lensshift_h_range; + fit.shear_range = g->shear_range; + fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED; + fit.linemask = ASHIFT_LINE_MASK; + fit.params_count = 0; + fit.weight = 0.0f; + + // if the image is flipped and if we do not want to fit both lens shift + // directions or none at all, then we need to change direction + dt_iop_ashift_fitaxis_t mdir = dir; + if((mdir & ASHIFT_FIT_LENS_BOTH) != ASHIFT_FIT_LENS_BOTH && + (mdir & ASHIFT_FIT_LENS_BOTH) != 0) + { + // flip all directions + mdir ^= g->isflipped ? ASHIFT_FIT_FLIP : 0; + // special case that needs to be corrected + mdir |= (mdir & ASHIFT_FIT_LINES_BOTH) == 0 ? ASHIFT_FIT_LINES_BOTH : 0; + } + + + // prepare fit structure and starting parameters for simplex fit. + // note: the sequence of parameters in params[] needs to match the + // respective order in dt_iop_ashift_fit_params_t. Parameters which are + // to be fittet are marked with NAN in the fit structure. Non-NAN + // parameters are assumed to be constant. + if(mdir & ASHIFT_FIT_ROTATION) + { + // we fit rotation + fit.params_count++; + params[pcount] = logit(fit.rotation, -fit.rotation_range, fit.rotation_range); + pcount++; + fit.rotation = NAN; + } + + if(mdir & ASHIFT_FIT_LENS_VERT) + { + // we fit vertical lens shift + fit.params_count++; + params[pcount] = logit(fit.lensshift_v, -fit.lensshift_v_range, fit.lensshift_v_range); + pcount++; + fit.lensshift_v = NAN; + } + + if(mdir & ASHIFT_FIT_LENS_HOR) + { + // we fit horizontal lens shift + fit.params_count++; + params[pcount] = logit(fit.lensshift_h, -fit.lensshift_h_range, fit.lensshift_h_range); + pcount++; + fit.lensshift_h = NAN; + } + + if(mdir & ASHIFT_FIT_SHEAR) + { + // we fit the shear parameter + fit.params_count++; + params[pcount] = logit(fit.shear, -fit.shear_range, fit.shear_range); + pcount++; + fit.shear = NAN; + } + + if(mdir & ASHIFT_FIT_LINES_VERT) + { + // we use vertical lines for fitting + fit.linetype |= ASHIFT_LINE_DIRVERT; + fit.weight += g->vertical_weight; + enough_lines = enough_lines && (g->vertical_count >= MINIMUM_FITLINES); + } + + if(mdir & ASHIFT_FIT_LINES_HOR) + { + // we use horizontal lines for fitting + fit.linetype |= 0; + fit.weight += g->horizontal_weight; + enough_lines = enough_lines && (g->horizontal_count >= MINIMUM_FITLINES); + } + + // this needs to come after ASHIFT_FIT_LINES_VERT and ASHIFT_FIT_LINES_HOR + if((mdir & ASHIFT_FIT_LINES_BOTH) == ASHIFT_FIT_LINES_BOTH) + { + // if we use fitting in both directions we need to + // adjust fit.linetype and fit.linemask to match all selected lines + fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED; + fit.linemask = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED; + } + + // error case: we do not run simplex if there are not enough lines + if(!enough_lines) + { +#ifdef ASHIFT_DEBUG + printf("optimization not possible: insufficient number of lines\n"); +#endif + return NMS_NOT_ENOUGH_LINES; + } + + // start the simplex fit + int iter = simplex(model_fitness, params, fit.params_count, NMS_EPSILON, NMS_SCALE, NMS_ITERATIONS, NULL, (void*)&fit); + + // error case: the fit did not converge + if(iter >= NMS_ITERATIONS) + { +#ifdef ASHIFT_DEBUG + printf("optimization not successful: maximum number of iterations reached (%d)\n", iter); +#endif + return NMS_DID_NOT_CONVERGE; + } + + // fit was successful: now consolidate the results (order matters!!!) + pcount = 0; + fit.rotation = isnan(fit.rotation) ? ilogit(params[pcount++], -fit.rotation_range, fit.rotation_range) : fit.rotation; + fit.lensshift_v = isnan(fit.lensshift_v) ? ilogit(params[pcount++], -fit.lensshift_v_range, fit.lensshift_v_range) : fit.lensshift_v; + fit.lensshift_h = isnan(fit.lensshift_h) ? ilogit(params[pcount++], -fit.lensshift_h_range, fit.lensshift_h_range) : fit.lensshift_h; + fit.shear = isnan(fit.shear) ? ilogit(params[pcount++], -fit.shear_range, fit.shear_range) : fit.shear; +#ifdef ASHIFT_DEBUG + printf("params after optimization (%d iterations): rotation %f, lensshift_v %f, lensshift_h %f, shear %f\n", + iter, fit.rotation, fit.lensshift_v, fit.lensshift_h, fit.shear); +#endif + + // sanity check: in case of extreme values the image gets distorted so strongly that it spans an insanely huge area. we check that + // case and assume values that increase the image area by more than a factor of 4 as being insane. + float homograph[3][3]; + homography((float *)homograph, fit.rotation, fit.lensshift_v, fit.lensshift_h, fit.shear, fit.f_length_kb, + fit.orthocorr, fit.aspect, fit.width, fit.height, ASHIFT_HOMOGRAPH_FORWARD); + + // visit all four corners and find maximum span + float xm = FLT_MAX, xM = -FLT_MAX, ym = FLT_MAX, yM = -FLT_MAX; + for(int y = 0; y < fit.height; y += fit.height - 1) + for(int x = 0; x < fit.width; x += fit.width - 1) + { + float pi[3], po[3]; + pi[0] = x; + pi[1] = y; + pi[2] = 1.0f; + mat3mulv(po, (float *)homograph, pi); + po[0] /= po[2]; + po[1] /= po[2]; + xm = fmin(xm, po[0]); + ym = fmin(ym, po[1]); + xM = fmax(xM, po[0]); + yM = fmax(yM, po[1]); + } + + if((xM - xm) * (yM - ym) > 4.0f * fit.width * fit.height) + { +#ifdef ASHIFT_DEBUG + printf("optimization not successful: degenerate case with area growth factor (%f) exceeding limits\n", + (xM - xm) * (yM - ym) / (fit.width * fit.height)); +#endif + return NMS_INSANE; + } + + // now write the results into structure p + p->rotation = fit.rotation; + p->lensshift_v = fit.lensshift_v; + p->lensshift_h = fit.lensshift_h; + p->shear = fit.shear; + return NMS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 +#ifdef ASHIFT_DEBUG +// only used in development phase. call model_fitness() with current parameters and +// print some useful information +static void model_probe(dt_iop_module_t *module, dt_iop_ashift_params_t *p, dt_iop_ashift_fitaxis_t dir) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + if(!g->lines) return; + if(dir == ASHIFT_FIT_NONE) return; + + double params[4]; + int enough_lines = TRUE; + + // initialize fit parameters + dt_iop_ashift_fit_params_t fit; + fit.lines = g->lines; + fit.lines_count = g->lines_count; + fit.width = g->lines_in_width; + fit.height = g->lines_in_height; + fit.f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor; + fit.orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr; + fit.aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect; + fit.rotation = p->rotation; + fit.lensshift_v = p->lensshift_v; + fit.lensshift_h = p->lensshift_h; + fit.shear = p->shear; + fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED; + fit.linemask = ASHIFT_LINE_MASK; + fit.params_count = 0; + fit.weight = 0.0f; + + // if the image is flipped and if we do not want to fit both lens shift + // directions or none at all, then we need to change direction + dt_iop_ashift_fitaxis_t mdir = dir; + if((mdir & ASHIFT_FIT_LENS_BOTH) != ASHIFT_FIT_LENS_BOTH && + (mdir & ASHIFT_FIT_LENS_BOTH) != 0) + { + // flip all directions + mdir ^= g->isflipped ? ASHIFT_FIT_FLIP : 0; + // special case that needs to be corrected + mdir |= (mdir & ASHIFT_FIT_LINES_BOTH) == 0 ? ASHIFT_FIT_LINES_BOTH : 0; + } + + if(mdir & ASHIFT_FIT_LINES_VERT) + { + // we use vertical lines for fitting + fit.linetype |= ASHIFT_LINE_DIRVERT; + fit.weight += g->vertical_weight; + enough_lines = enough_lines && (g->vertical_count >= MINIMUM_FITLINES); + } + + if(mdir & ASHIFT_FIT_LINES_HOR) + { + // we use horizontal lines for fitting + fit.linetype |= 0; + fit.weight += g->horizontal_weight; + enough_lines = enough_lines && (g->horizontal_count >= MINIMUM_FITLINES); + } + + // this needs to come after ASHIFT_FIT_LINES_VERT and ASHIFT_FIT_LINES_HOR + if((mdir & ASHIFT_FIT_LINES_BOTH) == ASHIFT_FIT_LINES_BOTH) + { + // if we use fitting in both directions we need to + // adjust fit.linetype and fit.linemask to match all selected lines + fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED; + fit.linemask = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED; + } + + double quality = model_fitness(params, (void *)&fit); + + printf("model fitness: %.8f (rotation %f, lensshift_v %f, lensshift_h %f, shear %f)\n", + quality, p->rotation, p->lensshift_v, p->lensshift_h, p->shear); +} +#endif +#endif // if 0 +//----------------------------------------------------------------------------- + +// function to keep crop fitting parameters within constraints +static void crop_constraint(double *params, int pcount) +{ + if(pcount > 0) params[0] = fabs(params[0]); + if(pcount > 1) params[1] = fabs(params[1]); + if(pcount > 2) params[2] = fabs(params[2]); + + if(pcount > 0 && params[0] > 1.0) params[0] = 1.0 - params[0]; + if(pcount > 1 && params[1] > 1.0) params[1] = 1.0 - params[1]; + if(pcount > 2 && params[2] > 0.5*M_PI) params[2] = 0.5*M_PI - params[2]; +} + +// helper function for getting the best fitting crop area; +// returns the negative area of the largest rectangle that fits within the +// defined image with a given rectangle's center and its aspect angle; +// the trick: the rectangle center coordinates are given in the input +// image coordinates so we know for sure that it also lies within the image after +// conversion to the output coordinates +static double crop_fitness(double *params, void *data) +{ + dt_iop_ashift_cropfit_params_t *cropfit = (dt_iop_ashift_cropfit_params_t *)data; + + const float wd = cropfit->width; + const float ht = cropfit->height; + + // get variable and constant parameters, respectively + const float x = isnan(cropfit->x) ? params[0] : cropfit->x; + const float y = isnan(cropfit->y) ? params[1] : cropfit->y; + const float alpha = isnan(cropfit->alpha) ? params[2] : cropfit->alpha; + + // the center of the rectangle in input image coordinates + const float Pc[3] = { x * wd, y * ht, 1.0f }; + + // convert to the output image coordinates and normalize + float P[3]; + mat3mulv(P, (float *)cropfit->homograph, Pc); + P[0] /= P[2]; + P[1] /= P[2]; + P[2] = 1.0f; + + // two auxiliary points (some arbitrary distance away from P) to construct the diagonals + const float Pa[2][3] = { { P[0] + 10.0f * cos(alpha), P[1] + 10.0f * sin(alpha), 1.0f }, + { P[0] + 10.0f * cos(alpha), P[1] - 10.0f * sin(alpha), 1.0f } }; + + // the two diagonals: D = P x Pa + float D[2][3]; + vec3prodn(D[0], P, Pa[0]); + vec3prodn(D[1], P, Pa[1]); + + // find all intersection points of all four edges with both diagonals (I = E x D); + // the shortest distance d2min of the intersection point I to the crop area center P determines + // the size of the crop area that still fits into the image (for the given center and aspect angle) + float d2min = FLT_MAX; + for(int k = 0; k < 4; k++) + for(int l = 0; l < 2; l++) + { + // the intersection point + float I[3]; + vec3prodn(I, cropfit->edges[k], D[l]); + + // special case: I is all null -> E and D are identical -> P lies on E -> d2min = 0 + if(vec3isnull(I)) + { + d2min = 0.0f; + break; + } + + // special case: I[2] is 0.0f -> E and D are parallel and intersect at infinity -> no relevant point + if(I[2] == 0.0f) + continue; + + // the default case -> normalize I + I[0] /= I[2]; + I[1] /= I[2]; + + // calculate distance from I to P + const float d2 = SQR(P[0] - I[0]) + SQR(P[1] - I[1]); + + // the minimum distance over all intersection points + d2min = MIN(d2min, d2); + } + + // calculate the area of the rectangle + const float A = 2.0f * d2min * sin(2.0f * alpha); + +#ifdef ASHIFT_DEBUG + printf("crop fitness with x %f, y %f, angle %f -> distance %f, area %f\n", + x, y, alpha, d2min, A); +#endif + // and return -A to allow Nelder-Mead simplex to search for the minimum + return -A; +} + +// strategy: for a given center of the crop area and a specific aspect angle +// we calculate the largest crop area that still lies within the output image; +// now we allow a Nelder-Mead simplex to search for the center coordinates +// (and optionally the aspect angle) that delivers the largest overall crop area. +static void do_crop(dt_iop_module_t *module, dt_iop_ashift_params_t *p) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + // skip if fitting is still running + if(g->fitting) return; + + // reset fit margins if auto-cropping is off + if(p->cropmode == ASHIFT_CROP_OFF) + { + p->cl = 0.0f; + p->cr = 1.0f; + p->ct = 0.0f; + p->cb = 1.0f; + return; + } + + g->fitting = 1; + + double params[3]; + int pcount; + + // get parameters for the homograph + const float f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor; + const float orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr; + const float aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect; + const float rotation = p->rotation; + const float lensshift_v = p->lensshift_v; + const float lensshift_h = p->lensshift_h; + const float shear = p->shear; + + // prepare structure of constant parameters + dt_iop_ashift_cropfit_params_t cropfit; + cropfit.width = g->buf_width; + cropfit.height = g->buf_height; + homography((float *)cropfit.homograph, rotation, lensshift_v, lensshift_h, shear, f_length_kb, + orthocorr, aspect, cropfit.width, cropfit.height, ASHIFT_HOMOGRAPH_FORWARD); + + const float wd = cropfit.width; + const float ht = cropfit.height; + + // the four vertices of the image in input image coordinates + const float Vc[4][3] = { { 0.0f, 0.0f, 1.0f }, + { 0.0f, ht, 1.0f }, + { wd, ht, 1.0f }, + { wd, 0.0f, 1.0f } }; + + // convert the vertices to output image coordinates + float V[4][3]; + for(int n = 0; n < 4; n++) + mat3mulv(V[n], (float *)cropfit.homograph, Vc[n]); + + // get width and height of output image for later use + float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN; + for(int n = 0; n < 4; n++) + { + // normalize V + V[n][0] /= V[n][2]; + V[n][1] /= V[n][2]; + V[n][2] = 1.0f; + xmin = MIN(xmin, V[n][0]); + xmax = MAX(xmax, V[n][0]); + ymin = MIN(ymin, V[n][1]); + ymax = MAX(ymax, V[n][1]); + } + const float owd = xmax - xmin; + const float oht = ymax - ymin; + + // calculate the lines defining the four edges of the image area: E = V[n] x V[n+1] + for(int n = 0; n < 4; n++) + vec3prodn(cropfit.edges[n], V[n], V[(n + 1) % 4]); + + // initial fit parameters: crop area is centered and aspect angle is that of the original image + // number of parameters: fit only crop center coordinates with a fixed aspect ratio, or fit all three variables + if(p->cropmode == ASHIFT_CROP_LARGEST) + { + params[0] = 0.5; + params[1] = 0.5; + params[2] = atan2((float)cropfit.height, (float)cropfit.width); + cropfit.x = NAN; + cropfit.y = NAN; + cropfit.alpha = NAN; + pcount = 3; + } + else //(p->cropmode == ASHIFT_CROP_ASPECT) + { + params[0] = 0.5; + params[1] = 0.5; + cropfit.x = NAN; + cropfit.y = NAN; + cropfit.alpha = atan2((float)cropfit.height, (float)cropfit.width); + pcount = 2; + } + + // start the simplex fit + const int iter = simplex(crop_fitness, params, pcount, NMS_CROP_EPSILON, NMS_CROP_SCALE, NMS_CROP_ITERATIONS, + crop_constraint, (void*)&cropfit); + + float A; // RT + float d; // RT + float Pc[3] = { 0.f, 0.f, 1.f }; // RT + + // in case the fit did not converge -> failed + if(iter >= NMS_CROP_ITERATIONS) goto failed; + + // the fit did converge -> get clipping margins out of params: + cropfit.x = isnan(cropfit.x) ? params[0] : cropfit.x; + cropfit.y = isnan(cropfit.y) ? params[1] : cropfit.y; + cropfit.alpha = isnan(cropfit.alpha) ? params[2] : cropfit.alpha; + + // the area of the best fitting rectangle + /*RT const float*/ A = fabs(crop_fitness(params, (void*)&cropfit)); + + // unlikely to happen but we need to catch this case + if(A == 0.0f) goto failed; + + // we need the half diagonal of that rectangle (this is in output image dimensions); + // no need to check for division by zero here as this case implies A == 0.0f, caught above + /*RT const float*/ d = sqrt(A / (2.0f * sin(2.0f * cropfit.alpha))); + + // the rectangle's center in input image (homogeneous) coordinates + // RT const float Pc[3] = { cropfit.x * wd, cropfit.y * ht, 1.0f }; + Pc[0] = cropfit.x * wd; // RT + Pc[1] = cropfit.y * ht; // RT + + // convert rectangle center to output image coordinates and normalize + float P[3]; + mat3mulv(P, (float *)cropfit.homograph, Pc); + P[0] /= P[2]; + P[1] /= P[2]; + + // calculate clipping margins relative to output image dimensions + p->cl = CLAMP((P[0] - d * cos(cropfit.alpha)) / owd, 0.0f, 1.0f); + p->cr = CLAMP((P[0] + d * cos(cropfit.alpha)) / owd, 0.0f, 1.0f); + p->ct = CLAMP((P[1] - d * sin(cropfit.alpha)) / oht, 0.0f, 1.0f); + p->cb = CLAMP((P[1] + d * sin(cropfit.alpha)) / oht, 0.0f, 1.0f); + + // final sanity check + if(p->cr - p->cl <= 0.0f || p->cb - p->ct <= 0.0f) goto failed; + + g->fitting = 0; + +#ifdef ASHIFT_DEBUG + printf("margins after crop fitting: iter %d, x %f, y %f, angle %f, crop area (%f %f %f %f), width %f, height %f\n", + iter, cropfit.x, cropfit.y, cropfit.alpha, p->cl, p->cr, p->ct, p->cb, wd, ht); +#endif +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 + dt_control_queue_redraw_center(); +#endif // if 0 +//----------------------------------------------------------------------------- + + return; + +failed: + // in case of failure: reset clipping margins, set "automatic cropping" parameter + // to "off" state, and display warning message + p->cl = 0.0f; + p->cr = 1.0f; + p->ct = 0.0f; + p->cb = 1.0f; + p->cropmode = ASHIFT_CROP_OFF; +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 + dt_bauhaus_combobox_set(g->cropmode, p->cropmode); +#endif // if 0 +//----------------------------------------------------------------------------- + g->fitting = 0; + dt_control_log(_("automatic cropping failed")); + return; +} + +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +//----------------------------------------------------------------------------- +#if 0 +// manually adjust crop area by shifting its center +static void crop_adjust(dt_iop_module_t *module, dt_iop_ashift_params_t *p, const float newx, const float newy) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + // skip if fitting is still running + if(g->fitting) return; + + // get parameters for the homograph + const float f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor; + const float orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr; + const float aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect; + const float rotation = p->rotation; + const float lensshift_v = p->lensshift_v; + const float lensshift_h = p->lensshift_h; + const float shear = p->shear; + + const float wd = g->buf_width; + const float ht = g->buf_height; + + const float alpha = atan2(ht, wd); + + float homograph[3][3]; + homography((float *)homograph, rotation, lensshift_v, lensshift_h, shear, f_length_kb, + orthocorr, aspect, wd, ht, ASHIFT_HOMOGRAPH_FORWARD); + + // the four vertices of the image in input image coordinates + const float Vc[4][3] = { { 0.0f, 0.0f, 1.0f }, + { 0.0f, ht, 1.0f }, + { wd, ht, 1.0f }, + { wd, 0.0f, 1.0f } }; + + // convert the vertices to output image coordinates + float V[4][3]; + for(int n = 0; n < 4; n++) + mat3mulv(V[n], (float *)homograph, Vc[n]); + + // get width and height of output image + float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN; + for(int n = 0; n < 4; n++) + { + // normalize V + V[n][0] /= V[n][2]; + V[n][1] /= V[n][2]; + V[n][2] = 1.0f; + xmin = MIN(xmin, V[n][0]); + xmax = MAX(xmax, V[n][0]); + ymin = MIN(ymin, V[n][1]); + ymax = MAX(ymax, V[n][1]); + } + const float owd = xmax - xmin; + const float oht = ymax - ymin; + + // calculate the lines defining the four edges of the image area: E = V[n] x V[n+1] + float E[4][3]; + for(int n = 0; n < 4; n++) + vec3prodn(E[n], V[n], V[(n + 1) % 4]); + + // the center of the rectangle in output image coordinates + const float P[3] = { newx * owd, newy * oht, 1.0f }; + + // two auxiliary points (some arbitrary distance away from P) to construct the diagonals + const float Pa[2][3] = { { P[0] + 10.0f * cos(alpha), P[1] + 10.0f * sin(alpha), 1.0f }, + { P[0] + 10.0f * cos(alpha), P[1] - 10.0f * sin(alpha), 1.0f } }; + + // the two diagonals: D = P x Pa + float D[2][3]; + vec3prodn(D[0], P, Pa[0]); + vec3prodn(D[1], P, Pa[1]); + + // find all intersection points of all four edges with both diagonals (I = E x D); + // the shortest distance d2min of the intersection point I to the crop area center P determines + // the size of the crop area that still fits into the image (for the given center and aspect angle) + float d2min = FLT_MAX; + for(int k = 0; k < 4; k++) + for(int l = 0; l < 2; l++) + { + // the intersection point + float I[3]; + vec3prodn(I, E[k], D[l]); + + // special case: I is all null -> E and D are identical -> P lies on E -> d2min = 0 + if(vec3isnull(I)) + { + d2min = 0.0f; + break; + } + + // special case: I[2] is 0.0f -> E and D are parallel and intersect at infinity -> no relevant point + if(I[2] == 0.0f) + continue; + + // the default case -> normalize I + I[0] /= I[2]; + I[1] /= I[2]; + + // calculate distance from I to P + const float d2 = SQR(P[0] - I[0]) + SQR(P[1] - I[1]); + + // the minimum distance over all intersection points + d2min = MIN(d2min, d2); + } + + const float d = sqrt(d2min); + + // do not allow crop area to drop below 1% of input image area + const float A = 2.0f * d * d * sin(2.0f * alpha); + if(A < 0.01f * wd * ht) return; + + // calculate clipping margins relative to output image dimensions + p->cl = CLAMP((P[0] - d * cos(alpha)) / owd, 0.0f, 1.0f); + p->cr = CLAMP((P[0] + d * cos(alpha)) / owd, 0.0f, 1.0f); + p->ct = CLAMP((P[1] - d * sin(alpha)) / oht, 0.0f, 1.0f); + p->cb = CLAMP((P[1] + d * sin(alpha)) / oht, 0.0f, 1.0f); + +#ifdef ASHIFT_DEBUG + printf("margins after crop adjustment: x %f, y %f, angle %f, crop area (%f %f %f %f), width %f, height %f\n", + 0.5f * (p->cl + p->cr), 0.5f * (p->ct + p->cb), alpha, p->cl, p->cr, p->ct, p->cb, wd, ht); +#endif + dt_control_queue_redraw_center(); + return; +} +#endif // if 0 +//----------------------------------------------------------------------------- + +// helper function to start analysis for structural data and report about errors +static int do_get_structure(dt_iop_module_t *module, dt_iop_ashift_params_t *p, + dt_iop_ashift_enhance_t enhance) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + if(g->fitting) return FALSE; + + g->fitting = 1; + + float *b = NULL; + { + MyMutex::MyLock lock(g->lock); + b = g->buf; + } + /* dt_pthread_mutex_lock(&g->lock); */ + /* float *b = g->buf; */ + /* dt_pthread_mutex_unlock(&g->lock); */ + + if(b == NULL) + { + dt_control_log(_("data pending - please repeat")); + goto error; + } + + if(!get_structure(module, enhance)) + { + dt_control_log(_("could not detect structural data in image")); +#ifdef ASHIFT_DEBUG + // find out more + printf("do_get_structure: buf %p, buf_hash %lu, buf_width %d, buf_height %d, lines %p, lines_count %d\n", + g->buf, g->buf_hash, g->buf_width, g->buf_height, g->lines, g->lines_count); +#endif + goto error; + } + + if(!remove_outliers(module)) + { + dt_control_log(_("could not run outlier removal")); +#ifdef ASHIFT_DEBUG + // find out more + printf("remove_outliers: buf %p, buf_hash %lu, buf_width %d, buf_height %d, lines %p, lines_count %d\n", + g->buf, g->buf_hash, g->buf_width, g->buf_height, g->lines, g->lines_count); +#endif + goto error; + } + + g->fitting = 0; + return TRUE; + +error: + g->fitting = 0; + return FALSE; +} + +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 +// helper function to clean structural data +static int do_clean_structure(dt_iop_module_t *module, dt_iop_ashift_params_t *p) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + if(g->fitting) return FALSE; + + g->fitting = 1; + g->lines_count = 0; + g->vertical_count = 0; + g->horizontal_count = 0; + free(g->lines); + g->lines = NULL; + g->lines_version++; + g->lines_suppressed = 0; + g->fitting = 0; + return TRUE; +} +#endif // if 0 +//----------------------------------------------------------------------------- + +// helper function to start parameter fit and report about errors +static int do_fit(dt_iop_module_t *module, dt_iop_ashift_params_t *p, dt_iop_ashift_fitaxis_t dir) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + dt_iop_ashift_nmsresult_t res; + + if(g->fitting) return FALSE; + + // if no structure available get it + if(g->lines == NULL) + if(!do_get_structure(module, p, ASHIFT_ENHANCE_NONE)) goto error; + + g->fitting = 1; + + res = nmsfit(module, p, dir); + + switch(res) + { + case NMS_NOT_ENOUGH_LINES: + dt_control_log(_("not enough structure for automatic correction")); + goto error; + break; + case NMS_DID_NOT_CONVERGE: + case NMS_INSANE: + dt_control_log(_("automatic correction failed, please correct manually")); + goto error; + break; + case NMS_SUCCESS: + default: + break; + } + + g->fitting = 0; + + // finally apply cropping + do_crop(module, p); + + return TRUE; + +error: + g->fitting = 0; + return FALSE; +} + +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 +//----------------------------------------------------------------------------- +void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, + void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) +{ + dt_iop_ashift_data_t *data = (dt_iop_ashift_data_t *)piece->data; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + const int ch = piece->colors; + const int ch_width = ch * roi_in->width; + + // only for preview pipe: collect input buffer data and do some other evaluations + if(self->dev->gui_attached && g && piece->pipe->type == DT_DEV_PIXELPIPE_PREVIEW) + { + // we want to find out if the final output image is flipped in relation to this iop + // so we can adjust the gui labels accordingly + + const int width = roi_in->width; + const int height = roi_in->height; + const int x_off = roi_in->x; + const int y_off = roi_in->y; + const float scale = roi_in->scale; + + // origin of image and opposite corner as reference points + float points[4] = { 0.0f, 0.0f, (float)piece->buf_in.width, (float)piece->buf_in.height }; + float ivec[2] = { points[2] - points[0], points[3] - points[1] }; + float ivecl = sqrt(ivec[0] * ivec[0] + ivec[1] * ivec[1]); + + // where do they go? + dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe, self->priority + 1, 9999999, points, + 2); + + float ovec[2] = { points[2] - points[0], points[3] - points[1] }; + float ovecl = sqrt(ovec[0] * ovec[0] + ovec[1] * ovec[1]); + + // angle between input vector and output vector + float alpha = acos(CLAMP((ivec[0] * ovec[0] + ivec[1] * ovec[1]) / (ivecl * ovecl), -1.0f, 1.0f)); + + // we are interested if |alpha| is in the range of 90° +/- 45° -> we assume the image is flipped + int isflipped = fabs(fmod(alpha + M_PI, M_PI) - M_PI / 2.0f) < M_PI / 4.0f ? 1 : 0; + + // did modules prior to this one in pixelpipe have changed? -> check via hash value + uint64_t hash = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, 0, self->priority - 1); + + dt_pthread_mutex_lock(&g->lock); + g->isflipped = isflipped; + + // save a copy of preview input buffer for parameter fitting + if(g->buf == NULL || (size_t)g->buf_width * g->buf_height < (size_t)width * height) + { + // if needed to allocate buffer + free(g->buf); // a no-op if g->buf is NULL + // only get new buffer if no old buffer available or old buffer does not fit in terms of size + g->buf = malloc((size_t)width * height * 4 * sizeof(float)); + } + + if(g->buf /* && hash != g->buf_hash */) + { + // copy data + memcpy(g->buf, ivoid, (size_t)width * height * ch * sizeof(float)); + + g->buf_width = width; + g->buf_height = height; + g->buf_x_off = x_off; + g->buf_y_off = y_off; + g->buf_scale = scale; + g->buf_hash = hash; + } + + dt_pthread_mutex_unlock(&g->lock); + } + + // if module is set to neutral parameters we just copy input->output and are done + if(isneutral(data)) + { + memcpy(ovoid, ivoid, (size_t)roi_out->width * roi_out->height * ch * sizeof(float)); + return; + } + + const struct dt_interpolation *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF); + + float ihomograph[3][3]; + homography((float *)ihomograph, data->rotation, data->lensshift_v, data->lensshift_h, data->shear, data->f_length_kb, + data->orthocorr, data->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED); + + // clipping offset + const float fullwidth = (float)piece->buf_out.width / (data->cr - data->cl); + const float fullheight = (float)piece->buf_out.height / (data->cb - data->ct); + const float cx = roi_out->scale * fullwidth * data->cl; + const float cy = roi_out->scale * fullheight * data->ct; + + +#ifdef _OPENMP +//#pragma omp parallel for schedule(static) shared(ihomograph, interpolation) +#endif + // go over all pixels of output image + for(int j = 0; j < roi_out->height; j++) + { + float *out = ((float *)ovoid) + (size_t)ch * j * roi_out->width; + for(int i = 0; i < roi_out->width; i++, out += ch) + { + float pin[3], pout[3]; + + /* if (j == roi_out->height - 1) { */ + /* printf("HERE\n"); */ + /* } */ + + // convert output pixel coordinates to original image coordinates + pout[0] = roi_out->x + i + cx; + pout[1] = roi_out->y + j + cy; + pout[0] /= roi_out->scale; + pout[1] /= roi_out->scale; + pout[2] = 1.0f; + + // apply homograph + mat3mulv(pin, (float *)ihomograph, pout); + + // convert to input pixel coordinates + pin[0] /= pin[2]; + pin[1] /= pin[2]; + pin[0] *= roi_in->scale; + pin[1] *= roi_in->scale; + + /* if (pin[0] < 0 || pin[1] < 0) { */ + /* printf("NEGATIVE: %f %f -> %f %f\n", pout[0], pout[1], pin[0], pin[1]); */ + /* fflush(stdout); */ + /* } */ + + + pin[0] -= roi_in->x; + pin[1] -= roi_in->y; + + // get output values by interpolation from input image + dt_interpolation_compute_pixel4c(interpolation, (float *)ivoid, out, pin[0], pin[1], roi_in->width, + roi_in->height, ch_width); + } + } +} + +#ifdef HAVE_OPENCL +int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out, + const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) +{ + dt_iop_ashift_data_t *d = (dt_iop_ashift_data_t *)piece->data; + dt_iop_ashift_global_data_t *gd = (dt_iop_ashift_global_data_t *)self->data; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + const int devid = piece->pipe->devid; + const int iwidth = roi_in->width; + const int iheight = roi_in->height; + const int width = roi_out->width; + const int height = roi_out->height; + + cl_int err = -999; + cl_mem dev_homo = NULL; + + // only for preview pipe: collect input buffer data and do some other evaluations + if(self->dev->gui_attached && g && piece->pipe->type == DT_DEV_PIXELPIPE_PREVIEW) + { + // we want to find out if the final output image is flipped in relation to this iop + // so we can adjust the gui labels accordingly + + const int x_off = roi_in->x; + const int y_off = roi_in->y; + const float scale = roi_in->scale; + + // origin of image and opposite corner as reference points + float points[4] = { 0.0f, 0.0f, (float)piece->buf_in.width, (float)piece->buf_in.height }; + float ivec[2] = { points[2] - points[0], points[3] - points[1] }; + float ivecl = sqrt(ivec[0] * ivec[0] + ivec[1] * ivec[1]); + + // where do they go? + dt_dev_distort_backtransform_plus(self->dev, self->dev->preview_pipe, self->priority + 1, 9999999, points, + 2); + + float ovec[2] = { points[2] - points[0], points[3] - points[1] }; + float ovecl = sqrt(ovec[0] * ovec[0] + ovec[1] * ovec[1]); + + // angle between input vector and output vector + float alpha = acos(CLAMP((ivec[0] * ovec[0] + ivec[1] * ovec[1]) / (ivecl * ovecl), -1.0f, 1.0f)); + + // we are interested if |alpha| is in the range of 90° +/- 45° -> we assume the image is flipped + int isflipped = fabs(fmod(alpha + M_PI, M_PI) - M_PI / 2.0f) < M_PI / 4.0f ? 1 : 0; + + // do modules coming before this one in pixelpipe have changed? -> check via hash value + uint64_t hash = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, 0, self->priority - 1); + + dt_pthread_mutex_lock(&g->lock); + g->isflipped = isflipped; + + // save a copy of preview input buffer for parameter fitting + if(g->buf == NULL || (size_t)g->buf_width * g->buf_height < (size_t)iwidth * iheight) + { + // if needed allocate buffer + free(g->buf); // a no-op if g->buf is NULL + // only get new buffer if no old buffer or old buffer does not fit in terms of size + g->buf = malloc((size_t)iwidth * iheight * 4 * sizeof(float)); + } + + if(g->buf /* && hash != g->buf_hash */) + { + // copy data + err = dt_opencl_copy_device_to_host(devid, g->buf, dev_in, iwidth, iheight, 4 * sizeof(float)); + + g->buf_width = iwidth; + g->buf_height = iheight; + g->buf_x_off = x_off; + g->buf_y_off = y_off; + g->buf_scale = scale; + g->buf_hash = hash; + } + dt_pthread_mutex_unlock(&g->lock); + if(err != CL_SUCCESS) goto error; + } + + // if module is set to neutral parameters we just copy input->output and are done + if(isneutral(d)) + { + size_t origin[] = { 0, 0, 0 }; + size_t region[] = { width, height, 1 }; + err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, origin, origin, region); + if(err != CL_SUCCESS) goto error; + return TRUE; + } + + float ihomograph[3][3]; + homography((float *)ihomograph, d->rotation, d->lensshift_v, d->lensshift_h, d->shear, d->f_length_kb, + d->orthocorr, d->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED); + + // clipping offset + const float fullwidth = (float)piece->buf_out.width / (d->cr - d->cl); + const float fullheight = (float)piece->buf_out.height / (d->cb - d->ct); + const float cx = roi_out->scale * fullwidth * d->cl; + const float cy = roi_out->scale * fullheight * d->ct; + + dev_homo = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 9, ihomograph); + if(dev_homo == NULL) goto error; + + const int iroi[2] = { roi_in->x, roi_in->y }; + const int oroi[2] = { roi_out->x, roi_out->y }; + const float in_scale = roi_in->scale; + const float out_scale = roi_out->scale; + const float clip[2] = { cx, cy }; + + size_t sizes[] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 }; + + const struct dt_interpolation *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF); + + int ldkernel = -1; + + switch(interpolation->id) + { + case DT_INTERPOLATION_BILINEAR: + ldkernel = gd->kernel_ashift_bilinear; + break; + case DT_INTERPOLATION_BICUBIC: + ldkernel = gd->kernel_ashift_bicubic; + break; + case DT_INTERPOLATION_LANCZOS2: + ldkernel = gd->kernel_ashift_lanczos2; + break; + case DT_INTERPOLATION_LANCZOS3: + ldkernel = gd->kernel_ashift_lanczos3; + break; + default: + goto error; + } + + dt_opencl_set_kernel_arg(devid, ldkernel, 0, sizeof(cl_mem), (void *)&dev_in); + dt_opencl_set_kernel_arg(devid, ldkernel, 1, sizeof(cl_mem), (void *)&dev_out); + dt_opencl_set_kernel_arg(devid, ldkernel, 2, sizeof(int), (void *)&width); + dt_opencl_set_kernel_arg(devid, ldkernel, 3, sizeof(int), (void *)&height); + dt_opencl_set_kernel_arg(devid, ldkernel, 4, sizeof(int), (void *)&iwidth); + dt_opencl_set_kernel_arg(devid, ldkernel, 5, sizeof(int), (void *)&iheight); + dt_opencl_set_kernel_arg(devid, ldkernel, 6, 2 * sizeof(int), (void *)iroi); + dt_opencl_set_kernel_arg(devid, ldkernel, 7, 2 * sizeof(int), (void *)oroi); + dt_opencl_set_kernel_arg(devid, ldkernel, 8, sizeof(float), (void *)&in_scale); + dt_opencl_set_kernel_arg(devid, ldkernel, 9, sizeof(float), (void *)&out_scale); + dt_opencl_set_kernel_arg(devid, ldkernel, 10, 2 * sizeof(float), (void *)clip); + dt_opencl_set_kernel_arg(devid, ldkernel, 11, sizeof(cl_mem), (void *)&dev_homo); + err = dt_opencl_enqueue_kernel_2d(devid, ldkernel, sizes); + if(err != CL_SUCCESS) goto error; + + dt_opencl_release_mem_object(dev_homo); + return TRUE; + +error: + dt_opencl_release_mem_object(dev_homo); + dt_print(DT_DEBUG_OPENCL, "[opencl_ashift] couldn't enqueue kernel! %d\n", err); + return FALSE; +} +#endif + +// gather information about "near"-ness in g->points_idx +static void get_near(const float *points, dt_iop_ashift_points_idx_t *points_idx, const int lines_count, + float pzx, float pzy, float delta) +{ + const float delta2 = delta * delta; + + for(int n = 0; n < lines_count; n++) + { + points_idx[n].near = 0; + + // skip irrelevant lines + if(points_idx[n].type == ASHIFT_LINE_IRRELEVANT) + continue; + + // first check if the mouse pointer is outside the bounding box of the line -> skip this line + if(pzx < points_idx[n].bbx - delta && + pzx > points_idx[n].bbX + delta && + pzy < points_idx[n].bby - delta && + pzy > points_idx[n].bbY + delta) + continue; + + // pointer is inside bounding box + size_t offset = points_idx[n].offset; + const int length = points_idx[n].length; + + // sanity check (this should not happen) + if(length < 2) continue; + + // check line point by point + for(int l = 0; l < length; l++, offset++) + { + float dx = pzx - points[offset * 2]; + float dy = pzy - points[offset * 2 + 1]; + + if(dx * dx + dy * dy < delta2) + { + points_idx[n].near = 1; + break; + } + } + } +} + +// mark lines which are inside a rectangular area in isbounding mode +static void get_bounded_inside(const float *points, dt_iop_ashift_points_idx_t *points_idx, + const int points_lines_count, float pzx, float pzy, float pzx2, float pzy2, + dt_iop_ashift_bounding_t mode) +{ + // get bounding box coordinates + float ax = pzx; + float ay = pzy; + float bx = pzx2; + float by = pzy2; + if(pzx > pzx2) + { + ax = pzx2; + bx = pzx; + } + if(pzy > pzy2) + { + ay = pzy2; + by = pzy; + } + + // we either look for the selected or the deselected lines + dt_iop_ashift_linetype_t mask = ASHIFT_LINE_SELECTED; + dt_iop_ashift_linetype_t state = (mode == ASHIFT_BOUNDING_DESELECT) ? ASHIFT_LINE_SELECTED : 0; + + for(int n = 0; n < points_lines_count; n++) + { + // mark line as "not near" and "not bounded" + points_idx[n].near = 0; + points_idx[n].bounded = 0; + + // skip irrelevant lines + if(points_idx[n].type == ASHIFT_LINE_IRRELEVANT) + continue; + + // is the line inside the box ? + if(points_idx[n].bbx >= ax && points_idx[n].bbx <= bx && points_idx[n].bbX >= ax + && points_idx[n].bbX <= bx && points_idx[n].bby >= ay && points_idx[n].bby <= by + && points_idx[n].bbY >= ay && points_idx[n].bbY <= by) + { + points_idx[n].bounded = 1; + // only mark "near"-ness of those lines we are interested in + points_idx[n].near = ((points_idx[n].type & mask) != state) ? 0 : 1; + } + } +} + +// generate hash value for lines taking into account only the end point coordinates +static uint64_t get_lines_hash(const dt_iop_ashift_line_t *lines, const int lines_count) +{ + uint64_t hash = 5381; + for(int n = 0; n < lines_count; n++) + { + float v[4] = { lines[n].p1[0], lines[n].p1[1], lines[n].p2[0], lines[n].p2[1] }; + + for(int i = 0; i < 4; i++) + hash = ((hash << 5) + hash) ^ ((uint32_t *)v)[i]; + } + return hash; +} + +// update color information in points_idx if lines have changed in terms of type (but not in terms +// of number or position) +static int update_colors(struct dt_iop_module_t *self, dt_iop_ashift_points_idx_t *points_idx, + int points_lines_count) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + // is the display flipped relative to the original image? + const int isflipped = g->isflipped; + + // go through all lines + for(int n = 0; n < points_lines_count; n++) + { + const dt_iop_ashift_linetype_t type = points_idx[n].type; + + // set line color according to line type/orientation + // note: if the screen display is flipped versus the original image we need + // to respect that fact in the color selection + if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_SELECTED) + points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_BLUE : ASHIFT_LINECOLOR_GREEN; + else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_NOT_SELECTED) + points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_YELLOW : ASHIFT_LINECOLOR_RED; + else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_SELECTED) + points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_GREEN : ASHIFT_LINECOLOR_BLUE; + else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_NOT_SELECTED) + points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_RED : ASHIFT_LINECOLOR_YELLOW; + else + points_idx[n].color = ASHIFT_LINECOLOR_GREY; + } + + return TRUE; +} + +// get all the points to display lines in the gui +static int get_points(struct dt_iop_module_t *self, const dt_iop_ashift_line_t *lines, const int lines_count, + const int lines_version, float **points, dt_iop_ashift_points_idx_t **points_idx, + int *points_lines_count) +{ + dt_develop_t *dev = self->dev; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + dt_iop_ashift_points_idx_t *my_points_idx = NULL; + float *my_points = NULL; + + // is the display flipped relative to the original image? + const int isflipped = g->isflipped; + + // allocate new index array + my_points_idx = (dt_iop_ashift_points_idx_t *)malloc(lines_count * sizeof(dt_iop_ashift_points_idx_t)); + if(my_points_idx == NULL) goto error; + + // account for total number of points + size_t total_points = 0; + + // first step: basic initialization of my_points_idx and counting of total_points + for(int n = 0; n < lines_count; n++) + { + const int length = lines[n].length; + + total_points += length; + + my_points_idx[n].length = length; + my_points_idx[n].near = 0; + my_points_idx[n].bounded = 0; + + const dt_iop_ashift_linetype_t type = lines[n].type; + my_points_idx[n].type = type; + + // set line color according to line type/orientation + // note: if the screen display is flipped versus the original image we need + // to respect that fact in the color selection + if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_SELECTED) + my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_BLUE : ASHIFT_LINECOLOR_GREEN; + else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_NOT_SELECTED) + my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_YELLOW : ASHIFT_LINECOLOR_RED; + else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_SELECTED) + my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_GREEN : ASHIFT_LINECOLOR_BLUE; + else if((type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_NOT_SELECTED) + my_points_idx[n].color = isflipped ? ASHIFT_LINECOLOR_RED : ASHIFT_LINECOLOR_YELLOW; + else + my_points_idx[n].color = ASHIFT_LINECOLOR_GREY; + } + + // now allocate new points buffer + my_points = (float *)malloc((size_t)2 * total_points * sizeof(float)); + if(my_points == NULL) goto error; + + // second step: generate points for each line + for(int n = 0, offset = 0; n < lines_count; n++) + { + my_points_idx[n].offset = offset; + + float x = lines[n].p1[0]; + float y = lines[n].p1[1]; + const int length = lines[n].length; + + const float dx = (lines[n].p2[0] - x) / (float)(length - 1); + const float dy = (lines[n].p2[1] - y) / (float)(length - 1); + + for(int l = 0; l < length && offset < total_points; l++, offset++) + { + my_points[2 * offset] = x; + my_points[2 * offset + 1] = y; + + x += dx; + y += dy; + } + } + + // third step: transform all points + if(!dt_dev_distort_transform_plus(dev, dev->preview_pipe, self->priority, 9999999, my_points, total_points)) + goto error; + + // fourth step: get bounding box in final coordinates (used later for checking "near"-ness to mouse pointer) + for(int n = 0; n < lines_count; n++) + { + float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN; + + size_t offset = my_points_idx[n].offset; + int length = my_points_idx[n].length; + + for(int l = 0; l < length; l++) + { + xmin = fmin(xmin, my_points[2 * offset]); + xmax = fmax(xmax, my_points[2 * offset]); + ymin = fmin(ymin, my_points[2 * offset + 1]); + ymax = fmax(ymax, my_points[2 * offset + 1]); + } + + my_points_idx[n].bbx = xmin; + my_points_idx[n].bbX = xmax; + my_points_idx[n].bby = ymin; + my_points_idx[n].bbY = ymax; + } + + // check if lines_version has changed in-between -> too bad: we can forget about all we did :( + if(g->lines_version > lines_version) + goto error; + + *points = my_points; + *points_idx = my_points_idx; + *points_lines_count = lines_count; + + return TRUE; + +error: + if(my_points_idx != NULL) free(my_points_idx); + if(my_points != NULL) free(my_points); + return FALSE; +} + +// does this gui have focus? +static int gui_has_focus(struct dt_iop_module_t *self) +{ + return self->dev->gui_module == self; +} + +void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, + int32_t pointerx, int32_t pointery) +{ + dt_develop_t *dev = self->dev; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + + // the usual rescaling stuff + const float wd = dev->preview_pipe->backbuf_width; + const float ht = dev->preview_pipe->backbuf_height; + if(wd < 1.0 || ht < 1.0) return; + const float zoom_y = dt_control_get_dev_zoom_y(); + const float zoom_x = dt_control_get_dev_zoom_x(); + const dt_dev_zoom_t zoom = dt_control_get_dev_zoom(); + const int closeup = dt_control_get_dev_closeup(); + const float zoom_scale = dt_dev_get_zoom_scale(dev, zoom, 1<buf has been processed + if(g->buf && (p->cropmode != ASHIFT_CROP_OFF) && self->enabled) + { + // roi data of the preview pipe input buffer + const float iwd = g->buf_width; + const float iht = g->buf_height; + const float ixo = g->buf_x_off; + const float iyo = g->buf_y_off; + + // the four corners of the input buffer of this module + const float V[4][2] = { { ixo, iyo }, + { ixo, iyo + iht }, + { ixo + iwd, iyo + iht }, + { ixo + iwd, iyo } }; + + // convert coordinates of corners to coordinates of this module's output + if(!dt_dev_distort_transform_plus(self->dev, self->dev->preview_pipe, self->priority, self->priority + 1, + (float *)V, 4)) + return; + + // get x/y-offset as well as width and height of output buffer + float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN; + for(int n = 0; n < 4; n++) + { + xmin = MIN(xmin, V[n][0]); + xmax = MAX(xmax, V[n][0]); + ymin = MIN(ymin, V[n][1]); + ymax = MAX(ymax, V[n][1]); + } + const float owd = xmax - xmin; + const float oht = ymax - ymin; + + // the four clipping corners + const float C[4][2] = { { xmin + p->cl * owd, ymin + p->ct * oht }, + { xmin + p->cl * owd, ymin + p->cb * oht }, + { xmin + p->cr * owd, ymin + p->cb * oht }, + { xmin + p->cr * owd, ymin + p->ct * oht } }; + + // convert clipping corners to final output image + if(!dt_dev_distort_transform_plus(self->dev, self->dev->preview_pipe, self->priority + 1, 9999999, + (float *)C, 4)) + return; + + cairo_save(cr); + + double dashes = DT_PIXEL_APPLY_DPI(5.0) / zoom_scale; + cairo_set_dash(cr, &dashes, 0, 0); + + cairo_rectangle(cr, 0, 0, width, height); + cairo_clip(cr); + + // mask parts of image outside of clipping area in dark grey + cairo_set_source_rgba(cr, .2, .2, .2, .8); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_rectangle(cr, 0, 0, width, height); + cairo_translate(cr, width / 2.0, height / 2.0); + cairo_scale(cr, zoom_scale, zoom_scale); + cairo_translate(cr, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht); + cairo_move_to(cr, C[0][0], C[0][1]); + cairo_line_to(cr, C[1][0], C[1][1]); + cairo_line_to(cr, C[2][0], C[2][1]); + cairo_line_to(cr, C[3][0], C[3][1]); + cairo_close_path(cr); + cairo_fill(cr); + + // draw white outline around clipping area + cairo_set_source_rgb(cr, .7, .7, .7); + cairo_move_to(cr, C[0][0], C[0][1]); + cairo_line_to(cr, C[1][0], C[1][1]); + cairo_line_to(cr, C[2][0], C[2][1]); + cairo_line_to(cr, C[3][0], C[3][1]); + cairo_close_path(cr); + cairo_stroke(cr); + + // if adjusting crop, draw indicator + if (g->adjust_crop && p->cropmode == ASHIFT_CROP_ASPECT) + { + const double xpos = (C[1][0] + C[2][0]) / 2.0f; + const double ypos = (C[0][1] + C[1][1]) / 2.0f; + const double size_circle = (C[2][0] - C[1][0]) / 30.0f; + const double size_line = (C[2][0] - C[1][0]) / 5.0f; + const double size_arrow = (C[2][0] - C[1][0]) / 25.0f; + + cairo_set_line_width(cr, 2.0 / zoom_scale); + cairo_set_source_rgb(cr, .7, .7, .7); + cairo_arc (cr, xpos, ypos, size_circle, 0, 2.0 * M_PI); + cairo_stroke(cr); + cairo_fill(cr); + + cairo_set_line_width(cr, 2.0 / zoom_scale); + cairo_set_source_rgb(cr, .7, .7, .7); + + // horizontal line + cairo_move_to(cr, xpos - size_line, ypos); + cairo_line_to(cr, xpos + size_line, ypos); + + cairo_move_to(cr, xpos - size_line, ypos); + cairo_rel_line_to(cr, size_arrow, size_arrow); + cairo_move_to(cr, xpos - size_line, ypos); + cairo_rel_line_to(cr, size_arrow, -size_arrow); + + cairo_move_to(cr, xpos + size_line, ypos); + cairo_rel_line_to(cr, -size_arrow, size_arrow); + cairo_move_to(cr, xpos + size_line, ypos); + cairo_rel_line_to(cr, -size_arrow, -size_arrow); + + // vertical line + cairo_move_to(cr, xpos, ypos - size_line); + cairo_line_to(cr, xpos, ypos + size_line); + + cairo_move_to(cr, xpos, ypos - size_line); + cairo_rel_line_to(cr, -size_arrow, size_arrow); + cairo_move_to(cr, xpos, ypos - size_line); + cairo_rel_line_to(cr, size_arrow, size_arrow); + + cairo_move_to(cr, xpos, ypos + size_line); + cairo_rel_line_to(cr, -size_arrow, -size_arrow); + cairo_move_to(cr, xpos, ypos + size_line); + cairo_rel_line_to(cr, size_arrow, -size_arrow); + + cairo_stroke(cr); + } + + cairo_restore(cr); + } + + // show guide lines on request + if(g->show_guides) + { + dt_guides_t *guide = (dt_guides_t *)g_list_nth_data(darktable.guides, 0); + double dashes = DT_PIXEL_APPLY_DPI(5.0); + cairo_save(cr); + cairo_rectangle(cr, 0, 0, width, height); + cairo_clip(cr); + cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0)); + cairo_set_source_rgb(cr, .8, .8, .8); + cairo_set_dash(cr, &dashes, 1, 0); + guide->draw(cr, 0, 0, width, height, 1.0, guide->user_data); + cairo_stroke_preserve(cr); + cairo_set_dash(cr, &dashes, 0, 0); + cairo_set_source_rgba(cr, 0.3, .3, .3, .8); + cairo_stroke(cr); + cairo_restore(cr); + } + + // structural data are currently being collected or fit procedure is running? -> skip + if(g->fitting) return; + + // no structural data or visibility switched off? -> stop here + if(g->lines == NULL || g->lines_suppressed || !gui_has_focus(self)) return; + + // get hash value that changes if distortions from here to the end of the pixelpipe changed + uint64_t hash = dt_dev_hash_distort(dev); + // get hash value that changes if coordinates of lines have changed + uint64_t lines_hash = get_lines_hash(g->lines, g->lines_count); + + // points data are missing or outdated, or distortion has changed? + if(g->points == NULL || g->points_idx == NULL || hash != g->grid_hash || + (g->lines_version > g->points_version && g->lines_hash != lines_hash)) + { + // we need to reprocess points + free(g->points); + g->points = NULL; + free(g->points_idx); + g->points_idx = NULL; + g->points_lines_count = 0; + + if(!get_points(self, g->lines, g->lines_count, g->lines_version, &g->points, &g->points_idx, + &g->points_lines_count)) + return; + + g->points_version = g->lines_version; + g->grid_hash = hash; + g->lines_hash = lines_hash; + } + else if(g->lines_hash == lines_hash) + { + // update line type information in points_idx + for(int n = 0; n < g->points_lines_count; n++) + g->points_idx[n].type = g->lines[n].type; + + // coordinates of lines are unchanged -> we only need to update colors + if(!update_colors(self, g->points_idx, g->points_lines_count)) + return; + + g->points_version = g->lines_version; + } + + // a final check + if(g->points == NULL || g->points_idx == NULL) return; + + cairo_save(cr); + cairo_rectangle(cr, 0, 0, width, height); + cairo_clip(cr); + cairo_translate(cr, width / 2.0, height / 2.0); + cairo_scale(cr, zoom_scale, zoom_scale); + cairo_translate(cr, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht); + + // this must match the sequence of enum dt_iop_ashift_linecolor_t! + const float line_colors[5][4] = + { { 0.3f, 0.3f, 0.3f, 0.8f }, // grey (misc. lines) + { 0.0f, 1.0f, 0.0f, 0.8f }, // green (selected vertical lines) + { 0.8f, 0.0f, 0.0f, 0.8f }, // red (de-selected vertical lines) + { 0.0f, 0.0f, 1.0f, 0.8f }, // blue (selected horizontal lines) + { 0.8f, 0.8f, 0.0f, 0.8f } }; // yellow (de-selected horizontal lines) + + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); + + // now draw all lines + for(int n = 0; n < g->points_lines_count; n++) + { + // is the near flag set? -> draw line a bit thicker + if(g->points_idx[n].near) + cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3.0) / zoom_scale); + else + cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5) / zoom_scale); + + // the color of this line + const float *color = line_colors[g->points_idx[n].color]; + cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]); + + size_t offset = g->points_idx[n].offset; + const int length = g->points_idx[n].length; + + // sanity check (this should not happen) + if(length < 2) continue; + + // set starting point of multi-segment line + cairo_move_to(cr, g->points[offset * 2], g->points[offset * 2 + 1]); + + offset++; + // draw individual line segments + for(int l = 1; l < length; l++, offset++) + { + cairo_line_to(cr, g->points[offset * 2], g->points[offset * 2 + 1]); + } + + // finally stroke the line + cairo_stroke(cr); + } + + // and we draw the selection box if any + if(g->isbounding != ASHIFT_BOUNDING_OFF) + { + float pzx, pzy; + dt_dev_get_pointer_zoom_pos(dev, pointerx, pointery, &pzx, &pzy); + pzx += 0.5f; + pzy += 0.5f; + + double dashed[] = { 4.0, 4.0 }; + dashed[0] /= zoom_scale; + dashed[1] /= zoom_scale; + int len = sizeof(dashed) / sizeof(dashed[0]); + + cairo_rectangle(cr, g->lastx * wd, g->lasty * ht, (pzx - g->lastx) * wd, (pzy - g->lasty) * ht); + + cairo_set_source_rgba(cr, .3, .3, .3, .8); + cairo_set_line_width(cr, 1.0 / zoom_scale); + cairo_set_dash(cr, dashed, len, 0); + cairo_stroke_preserve(cr); + cairo_set_source_rgba(cr, .8, .8, .8, .8); + cairo_set_dash(cr, dashed, len, 4); + cairo_stroke(cr); + } + + // indicate which area is used for "near"-ness detection when selecting/deselecting lines + if(g->near_delta > 0) + { + float pzx, pzy; + dt_dev_get_pointer_zoom_pos(dev, pointerx, pointery, &pzx, &pzy); + pzx += 0.5f; + pzy += 0.5f; + + double dashed[] = { 4.0, 4.0 }; + dashed[0] /= zoom_scale; + dashed[1] /= zoom_scale; + int len = sizeof(dashed) / sizeof(dashed[0]); + + cairo_arc(cr, pzx * wd, pzy * ht, g->near_delta, 0, 2.0 * M_PI); + + cairo_set_source_rgba(cr, .3, .3, .3, .8); + cairo_set_line_width(cr, 1.0 / zoom_scale); + cairo_set_dash(cr, dashed, len, 0); + cairo_stroke_preserve(cr); + cairo_set_source_rgba(cr, .8, .8, .8, .8); + cairo_set_dash(cr, dashed, len, 4); + cairo_stroke(cr); + } + + cairo_restore(cr); +} + +update the number of selected vertical and horizontal lines +static void update_lines_count(const dt_iop_ashift_line_t *lines, const int lines_count, + int *vertical_count, int *horizontal_count) +{ + int vlines = 0; + int hlines = 0; + + for(int n = 0; n < lines_count; n++) + { + if((lines[n].type & ASHIFT_LINE_MASK) == ASHIFT_LINE_VERTICAL_SELECTED) + vlines++; + else if((lines[n].type & ASHIFT_LINE_MASK) == ASHIFT_LINE_HORIZONTAL_SELECTED) + hlines++; + } + + *vertical_count = vlines; + *horizontal_count = hlines; +} + +int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + int handled = 0; + + const float wd = self->dev->preview_pipe->backbuf_width; + const float ht = self->dev->preview_pipe->backbuf_height; + if(wd < 1.0 || ht < 1.0) return 1; + + float pzx, pzy; + dt_dev_get_pointer_zoom_pos(self->dev, x, y, &pzx, &pzy); + pzx += 0.5f; + pzy += 0.5f; + + if (g->adjust_crop) + { + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + const float newx = g->crop_cx + pzx - g->lastx; + const float newy = g->crop_cy + pzy - g->lasty; + crop_adjust(self, p, newx, newy); + dt_dev_add_history_item(darktable.develop, self, TRUE); + return TRUE; + } + + // if in rectangle selecting mode adjust "near"-ness of lines according to + // the rectangular selection + if(g->isbounding != ASHIFT_BOUNDING_OFF) + { + if(wd >= 1.0 && ht >= 1.0) + { + // mark lines inside the rectangle + get_bounded_inside(g->points, g->points_idx, g->points_lines_count, pzx * wd, pzy * ht, g->lastx * wd, + g->lasty * ht, g->isbounding); + } + + dt_control_queue_redraw_center(); + return FALSE; + } + + // gather information about "near"-ness in g->points_idx + get_near(g->points, g->points_idx, g->points_lines_count, pzx * wd, pzy * ht, g->near_delta); + + // if we are in sweeping mode iterate over lines as we move the pointer and change "selected" state. + if(g->isdeselecting || g->isselecting) + { + for(int n = 0; g->selecting_lines_version == g->lines_version && n < g->points_lines_count; n++) + { + if(g->points_idx[n].near == 0) + continue; + + if(g->isdeselecting) + g->lines[n].type &= ~ASHIFT_LINE_SELECTED; + else if(g->isselecting) + g->lines[n].type |= ASHIFT_LINE_SELECTED; + + handled = 1; + } + } + + if(handled) + { + update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count); + g->lines_version++; + g->selecting_lines_version++; + } + + dt_control_queue_redraw_center(); + + // if not in sweeping mode we need to pass the event + return (g->isdeselecting || g->isselecting); +} + +int button_pressed(struct dt_iop_module_t *self, double x, double y, double pressure, int which, int type, + uint32_t state) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + int handled = 0; + + float pzx, pzy; + dt_dev_get_pointer_zoom_pos(self->dev, x, y, &pzx, &pzy); + pzx += 0.5f; + pzy += 0.5f; + + const float wd = self->dev->preview_pipe->backbuf_width; + const float ht = self->dev->preview_pipe->backbuf_height; + if(wd < 1.0 || ht < 1.0) return 1; + + + // if visibility of lines is switched off or no lines available -> potentially adjust crop area + if(g->lines_suppressed || g->lines == NULL) + { + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + if (p->cropmode == ASHIFT_CROP_ASPECT) + { + dt_control_change_cursor(GDK_HAND1); + g->adjust_crop = TRUE; + g->lastx = pzx; + g->lasty = pzy; + g->crop_cx = 0.5f * (p->cl + p->cr); + g->crop_cy = 0.5f * (p->ct + p->cb); + return TRUE; + } + else + return FALSE; + } + + // remember lines version at this stage so we can continuously monitor if the + // lines have changed in-between + g->selecting_lines_version = g->lines_version; + + // if shift button is pressed go into bounding mode (selecting or deselecting + // in a rectangle area) + if((state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) + { + g->lastx = pzx; + g->lasty = pzy; + + g->isbounding = (which == 3) ? ASHIFT_BOUNDING_DESELECT : ASHIFT_BOUNDING_SELECT; + dt_control_change_cursor(GDK_CROSS); + + return TRUE; + } + + dt_dev_zoom_t zoom = dt_control_get_dev_zoom(); + const int closeup = dt_control_get_dev_closeup(); + const float min_scale = dt_dev_get_zoom_scale(self->dev, DT_ZOOM_FIT, 1<dev, zoom, 1<points_lines_count > 0); + + g->near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta"); + + // gather information about "near"-ness in g->points_idx + get_near(g->points, g->points_idx, g->points_lines_count, pzx * wd, pzy * ht, g->near_delta); + + // iterate over all lines close to the pointer and change "selected" state. + // left-click selects and right-click deselects the line + for(int n = 0; g->selecting_lines_version == g->lines_version && n < g->points_lines_count; n++) + { + if(g->points_idx[n].near == 0) + continue; + + if(which == 3) + g->lines[n].type &= ~ASHIFT_LINE_SELECTED; + else + g->lines[n].type |= ASHIFT_LINE_SELECTED; + + handled = 1; + } + + // we switch into sweeping mode either if we anyhow take control + // or if cursor was close to a line when button was pressed. in other + // cases we hand over the event (for image panning) + if((take_control || handled) && which == 3) + { + dt_control_change_cursor(GDK_PIRATE); + g->isdeselecting = 1; + } + else if(take_control || handled) + { + dt_control_change_cursor(GDK_PLUS); + g->isselecting = 1; + } + + if(handled) + { + update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count); + g->lines_version++; + g->selecting_lines_version++; + } + + return (take_control || handled); +} + +int button_released(struct dt_iop_module_t *self, double x, double y, int which, uint32_t state) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + // stop adjust crop + g->adjust_crop = FALSE; + dt_control_change_cursor(GDK_LEFT_PTR); + + // finalize the isbounding mode + // if user has released the shift button in-between -> do nothing + if(g->isbounding != ASHIFT_BOUNDING_OFF && (state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) + { + int handled = 0; + + // we compute the rectangle selection + float pzx, pzy; + dt_dev_get_pointer_zoom_pos(self->dev, x, y, &pzx, &pzy); + + pzx += 0.5f; + pzy += 0.5f; + + const float wd = self->dev->preview_pipe->backbuf_width; + const float ht = self->dev->preview_pipe->backbuf_height; + + if(wd >= 1.0 && ht >= 1.0) + { + // mark lines inside the rectangle + get_bounded_inside(g->points, g->points_idx, g->points_lines_count, pzx * wd, pzy * ht, g->lastx * wd, + g->lasty * ht, g->isbounding); + + // select or deselect lines within the rectangle according to isbounding state + for(int n = 0; g->selecting_lines_version == g->lines_version && n < g->points_lines_count; n++) + { + if(g->points_idx[n].bounded == 0) continue; + + if(g->isbounding == ASHIFT_BOUNDING_DESELECT) + g->lines[n].type &= ~ASHIFT_LINE_SELECTED; + else + g->lines[n].type |= ASHIFT_LINE_SELECTED; + + handled = 1; + } + + if(handled) + { + update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count); + g->lines_version++; + g->selecting_lines_version++; + } + + dt_control_queue_redraw_center(); + } + } + + // end of sweeping/isbounding mode + dt_control_change_cursor(GDK_LEFT_PTR); + g->isselecting = g->isdeselecting = 0; + g->isbounding = ASHIFT_BOUNDING_OFF; + g->near_delta = 0; + g->lastx = g->lasty = -1.0f; + g->crop_cx = g->crop_cy = -1.0f; + + return 0; +} + +int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + // do nothing if visibility of lines is switched off or no lines available + if(g->lines_suppressed || g->lines == NULL) + return FALSE; + + if(g->near_delta > 0 && (g->isdeselecting || g->isselecting)) + { + int handled = 0; + + float pzx, pzy; + dt_dev_get_pointer_zoom_pos(self->dev, x, y, &pzx, &pzy); + pzx += 0.5f; + pzy += 0.5f; + + const float wd = self->dev->preview_pipe->backbuf_width; + const float ht = self->dev->preview_pipe->backbuf_height; + + float near_delta = dt_conf_get_float("plugins/darkroom/ashift/near_delta"); + const float amount = up ? 0.8f : 1.25f; + near_delta = MAX(4.0f, MIN(near_delta * amount, 100.0f)); + dt_conf_set_float("plugins/darkroom/ashift/near_delta", near_delta); + g->near_delta = near_delta; + + // gather information about "near"-ness in g->points_idx + get_near(g->points, g->points_idx, g->points_lines_count, pzx * wd, pzy * ht, g->near_delta); + + // iterate over all lines close to the pointer and change "selected" state. + for(int n = 0; g->selecting_lines_version == g->lines_version && n < g->points_lines_count; n++) + { + if(g->points_idx[n].near == 0) + continue; + + if(g->isdeselecting) + g->lines[n].type &= ~ASHIFT_LINE_SELECTED; + else if(g->isselecting) + g->lines[n].type |= ASHIFT_LINE_SELECTED; + + handled = 1; + } + + if(handled) + { + update_lines_count(g->lines, g->lines_count, &g->vertical_count, &g->horizontal_count); + g->lines_version++; + g->selecting_lines_version++; + } + + dt_control_queue_redraw_center(); + return TRUE; + } + + return FALSE; +} + +static void rotation_callback(GtkWidget *slider, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + p->rotation = dt_bauhaus_slider_get(slider); +#ifdef ASHIFT_DEBUG + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + model_probe(self, p, g->lastfit); +#endif + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void lensshift_v_callback(GtkWidget *slider, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + p->lensshift_v = dt_bauhaus_slider_get(slider); +#ifdef ASHIFT_DEBUG + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + model_probe(self, p, g->lastfit); +#endif + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void lensshift_h_callback(GtkWidget *slider, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + p->lensshift_h = dt_bauhaus_slider_get(slider); +#ifdef ASHIFT_DEBUG + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + model_probe(self, p, g->lastfit); +#endif + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void shear_callback(GtkWidget *slider, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + p->shear = dt_bauhaus_slider_get(slider); +#ifdef ASHIFT_DEBUG + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + model_probe(self, p, g->lastfit); +#endif + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void guide_lines_callback(GtkWidget *widget, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + g->show_guides = dt_bauhaus_combobox_get(widget); + dt_iop_request_focus(self); + dt_dev_reprocess_all(self->dev); +} + +static void cropmode_callback(GtkWidget *widget, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + p->cropmode = dt_bauhaus_combobox_get(widget); + if(g->lines != NULL && !g->lines_suppressed) + { + g->lines_suppressed = 1; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->eye), g->lines_suppressed); + } + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void mode_callback(GtkWidget *widget, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + p->mode = dt_bauhaus_combobox_get(widget); + + switch(p->mode) + { + case ASHIFT_MODE_GENERIC: + gtk_widget_hide(g->f_length); + gtk_widget_hide(g->crop_factor); + gtk_widget_hide(g->orthocorr); + gtk_widget_hide(g->aspect); + break; + case ASHIFT_MODE_SPECIFIC: + default: + gtk_widget_show(g->f_length); + gtk_widget_show(g->crop_factor); + gtk_widget_show(g->orthocorr); + gtk_widget_show(g->aspect); + break; + } + + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void f_length_callback(GtkWidget *slider, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + p->f_length = dt_bauhaus_slider_get(slider); + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void crop_factor_callback(GtkWidget *slider, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + p->crop_factor = dt_bauhaus_slider_get(slider); + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void orthocorr_callback(GtkWidget *slider, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + p->orthocorr = dt_bauhaus_slider_get(slider); + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static void aspect_callback(GtkWidget *slider, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(self->dt->gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + p->aspect = dt_bauhaus_slider_get(slider); + do_crop(self, p); + dt_dev_add_history_item(darktable.develop, self, TRUE); +} + +static int fit_v_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(darktable.gui->reset) return FALSE; + + if(event->button == 1) + { + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + const int control = (event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK; + const int shift = (event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK; + + dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE; + + if(control) + g->lastfit = fitaxis = ASHIFT_FIT_ROTATION_VERTICAL_LINES; + else if(shift) + g->lastfit = fitaxis = ASHIFT_FIT_VERTICALLY_NO_ROTATION; + else + g->lastfit = fitaxis = ASHIFT_FIT_VERTICALLY; + + dt_iop_request_focus(self); + dt_dev_reprocess_all(self->dev); + + if(self->enabled) + { + // module is enable -> we process directly + if(do_fit(self, p, fitaxis)) + { + darktable.gui->reset = 1; + dt_bauhaus_slider_set_soft(g->rotation, p->rotation); + dt_bauhaus_slider_set_soft(g->lensshift_v, p->lensshift_v); + dt_bauhaus_slider_set_soft(g->lensshift_h, p->lensshift_h); + dt_bauhaus_slider_set_soft(g->shear, p->shear); + darktable.gui->reset = 0; + } + } + else + { + // module is not enabled -> invoke it and queue the job to be processed once + // the preview image is ready + g->jobcode = ASHIFT_JOBCODE_FIT; + g->jobparams = g->lastfit = fitaxis; + p->toggle ^= 1; + } + + dt_dev_add_history_item(darktable.develop, self, TRUE); + return TRUE; + } + return FALSE; +} + +static int fit_h_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(darktable.gui->reset) return FALSE; + + if(event->button == 1) + { + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + const int control = (event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK; + const int shift = (event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK; + + dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE; + + if(control) + g->lastfit = fitaxis = ASHIFT_FIT_ROTATION_HORIZONTAL_LINES; + else if(shift) + g->lastfit = fitaxis = ASHIFT_FIT_HORIZONTALLY_NO_ROTATION; + else + g->lastfit = fitaxis = ASHIFT_FIT_HORIZONTALLY; + + dt_iop_request_focus(self); + dt_dev_reprocess_all(self->dev); + + if(self->enabled) + { + // module is enable -> we process directly + if(do_fit(self, p, fitaxis)) + { + darktable.gui->reset = 1; + dt_bauhaus_slider_set_soft(g->rotation, p->rotation); + dt_bauhaus_slider_set_soft(g->lensshift_v, p->lensshift_v); + dt_bauhaus_slider_set_soft(g->lensshift_h, p->lensshift_h); + dt_bauhaus_slider_set_soft(g->shear, p->shear); + darktable.gui->reset = 0; + } + } + else + { + // module is not enabled -> invoke it and queue the job to be processed once + // the preview image is ready + g->jobcode = ASHIFT_JOBCODE_FIT; + g->jobparams = g->lastfit = fitaxis; + p->toggle ^= 1; + } + + dt_dev_add_history_item(darktable.develop, self, TRUE); + return TRUE; + } + return FALSE; +} + +static int fit_both_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(darktable.gui->reset) return FALSE; + + if(event->button == 1) + { + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + const int control = (event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK; + const int shift = (event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK; + + dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE; + + if(control && shift) + fitaxis = ASHIFT_FIT_BOTH; + else if(control) + fitaxis = ASHIFT_FIT_ROTATION_BOTH_LINES; + else if(shift) + fitaxis = ASHIFT_FIT_BOTH_NO_ROTATION; + else + fitaxis = ASHIFT_FIT_BOTH_SHEAR; + + dt_iop_request_focus(self); + dt_dev_reprocess_all(self->dev); + + if(self->enabled) + { + // module is enable -> we process directly + if(do_fit(self, p, fitaxis)) + { + darktable.gui->reset = 1; + dt_bauhaus_slider_set_soft(g->rotation, p->rotation); + dt_bauhaus_slider_set_soft(g->lensshift_v, p->lensshift_v); + dt_bauhaus_slider_set_soft(g->lensshift_h, p->lensshift_h); + dt_bauhaus_slider_set_soft(g->shear, p->shear); + darktable.gui->reset = 0; + } + } + else + { + // module is not enabled -> invoke it and queue the job to be processed once + // the preview image is ready + g->jobcode = ASHIFT_JOBCODE_FIT; + g->jobparams = g->lastfit = fitaxis; + p->toggle ^= 1; + } + + dt_dev_add_history_item(darktable.develop, self, TRUE); + return TRUE; + } + return FALSE; +} + +static int structure_button_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(darktable.gui->reset) return FALSE; + + if(event->button == 1) + { + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + const int control = (event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK; + const int shift = (event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK; + + dt_iop_ashift_enhance_t enhance; + + if(control && shift) + enhance = ASHIFT_ENHANCE_EDGES | ASHIFT_ENHANCE_DETAIL; + else if(shift) + enhance = ASHIFT_ENHANCE_DETAIL; + else if(control) + enhance = ASHIFT_ENHANCE_EDGES; + else + enhance = ASHIFT_ENHANCE_NONE; + + dt_iop_request_focus(self); + dt_dev_reprocess_all(self->dev); + + if(self->enabled) + { + // module is enabled -> process directly + (void)do_get_structure(self, p, enhance); + } + else + { + // module is not enabled -> invoke it and queue the job to be processed once + // the preview image is ready + g->jobcode = ASHIFT_JOBCODE_GET_STRUCTURE; + g->jobparams = enhance; + p->toggle ^= 1; + } + + dt_dev_add_history_item(darktable.develop, self, TRUE); + return TRUE; + } + return FALSE; +} + +static void clean_button_clicked(GtkButton *button, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + if(darktable.gui->reset) return; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + (void)do_clean_structure(self, p); + dt_iop_request_focus(self); + dt_control_queue_redraw_center(); +} + +static void eye_button_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + if(darktable.gui->reset) return; + if(g->lines == NULL) + { + g->lines_suppressed = 0; + gtk_toggle_button_set_active(togglebutton, 0); + } + else + { + g->lines_suppressed = gtk_toggle_button_get_active(togglebutton); + } + dt_iop_request_focus(self); + dt_control_queue_redraw_center(); +} + +// routine that is called after preview image has been processed. we use it +// to perform structure collection or fitting in case those have been triggered while +// the module had not yet been enabled +static void process_after_preview_callback(gpointer instance, gpointer user_data) +{ + dt_iop_module_t *self = (dt_iop_module_t *)user_data; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + + dt_iop_ashift_jobcode_t jobcode = g->jobcode; + int jobparams = g->jobparams; + + // purge + g->jobcode = ASHIFT_JOBCODE_NONE; + g->jobparams = 0; + + if(darktable.gui->reset) return; + + switch(jobcode) + { + case ASHIFT_JOBCODE_GET_STRUCTURE: + (void)do_get_structure(self, p, (dt_iop_ashift_enhance_t)jobparams); + break; + + case ASHIFT_JOBCODE_FIT: + if(do_fit(self, p, (dt_iop_ashift_fitaxis_t)jobparams)) + { + darktable.gui->reset = 1; + dt_bauhaus_slider_set_soft(g->rotation, p->rotation); + dt_bauhaus_slider_set_soft(g->lensshift_v, p->lensshift_v); + dt_bauhaus_slider_set_soft(g->lensshift_h, p->lensshift_h); + dt_bauhaus_slider_set_soft(g->shear, p->shear); + darktable.gui->reset = 0; + } + dt_dev_add_history_item(darktable.develop, self, TRUE); + break; + + case ASHIFT_JOBCODE_NONE: + default: + break; + } + + dt_control_queue_redraw_center(); +} + +void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, + dt_dev_pixelpipe_iop_t *piece) +{ + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)p1; + dt_iop_ashift_data_t *d = (dt_iop_ashift_data_t *)piece->data; + + d->rotation = p->rotation; + d->lensshift_v = p->lensshift_v; + d->lensshift_h = p->lensshift_h; + d->shear = p->shear; + d->f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor; + d->orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr; + d->aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect; + + if(gui_has_focus(self)) + { + // if gui has focus we want to see the full uncropped image + d->cl = 0.0f; + d->cr = 1.0f; + d->ct = 0.0f; + d->cb = 1.0f; + } + else + { + d->cl = p->cl; + d->cr = p->cr; + d->ct = p->ct; + d->cb = p->cb; + } +} + +void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) +{ + dt_iop_ashift_data_t *d = (dt_iop_ashift_data_t *)calloc(1, sizeof(dt_iop_ashift_data_t)); + piece->data = (void *)d; + self->commit_params(self, self->default_params, pipe, piece); +} + +void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) +{ + free(piece->data); + piece->data = NULL; +} + +void gui_update(struct dt_iop_module_t *self) +{ + dt_iop_module_t *module = (dt_iop_module_t *)self; + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)module->params; + dt_bauhaus_slider_set_soft(g->rotation, p->rotation); + dt_bauhaus_slider_set_soft(g->lensshift_v, p->lensshift_v); + dt_bauhaus_slider_set_soft(g->lensshift_h, p->lensshift_h); + dt_bauhaus_slider_set_soft(g->shear, p->shear); + dt_bauhaus_slider_set_soft(g->f_length, p->f_length); + dt_bauhaus_slider_set_soft(g->crop_factor, p->crop_factor); + dt_bauhaus_slider_set(g->orthocorr, p->orthocorr); + dt_bauhaus_slider_set(g->aspect, p->aspect); + dt_bauhaus_combobox_set(g->mode, p->mode); + dt_bauhaus_combobox_set(g->guide_lines, g->show_guides); + dt_bauhaus_combobox_set(g->cropmode, p->cropmode); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->eye), 0); + + switch(p->mode) + { + case ASHIFT_MODE_GENERIC: + gtk_widget_hide(g->f_length); + gtk_widget_hide(g->crop_factor); + gtk_widget_hide(g->orthocorr); + gtk_widget_hide(g->aspect); + break; + case ASHIFT_MODE_SPECIFIC: + default: + gtk_widget_show(g->f_length); + gtk_widget_show(g->crop_factor); + gtk_widget_show(g->orthocorr); + gtk_widget_show(g->aspect); + break; + } +} + +void init(dt_iop_module_t *module) +{ + module->params = calloc(1, sizeof(dt_iop_ashift_params_t)); + module->default_params = calloc(1, sizeof(dt_iop_ashift_params_t)); + module->default_enabled = 0; + module->priority = 214; // module order created by iop_dependencies.py, do not edit! + module->params_size = sizeof(dt_iop_ashift_params_t); + module->gui_data = NULL; + dt_iop_ashift_params_t tmp = (dt_iop_ashift_params_t){ 0.0f, 0.0f, 0.0f, 0.0f, DEFAULT_F_LENGTH, 1.0f, 100.0f, 1.0f, ASHIFT_MODE_GENERIC, 0, + ASHIFT_CROP_OFF, 0.0f, 1.0f, 0.0f, 1.0f }; + memcpy(module->params, &tmp, sizeof(dt_iop_ashift_params_t)); + memcpy(module->default_params, &tmp, sizeof(dt_iop_ashift_params_t)); +} + +void reload_defaults(dt_iop_module_t *module) +{ + // our module is disabled by default + module->default_enabled = 0; + + int isflipped = 0; + float f_length = DEFAULT_F_LENGTH; + float crop_factor = 1.0f; + + // try to get information on orientation, focal length and crop factor from image data + if(module->dev) + { + const dt_image_t *img = &module->dev->image_storage; + // orientation only needed as a-priori information to correctly label some sliders + // before pixelpipe has been set up. later we will get a definite result by + // assessing the pixelpipe + isflipped = (img->orientation == ORIENTATION_ROTATE_CCW_90_DEG + || img->orientation == ORIENTATION_ROTATE_CW_90_DEG) + ? 1 + : 0; + + // focal length should be available in exif data if lens is electronically coupled to the camera + f_length = isfinite(img->exif_focal_length) && img->exif_focal_length > 0.0f ? img->exif_focal_length : f_length; + // crop factor of the camera is often not available and user will need to set it manually in the gui + crop_factor = isfinite(img->exif_crop) && img->exif_crop > 0.0f ? img->exif_crop : crop_factor; + } + + // init defaults: + dt_iop_ashift_params_t tmp = (dt_iop_ashift_params_t){ 0.0f, 0.0f, 0.0f, 0.0f, f_length, crop_factor, 100.0f, 1.0f, ASHIFT_MODE_GENERIC, 0, + ASHIFT_CROP_OFF, 0.0f, 1.0f, 0.0f, 1.0f }; + memcpy(module->params, &tmp, sizeof(dt_iop_ashift_params_t)); + memcpy(module->default_params, &tmp, sizeof(dt_iop_ashift_params_t)); + + // reset gui elements + if(module->gui_data) + { + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)module->gui_data; + + char string_v[256]; + char string_h[256]; + + snprintf(string_v, sizeof(string_v), _("lens shift (%s)"), isflipped ? _("horizontal") : _("vertical")); + snprintf(string_h, sizeof(string_h), _("lens shift (%s)"), isflipped ? _("vertical") : _("horizontal")); + + dt_bauhaus_widget_set_label(g->lensshift_v, NULL, string_v); + dt_bauhaus_widget_set_label(g->lensshift_h, NULL, string_h); + + dt_bauhaus_slider_set_default(g->f_length, tmp.f_length); + dt_bauhaus_slider_set_default(g->crop_factor, tmp.crop_factor); + + dt_pthread_mutex_lock(&g->lock); + free(g->buf); + g->buf = NULL; + g->buf_width = 0; + g->buf_height = 0; + g->buf_x_off = 0; + g->buf_y_off = 0; + g->buf_scale = 1.0f; + g->buf_hash = 0; + g->isflipped = -1; + g->lastfit = ASHIFT_FIT_NONE; + dt_pthread_mutex_unlock(&g->lock); + + g->fitting = 0; + free(g->lines); + g->lines = NULL; + g->lines_count =0; + g->horizontal_count = 0; + g->vertical_count = 0; + g->grid_hash = 0; + g->lines_hash = 0; + g->rotation_range = ROTATION_RANGE_SOFT; + g->lensshift_v_range = LENSSHIFT_RANGE_SOFT; + g->lensshift_h_range = LENSSHIFT_RANGE_SOFT; + g->shear_range = SHEAR_RANGE_SOFT; + g->lines_suppressed = 0; + g->lines_version = 0; + g->show_guides = 0; + g->isselecting = 0; + g->isdeselecting = 0; + g->isbounding = ASHIFT_BOUNDING_OFF; + g->near_delta = 0; + g->selecting_lines_version = 0; + + free(g->points); + g->points = NULL; + free(g->points_idx); + g->points_idx = NULL; + g->points_lines_count = 0; + g->points_version = 0; + + g->jobcode = ASHIFT_JOBCODE_NONE; + g->jobparams = 0; + g->adjust_crop = FALSE; + g->lastx = g->lasty = -1.0f; + g->crop_cx = g->crop_cy = 1.0f; + } +} + + +void init_global(dt_iop_module_so_t *module) +{ + dt_iop_ashift_global_data_t *gd + = (dt_iop_ashift_global_data_t *)malloc(sizeof(dt_iop_ashift_global_data_t)); + module->data = gd; + + const int program = 2; // basic.cl, from programs.conf + gd->kernel_ashift_bilinear = dt_opencl_create_kernel(program, "ashift_bilinear"); + gd->kernel_ashift_bicubic = dt_opencl_create_kernel(program, "ashift_bicubic"); + gd->kernel_ashift_lanczos2 = dt_opencl_create_kernel(program, "ashift_lanczos2"); + gd->kernel_ashift_lanczos3 = dt_opencl_create_kernel(program, "ashift_lanczos3"); +} + +void cleanup(dt_iop_module_t *module) +{ + free(module->params); + module->params = NULL; +} + +void cleanup_global(dt_iop_module_so_t *module) +{ + dt_iop_ashift_global_data_t *gd = (dt_iop_ashift_global_data_t *)module->data; + dt_opencl_free_kernel(gd->kernel_ashift_bilinear); + dt_opencl_free_kernel(gd->kernel_ashift_bicubic); + dt_opencl_free_kernel(gd->kernel_ashift_lanczos2); + dt_opencl_free_kernel(gd->kernel_ashift_lanczos3); + free(module->data); + module->data = NULL; +} + +// adjust labels of lens shift parameters according to flip status of image +static gboolean draw(GtkWidget *widget, cairo_t *cr, dt_iop_module_t *self) +{ + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + if(darktable.gui->reset) return FALSE; + + dt_pthread_mutex_lock(&g->lock); + const int isflipped = g->isflipped; + dt_pthread_mutex_unlock(&g->lock); + + if(isflipped == -1) return FALSE; + + char string_v[256]; + char string_h[256]; + + snprintf(string_v, sizeof(string_v), _("lens shift (%s)"), isflipped ? _("horizontal") : _("vertical")); + snprintf(string_h, sizeof(string_h), _("lens shift (%s)"), isflipped ? _("vertical") : _("horizontal")); + + darktable.gui->reset = 1; + dt_bauhaus_widget_set_label(g->lensshift_v, NULL, string_v); + dt_bauhaus_widget_set_label(g->lensshift_h, NULL, string_h); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->eye), g->lines_suppressed); + darktable.gui->reset = 0; + + return FALSE; +} + +void gui_focus(struct dt_iop_module_t *self, gboolean in) +{ + if(self->enabled) + dt_dev_reprocess_all(self->dev); +} + +static float log10_callback(GtkWidget *self, float inval, dt_bauhaus_callback_t dir) +{ + float outval; + switch(dir) + { + case DT_BAUHAUS_SET: + outval = log10(fmax(inval, 1e-15f)); + break; + case DT_BAUHAUS_GET: + outval = exp(M_LN10 * inval); + break; + default: + outval = inval; + } + return outval; +} + +static float log2_callback(GtkWidget *self, float inval, dt_bauhaus_callback_t dir) +{ + float outval; + switch(dir) + { + case DT_BAUHAUS_SET: + outval = log(fmax(inval, 1e-15f)) / M_LN2; + break; + case DT_BAUHAUS_GET: + outval = exp(M_LN2 * inval); + break; + default: + outval = inval; + } + return outval; +} + +void gui_init(struct dt_iop_module_t *self) +{ + self->gui_data = malloc(sizeof(dt_iop_ashift_gui_data_t)); + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + dt_iop_ashift_params_t *p = (dt_iop_ashift_params_t *)self->params; + + dt_pthread_mutex_init(&g->lock, NULL); + dt_pthread_mutex_lock(&g->lock); + g->buf = NULL; + g->buf_width = 0; + g->buf_height = 0; + g->buf_x_off = 0; + g->buf_y_off = 0; + g->buf_scale = 1.0f; + g->buf_hash = 0; + g->isflipped = -1; + g->lastfit = ASHIFT_FIT_NONE; + dt_pthread_mutex_unlock(&g->lock); + + g->fitting = 0; + g->lines = NULL; + g->lines_count = 0; + g->vertical_count = 0; + g->horizontal_count = 0; + g->lines_version = 0; + g->lines_suppressed = 0; + g->points = NULL; + g->points_idx = NULL; + g->points_lines_count = 0; + g->points_version = 0; + g->grid_hash = 0; + g->lines_hash = 0; + g->rotation_range = ROTATION_RANGE_SOFT; + g->lensshift_v_range = LENSSHIFT_RANGE_SOFT; + g->lensshift_h_range = LENSSHIFT_RANGE_SOFT; + g->shear_range = SHEAR_RANGE_SOFT; + g->show_guides = 0; + g->isselecting = 0; + g->isdeselecting = 0; + g->isbounding = ASHIFT_BOUNDING_OFF; + g->near_delta = 0; + g->selecting_lines_version = 0; + + g->jobcode = ASHIFT_JOBCODE_NONE; + g->jobparams = 0; + g->adjust_crop = FALSE; + g->lastx = g->lasty = -1.0f; + g->crop_cx = g->crop_cy = 1.0f; + + self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE); + dt_gui_add_help_link(self->widget, dt_get_help_url(self->op)); + + g->rotation = dt_bauhaus_slider_new_with_range(self, -ROTATION_RANGE, ROTATION_RANGE, 0.01*ROTATION_RANGE, p->rotation, 2); + dt_bauhaus_widget_set_label(g->rotation, NULL, _("rotation")); + dt_bauhaus_slider_set_format(g->rotation, "%.2f°"); + dt_bauhaus_slider_enable_soft_boundaries(g->rotation, -ROTATION_RANGE_SOFT, ROTATION_RANGE_SOFT); + gtk_box_pack_start(GTK_BOX(self->widget), g->rotation, TRUE, TRUE, 0); + + g->lensshift_v = dt_bauhaus_slider_new_with_range(self, -LENSSHIFT_RANGE, LENSSHIFT_RANGE, 0.01*LENSSHIFT_RANGE, p->lensshift_v, 3); + dt_bauhaus_widget_set_label(g->lensshift_v, NULL, _("lens shift (vertical)")); + dt_bauhaus_slider_enable_soft_boundaries(g->lensshift_v, -LENSSHIFT_RANGE_SOFT, LENSSHIFT_RANGE_SOFT); + gtk_box_pack_start(GTK_BOX(self->widget), g->lensshift_v, TRUE, TRUE, 0); + + g->lensshift_h = dt_bauhaus_slider_new_with_range(self, -LENSSHIFT_RANGE, LENSSHIFT_RANGE, 0.01*LENSSHIFT_RANGE, p->lensshift_h, 3); + dt_bauhaus_widget_set_label(g->lensshift_h, NULL, _("lens shift (horizontal)")); + dt_bauhaus_slider_enable_soft_boundaries(g->lensshift_h, -LENSSHIFT_RANGE_SOFT, LENSSHIFT_RANGE_SOFT); + gtk_box_pack_start(GTK_BOX(self->widget), g->lensshift_h, TRUE, TRUE, 0); + + g->shear = dt_bauhaus_slider_new_with_range(self, -SHEAR_RANGE, SHEAR_RANGE, 0.01*SHEAR_RANGE, p->shear, 3); + dt_bauhaus_widget_set_label(g->shear, NULL, _("shear")); + dt_bauhaus_slider_enable_soft_boundaries(g->shear, -SHEAR_RANGE_SOFT, SHEAR_RANGE_SOFT); + gtk_box_pack_start(GTK_BOX(self->widget), g->shear, TRUE, TRUE, 0); + + g->guide_lines = dt_bauhaus_combobox_new(self); + dt_bauhaus_widget_set_label(g->guide_lines, NULL, _("guides")); + dt_bauhaus_combobox_add(g->guide_lines, _("off")); + dt_bauhaus_combobox_add(g->guide_lines, _("on")); + gtk_box_pack_start(GTK_BOX(self->widget), g->guide_lines, TRUE, TRUE, 0); + + g->cropmode = dt_bauhaus_combobox_new(self); + dt_bauhaus_widget_set_label(g->cropmode, NULL, _("automatic cropping")); + dt_bauhaus_combobox_add(g->cropmode, _("off")); + dt_bauhaus_combobox_add(g->cropmode, _("largest area")); + dt_bauhaus_combobox_add(g->cropmode, _("original format")); + gtk_box_pack_start(GTK_BOX(self->widget), g->cropmode, TRUE, TRUE, 0); + + g->mode = dt_bauhaus_combobox_new(self); + dt_bauhaus_widget_set_label(g->mode, NULL, _("lens model")); + dt_bauhaus_combobox_add(g->mode, _("generic")); + dt_bauhaus_combobox_add(g->mode, _("specific")); + gtk_box_pack_start(GTK_BOX(self->widget), g->mode, TRUE, TRUE, 0); + + g->f_length = dt_bauhaus_slider_new_with_range(self, 1.0f, 3.0f, 0.01f, 1.0f, 2); + dt_bauhaus_widget_set_label(g->f_length, NULL, _("focal length")); + dt_bauhaus_slider_set_callback(g->f_length, log10_callback); + dt_bauhaus_slider_set_format(g->f_length, "%.0fmm"); + dt_bauhaus_slider_set_default(g->f_length, DEFAULT_F_LENGTH); + dt_bauhaus_slider_set(g->f_length, DEFAULT_F_LENGTH); + dt_bauhaus_slider_enable_soft_boundaries(g->f_length, 1.0f, 2000.0f); + gtk_box_pack_start(GTK_BOX(self->widget), g->f_length, TRUE, TRUE, 0); + + g->crop_factor = dt_bauhaus_slider_new_with_range(self, 1.0f, 2.0f, 0.01f, p->crop_factor, 2); + dt_bauhaus_widget_set_label(g->crop_factor, NULL, _("crop factor")); + dt_bauhaus_slider_enable_soft_boundaries(g->crop_factor, 0.5f, 10.0f); + gtk_box_pack_start(GTK_BOX(self->widget), g->crop_factor, TRUE, TRUE, 0); + + g->orthocorr = dt_bauhaus_slider_new_with_range(self, 0.0f, 100.0f, 1.0f, p->orthocorr, 2); + dt_bauhaus_widget_set_label(g->orthocorr, NULL, _("lens dependence")); + dt_bauhaus_slider_set_format(g->orthocorr, "%.0f%%"); +#if 0 + // this parameter could serve to finetune between generic model (0%) and specific model (100%). + // however, users can more easily get the same effect with the aspect adjust parameter so we keep + // this one hidden. + gtk_box_pack_start(GTK_BOX(self->widget), g->orthocorr, TRUE, TRUE, 0); +#endif + + g->aspect = dt_bauhaus_slider_new_with_range(self, -1.0f, 1.0f, 0.01f, 0.0f, 2); + dt_bauhaus_widget_set_label(g->aspect, NULL, _("aspect adjust")); + dt_bauhaus_slider_set_callback(g->aspect, log2_callback); + dt_bauhaus_slider_set_default(g->aspect, 1.0f); + dt_bauhaus_slider_set(g->aspect, 1.0f); + gtk_box_pack_start(GTK_BOX(self->widget), g->aspect, TRUE, TRUE, 0); + + GtkWidget *grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 2 * DT_BAUHAUS_SPACE); + gtk_grid_set_column_spacing(GTK_GRID(grid), DT_PIXEL_APPLY_DPI(10)); + + GtkWidget *label1 = gtk_label_new(_("automatic fit")); + gtk_widget_set_halign(label1, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), label1, 0, 0, 1, 1); + + g->fit_v = dtgtk_button_new(dtgtk_cairo_paint_perspective, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | 1, NULL); + gtk_widget_set_hexpand(GTK_WIDGET(g->fit_v), TRUE); + gtk_widget_set_size_request(g->fit_v, -1, DT_PIXEL_APPLY_DPI(24)); + gtk_grid_attach_next_to(GTK_GRID(grid), g->fit_v, label1, GTK_POS_RIGHT, 1, 1); + + g->fit_h = dtgtk_button_new(dtgtk_cairo_paint_perspective, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | 2, NULL); + gtk_widget_set_hexpand(GTK_WIDGET(g->fit_h), TRUE); + gtk_widget_set_size_request(g->fit_h, -1, DT_PIXEL_APPLY_DPI(24)); + gtk_grid_attach_next_to(GTK_GRID(grid), g->fit_h, g->fit_v, GTK_POS_RIGHT, 1, 1); + + g->fit_both = dtgtk_button_new(dtgtk_cairo_paint_perspective, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | 3, NULL); + gtk_widget_set_hexpand(GTK_WIDGET(g->fit_both), TRUE); + gtk_widget_set_size_request(g->fit_both, -1, DT_PIXEL_APPLY_DPI(24)); + gtk_grid_attach_next_to(GTK_GRID(grid), g->fit_both, g->fit_h, GTK_POS_RIGHT, 1, 1); + + GtkWidget *label2 = gtk_label_new(_("get structure")); + gtk_widget_set_halign(label2, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(grid), label2, 0, 1, 1, 1); + + g->structure = dtgtk_button_new(dtgtk_cairo_paint_structure, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL); + gtk_widget_set_hexpand(GTK_WIDGET(g->structure), TRUE); + gtk_grid_attach_next_to(GTK_GRID(grid), g->structure, label2, GTK_POS_RIGHT, 1, 1); + + g->clean = dtgtk_button_new(dtgtk_cairo_paint_cancel, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL); + gtk_widget_set_hexpand(GTK_WIDGET(g->clean), TRUE); + gtk_grid_attach_next_to(GTK_GRID(grid), g->clean, g->structure, GTK_POS_RIGHT, 1, 1); + + g->eye = dtgtk_togglebutton_new(dtgtk_cairo_paint_eye_toggle, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL); + gtk_widget_set_hexpand(GTK_WIDGET(g->eye), TRUE); + gtk_grid_attach_next_to(GTK_GRID(grid), g->eye, g->clean, GTK_POS_RIGHT, 1, 1); + + gtk_box_pack_start(GTK_BOX(self->widget), grid, TRUE, TRUE, 0); + + gtk_widget_show_all(g->f_length); + gtk_widget_set_no_show_all(g->f_length, TRUE); + gtk_widget_show_all(g->crop_factor); + gtk_widget_set_no_show_all(g->crop_factor, TRUE); + gtk_widget_show_all(g->orthocorr); + gtk_widget_set_no_show_all(g->orthocorr, TRUE); + gtk_widget_show_all(g->aspect); + gtk_widget_set_no_show_all(g->aspect, TRUE); + + + switch(p->mode) + { + case ASHIFT_MODE_GENERIC: + gtk_widget_hide(g->f_length); + gtk_widget_hide(g->crop_factor); + gtk_widget_hide(g->orthocorr); + gtk_widget_hide(g->aspect); + break; + case ASHIFT_MODE_SPECIFIC: + default: + gtk_widget_show(g->f_length); + gtk_widget_show(g->crop_factor); + gtk_widget_show(g->orthocorr); + gtk_widget_show(g->aspect); + break; + } + + gtk_widget_set_tooltip_text(g->rotation, _("rotate image")); + gtk_widget_set_tooltip_text(g->lensshift_v, _("apply lens shift correction in one direction")); + gtk_widget_set_tooltip_text(g->lensshift_h, _("apply lens shift correction in one direction")); + gtk_widget_set_tooltip_text(g->shear, _("shear the image along one diagonal")); + gtk_widget_set_tooltip_text(g->guide_lines, _("display guide lines overlay")); + gtk_widget_set_tooltip_text(g->cropmode, _("automatically crop to avoid black edges")); + gtk_widget_set_tooltip_text(g->mode, _("lens model of the perspective correction: " + "generic or according to the focal length")); + gtk_widget_set_tooltip_text(g->f_length, _("focal length of the lens, " + "default value set from exif data if available")); + gtk_widget_set_tooltip_text(g->crop_factor, _("crop factor of the camera sensor, " + "default value set from exif data if available, " + "manual setting is often required")); + gtk_widget_set_tooltip_text(g->orthocorr, _("the level of lens dependent correction, set to maximum for full lens dependency, " + "set to zero for the generic case")); + gtk_widget_set_tooltip_text(g->aspect, _("adjust aspect ratio of image by horizontal and vertical scaling")); + gtk_widget_set_tooltip_text(g->fit_v, _("automatically correct for vertical perspective distortion\n" + "ctrl-click to only fit rotation\n" + "shift-click to only fit lens shift")); + gtk_widget_set_tooltip_text(g->fit_h, _("automatically correct for horizontal perspective distortion\n" + "ctrl-click to only fit rotation\n" + "shift-click to only fit lens shift")); + gtk_widget_set_tooltip_text(g->fit_both, _("automatically correct for vertical and " + "horizontal perspective distortions; fitting rotation," + "lens shift in both directions, and shear\n" + "ctrl-click to only fit rotation\n" + "shift-click to only fit lens shift\n" + "ctrl-shift-click to only fit rotation and lens shift")); + gtk_widget_set_tooltip_text(g->structure, _("analyse line structure in image\n" + "ctrl-click for an additional edge enhancement\n" + "shift-click for an additional detail enhancement\n" + "ctrl-shift-click for a combination of both methods")); + gtk_widget_set_tooltip_text(g->clean, _("remove line structure information")); + gtk_widget_set_tooltip_text(g->eye, _("toggle visibility of structure lines")); + + g_signal_connect(G_OBJECT(g->rotation), "value-changed", G_CALLBACK(rotation_callback), self); + g_signal_connect(G_OBJECT(g->lensshift_v), "value-changed", G_CALLBACK(lensshift_v_callback), self); + g_signal_connect(G_OBJECT(g->lensshift_h), "value-changed", G_CALLBACK(lensshift_h_callback), self); + g_signal_connect(G_OBJECT(g->shear), "value-changed", G_CALLBACK(shear_callback), self); + g_signal_connect(G_OBJECT(g->guide_lines), "value-changed", G_CALLBACK(guide_lines_callback), self); + g_signal_connect(G_OBJECT(g->cropmode), "value-changed", G_CALLBACK(cropmode_callback), self); + g_signal_connect(G_OBJECT(g->mode), "value-changed", G_CALLBACK(mode_callback), self); + g_signal_connect(G_OBJECT(g->f_length), "value-changed", G_CALLBACK(f_length_callback), self); + g_signal_connect(G_OBJECT(g->crop_factor), "value-changed", G_CALLBACK(crop_factor_callback), self); + g_signal_connect(G_OBJECT(g->orthocorr), "value-changed", G_CALLBACK(orthocorr_callback), self); + g_signal_connect(G_OBJECT(g->aspect), "value-changed", G_CALLBACK(aspect_callback), self); + g_signal_connect(G_OBJECT(g->fit_v), "button-press-event", G_CALLBACK(fit_v_button_clicked), (gpointer)self); + g_signal_connect(G_OBJECT(g->fit_h), "button-press-event", G_CALLBACK(fit_h_button_clicked), (gpointer)self); + g_signal_connect(G_OBJECT(g->fit_both), "button-press-event", G_CALLBACK(fit_both_button_clicked), (gpointer)self); + g_signal_connect(G_OBJECT(g->structure), "button-press-event", G_CALLBACK(structure_button_clicked), (gpointer)self); + g_signal_connect(G_OBJECT(g->clean), "clicked", G_CALLBACK(clean_button_clicked), (gpointer)self); + g_signal_connect(G_OBJECT(g->eye), "toggled", G_CALLBACK(eye_button_toggled), (gpointer)self); + g_signal_connect(G_OBJECT(self->widget), "draw", G_CALLBACK(draw), self); + + /* add signal handler for preview pipe finish to redraw the overlay */ + dt_control_signal_connect(darktable.signals, DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED, + G_CALLBACK(process_after_preview_callback), self); + +} + +void gui_cleanup(struct dt_iop_module_t *self) +{ + dt_control_signal_disconnect(darktable.signals, G_CALLBACK(process_after_preview_callback), self); + + dt_iop_ashift_gui_data_t *g = (dt_iop_ashift_gui_data_t *)self->gui_data; + dt_pthread_mutex_destroy(&g->lock); + free(g->lines); + free(g->buf); + free(g->points); + free(g->points_idx); + free(self->gui_data); + self->gui_data = NULL; +} +#endif // if 0 +//----------------------------------------------------------------------------- + +// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh +// vim: shiftwidth=2 expandtab tabstop=2 cindent +// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; diff --git a/rtengine/ashift_lsd.c b/rtengine/ashift_lsd.c new file mode 100644 index 000000000..8f4f672b1 --- /dev/null +++ b/rtengine/ashift_lsd.c @@ -0,0 +1,2335 @@ +/* + This file is part of darktable, + copyright (c) 2016 Ulrich Pegelow. + + darktable 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. + + darktable 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 darktable. If not, see . +*/ + + +/* For line detection we are using the LSD code as published below. + * Changes versus the original code: + * do not include "lsd.h" (not needed) + * make all interface functions static + * comment out unsused interface functions + * catch (unlikely) division by zero near line 2035 + * rename rad1 and rad2 to radius1 and radius2 in reduce_region_radius() + * to avoid naming conflict in windows build + * + */ + +/* TODO: LSD employs intensive sanity checks of input data and allocation + * results. In case of errors it will terminate the program. On the one side + * this is not optimal for a module within darktable - it should better + * report errors upstream where they should be handled properly. On the other + * hand the kind of error which could be triggered in LSD would make it very unlikely + * that the darktable process could survive anyhow. + */ + +// clang-format off + +/*================================================================================== + * begin LSD code version 1.6. downloaded on January 30, 2016 + *==================================================================================*/ + +/*---------------------------------------------------------------------------- + + LSD - Line Segment Detector on digital images + + This code is part of the following publication and was subject + to peer review: + + "LSD: a Line Segment Detector" by Rafael Grompone von Gioi, + Jeremie Jakubowicz, Jean-Michel Morel, and Gregory Randall, + Image Processing On Line, 2012. DOI:10.5201/ipol.2012.gjmr-lsd + http://dx.doi.org/10.5201/ipol.2012.gjmr-lsd + + Copyright (c) 2007-2011 rafael grompone von gioi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + ----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** @file lsd.c + LSD module code + @author rafael grompone von gioi + */ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** @mainpage LSD code documentation + + This is an implementation of the Line Segment Detector described + in the paper: + + "LSD: A Fast Line Segment Detector with a False Detection Control" + by Rafael Grompone von Gioi, Jeremie Jakubowicz, Jean-Michel Morel, + and Gregory Randall, IEEE Transactions on Pattern Analysis and + Machine Intelligence, vol. 32, no. 4, pp. 722-732, April, 2010. + + and in more details in the CMLA Technical Report: + + "LSD: A Line Segment Detector, Technical Report", + by Rafael Grompone von Gioi, Jeremie Jakubowicz, Jean-Michel Morel, + Gregory Randall, CMLA, ENS Cachan, 2010. + + The version implemented here includes some further improvements + described in the following publication, of which this code is part: + + "LSD: a Line Segment Detector" by Rafael Grompone von Gioi, + Jeremie Jakubowicz, Jean-Michel Morel, and Gregory Randall, + Image Processing On Line, 2012. DOI:10.5201/ipol.2012.gjmr-lsd + http://dx.doi.org/10.5201/ipol.2012.gjmr-lsd + + The module's main function is lsd(). + + The source code is contained in two files: lsd.h and lsd.c. + + HISTORY: + - version 1.6 - nov 2011: + - changes in the interface, + - max_grad parameter removed, + - the factor 11 was added to the number of test + to consider the different precision values + tested, + - a minor bug corrected in the gradient sorting + code, + - the algorithm now also returns p and log_nfa + for each detection, + - a minor bug was corrected in the image scaling, + - the angle comparison in "isaligned" changed + from < to <=, + - "eps" variable renamed "log_eps", + - "lsd_scale_region" interface was added, + - minor changes to comments. + - version 1.5 - dec 2010: Changes in 'refine', -W option added, + and more comments added. + - version 1.4 - jul 2010: lsd_scale interface added and doxygen doc. + - version 1.3 - feb 2010: Multiple bug correction and improved code. + - version 1.2 - dec 2009: First full Ansi C Language version. + - version 1.1 - sep 2009: Systematic subsampling to scale 0.8 and + correction to partially handle "angle problem". + - version 1.0 - jan 2009: First complete Megawave2 and Ansi C Language + version. + + @author rafael grompone von gioi + */ +/*----------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +//#include "lsd.h" + +/** ln(10) */ +#ifndef M_LN10 +#define M_LN10 2.30258509299404568402 +#endif /* !M_LN10 */ + +/** PI */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif /* !M_PI */ + +#ifndef FALSE +#define FALSE 0 +#endif /* !FALSE */ + +#ifndef TRUE +#define TRUE 1 +#endif /* !TRUE */ + +/** Label for pixels with undefined gradient. */ +#define NOTDEF -1024.0 + +/** 3/2 pi */ +#define M_3_2_PI 4.71238898038 + +/** 2 pi */ +#define M_2__PI 6.28318530718 + +/** Label for pixels not used in yet. */ +#define NOTUSED 0 + +/** Label for pixels already used in detection. */ +#define USED 1 + +/*----------------------------------------------------------------------------*/ +/** Chained list of coordinates. + */ +struct coorlist +{ + int x,y; + struct coorlist * next; +}; + +/*----------------------------------------------------------------------------*/ +/** A point (or pixel). + */ +struct point {int x,y;}; + + +/*----------------------------------------------------------------------------*/ +/*------------------------- Miscellaneous functions --------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Fatal error, print a message to standard-error output and exit. + */ +static void error(const char * msg) +{ + fprintf(stderr,"LSD Error: %s\n",msg); + exit(EXIT_FAILURE); +} + +/*----------------------------------------------------------------------------*/ +/** Doubles relative error factor + */ +#define RELATIVE_ERROR_FACTOR 100.0 + +/*----------------------------------------------------------------------------*/ +/** Compare doubles by relative error. + + The resulting rounding error after floating point computations + depend on the specific operations done. The same number computed by + different algorithms could present different rounding errors. For a + useful comparison, an estimation of the relative rounding error + should be considered and compared to a factor times EPS. The factor + should be related to the cumulated rounding error in the chain of + computation. Here, as a simplification, a fixed factor is used. + */ +static int double_equal(double a, double b) +{ + double abs_diff,aa,bb,abs_max; + + /* trivial case */ + if( a == b ) return TRUE; + + abs_diff = fabs(a-b); + aa = fabs(a); + bb = fabs(b); + abs_max = aa > bb ? aa : bb; + + /* DBL_MIN is the smallest normalized number, thus, the smallest + number whose relative error is bounded by DBL_EPSILON. For + smaller numbers, the same quantization steps as for DBL_MIN + are used. Then, for smaller numbers, a meaningful "relative" + error should be computed by dividing the difference by DBL_MIN. */ + if( abs_max < DBL_MIN ) abs_max = DBL_MIN; + + /* equal if relative error <= factor x eps */ + return (abs_diff / abs_max) <= (RELATIVE_ERROR_FACTOR * DBL_EPSILON); +} + +/*----------------------------------------------------------------------------*/ +/** Computes Euclidean distance between point (x1,y1) and point (x2,y2). + */ +static double dist(double x1, double y1, double x2, double y2) +{ + return sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ); +} + + +/*----------------------------------------------------------------------------*/ +/*----------------------- 'list of n-tuple' data type ------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** 'list of n-tuple' data type + + The i-th component of the j-th n-tuple of an n-tuple list 'ntl' + is accessed with: + + ntl->values[ i + j * ntl->dim ] + + The dimension of the n-tuple (n) is: + + ntl->dim + + The number of n-tuples in the list is: + + ntl->size + + The maximum number of n-tuples that can be stored in the + list with the allocated memory at a given time is given by: + + ntl->max_size + */ +typedef struct ntuple_list_s +{ + unsigned int size; + unsigned int max_size; + unsigned int dim; + double * values; +} * ntuple_list; + +/*----------------------------------------------------------------------------*/ +/** Free memory used in n-tuple 'in'. + */ +static void free_ntuple_list(ntuple_list in) +{ + if( in == NULL || in->values == NULL ) + error("free_ntuple_list: invalid n-tuple input."); + free( (void *) in->values ); + free( (void *) in ); +} + +/*----------------------------------------------------------------------------*/ +/** Create an n-tuple list and allocate memory for one element. + @param dim the dimension (n) of the n-tuple. + */ +static ntuple_list new_ntuple_list(unsigned int dim) +{ + ntuple_list n_tuple; + + /* check parameters */ + if( dim == 0 ) error("new_ntuple_list: 'dim' must be positive."); + + /* get memory for list structure */ + n_tuple = (ntuple_list) malloc( sizeof(struct ntuple_list_s) ); + if( n_tuple == NULL ) error("not enough memory."); + + /* initialize list */ + n_tuple->size = 0; + n_tuple->max_size = 1; + n_tuple->dim = dim; + + /* get memory for tuples */ + n_tuple->values = (double *) malloc( dim*n_tuple->max_size * sizeof(double) ); + if( n_tuple->values == NULL ) error("not enough memory."); + + return n_tuple; +} + +/*----------------------------------------------------------------------------*/ +/** Enlarge the allocated memory of an n-tuple list. + */ +static void enlarge_ntuple_list(ntuple_list n_tuple) +{ + /* check parameters */ + if( n_tuple == NULL || n_tuple->values == NULL || n_tuple->max_size == 0 ) + error("enlarge_ntuple_list: invalid n-tuple."); + + /* duplicate number of tuples */ + n_tuple->max_size *= 2; + + /* realloc memory */ + n_tuple->values = (double *) realloc( (void *) n_tuple->values, + n_tuple->dim * n_tuple->max_size * sizeof(double) ); + if( n_tuple->values == NULL ) error("not enough memory."); +} + +/*----------------------------------------------------------------------------*/ +/** Add a 7-tuple to an n-tuple list. + */ +static void add_7tuple( ntuple_list out, double v1, double v2, double v3, + double v4, double v5, double v6, double v7 ) +{ + /* check parameters */ + if( out == NULL ) error("add_7tuple: invalid n-tuple input."); + if( out->dim != 7 ) error("add_7tuple: the n-tuple must be a 7-tuple."); + + /* if needed, alloc more tuples to 'out' */ + if( out->size == out->max_size ) enlarge_ntuple_list(out); + if( out->values == NULL ) error("add_7tuple: invalid n-tuple input."); + + /* add new 7-tuple */ + out->values[ out->size * out->dim + 0 ] = v1; + out->values[ out->size * out->dim + 1 ] = v2; + out->values[ out->size * out->dim + 2 ] = v3; + out->values[ out->size * out->dim + 3 ] = v4; + out->values[ out->size * out->dim + 4 ] = v5; + out->values[ out->size * out->dim + 5 ] = v6; + out->values[ out->size * out->dim + 6 ] = v7; + + /* update number of tuples counter */ + out->size++; +} + + +/*----------------------------------------------------------------------------*/ +/*----------------------------- Image Data Types -----------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** char image data type + + The pixel value at (x,y) is accessed by: + + image->data[ x + y * image->xsize ] + + with x and y integer. + */ +typedef struct image_char_s +{ + unsigned char * data; + unsigned int xsize,ysize; +} * image_char; + +/*----------------------------------------------------------------------------*/ +/** Free memory used in image_char 'i'. + */ +static void free_image_char(image_char i) +{ + if( i == NULL || i->data == NULL ) + error("free_image_char: invalid input image."); + free( (void *) i->data ); + free( (void *) i ); +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_char of size 'xsize' times 'ysize'. + */ +static image_char new_image_char(unsigned int xsize, unsigned int ysize) +{ + image_char image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) error("new_image_char: invalid image size."); + + /* get memory */ + image = (image_char) malloc( sizeof(struct image_char_s) ); + if( image == NULL ) error("not enough memory."); + image->data = (unsigned char *) calloc( (size_t) (xsize*ysize), + sizeof(unsigned char) ); + if( image->data == NULL ) error("not enough memory."); + + /* set image size */ + image->xsize = xsize; + image->ysize = ysize; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_char of size 'xsize' times 'ysize', + initialized to the value 'fill_value'. + */ +static image_char new_image_char_ini( unsigned int xsize, unsigned int ysize, + unsigned char fill_value ) +{ + image_char image = new_image_char(xsize,ysize); /* create image */ + unsigned int N = xsize*ysize; + unsigned int i; + + /* check parameters */ + if( image == NULL || image->data == NULL ) + error("new_image_char_ini: invalid image."); + + /* initialize */ + for(i=0; idata[i] = fill_value; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** int image data type + + The pixel value at (x,y) is accessed by: + + image->data[ x + y * image->xsize ] + + with x and y integer. + */ +typedef struct image_int_s +{ + int * data; + unsigned int xsize,ysize; +} * image_int; + +/*----------------------------------------------------------------------------*/ +/** Create a new image_int of size 'xsize' times 'ysize'. + */ +static image_int new_image_int(unsigned int xsize, unsigned int ysize) +{ + image_int image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) error("new_image_int: invalid image size."); + + /* get memory */ + image = (image_int) malloc( sizeof(struct image_int_s) ); + if( image == NULL ) error("not enough memory."); + image->data = (int *) calloc( (size_t) (xsize*ysize), sizeof(int) ); + if( image->data == NULL ) error("not enough memory."); + + /* set image size */ + image->xsize = xsize; + image->ysize = ysize; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_int of size 'xsize' times 'ysize', + initialized to the value 'fill_value'. + */ +static image_int new_image_int_ini( unsigned int xsize, unsigned int ysize, + int fill_value ) +{ + image_int image = new_image_int(xsize,ysize); /* create image */ + unsigned int N = xsize*ysize; + unsigned int i; + + /* initialize */ + for(i=0; idata[i] = fill_value; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** double image data type + + The pixel value at (x,y) is accessed by: + + image->data[ x + y * image->xsize ] + + with x and y integer. + */ +typedef struct image_double_s +{ + double * data; + unsigned int xsize,ysize; +} * image_double; + +/*----------------------------------------------------------------------------*/ +/** Free memory used in image_double 'i'. + */ +static void free_image_double(image_double i) +{ + if( i == NULL || i->data == NULL ) + error("free_image_double: invalid input image."); + free( (void *) i->data ); + free( (void *) i ); +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_double of size 'xsize' times 'ysize'. + */ +static image_double new_image_double(unsigned int xsize, unsigned int ysize) +{ + image_double image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) error("new_image_double: invalid image size."); + + /* get memory */ + image = (image_double) malloc( sizeof(struct image_double_s) ); + if( image == NULL ) error("not enough memory."); + image->data = (double *) calloc( (size_t) (xsize*ysize), sizeof(double) ); + if( image->data == NULL ) error("not enough memory."); + + /* set image size */ + image->xsize = xsize; + image->ysize = ysize; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_double of size 'xsize' times 'ysize' + with the data pointed by 'data'. + */ +static image_double new_image_double_ptr( unsigned int xsize, + unsigned int ysize, double * data ) +{ + image_double image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) + error("new_image_double_ptr: invalid image size."); + if( data == NULL ) error("new_image_double_ptr: NULL data pointer."); + + /* get memory */ + image = (image_double) malloc( sizeof(struct image_double_s) ); + if( image == NULL ) error("not enough memory."); + + /* set image */ + image->xsize = xsize; + image->ysize = ysize; + image->data = data; + + return image; +} + + +/*----------------------------------------------------------------------------*/ +/*----------------------------- Gaussian filter ------------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Compute a Gaussian kernel of length 'kernel->dim', + standard deviation 'sigma', and centered at value 'mean'. + + For example, if mean=0.5, the Gaussian will be centered + in the middle point between values 'kernel->values[0]' + and 'kernel->values[1]'. + */ +static void gaussian_kernel(ntuple_list kernel, double sigma, double mean) +{ + double sum = 0.0; + double val; + unsigned int i; + + /* check parameters */ + if( kernel == NULL || kernel->values == NULL ) + error("gaussian_kernel: invalid n-tuple 'kernel'."); + if( sigma <= 0.0 ) error("gaussian_kernel: 'sigma' must be positive."); + + /* compute Gaussian kernel */ + if( kernel->max_size < 1 ) enlarge_ntuple_list(kernel); + kernel->size = 1; + for(i=0;idim;i++) + { + val = ( (double) i - mean ) / sigma; + kernel->values[i] = exp( -0.5 * val * val ); + sum += kernel->values[i]; + } + + /* normalization */ + if( sum >= 0.0 ) for(i=0;idim;i++) kernel->values[i] /= sum; +} + +/*----------------------------------------------------------------------------*/ +/** Scale the input image 'in' by a factor 'scale' by Gaussian sub-sampling. + + For example, scale=0.8 will give a result at 80% of the original size. + + The image is convolved with a Gaussian kernel + @f[ + G(x,y) = \frac{1}{2\pi\sigma^2} e^{-\frac{x^2+y^2}{2\sigma^2}} + @f] + before the sub-sampling to prevent aliasing. + + The standard deviation sigma given by: + - sigma = sigma_scale / scale, if scale < 1.0 + - sigma = sigma_scale, if scale >= 1.0 + + To be able to sub-sample at non-integer steps, some interpolation + is needed. In this implementation, the interpolation is done by + the Gaussian kernel, so both operations (filtering and sampling) + are done at the same time. The Gaussian kernel is computed + centered on the coordinates of the required sample. In this way, + when applied, it gives directly the result of convolving the image + with the kernel and interpolated to that particular position. + + A fast algorithm is done using the separability of the Gaussian + kernel. Applying the 2D Gaussian kernel is equivalent to applying + first a horizontal 1D Gaussian kernel and then a vertical 1D + Gaussian kernel (or the other way round). The reason is that + @f[ + G(x,y) = G(x) * G(y) + @f] + where + @f[ + G(x) = \frac{1}{\sqrt{2\pi}\sigma} e^{-\frac{x^2}{2\sigma^2}}. + @f] + The algorithm first applies a combined Gaussian kernel and sampling + in the x axis, and then the combined Gaussian kernel and sampling + in the y axis. + */ +static image_double gaussian_sampler( image_double in, double scale, + double sigma_scale ) +{ + image_double aux,out; + ntuple_list kernel; + unsigned int N,M,h,n,x,y,i; + int xc,yc,j,double_x_size,double_y_size; + double sigma,xx,yy,sum,prec; + + /* check parameters */ + if( in == NULL || in->data == NULL || in->xsize == 0 || in->ysize == 0 ) + error("gaussian_sampler: invalid image."); + if( scale <= 0.0 ) error("gaussian_sampler: 'scale' must be positive."); + if( sigma_scale <= 0.0 ) + error("gaussian_sampler: 'sigma_scale' must be positive."); + + /* compute new image size and get memory for images */ + if( in->xsize * scale > (double) UINT_MAX || + in->ysize * scale > (double) UINT_MAX ) + error("gaussian_sampler: the output image size exceeds the handled size."); + N = (unsigned int) ceil( in->xsize * scale ); + M = (unsigned int) ceil( in->ysize * scale ); + aux = new_image_double(N,in->ysize); + out = new_image_double(N,M); + + /* sigma, kernel size and memory for the kernel */ + sigma = scale < 1.0 ? sigma_scale / scale : sigma_scale; + /* + The size of the kernel is selected to guarantee that the + the first discarded term is at least 10^prec times smaller + than the central value. For that, h should be larger than x, with + e^(-x^2/2sigma^2) = 1/10^prec. + Then, + x = sigma * sqrt( 2 * prec * ln(10) ). + */ + prec = 3.0; + h = (unsigned int) ceil( sigma * sqrt( 2.0 * prec * log(10.0) ) ); + n = 1+2*h; /* kernel size */ + kernel = new_ntuple_list(n); + + /* auxiliary double image size variables */ + double_x_size = (int) (2 * in->xsize); + double_y_size = (int) (2 * in->ysize); + + /* First subsampling: x axis */ + for(x=0;xxsize;x++) + { + /* + x is the coordinate in the new image. + xx is the corresponding x-value in the original size image. + xc is the integer value, the pixel coordinate of xx. + */ + xx = (double) x / scale; + /* coordinate (0.0,0.0) is in the center of pixel (0,0), + so the pixel with xc=0 get the values of xx from -0.5 to 0.5 */ + xc = (int) floor( xx + 0.5 ); + gaussian_kernel( kernel, sigma, (double) h + xx - (double) xc ); + /* the kernel must be computed for each x because the fine + offset xx-xc is different in each case */ + + for(y=0;yysize;y++) + { + sum = 0.0; + for(i=0;idim;i++) + { + j = xc - h + i; + + /* symmetry boundary condition */ + while( j < 0 ) j += double_x_size; + while( j >= double_x_size ) j -= double_x_size; + if( j >= (int) in->xsize ) j = double_x_size-1-j; + + sum += in->data[ j + y * in->xsize ] * kernel->values[i]; + } + aux->data[ x + y * aux->xsize ] = sum; + } + } + + /* Second subsampling: y axis */ + for(y=0;yysize;y++) + { + /* + y is the coordinate in the new image. + yy is the corresponding x-value in the original size image. + yc is the integer value, the pixel coordinate of xx. + */ + yy = (double) y / scale; + /* coordinate (0.0,0.0) is in the center of pixel (0,0), + so the pixel with yc=0 get the values of yy from -0.5 to 0.5 */ + yc = (int) floor( yy + 0.5 ); + gaussian_kernel( kernel, sigma, (double) h + yy - (double) yc ); + /* the kernel must be computed for each y because the fine + offset yy-yc is different in each case */ + + for(x=0;xxsize;x++) + { + sum = 0.0; + for(i=0;idim;i++) + { + j = yc - h + i; + + /* symmetry boundary condition */ + while( j < 0 ) j += double_y_size; + while( j >= double_y_size ) j -= double_y_size; + if( j >= (int) in->ysize ) j = double_y_size-1-j; + + sum += aux->data[ x + j * aux->xsize ] * kernel->values[i]; + } + out->data[ x + y * out->xsize ] = sum; + } + } + + /* free memory */ + free_ntuple_list(kernel); + free_image_double(aux); + + return out; +} + + +/*----------------------------------------------------------------------------*/ +/*--------------------------------- Gradient ---------------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Computes the direction of the level line of 'in' at each point. + + The result is: + - an image_double with the angle at each pixel, or NOTDEF if not defined. + - the image_double 'modgrad' (a pointer is passed as argument) + with the gradient magnitude at each point. + - a list of pixels 'list_p' roughly ordered by decreasing + gradient magnitude. (The order is made by classifying points + into bins by gradient magnitude. The parameters 'n_bins' and + 'max_grad' specify the number of bins and the gradient modulus + at the highest bin. The pixels in the list would be in + decreasing gradient magnitude, up to a precision of the size of + the bins.) + - a pointer 'mem_p' to the memory used by 'list_p' to be able to + free the memory when it is not used anymore. + */ +static image_double ll_angle( image_double in, double threshold, + struct coorlist ** list_p, void ** mem_p, + image_double * modgrad, unsigned int n_bins ) +{ + image_double g; + unsigned int n,p,x,y,adr,i; + double com1,com2,gx,gy,norm,norm2; + /* the rest of the variables are used for pseudo-ordering + the gradient magnitude values */ + int list_count = 0; + struct coorlist * list; + struct coorlist ** range_l_s; /* array of pointers to start of bin list */ + struct coorlist ** range_l_e; /* array of pointers to end of bin list */ + struct coorlist * start; + struct coorlist * end; + double max_grad = 0.0; + + /* check parameters */ + if( in == NULL || in->data == NULL || in->xsize == 0 || in->ysize == 0 ) + error("ll_angle: invalid image."); + if( threshold < 0.0 ) error("ll_angle: 'threshold' must be positive."); + if( list_p == NULL ) error("ll_angle: NULL pointer 'list_p'."); + if( mem_p == NULL ) error("ll_angle: NULL pointer 'mem_p'."); + if( modgrad == NULL ) error("ll_angle: NULL pointer 'modgrad'."); + if( n_bins == 0 ) error("ll_angle: 'n_bins' must be positive."); + + /* image size shortcuts */ + n = in->ysize; + p = in->xsize; + + /* allocate output image */ + g = new_image_double(in->xsize,in->ysize); + + /* get memory for the image of gradient modulus */ + *modgrad = new_image_double(in->xsize,in->ysize); + + /* get memory for "ordered" list of pixels */ + list = (struct coorlist *) calloc( (size_t) (n*p), sizeof(struct coorlist) ); + *mem_p = (void *) list; + range_l_s = (struct coorlist **) calloc( (size_t) n_bins, + sizeof(struct coorlist *) ); + range_l_e = (struct coorlist **) calloc( (size_t) n_bins, + sizeof(struct coorlist *) ); + if( list == NULL || range_l_s == NULL || range_l_e == NULL ) + error("not enough memory."); + for(i=0;idata[(n-1)*p+x] = NOTDEF; + for(y=0;ydata[p*y+p-1] = NOTDEF; + + /* compute gradient on the remaining pixels */ + for(x=0;xdata[adr+p+1] - in->data[adr]; + com2 = in->data[adr+1] - in->data[adr+p]; + + gx = com1+com2; /* gradient x component */ + gy = com1-com2; /* gradient y component */ + norm2 = gx*gx+gy*gy; + norm = sqrt( norm2 / 4.0 ); /* gradient norm */ + + (*modgrad)->data[adr] = norm; /* store gradient norm */ + + if( norm <= threshold ) /* norm too small, gradient no defined */ + g->data[adr] = NOTDEF; /* gradient angle not defined */ + else + { + /* gradient angle computation */ + g->data[adr] = atan2(gx,-gy); + + /* look for the maximum of the gradient */ + if( norm > max_grad ) max_grad = norm; + } + } + + /* compute histogram of gradient values */ + for(x=0;xdata[y*p+x]; + + /* store the point in the right bin according to its norm */ + i = (unsigned int) (norm * (double) n_bins / max_grad); + if( i >= n_bins ) i = n_bins-1; + if( range_l_e[i] == NULL ) + range_l_s[i] = range_l_e[i] = list+list_count++; + else + { + range_l_e[i]->next = list+list_count; + range_l_e[i] = list+list_count++; + } + range_l_e[i]->x = (int) x; + range_l_e[i]->y = (int) y; + range_l_e[i]->next = NULL; + } + + /* Make the list of pixels (almost) ordered by norm value. + It starts by the larger bin, so the list starts by the + pixels with the highest gradient value. Pixels would be ordered + by norm value, up to a precision given by max_grad/n_bins. + */ + for(i=n_bins-1; i>0 && range_l_s[i]==NULL; i--); + start = range_l_s[i]; + end = range_l_e[i]; + if( start != NULL ) + while(i>0) + { + --i; + if( range_l_s[i] != NULL ) + { + end->next = range_l_s[i]; + end = range_l_e[i]; + } + } + *list_p = start; + + /* free memory */ + free( (void *) range_l_s ); + free( (void *) range_l_e ); + + return g; +} + +/*----------------------------------------------------------------------------*/ +/** Is point (x,y) aligned to angle theta, up to precision 'prec'? + */ +static int isaligned( int x, int y, image_double angles, double theta, + double prec ) +{ + double a; + + /* check parameters */ + if( angles == NULL || angles->data == NULL ) + error("isaligned: invalid image 'angles'."); + if( x < 0 || y < 0 || x >= (int) angles->xsize || y >= (int) angles->ysize ) + error("isaligned: (x,y) out of the image."); + if( prec < 0.0 ) error("isaligned: 'prec' must be positive."); + + /* angle at pixel (x,y) */ + a = angles->data[ x + y * angles->xsize ]; + + /* pixels whose level-line angle is not defined + are considered as NON-aligned */ + if( a == NOTDEF ) return FALSE; /* there is no need to call the function + 'double_equal' here because there is + no risk of problems related to the + comparison doubles, we are only + interested in the exact NOTDEF value */ + + /* it is assumed that 'theta' and 'a' are in the range [-pi,pi] */ + theta -= a; + if( theta < 0.0 ) theta = -theta; + if( theta > M_3_2_PI ) + { + theta -= M_2__PI; + if( theta < 0.0 ) theta = -theta; + } + + return theta <= prec; +} + +/*----------------------------------------------------------------------------*/ +/** Absolute value angle difference. + */ +static double angle_diff(double a, double b) +{ + a -= b; + while( a <= -M_PI ) a += M_2__PI; + while( a > M_PI ) a -= M_2__PI; + if( a < 0.0 ) a = -a; + return a; +} + +/*----------------------------------------------------------------------------*/ +/** Signed angle difference. + */ +static double angle_diff_signed(double a, double b) +{ + a -= b; + while( a <= -M_PI ) a += M_2__PI; + while( a > M_PI ) a -= M_2__PI; + return a; +} + + +/*----------------------------------------------------------------------------*/ +/*----------------------------- NFA computation ------------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Computes the natural logarithm of the absolute value of + the gamma function of x using the Lanczos approximation. + See http://www.rskey.org/gamma.htm + + The formula used is + @f[ + \Gamma(x) = \frac{ \sum_{n=0}^{N} q_n x^n }{ \Pi_{n=0}^{N} (x+n) } + (x+5.5)^{x+0.5} e^{-(x+5.5)} + @f] + so + @f[ + \log\Gamma(x) = \log\left( \sum_{n=0}^{N} q_n x^n \right) + + (x+0.5) \log(x+5.5) - (x+5.5) - \sum_{n=0}^{N} \log(x+n) + @f] + and + q0 = 75122.6331530, + q1 = 80916.6278952, + q2 = 36308.2951477, + q3 = 8687.24529705, + q4 = 1168.92649479, + q5 = 83.8676043424, + q6 = 2.50662827511. + */ +static double log_gamma_lanczos(double x) +{ + static double q[7] = { 75122.6331530, 80916.6278952, 36308.2951477, + 8687.24529705, 1168.92649479, 83.8676043424, + 2.50662827511 }; + double a = (x+0.5) * log(x+5.5) - (x+5.5); + double b = 0.0; + int n; + + for(n=0;n<7;n++) + { + a -= log( x + (double) n ); + b += q[n] * pow( x, (double) n ); + } + return a + log(b); +} + +/*----------------------------------------------------------------------------*/ +/** Computes the natural logarithm of the absolute value of + the gamma function of x using Windschitl method. + See http://www.rskey.org/gamma.htm + + The formula used is + @f[ + \Gamma(x) = \sqrt{\frac{2\pi}{x}} \left( \frac{x}{e} + \sqrt{ x\sinh(1/x) + \frac{1}{810x^6} } \right)^x + @f] + so + @f[ + \log\Gamma(x) = 0.5\log(2\pi) + (x-0.5)\log(x) - x + + 0.5x\log\left( x\sinh(1/x) + \frac{1}{810x^6} \right). + @f] + This formula is a good approximation when x > 15. + */ +static double log_gamma_windschitl(double x) +{ + return 0.918938533204673 + (x-0.5)*log(x) - x + + 0.5*x*log( x*sinh(1/x) + 1/(810.0*pow(x,6.0)) ); +} + +/*----------------------------------------------------------------------------*/ +/** Computes the natural logarithm of the absolute value of + the gamma function of x. When x>15 use log_gamma_windschitl(), + otherwise use log_gamma_lanczos(). + */ +#define log_gamma(x) ((x)>15.0?log_gamma_windschitl(x):log_gamma_lanczos(x)) + +/*----------------------------------------------------------------------------*/ +/** Size of the table to store already computed inverse values. + */ +#define TABSIZE 100000 + +// clang-format on + +static double *inv = NULL; /* table to keep computed inverse values */ + +__attribute__((constructor)) static void invConstructor() +{ + if(inv) return; + inv = (double *)malloc(sizeof(double) * TABSIZE); +} + +__attribute__((destructor)) static void invDestructor() +{ + free(inv); + inv = NULL; +} + +// clang-format off + +/*----------------------------------------------------------------------------*/ +/** Computes -log10(NFA). + + NFA stands for Number of False Alarms: + @f[ + \mathrm{NFA} = NT \cdot B(n,k,p) + @f] + + - NT - number of tests + - B(n,k,p) - tail of binomial distribution with parameters n,k and p: + @f[ + B(n,k,p) = \sum_{j=k}^n + \left(\begin{array}{c}n\\j\end{array}\right) + p^{j} (1-p)^{n-j} + @f] + + The value -log10(NFA) is equivalent but more intuitive than NFA: + - -1 corresponds to 10 mean false alarms + - 0 corresponds to 1 mean false alarm + - 1 corresponds to 0.1 mean false alarms + - 2 corresponds to 0.01 mean false alarms + - ... + + Used this way, the bigger the value, better the detection, + and a logarithmic scale is used. + + @param n,k,p binomial parameters. + @param logNT logarithm of Number of Tests + + The computation is based in the gamma function by the following + relation: + @f[ + \left(\begin{array}{c}n\\k\end{array}\right) + = \frac{ \Gamma(n+1) }{ \Gamma(k+1) \cdot \Gamma(n-k+1) }. + @f] + We use efficient algorithms to compute the logarithm of + the gamma function. + + To make the computation faster, not all the sum is computed, part + of the terms are neglected based on a bound to the error obtained + (an error of 10% in the result is accepted). + */ +static double nfa(int n, int k, double p, double logNT) +{ + double tolerance = 0.1; /* an error of 10% in the result is accepted */ + double log1term,term,bin_term,mult_term,bin_tail,err,p_term; + int i; + + /* check parameters */ + if( n<0 || k<0 || k>n || p<=0.0 || p>=1.0 ) + error("nfa: wrong n, k or p values."); + + /* trivial cases */ + if( n==0 || k==0 ) return -logNT; + if( n==k ) return -logNT - (double) n * log10(p); + + /* probability term */ + p_term = p / (1.0-p); + + /* compute the first term of the series */ + /* + binomial_tail(n,k,p) = sum_{i=k}^n bincoef(n,i) * p^i * (1-p)^{n-i} + where bincoef(n,i) are the binomial coefficients. + But + bincoef(n,k) = gamma(n+1) / ( gamma(k+1) * gamma(n-k+1) ). + We use this to compute the first term. Actually the log of it. + */ + log1term = log_gamma( (double) n + 1.0 ) - log_gamma( (double) k + 1.0 ) + - log_gamma( (double) (n-k) + 1.0 ) + + (double) k * log(p) + (double) (n-k) * log(1.0-p); + term = exp(log1term); + + /* in some cases no more computations are needed */ + if( double_equal(term,0.0) ) /* the first term is almost zero */ + { + if( (double) k > (double) n * p ) /* at begin or end of the tail? */ + return -log1term / M_LN10 - logNT; /* end: use just the first term */ + else + return -logNT; /* begin: the tail is roughly 1 */ + } + + /* compute more terms if needed */ + bin_tail = term; + for(i=k+1;i<=n;i++) + { + /* + As + term_i = bincoef(n,i) * p^i * (1-p)^(n-i) + and + bincoef(n,i)/bincoef(n,i-1) = n-1+1 / i, + then, + term_i / term_i-1 = (n-i+1)/i * p/(1-p) + and + term_i = term_i-1 * (n-i+1)/i * p/(1-p). + 1/i is stored in a table as they are computed, + because divisions are expensive. + p/(1-p) is computed only once and stored in 'p_term'. + */ + bin_term = (double) (n-i+1) * ( ii. + Then, the error on the binomial tail when truncated at + the i term can be bounded by a geometric series of form + term_i * sum mult_term_i^j. */ + err = term * ( ( 1.0 - pow( mult_term, (double) (n-i+1) ) ) / + (1.0-mult_term) - 1.0 ); + + /* One wants an error at most of tolerance*final_result, or: + tolerance * abs(-log10(bin_tail)-logNT). + Now, the error that can be accepted on bin_tail is + given by tolerance*final_result divided by the derivative + of -log10(x) when x=bin_tail. that is: + tolerance * abs(-log10(bin_tail)-logNT) / (1/bin_tail) + Finally, we truncate the tail if the error is less than: + tolerance * abs(-log10(bin_tail)-logNT) * bin_tail */ + if( err < tolerance * fabs(-log10(bin_tail)-logNT) * bin_tail ) break; + } + } + return -log10(bin_tail) - logNT; +} + + +/*----------------------------------------------------------------------------*/ +/*--------------------------- Rectangle structure ----------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Rectangle structure: line segment with width. + */ +struct rect +{ + double x1,y1,x2,y2; /* first and second point of the line segment */ + double width; /* rectangle width */ + double x,y; /* center of the rectangle */ + double theta; /* angle */ + double dx,dy; /* (dx,dy) is vector oriented as the line segment */ + double prec; /* tolerance angle */ + double p; /* probability of a point with angle within 'prec' */ +}; + +/*----------------------------------------------------------------------------*/ +/** Copy one rectangle structure to another. + */ +static void rect_copy(struct rect * in, struct rect * out) +{ + /* check parameters */ + if( in == NULL || out == NULL ) error("rect_copy: invalid 'in' or 'out'."); + + /* copy values */ + out->x1 = in->x1; + out->y1 = in->y1; + out->x2 = in->x2; + out->y2 = in->y2; + out->width = in->width; + out->x = in->x; + out->y = in->y; + out->theta = in->theta; + out->dx = in->dx; + out->dy = in->dy; + out->prec = in->prec; + out->p = in->p; +} + +/*----------------------------------------------------------------------------*/ +/** Rectangle points iterator. + + The integer coordinates of pixels inside a rectangle are + iteratively explored. This structure keep track of the process and + functions ri_ini(), ri_inc(), ri_end(), and ri_del() are used in + the process. An example of how to use the iterator is as follows: + \code + + struct rect * rec = XXX; // some rectangle + rect_iter * i; + for( i=ri_ini(rec); !ri_end(i); ri_inc(i) ) + { + // your code, using 'i->x' and 'i->y' as coordinates + } + ri_del(i); // delete iterator + + \endcode + The pixels are explored 'column' by 'column', where we call + 'column' a set of pixels with the same x value that are inside the + rectangle. The following is an schematic representation of a + rectangle, the 'column' being explored is marked by colons, and + the current pixel being explored is 'x,y'. + \verbatim + + vx[1],vy[1] + * * + * * + * * + * ye + * : * + vx[0],vy[0] : * + * : * + * x,y * + * : * + * : vx[2],vy[2] + * : * + y ys * + ^ * * + | * * + | * * + +---> x vx[3],vy[3] + + \endverbatim + The first 'column' to be explored is the one with the smaller x + value. Each 'column' is explored starting from the pixel of the + 'column' (inside the rectangle) with the smallest y value. + + The four corners of the rectangle are stored in order that rotates + around the corners at the arrays 'vx[]' and 'vy[]'. The first + point is always the one with smaller x value. + + 'x' and 'y' are the coordinates of the pixel being explored. 'ys' + and 'ye' are the start and end values of the current column being + explored. So, 'ys' < 'ye'. + */ +typedef struct +{ + double vx[4]; /* rectangle's corner X coordinates in circular order */ + double vy[4]; /* rectangle's corner Y coordinates in circular order */ + double ys,ye; /* start and end Y values of current 'column' */ + int x,y; /* coordinates of currently explored pixel */ +} rect_iter; + +/*----------------------------------------------------------------------------*/ +/** Interpolate y value corresponding to 'x' value given, in + the line 'x1,y1' to 'x2,y2'; if 'x1=x2' return the smaller + of 'y1' and 'y2'. + + The following restrictions are required: + - x1 <= x2 + - x1 <= x + - x <= x2 + */ +static double inter_low(double x, double x1, double y1, double x2, double y2) +{ + /* check parameters */ + if( x1 > x2 || x < x1 || x > x2 ) + error("inter_low: unsuitable input, 'x1>x2' or 'xx2'."); + + /* interpolation */ + if( double_equal(x1,x2) && y1y2 ) return y2; + return y1 + (x-x1) * (y2-y1) / (x2-x1); +} + +/*----------------------------------------------------------------------------*/ +/** Interpolate y value corresponding to 'x' value given, in + the line 'x1,y1' to 'x2,y2'; if 'x1=x2' return the larger + of 'y1' and 'y2'. + + The following restrictions are required: + - x1 <= x2 + - x1 <= x + - x <= x2 + */ +static double inter_hi(double x, double x1, double y1, double x2, double y2) +{ + /* check parameters */ + if( x1 > x2 || x < x1 || x > x2 ) + error("inter_hi: unsuitable input, 'x1>x2' or 'xx2'."); + + /* interpolation */ + if( double_equal(x1,x2) && y1y2 ) return y1; + return y1 + (x-x1) * (y2-y1) / (x2-x1); +} + +/*----------------------------------------------------------------------------*/ +/** Free memory used by a rectangle iterator. + */ +static void ri_del(rect_iter * iter) +{ + if( iter == NULL ) error("ri_del: NULL iterator."); + free( (void *) iter ); +} + +/*----------------------------------------------------------------------------*/ +/** Check if the iterator finished the full iteration. + + See details in \ref rect_iter + */ +static int ri_end(rect_iter * i) +{ + /* check input */ + if( i == NULL ) error("ri_end: NULL iterator."); + + /* if the current x value is larger than the largest + x value in the rectangle (vx[2]), we know the full + exploration of the rectangle is finished. */ + return (double)(i->x) > i->vx[2]; +} + +/*----------------------------------------------------------------------------*/ +/** Increment a rectangle iterator. + + See details in \ref rect_iter + */ +static void ri_inc(rect_iter * i) +{ + /* check input */ + if( i == NULL ) error("ri_inc: NULL iterator."); + + /* if not at end of exploration, + increase y value for next pixel in the 'column' */ + if( !ri_end(i) ) i->y++; + + /* if the end of the current 'column' is reached, + and it is not the end of exploration, + advance to the next 'column' */ + while( (double) (i->y) > i->ye && !ri_end(i) ) + { + /* increase x, next 'column' */ + i->x++; + + /* if end of exploration, return */ + if( ri_end(i) ) return; + + /* update lower y limit (start) for the new 'column'. + + We need to interpolate the y value that corresponds to the + lower side of the rectangle. The first thing is to decide if + the corresponding side is + + vx[0],vy[0] to vx[3],vy[3] or + vx[3],vy[3] to vx[2],vy[2] + + Then, the side is interpolated for the x value of the + 'column'. But, if the side is vertical (as it could happen if + the rectangle is vertical and we are dealing with the first + or last 'columns') then we pick the lower value of the side + by using 'inter_low'. + */ + if( (double) i->x < i->vx[3] ) + i->ys = inter_low((double)i->x,i->vx[0],i->vy[0],i->vx[3],i->vy[3]); + else + i->ys = inter_low((double)i->x,i->vx[3],i->vy[3],i->vx[2],i->vy[2]); + + /* update upper y limit (end) for the new 'column'. + + We need to interpolate the y value that corresponds to the + upper side of the rectangle. The first thing is to decide if + the corresponding side is + + vx[0],vy[0] to vx[1],vy[1] or + vx[1],vy[1] to vx[2],vy[2] + + Then, the side is interpolated for the x value of the + 'column'. But, if the side is vertical (as it could happen if + the rectangle is vertical and we are dealing with the first + or last 'columns') then we pick the lower value of the side + by using 'inter_low'. + */ + if( (double)i->x < i->vx[1] ) + i->ye = inter_hi((double)i->x,i->vx[0],i->vy[0],i->vx[1],i->vy[1]); + else + i->ye = inter_hi((double)i->x,i->vx[1],i->vy[1],i->vx[2],i->vy[2]); + + /* new y */ + i->y = (int) ceil(i->ys); + } +} + +/*----------------------------------------------------------------------------*/ +/** Create and initialize a rectangle iterator. + + See details in \ref rect_iter + */ +static rect_iter * ri_ini(struct rect * r) +{ + double vx[4],vy[4]; + int n,offset; + rect_iter * i; + + /* check parameters */ + if( r == NULL ) error("ri_ini: invalid rectangle."); + + /* get memory */ + i = (rect_iter *) malloc(sizeof(rect_iter)); + if( i == NULL ) error("ri_ini: Not enough memory."); + + /* build list of rectangle corners ordered + in a circular way around the rectangle */ + vx[0] = r->x1 - r->dy * r->width / 2.0; + vy[0] = r->y1 + r->dx * r->width / 2.0; + vx[1] = r->x2 - r->dy * r->width / 2.0; + vy[1] = r->y2 + r->dx * r->width / 2.0; + vx[2] = r->x2 + r->dy * r->width / 2.0; + vy[2] = r->y2 - r->dx * r->width / 2.0; + vx[3] = r->x1 + r->dy * r->width / 2.0; + vy[3] = r->y1 - r->dx * r->width / 2.0; + + /* compute rotation of index of corners needed so that the first + point has the smaller x. + + if one side is vertical, thus two corners have the same smaller x + value, the one with the largest y value is selected as the first. + */ + if( r->x1 < r->x2 && r->y1 <= r->y2 ) offset = 0; + else if( r->x1 >= r->x2 && r->y1 < r->y2 ) offset = 1; + else if( r->x1 > r->x2 && r->y1 >= r->y2 ) offset = 2; + else offset = 3; + + /* apply rotation of index. */ + for(n=0; n<4; n++) + { + i->vx[n] = vx[(offset+n)%4]; + i->vy[n] = vy[(offset+n)%4]; + } + + /* Set an initial condition. + + The values are set to values that will cause 'ri_inc' (that will + be called immediately) to initialize correctly the first 'column' + and compute the limits 'ys' and 'ye'. + + 'y' is set to the integer value of vy[0], the starting corner. + + 'ys' and 'ye' are set to very small values, so 'ri_inc' will + notice that it needs to start a new 'column'. + + The smallest integer coordinate inside of the rectangle is + 'ceil(vx[0])'. The current 'x' value is set to that value minus + one, so 'ri_inc' (that will increase x by one) will advance to + the first 'column'. + */ + i->x = (int) ceil(i->vx[0]) - 1; + i->y = (int) ceil(i->vy[0]); + i->ys = i->ye = -DBL_MAX; + + /* advance to the first pixel */ + ri_inc(i); + + return i; +} + +/*----------------------------------------------------------------------------*/ +/** Compute a rectangle's NFA value. + */ +static double rect_nfa(struct rect * rec, image_double angles, double logNT) +{ + rect_iter * i; + int pts = 0; + int alg = 0; + + /* check parameters */ + if( rec == NULL ) error("rect_nfa: invalid rectangle."); + if( angles == NULL ) error("rect_nfa: invalid 'angles'."); + + /* compute the total number of pixels and of aligned points in 'rec' */ + for(i=ri_ini(rec); !ri_end(i); ri_inc(i)) /* rectangle iterator */ + if( i->x >= 0 && i->y >= 0 && + i->x < (int) angles->xsize && i->y < (int) angles->ysize ) + { + ++pts; /* total number of pixels counter */ + if( isaligned(i->x, i->y, angles, rec->theta, rec->prec) ) + ++alg; /* aligned points counter */ + } + ri_del(i); /* delete iterator */ + + return nfa(pts,alg,rec->p,logNT); /* compute NFA value */ +} + + +/*----------------------------------------------------------------------------*/ +/*---------------------------------- Regions ---------------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Compute region's angle as the principal inertia axis of the region. + + The following is the region inertia matrix A: + @f[ + + A = \left(\begin{array}{cc} + Ixx & Ixy \\ + Ixy & Iyy \\ + \end{array}\right) + + @f] + where + + Ixx = sum_i G(i).(y_i - cx)^2 + + Iyy = sum_i G(i).(x_i - cy)^2 + + Ixy = - sum_i G(i).(x_i - cx).(y_i - cy) + + and + - G(i) is the gradient norm at pixel i, used as pixel's weight. + - x_i and y_i are the coordinates of pixel i. + - cx and cy are the coordinates of the center of th region. + + lambda1 and lambda2 are the eigenvalues of matrix A, + with lambda1 >= lambda2. They are found by solving the + characteristic polynomial: + + det( lambda I - A) = 0 + + that gives: + + lambda1 = ( Ixx + Iyy + sqrt( (Ixx-Iyy)^2 + 4.0*Ixy*Ixy) ) / 2 + + lambda2 = ( Ixx + Iyy - sqrt( (Ixx-Iyy)^2 + 4.0*Ixy*Ixy) ) / 2 + + To get the line segment direction we want to get the angle the + eigenvector associated to the smallest eigenvalue. We have + to solve for a,b in: + + a.Ixx + b.Ixy = a.lambda2 + + a.Ixy + b.Iyy = b.lambda2 + + We want the angle theta = atan(b/a). It can be computed with + any of the two equations: + + theta = atan( (lambda2-Ixx) / Ixy ) + + or + + theta = atan( Ixy / (lambda2-Iyy) ) + + When |Ixx| > |Iyy| we use the first, otherwise the second (just to + get better numeric precision). + */ +static double get_theta( struct point * reg, int reg_size, double x, double y, + image_double modgrad, double reg_angle, double prec ) +{ + double lambda,theta,weight; + double Ixx = 0.0; + double Iyy = 0.0; + double Ixy = 0.0; + int i; + + /* check parameters */ + if( reg == NULL ) error("get_theta: invalid region."); + if( reg_size <= 1 ) error("get_theta: region size <= 1."); + if( modgrad == NULL || modgrad->data == NULL ) + error("get_theta: invalid 'modgrad'."); + if( prec < 0.0 ) error("get_theta: 'prec' must be positive."); + + /* compute inertia matrix */ + for(i=0; idata[ reg[i].x + reg[i].y * modgrad->xsize ]; + Ixx += ( (double) reg[i].y - y ) * ( (double) reg[i].y - y ) * weight; + Iyy += ( (double) reg[i].x - x ) * ( (double) reg[i].x - x ) * weight; + Ixy -= ( (double) reg[i].x - x ) * ( (double) reg[i].y - y ) * weight; + } + if( double_equal(Ixx,0.0) && double_equal(Iyy,0.0) && double_equal(Ixy,0.0) ) + error("get_theta: null inertia matrix."); + + /* compute smallest eigenvalue */ + lambda = 0.5 * ( Ixx + Iyy - sqrt( (Ixx-Iyy)*(Ixx-Iyy) + 4.0*Ixy*Ixy ) ); + + /* compute angle */ + theta = fabs(Ixx)>fabs(Iyy) ? atan2(lambda-Ixx,Ixy) : atan2(Ixy,lambda-Iyy); + + /* The previous procedure doesn't cares about orientation, + so it could be wrong by 180 degrees. Here is corrected if necessary. */ + if( angle_diff(theta,reg_angle) > prec ) theta += M_PI; + + return theta; +} + +/*----------------------------------------------------------------------------*/ +/** Computes a rectangle that covers a region of points. + */ +static void region2rect( struct point * reg, int reg_size, + image_double modgrad, double reg_angle, + double prec, double p, struct rect * rec ) +{ + double x,y,dx,dy,l,w,theta,weight,sum,l_min,l_max,w_min,w_max; + int i; + + /* check parameters */ + if( reg == NULL ) error("region2rect: invalid region."); + if( reg_size <= 1 ) error("region2rect: region size <= 1."); + if( modgrad == NULL || modgrad->data == NULL ) + error("region2rect: invalid image 'modgrad'."); + if( rec == NULL ) error("region2rect: invalid 'rec'."); + + /* center of the region: + + It is computed as the weighted sum of the coordinates + of all the pixels in the region. The norm of the gradient + is used as the weight of a pixel. The sum is as follows: + cx = \sum_i G(i).x_i + cy = \sum_i G(i).y_i + where G(i) is the norm of the gradient of pixel i + and x_i,y_i are its coordinates. + */ + x = y = sum = 0.0; + for(i=0; idata[ reg[i].x + reg[i].y * modgrad->xsize ]; + x += (double) reg[i].x * weight; + y += (double) reg[i].y * weight; + sum += weight; + } + if( sum <= 0.0 ) error("region2rect: weights sum equal to zero."); + x /= sum; + y /= sum; + + /* theta */ + theta = get_theta(reg,reg_size,x,y,modgrad,reg_angle,prec); + + /* length and width: + + 'l' and 'w' are computed as the distance from the center of the + region to pixel i, projected along the rectangle axis (dx,dy) and + to the orthogonal axis (-dy,dx), respectively. + + The length of the rectangle goes from l_min to l_max, where l_min + and l_max are the minimum and maximum values of l in the region. + Analogously, the width is selected from w_min to w_max, where + w_min and w_max are the minimum and maximum of w for the pixels + in the region. + */ + dx = cos(theta); + dy = sin(theta); + l_min = l_max = w_min = w_max = 0.0; + for(i=0; i l_max ) l_max = l; + if( l < l_min ) l_min = l; + if( w > w_max ) w_max = w; + if( w < w_min ) w_min = w; + } + + /* store values */ + rec->x1 = x + l_min * dx; + rec->y1 = y + l_min * dy; + rec->x2 = x + l_max * dx; + rec->y2 = y + l_max * dy; + rec->width = w_max - w_min; + rec->x = x; + rec->y = y; + rec->theta = theta; + rec->dx = dx; + rec->dy = dy; + rec->prec = prec; + rec->p = p; + + /* we impose a minimal width of one pixel + + A sharp horizontal or vertical step would produce a perfectly + horizontal or vertical region. The width computed would be + zero. But that corresponds to a one pixels width transition in + the image. + */ + if( rec->width < 1.0 ) rec->width = 1.0; +} + +/*----------------------------------------------------------------------------*/ +/** Build a region of pixels that share the same angle, up to a + tolerance 'prec', starting at point (x,y). + */ +static void region_grow( int x, int y, image_double angles, struct point * reg, + int * reg_size, double * reg_angle, image_char used, + double prec ) +{ + double sumdx,sumdy; + int xx,yy,i; + + /* check parameters */ + if( x < 0 || y < 0 || x >= (int) angles->xsize || y >= (int) angles->ysize ) + error("region_grow: (x,y) out of the image."); + if( angles == NULL || angles->data == NULL ) + error("region_grow: invalid image 'angles'."); + if( reg == NULL ) error("region_grow: invalid 'reg'."); + if( reg_size == NULL ) error("region_grow: invalid pointer 'reg_size'."); + if( reg_angle == NULL ) error("region_grow: invalid pointer 'reg_angle'."); + if( used == NULL || used->data == NULL ) + error("region_grow: invalid image 'used'."); + + /* first point of the region */ + *reg_size = 1; + reg[0].x = x; + reg[0].y = y; + *reg_angle = angles->data[x+y*angles->xsize]; /* region's angle */ + sumdx = cos(*reg_angle); + sumdy = sin(*reg_angle); + used->data[x+y*used->xsize] = USED; + + /* try neighbors as new region points */ + for(i=0; i<*reg_size; i++) + for(xx=reg[i].x-1; xx<=reg[i].x+1; xx++) + for(yy=reg[i].y-1; yy<=reg[i].y+1; yy++) + if( xx>=0 && yy>=0 && xx<(int)used->xsize && yy<(int)used->ysize && + used->data[xx+yy*used->xsize] != USED && + isaligned(xx,yy,angles,*reg_angle,prec) ) + { + /* add point */ + used->data[xx+yy*used->xsize] = USED; + reg[*reg_size].x = xx; + reg[*reg_size].y = yy; + ++(*reg_size); + + /* update region's angle */ + sumdx += cos( angles->data[xx+yy*angles->xsize] ); + sumdy += sin( angles->data[xx+yy*angles->xsize] ); + *reg_angle = atan2(sumdy,sumdx); + } +} + +/*----------------------------------------------------------------------------*/ +/** Try some rectangles variations to improve NFA value. Only if the + rectangle is not meaningful (i.e., log_nfa <= log_eps). + */ +static double rect_improve( struct rect * rec, image_double angles, + double logNT, double log_eps ) +{ + struct rect r; + double log_nfa,log_nfa_new; + double delta = 0.5; + double delta_2 = delta / 2.0; + int n; + + log_nfa = rect_nfa(rec,angles,logNT); + + if( log_nfa > log_eps ) return log_nfa; + + /* try finer precisions */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + r.p /= 2.0; + r.prec = r.p * M_PI; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + log_nfa = log_nfa_new; + rect_copy(&r,rec); + } + } + + if( log_nfa > log_eps ) return log_nfa; + + /* try to reduce width */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + if( (r.width - delta) >= 0.5 ) + { + r.width -= delta; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + rect_copy(&r,rec); + log_nfa = log_nfa_new; + } + } + } + + if( log_nfa > log_eps ) return log_nfa; + + /* try to reduce one side of the rectangle */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + if( (r.width - delta) >= 0.5 ) + { + r.x1 += -r.dy * delta_2; + r.y1 += r.dx * delta_2; + r.x2 += -r.dy * delta_2; + r.y2 += r.dx * delta_2; + r.width -= delta; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + rect_copy(&r,rec); + log_nfa = log_nfa_new; + } + } + } + + if( log_nfa > log_eps ) return log_nfa; + + /* try to reduce the other side of the rectangle */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + if( (r.width - delta) >= 0.5 ) + { + r.x1 -= -r.dy * delta_2; + r.y1 -= r.dx * delta_2; + r.x2 -= -r.dy * delta_2; + r.y2 -= r.dx * delta_2; + r.width -= delta; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + rect_copy(&r,rec); + log_nfa = log_nfa_new; + } + } + } + + if( log_nfa > log_eps ) return log_nfa; + + /* try even finer precisions */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + r.p /= 2.0; + r.prec = r.p * M_PI; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + log_nfa = log_nfa_new; + rect_copy(&r,rec); + } + } + + return log_nfa; +} + +/*----------------------------------------------------------------------------*/ +/** Reduce the region size, by elimination the points far from the + starting point, until that leads to rectangle with the right + density of region points or to discard the region if too small. + */ +static int reduce_region_radius( struct point * reg, int * reg_size, + image_double modgrad, double reg_angle, + double prec, double p, struct rect * rec, + image_char used, image_double angles, + double density_th ) +{ + double density,radius1,radius2,rad,xc,yc; + int i; + + /* check parameters */ + if( reg == NULL ) error("reduce_region_radius: invalid pointer 'reg'."); + if( reg_size == NULL ) + error("reduce_region_radius: invalid pointer 'reg_size'."); + if( prec < 0.0 ) error("reduce_region_radius: 'prec' must be positive."); + if( rec == NULL ) error("reduce_region_radius: invalid pointer 'rec'."); + if( used == NULL || used->data == NULL ) + error("reduce_region_radius: invalid image 'used'."); + if( angles == NULL || angles->data == NULL ) + error("reduce_region_radius: invalid image 'angles'."); + + /* compute region points density */ + density = (double) *reg_size / + ( dist(rec->x1,rec->y1,rec->x2,rec->y2) * rec->width ); + + /* if the density criterion is satisfied there is nothing to do */ + if( density >= density_th ) return TRUE; + + /* compute region's radius */ + xc = (double) reg[0].x; + yc = (double) reg[0].y; + radius1 = dist( xc, yc, rec->x1, rec->y1 ); + radius2 = dist( xc, yc, rec->x2, rec->y2 ); + rad = radius1 > radius2 ? radius1 : radius2; + + /* while the density criterion is not satisfied, remove farther pixels */ + while( density < density_th ) + { + rad *= 0.75; /* reduce region's radius to 75% of its value */ + + /* remove points from the region and update 'used' map */ + for(i=0; i<*reg_size; i++) + if( dist( xc, yc, (double) reg[i].x, (double) reg[i].y ) > rad ) + { + /* point not kept, mark it as NOTUSED */ + used->data[ reg[i].x + reg[i].y * used->xsize ] = NOTUSED; + /* remove point from the region */ + reg[i].x = reg[*reg_size-1].x; /* if i==*reg_size-1 copy itself */ + reg[i].y = reg[*reg_size-1].y; + --(*reg_size); + --i; /* to avoid skipping one point */ + } + + /* reject if the region is too small. + 2 is the minimal region size for 'region2rect' to work. */ + if( *reg_size < 2 ) return FALSE; + + /* re-compute rectangle */ + region2rect(reg,*reg_size,modgrad,reg_angle,prec,p,rec); + + /* re-compute region points density */ + density = (double) *reg_size / + ( dist(rec->x1,rec->y1,rec->x2,rec->y2) * rec->width ); + } + + /* if this point is reached, the density criterion is satisfied */ + return TRUE; +} + +/*----------------------------------------------------------------------------*/ +/** Refine a rectangle. + + For that, an estimation of the angle tolerance is performed by the + standard deviation of the angle at points near the region's + starting point. Then, a new region is grown starting from the same + point, but using the estimated angle tolerance. If this fails to + produce a rectangle with the right density of region points, + 'reduce_region_radius' is called to try to satisfy this condition. + */ +static int refine( struct point * reg, int * reg_size, image_double modgrad, + double reg_angle, double prec, double p, struct rect * rec, + image_char used, image_double angles, double density_th ) +{ + double angle,ang_d,mean_angle,tau,density,xc,yc,ang_c,sum,s_sum; + int i,n; + + /* check parameters */ + if( reg == NULL ) error("refine: invalid pointer 'reg'."); + if( reg_size == NULL ) error("refine: invalid pointer 'reg_size'."); + if( prec < 0.0 ) error("refine: 'prec' must be positive."); + if( rec == NULL ) error("refine: invalid pointer 'rec'."); + if( used == NULL || used->data == NULL ) + error("refine: invalid image 'used'."); + if( angles == NULL || angles->data == NULL ) + error("refine: invalid image 'angles'."); + + /* compute region points density */ + density = (double) *reg_size / + ( dist(rec->x1,rec->y1,rec->x2,rec->y2) * rec->width ); + + /* if the density criterion is satisfied there is nothing to do */ + if( density >= density_th ) return TRUE; + + /*------ First try: reduce angle tolerance ------*/ + + /* compute the new mean angle and tolerance */ + xc = (double) reg[0].x; + yc = (double) reg[0].y; + ang_c = angles->data[ reg[0].x + reg[0].y * angles->xsize ]; + sum = s_sum = 0.0; + n = 0; + for(i=0; i<*reg_size; i++) + { + used->data[ reg[i].x + reg[i].y * used->xsize ] = NOTUSED; + if( dist( xc, yc, (double) reg[i].x, (double) reg[i].y ) < rec->width ) + { + angle = angles->data[ reg[i].x + reg[i].y * angles->xsize ]; + ang_d = angle_diff_signed(angle,ang_c); + sum += ang_d; + s_sum += ang_d * ang_d; + ++n; + } + } + + /* should not happen */ + if(n == 0) return FALSE; + + mean_angle = sum / (double) n; + tau = 2.0 * sqrt( (s_sum - 2.0 * mean_angle * sum) / (double) n + + mean_angle*mean_angle ); /* 2 * standard deviation */ + + /* find a new region from the same starting point and new angle tolerance */ + region_grow(reg[0].x,reg[0].y,angles,reg,reg_size,®_angle,used,tau); + + /* if the region is too small, reject */ + if( *reg_size < 2 ) return FALSE; + + /* re-compute rectangle */ + region2rect(reg,*reg_size,modgrad,reg_angle,prec,p,rec); + + /* re-compute region points density */ + density = (double) *reg_size / + ( dist(rec->x1,rec->y1,rec->x2,rec->y2) * rec->width ); + + /*------ Second try: reduce region radius ------*/ + if( density < density_th ) + return reduce_region_radius( reg, reg_size, modgrad, reg_angle, prec, p, + rec, used, angles, density_th ); + + /* if this point is reached, the density criterion is satisfied */ + return TRUE; +} + + +/*----------------------------------------------------------------------------*/ +/*-------------------------- Line Segment Detector ---------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** LSD full interface. + */ +static +double * LineSegmentDetection( int * n_out, + double * img, int X, int Y, + double scale, double sigma_scale, double quant, + double ang_th, double log_eps, double density_th, + int n_bins, + int ** reg_img, int * reg_x, int * reg_y ) +{ + image_double image; + ntuple_list out = new_ntuple_list(7); + double * return_value; + image_double scaled_image,angles,modgrad; + image_char used; + image_int region = NULL; + struct coorlist * list_p; + void * mem_p; + struct rect rec; + struct point * reg; + int reg_size,min_reg_size,i; + unsigned int xsize,ysize; + double rho,reg_angle,prec,p,log_nfa,logNT; + int ls_count = 0; /* line segments are numbered 1,2,3,... */ + + + /* check parameters */ + if( img == NULL || X <= 0 || Y <= 0 ) error("invalid image input."); + if( scale <= 0.0 ) error("'scale' value must be positive."); + if( sigma_scale <= 0.0 ) error("'sigma_scale' value must be positive."); + if( quant < 0.0 ) error("'quant' value must be positive."); + if( ang_th <= 0.0 || ang_th >= 180.0 ) + error("'ang_th' value must be in the range (0,180)."); + if( density_th < 0.0 || density_th > 1.0 ) + error("'density_th' value must be in the range [0,1]."); + if( n_bins <= 0 ) error("'n_bins' value must be positive."); + + + /* angle tolerance */ + prec = M_PI * ang_th / 180.0; + p = ang_th / 180.0; + rho = quant / sin(prec); /* gradient magnitude threshold */ + + + /* load and scale image (if necessary) and compute angle at each pixel */ + image = new_image_double_ptr( (unsigned int) X, (unsigned int) Y, img ); + if( scale != 1.0 ) + { + scaled_image = gaussian_sampler( image, scale, sigma_scale ); + angles = ll_angle( scaled_image, rho, &list_p, &mem_p, + &modgrad, (unsigned int) n_bins ); + free_image_double(scaled_image); + } + else + angles = ll_angle( image, rho, &list_p, &mem_p, &modgrad, + (unsigned int) n_bins ); + xsize = angles->xsize; + ysize = angles->ysize; + + /* Number of Tests - NT + + The theoretical number of tests is Np.(XY)^(5/2) + where X and Y are number of columns and rows of the image. + Np corresponds to the number of angle precisions considered. + As the procedure 'rect_improve' tests 5 times to halve the + angle precision, and 5 more times after improving other factors, + 11 different precision values are potentially tested. Thus, + the number of tests is + 11 * (X*Y)^(5/2) + whose logarithm value is + log10(11) + 5/2 * (log10(X) + log10(Y)). + */ + logNT = 5.0 * ( log10( (double) xsize ) + log10( (double) ysize ) ) / 2.0 + + log10(11.0); + min_reg_size = (int) (-logNT/log10(p)); /* minimal number of points in region + that can give a meaningful event */ + + + /* initialize some structures */ + if( reg_img != NULL && reg_x != NULL && reg_y != NULL ) /* save region data */ + region = new_image_int_ini(angles->xsize,angles->ysize,0); + used = new_image_char_ini(xsize,ysize,NOTUSED); + reg = (struct point *) calloc( (size_t) (xsize*ysize), sizeof(struct point) ); + if( reg == NULL ) error("not enough memory!"); + + + /* search for line segments */ + for(; list_p != NULL; list_p = list_p->next ) + if( used->data[ list_p->x + list_p->y * used->xsize ] == NOTUSED && + angles->data[ list_p->x + list_p->y * angles->xsize ] != NOTDEF ) + /* there is no risk of double comparison problems here + because we are only interested in the exact NOTDEF value */ + { + /* find the region of connected point and ~equal angle */ + region_grow( list_p->x, list_p->y, angles, reg, ®_size, + ®_angle, used, prec ); + + /* reject small regions */ + if( reg_size < min_reg_size ) continue; + + /* construct rectangular approximation for the region */ + region2rect(reg,reg_size,modgrad,reg_angle,prec,p,&rec); + + /* Check if the rectangle exceeds the minimal density of + region points. If not, try to improve the region. + The rectangle will be rejected if the final one does + not fulfill the minimal density condition. + This is an addition to the original LSD algorithm published in + "LSD: A Fast Line Segment Detector with a False Detection Control" + by R. Grompone von Gioi, J. Jakubowicz, J.M. Morel, and G. Randall. + The original algorithm is obtained with density_th = 0.0. + */ + if( !refine( reg, ®_size, modgrad, reg_angle, + prec, p, &rec, used, angles, density_th ) ) continue; + + /* compute NFA value */ + log_nfa = rect_improve(&rec,angles,logNT,log_eps); + if( log_nfa <= log_eps ) continue; + + /* A New Line Segment was found! */ + ++ls_count; /* increase line segment counter */ + + /* + The gradient was computed with a 2x2 mask, its value corresponds to + points with an offset of (0.5,0.5), that should be added to output. + The coordinates origin is at the center of pixel (0,0). + */ + rec.x1 += 0.5; rec.y1 += 0.5; + rec.x2 += 0.5; rec.y2 += 0.5; + + /* scale the result values if a subsampling was performed */ + if( scale != 1.0 ) + { + rec.x1 /= scale; rec.y1 /= scale; + rec.x2 /= scale; rec.y2 /= scale; + rec.width /= scale; + } + + /* add line segment found to output */ + add_7tuple( out, rec.x1, rec.y1, rec.x2, rec.y2, + rec.width, rec.p, log_nfa ); + + /* add region number to 'region' image if needed */ + if( region != NULL ) + for(i=0; idata[ reg[i].x + reg[i].y * region->xsize ] = ls_count; + } + + + /* free memory */ + free( (void *) image ); /* only the double_image structure should be freed, + the data pointer was provided to this functions + and should not be destroyed. */ + free_image_double(angles); + free_image_double(modgrad); + free_image_char(used); + free( (void *) reg ); + free( (void *) mem_p ); + + /* return the result */ + if( reg_img != NULL && reg_x != NULL && reg_y != NULL ) + { + if( region == NULL ) error("'region' should be a valid image."); + *reg_img = region->data; + if( region->xsize > (unsigned int) INT_MAX || + region->ysize > (unsigned int) INT_MAX ) + error("region image to big to fit in INT sizes."); + *reg_x = (int) (region->xsize); + *reg_y = (int) (region->ysize); + + /* free the 'region' structure. + we cannot use the function 'free_image_int' because we need to keep + the memory with the image data to be returned by this function. */ + free( (void *) region ); + } + if( out->size > (unsigned int) INT_MAX ) + error("too many detections to fit in an INT."); + *n_out = (int) (out->size); + + return_value = out->values; + free( (void *) out ); /* only the 'ntuple_list' structure must be freed, + but the 'values' pointer must be keep to return + as a result. */ + + return return_value; +} +#if 0 +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface with Scale and Region output. + */ +static +double * lsd_scale_region( int * n_out, + double * img, int X, int Y, double scale, + int ** reg_img, int * reg_x, int * reg_y ) +{ + /* LSD parameters */ + double sigma_scale = 0.6; /* Sigma for Gaussian filter is computed as + sigma = sigma_scale/scale. */ + double quant = 2.0; /* Bound to the quantization error on the + gradient norm. */ + double ang_th = 22.5; /* Gradient angle tolerance in degrees. */ + double log_eps = 0.0; /* Detection threshold: -log10(NFA) > log_eps */ + double density_th = 0.7; /* Minimal density of region points in rectangle. */ + int n_bins = 1024; /* Number of bins in pseudo-ordering of gradient + modulus. */ + + return LineSegmentDetection( n_out, img, X, Y, scale, sigma_scale, quant, + ang_th, log_eps, density_th, n_bins, + reg_img, reg_x, reg_y ); +} + +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface with Scale. + */ +static +double * lsd_scale(int * n_out, double * img, int X, int Y, double scale) +{ + return lsd_scale_region(n_out,img,X,Y,scale,NULL,NULL,NULL); +} + +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface. + */ +static +double * lsd(int * n_out, double * img, int X, int Y) +{ + /* LSD parameters */ + double scale = 0.8; /* Scale the image by Gaussian filter to 'scale'. */ + + return lsd_scale(n_out,img,X,Y,scale); +} +/*----------------------------------------------------------------------------*/ +#endif +/*================================================================================== + * end of LSD code + *==================================================================================*/ + +// clang-format on + +#undef NOTDEF +#undef NOTUSED +#undef USED +#undef RELATIVE_ERROR_FACTOR +#undef TABSIZE + +// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh +// vim: shiftwidth=2 expandtab tabstop=2 cindent +// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; diff --git a/rtengine/ashift_nmsimplex.c b/rtengine/ashift_nmsimplex.c new file mode 100644 index 000000000..512bac878 --- /dev/null +++ b/rtengine/ashift_nmsimplex.c @@ -0,0 +1,425 @@ +/* + This file is part of darktable, + copyright (c) 2016 Ulrich Pegelow. + + darktable 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. + + darktable 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 darktable. If not, see . +*/ + +/* For parameter optimization we are using the Nelder-Mead simplex method + * implemented by Michael F. Hutt. + * Changes versus the original code: + * do not include "nmsimplex.h" (not needed) + * renamed configuration variables to NMS_* + * add additional argument to objfun for arbitrary parameters + * simplex() returns number of used iterations instead of min value + * maximum number of iterations as function parameter + * make interface function simplex() static + * initialize i and j to avoid compiler warnings + * comment out printing of status inormation + * reformat according to darktable's clang standards + */ + +/*================================================================================== + * begin nmsimplex code downloaded from http://www.mikehutt.com/neldermead.html + * on February 6, 2016 + *==================================================================================*/ +/* + * Program: nmsimplex.c + * Author : Michael F. Hutt + * http://www.mikehutt.com + * 11/3/97 + * + * An implementation of the Nelder-Mead simplex method. + * + * Copyright (c) 1997-2011 + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * Jan. 6, 1999 + * Modified to conform to the algorithm presented + * in Margaret H. Wright's paper on Direct Search Methods. + * + * Jul. 23, 2007 + * Fixed memory leak. + * + * Mar. 1, 2011 + * Added constraints. + */ + +//#include "nmsimplex.h" + +static int simplex(double (*objfunc)(double[], void *params), double start[], int n, double EPSILON, double scale, + int maxiter, void (*constrain)(double[], int n), void *params) +{ + + int vs; /* vertex with smallest value */ + int vh; /* vertex with next smallest value */ + int vg; /* vertex with largest value */ + + int i = 0, j = 0, m, row; + int k; /* track the number of function evaluations */ + int itr; /* track the number of iterations */ + + double **v; /* holds vertices of simplex */ + double pn, qn; /* values used to create initial simplex */ + double *f; /* value of function at each vertex */ + double fr; /* value of function at reflection point */ + double fe; /* value of function at expansion point */ + double fc; /* value of function at contraction point */ + double *vr; /* reflection - coordinates */ + double *ve; /* expansion - coordinates */ + double *vc; /* contraction - coordinates */ + double *vm; /* centroid - coordinates */ + //double min; + + double fsum, favg, s, cent; + + /* dynamically allocate arrays */ + + /* allocate the rows of the arrays */ + v = (double **)malloc((n + 1) * sizeof(double *)); + f = (double *)malloc((n + 1) * sizeof(double)); + vr = (double *)malloc(n * sizeof(double)); + ve = (double *)malloc(n * sizeof(double)); + vc = (double *)malloc(n * sizeof(double)); + vm = (double *)malloc(n * sizeof(double)); + + /* allocate the columns of the arrays */ + for(i = 0; i <= n; i++) + { + v[i] = (double *)malloc(n * sizeof(double)); + } + + /* create the initial simplex */ + /* assume one of the vertices is 0,0 */ + + pn = scale * (sqrt(n + 1) - 1 + n) / (n * sqrt(2)); + qn = scale * (sqrt(n + 1) - 1) / (n * sqrt(2)); + + for(i = 0; i < n; i++) + { + v[0][i] = start[i]; + } + + for(i = 1; i <= n; i++) + { + for(j = 0; j < n; j++) + { + if(i - 1 == j) + { + v[i][j] = pn + start[j]; + } + else + { + v[i][j] = qn + start[j]; + } + } + } + + if(constrain != NULL) + { + constrain(v[j], n); + } + /* find the initial function values */ + for(j = 0; j <= n; j++) + { + f[j] = objfunc(v[j], params); + } + + k = n + 1; +#if 0 + /* print out the initial values */ + printf("Initial Values\n"); + for(j = 0; j <= n; j++) + { + for(i = 0; i < n; i++) + { + printf("%f %f\n", v[j][i], f[j]); + } + } +#endif + + /* begin the main loop of the minimization */ + for(itr = 1; itr <= maxiter; itr++) + { + /* find the index of the largest value */ + vg = 0; + for(j = 0; j <= n; j++) + { + if(f[j] > f[vg]) + { + vg = j; + } + } + + /* find the index of the smallest value */ + vs = 0; + for(j = 0; j <= n; j++) + { + if(f[j] < f[vs]) + { + vs = j; + } + } + + /* find the index of the second largest value */ + vh = vs; + for(j = 0; j <= n; j++) + { + if(f[j] > f[vh] && f[j] < f[vg]) + { + vh = j; + } + } + + /* calculate the centroid */ + for(j = 0; j <= n - 1; j++) + { + cent = 0.0; + for(m = 0; m <= n; m++) + { + if(m != vg) + { + cent += v[m][j]; + } + } + vm[j] = cent / n; + } + + /* reflect vg to new vertex vr */ + for(j = 0; j <= n - 1; j++) + { + /*vr[j] = (1+NMS_ALPHA)*vm[j] - NMS_ALPHA*v[vg][j];*/ + vr[j] = vm[j] + NMS_ALPHA * (vm[j] - v[vg][j]); + } + if(constrain != NULL) + { + constrain(vr, n); + } + fr = objfunc(vr, params); + k++; + + if(fr < f[vh] && fr >= f[vs]) + { + for(j = 0; j <= n - 1; j++) + { + v[vg][j] = vr[j]; + } + f[vg] = fr; + } + + /* investigate a step further in this direction */ + if(fr < f[vs]) + { + for(j = 0; j <= n - 1; j++) + { + /*ve[j] = NMS_GAMMA*vr[j] + (1-NMS_GAMMA)*vm[j];*/ + ve[j] = vm[j] + NMS_GAMMA * (vr[j] - vm[j]); + } + if(constrain != NULL) + { + constrain(ve, n); + } + fe = objfunc(ve, params); + k++; + + /* by making fe < fr as opposed to fe < f[vs], + Rosenbrocks function takes 63 iterations as opposed + to 64 when using double variables. */ + + if(fe < fr) + { + for(j = 0; j <= n - 1; j++) + { + v[vg][j] = ve[j]; + } + f[vg] = fe; + } + else + { + for(j = 0; j <= n - 1; j++) + { + v[vg][j] = vr[j]; + } + f[vg] = fr; + } + } + + /* check to see if a contraction is necessary */ + if(fr >= f[vh]) + { + if(fr < f[vg] && fr >= f[vh]) + { + /* perform outside contraction */ + for(j = 0; j <= n - 1; j++) + { + /*vc[j] = NMS_BETA*v[vg][j] + (1-NMS_BETA)*vm[j];*/ + vc[j] = vm[j] + NMS_BETA * (vr[j] - vm[j]); + } + if(constrain != NULL) + { + constrain(vc, n); + } + fc = objfunc(vc, params); + k++; + } + else + { + /* perform inside contraction */ + for(j = 0; j <= n - 1; j++) + { + /*vc[j] = NMS_BETA*v[vg][j] + (1-NMS_BETA)*vm[j];*/ + vc[j] = vm[j] - NMS_BETA * (vm[j] - v[vg][j]); + } + if(constrain != NULL) + { + constrain(vc, n); + } + fc = objfunc(vc, params); + k++; + } + + + if(fc < f[vg]) + { + for(j = 0; j <= n - 1; j++) + { + v[vg][j] = vc[j]; + } + f[vg] = fc; + } + /* at this point the contraction is not successful, + we must halve the distance from vs to all the + vertices of the simplex and then continue. + 10/31/97 - modified to account for ALL vertices. + */ + else + { + for(row = 0; row <= n; row++) + { + if(row != vs) + { + for(j = 0; j <= n - 1; j++) + { + v[row][j] = v[vs][j] + (v[row][j] - v[vs][j]) / 2.0; + } + } + } + if(constrain != NULL) + { + constrain(v[vg], n); + } + f[vg] = objfunc(v[vg], params); + k++; + if(constrain != NULL) + { + constrain(v[vh], n); + } + f[vh] = objfunc(v[vh], params); + k++; + } + } +#if 0 + /* print out the value at each iteration */ + printf("Iteration %d\n", itr); + for(j = 0; j <= n; j++) + { + for(i = 0; i < n; i++) + { + printf("%f %f\n", v[j][i], f[j]); + } + } +#endif + /* test for convergence */ + fsum = 0.0; + for(j = 0; j <= n; j++) + { + fsum += f[j]; + } + favg = fsum / (n + 1); + s = 0.0; + for(j = 0; j <= n; j++) + { + s += pow((f[j] - favg), 2.0) / (n); + } + s = sqrt(s); + if(s < EPSILON) break; + } + /* end main loop of the minimization */ + + /* find the index of the smallest value */ + vs = 0; + for(j = 0; j <= n; j++) + { + if(f[j] < f[vs]) + { + vs = j; + } + } +#if 0 + printf("The minimum was found at\n"); + for(j = 0; j < n; j++) + { + printf("%e\n", v[vs][j]); + start[j] = v[vs][j]; + } + double min = objfunc(v[vs], params); + k++; + printf("The minimum value is %f\n", min); + printf("%d Function Evaluations\n", k); + printf("%d Iterations through program\n", itr); +#else + for(j = 0; j < n; j++) + { + start[j] = v[vs][j]; + } +#endif + free(f); + free(vr); + free(ve); + free(vc); + free(vm); + for(i = 0; i <= n; i++) + { + free(v[i]); + } + free(v); + return itr; +} + +/*================================================================================== + * end of nmsimplex code + *==================================================================================*/ + +// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh +// vim: shiftwidth=2 expandtab tabstop=2 cindent +// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; diff --git a/rtengine/perspectivecorrection.cc b/rtengine/perspectivecorrection.cc new file mode 100644 index 000000000..724262b15 --- /dev/null +++ b/rtengine/perspectivecorrection.cc @@ -0,0 +1,433 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2019 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 . + */ + +// taken from darktable (src/iop/ashift.c) +/* + This file is part of darktable, + copyright (c) 2016 Ulrich Pegelow. + + darktable 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. + + darktable 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 darktable. If not, see . +*/ +// Inspiration to this module comes from the program ShiftN (http://www.shiftn.de) by +// Marcus Hebel. + +// Thanks to Marcus for his support when implementing part of the ShiftN functionality +// to darktable. + + +#include "perspectivecorrection.h" +#include "improcfun.h" +#include "rt_math.h" +#include +#include +#include +#include +#include +#include +#include + +#include "../rtgui/threadutils.h" +#include "settings.h" + +namespace rtengine { extern const Settings *settings; } + +#define _(msg) (msg) +#define dt_control_log(msg) \ + if (settings->verbose) { \ + printf("%s\n", msg); \ + fflush(stdout); \ + } + + +namespace rtengine { + +namespace { + +inline int mat3inv(float *const dst, const float *const src) +{ + std::array, 3> tmpsrc; + std::array, 3> tmpdst; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + tmpsrc[i][j] = src[3 * i + j]; + } + } + if (invertMatrix(tmpsrc, tmpdst)) { + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + dst[3 * i + j] = tmpdst[i][j]; + } + } + return 0; + } else { + return 1; + } +} + + +// the darktable ashift iop (adapted to RT), which does most of the work +#include "ashift_dt.c" + + +procparams::PerspectiveParams import_meta(const procparams::PerspectiveParams &pp, const FramesMetaData *metadata) +{ + procparams::PerspectiveParams ret(pp); + if (metadata && ret.flength == 0) { + double f = metadata->getFocalLen(); + double f35 = metadata->getFocalLen35mm(); + if (f > 0 && f35 > 0) { + ret.flength = f; + ret.cropfactor = f35 / f; + } else if (f > 0) { + ret.flength = f; + } + } + return ret; +} + +} // namespace + + +PerspectiveCorrection::PerspectiveCorrection(): + ok_(false), + scale_(1.0), + offx_(0.0), + offy_(0.0) +{ +} + + +void PerspectiveCorrection::init(int width, int height, const procparams::PerspectiveParams ¶ms, bool fill, const FramesMetaData *metadata) +{ + if (params.enabled) { + auto pp = import_meta(params, metadata); + homography((float *)ihomograph_, params.angle, params.vertical / 100.0, -params.horizontal / 100.0, params.shear / 100.0, params.flength * params.cropfactor, 100.f, params.aspect, width, height, ASHIFT_HOMOGRAPH_INVERTED); + + ok_ = true; + calc_scale(width, height, pp, fill); + } else { + ok_ = false; + } +} + + +inline void PerspectiveCorrection::correct(double &x, double &y, double scale, double offx, double offy) +{ + if (ok_) { + float pin[3], pout[3]; + pout[0] = x; + pout[1] = y; + pout[0] *= scale; + pout[1] *= scale; + pout[0] += offx; + pout[1] += offy; + pout[2] = 1.f; + mat3mulv(pin, (float *)ihomograph_, pout); + pin[0] /= pin[2]; + pin[1] /= pin[2]; + x = pin[0]; + y = pin[1]; + } +} + + +void PerspectiveCorrection::operator()(double &x, double &y) +{ + correct(x, y, scale_, offx_, offy_); +} + + +namespace { + +std::vector get_corners(int w, int h) +{ + int x1 = 0, y1 = 0; + int x2 = w, y2 = h; + + std::vector corners = { + Coord2D(x1, y1), + Coord2D(x1, y2), + Coord2D(x2, y2), + Coord2D(x2, y1) + }; + return corners; +} + +void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g, + const procparams::PerspectiveParams *params) +{ + dt_iop_ashift_params_t dp = { + 0.0f, + 0.0f, + 0.0f, + 0.0f, + DEFAULT_F_LENGTH, + 1.f, + 0.0f, + 1.0f, + ASHIFT_MODE_SPECIFIC, + 0, + ASHIFT_CROP_OFF, + 0.0f, + 1.0f, + 0.0f, + 1.0f + }; + *p = dp; + + g->buf = NULL; + g->buf_width = 0; + g->buf_height = 0; + g->buf_x_off = 0; + g->buf_y_off = 0; + g->buf_scale = 1.0f; + g->buf_hash = 0; + g->isflipped = 0; + g->lastfit = ASHIFT_FIT_NONE; + g->fitting = 0; + g->lines = NULL; + g->lines_count =0; + g->horizontal_count = 0; + g->vertical_count = 0; + g->grid_hash = 0; + g->lines_hash = 0; + g->rotation_range = ROTATION_RANGE_SOFT; + g->lensshift_v_range = LENSSHIFT_RANGE_SOFT; + g->lensshift_h_range = LENSSHIFT_RANGE_SOFT; + g->shear_range = SHEAR_RANGE_SOFT; + g->lines_suppressed = 0; + g->lines_version = 0; + g->show_guides = 0; + g->isselecting = 0; + g->isdeselecting = 0; + g->isbounding = ASHIFT_BOUNDING_OFF; + g->near_delta = 0; + g->selecting_lines_version = 0; + g->points = NULL; + g->points_idx = NULL; + g->points_lines_count = 0; + g->points_version = 0; + g->jobcode = ASHIFT_JOBCODE_NONE; + g->jobparams = 0; + g->adjust_crop = FALSE; + g->lastx = g->lasty = -1.0f; + g->crop_cx = g->crop_cy = 1.0f; + + if (params) { + p->rotation = params->angle; + p->lensshift_v = params->vertical / 100.0; + p->lensshift_h = -params->horizontal / 100.0; + p->shear = params->shear / 100.0; + p->f_length = params->flength; + p->crop_factor = params->cropfactor; + p->aspect = params->aspect; + } +} + + +void get_view_size(int w, int h, const procparams::PerspectiveParams ¶ms, double &cw, double &ch) +{ + double min_x = RT_INFINITY, max_x = -RT_INFINITY; + double min_y = RT_INFINITY, max_y = -RT_INFINITY; + + auto corners = get_corners(w, h); + + float homo[3][3]; + homography((float *)homo, params.angle, params.vertical / 100.0, -params.horizontal / 100.0, params.shear / 100.0, params.flength * params.cropfactor, 100.f, params.aspect, w, h, ASHIFT_HOMOGRAPH_FORWARD); + + for (auto &c : corners) { + float pin[3] = { float(c.x), float(c.y), 1.f }; + float pout[3]; + mat3mulv(pout, (float *)homo, pin); + double x = pout[0] / pout[2]; + double y = pout[1] / pout[2]; + min_x = min(min_x, x); + max_x = max(max_x, x); + min_y = min(min_y, y); + max_y = max(max_y, y); + } + + cw = max_x - min_x; + ch = max_y - min_y; +} + +} // namespace + + +void PerspectiveCorrection::calc_scale(int w, int h, const procparams::PerspectiveParams ¶ms, bool fill) +{ + double cw, ch; + get_view_size(w, h, params, cw, ch); + + if (!fill) { + scale_ = max(cw / double(w), ch / double(h)); + offx_ = (cw - w * scale_) * 0.5; + offy_ = (ch - h * scale_) * 0.5; + } else { + dt_iop_ashift_params_t p; + dt_iop_ashift_gui_data_t g; + init_dt_structures(&p, &g, ¶ms); + dt_iop_module_t module = { &g, false }; + g.buf_width = w; + g.buf_height = h; + p.cropmode = ASHIFT_CROP_ASPECT; + do_crop(&module, &p); + offx_ = p.cl * cw; + offy_ = p.ct * ch; + scale_ = (p.cr - p.cl) * cw/double(w); + } +} + + +procparams::PerspectiveParams PerspectiveCorrection::autocompute(ImageSource *src, Direction dir, const procparams::ProcParams *pparams, const FramesMetaData *metadata) +{ + auto pcp = import_meta(pparams->perspective, metadata); + procparams::PerspectiveParams dflt; + pcp.horizontal = dflt.horizontal; + pcp.vertical = dflt.vertical; + pcp.angle = dflt.angle; + pcp.shear = dflt.shear; + + dt_iop_ashift_params_t p; + dt_iop_ashift_gui_data_t g; + init_dt_structures(&p, &g, &pcp); + dt_iop_module_t module; + module.gui_data = &g; + module.is_raw = src->isRAW(); + + int tr = getCoarseBitMask(pparams->coarse); + int fw, fh; + src->getFullSize(fw, fh, tr); + int skip = max(float(max(fw, fh)) / 900.f + 0.5f, 1.f); + PreviewProps pp(0, 0, fw, fh, skip); + int w, h; + src->getSize(pp, w, h); + std::unique_ptr img(new Imagefloat(w, h)); + + ProcParams neutral; + neutral.raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST); + neutral.raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST); + neutral.icm.outputProfile = ColorManagementParams::NoICMString; + src->getImage(src->getWB(), tr, img.get(), pp, neutral.exposure, neutral.raw); + src->convertColorSpace(img.get(), pparams->icm, src->getWB()); + + neutral.rotate = pparams->rotate; + neutral.distortion = pparams->distortion; + neutral.lensProf = pparams->lensProf; + ImProcFunctions ipf(&neutral, true); + if (ipf.needsTransform()) { + Imagefloat *tmp = new Imagefloat(w, h); + ipf.transform(img.get(), tmp, 0, 0, 0, 0, w, h, w, h, + src->getMetaData(), src->getRotateDegree(), false); + img.reset(tmp); + } + + // allocate the gui buffer + g.buf = static_cast(malloc(sizeof(float) * w * h * 4)); + g.buf_width = w; + g.buf_height = h; + + img->normalizeFloatTo1(); + +#ifdef _OPENMP +# pragma omp parallel for +#endif + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + int i = (y * w + x) * 4; + g.buf[i] = img->r(y, x); + g.buf[i+1] = img->g(y, x); + g.buf[i+2] = img->b(y, x); + g.buf[i+3] = 1.f; + } + } + + dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE; + switch (dir) { + case HORIZONTAL: + fitaxis = ASHIFT_FIT_HORIZONTALLY; + break; + case VERTICAL: + fitaxis = ASHIFT_FIT_VERTICALLY; + break; + default: + fitaxis = ASHIFT_FIT_BOTH_SHEAR; + break; + } + + // reset the pseudo-random seed for repeatability -- ashift_dt uses rand() + // internally! + srand(1); + + auto res = do_get_structure(&module, &p, ASHIFT_ENHANCE_EDGES) && do_fit(&module, &p, fitaxis); + procparams::PerspectiveParams retval = pparams->perspective; + + // cleanup the gui + if (g.lines) free(g.lines); + if (g.points) free(g.points); + if (g.points_idx) free(g.points_idx); + free(g.buf); + + if (res) { + retval.horizontal = -p.lensshift_h * 100; + retval.vertical = p.lensshift_v * 100; + retval.angle = p.rotation; + retval.shear = p.shear * 100; + } + return retval; +} + + +void PerspectiveCorrection::autocrop(int width, int height, bool fixratio, const procparams::PerspectiveParams ¶ms, const FramesMetaData *metadata, int &x, int &y, int &w, int &h) +{ + auto pp = import_meta(params, metadata); + double cw, ch; + get_view_size(width, height, params, cw, ch); + double s = min(double(width)/cw, double(height)/ch); + dt_iop_ashift_params_t p; + dt_iop_ashift_gui_data_t g; + init_dt_structures(&p, &g, &pp); + dt_iop_module_t module = { &g, false }; + g.buf_width = width; + g.buf_height = height; + p.cropmode = fixratio ? ASHIFT_CROP_ASPECT : ASHIFT_CROP_LARGEST; + do_crop(&module, &p); + cw *= s; + ch *= s; + double ox = p.cl * cw; + double oy = p.ct * ch; + x = ox - (cw - width)/2.0 + 0.5; + y = oy - (ch - height)/2.0 + 0.5; + w = (p.cr - p.cl) * cw; + h = (p.cb - p.ct) * ch; +} + +} // namespace rtengine diff --git a/rtengine/perspectivecorrection.h b/rtengine/perspectivecorrection.h new file mode 100644 index 000000000..1b52941b2 --- /dev/null +++ b/rtengine/perspectivecorrection.h @@ -0,0 +1,55 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2019 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 "coord2d.h" +#include "procparams.h" +#include "imagesource.h" + +namespace rtengine { + +class PerspectiveCorrection { +public: + PerspectiveCorrection(); + void init(int width, int height, const procparams::PerspectiveParams ¶ms, bool fill, const FramesMetaData *meta); + void operator()(double &x, double &y); + + enum Direction { + HORIZONTAL, + VERTICAL, + BOTH + }; + static procparams::PerspectiveParams autocompute(ImageSource *src, Direction dir, const procparams::ProcParams *pparams, const FramesMetaData *metadata); + + static void autocrop(int width, int height, bool fixratio, const procparams::PerspectiveParams ¶ms, const FramesMetaData *metadata, int &x, int &y, int &w, int &h); + +private: + void correct(double &x, double &y, double scale, double offx, double offy); + void calc_scale(int w, int h, const procparams::PerspectiveParams ¶ms, bool fill); + + bool ok_; + double scale_; + double offx_; + double offy_; + float ihomograph_[3][3]; +}; + +} // namespace rtengine From b2a5c6a0f3cd27e70d4e77a26d3e28d68dc86b2d Mon Sep 17 00:00:00 2001 From: Lawrence Date: Sat, 18 Jan 2020 10:59:57 -0800 Subject: [PATCH 08/20] Fix a homogeneous function parameter type The rotation matrix function signature used a generic type for the rotation angle. Change it to double. --- rtengine/homogeneouscoordinates.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtengine/homogeneouscoordinates.h b/rtengine/homogeneouscoordinates.h index 7c5924d1e..12a2227ae 100644 --- a/rtengine/homogeneouscoordinates.h +++ b/rtengine/homogeneouscoordinates.h @@ -51,7 +51,7 @@ Matrix projectionMatrix(T location, Axis normal); * @param axis Axis of rotation. */ template -Matrix rotationMatrix(T radians, Axis axis); +Matrix rotationMatrix(double radians, Axis axis); /** * Creates a 3 dimensional transformation matrix for scaling. From 6ab92eb1f55f2b4eec010401d89aa28c0b1e03de Mon Sep 17 00:00:00 2001 From: Lawrence Date: Sat, 18 Jan 2020 12:04:03 -0800 Subject: [PATCH 09/20] Adapt auto perspective for camera-based correction Comment out parameters that darktable/ART use in case we decide to use them later. Add yaw and pitch parameters used by the camera-based perspective correction. Modify homography matrix calculation to use the camera-based perspective model. --- rtengine/CMakeLists.txt | 1 + rtengine/ashift_dt.c | 146 +++++++++++++++++++++++++- rtengine/perspectivecorrection.cc | 163 +++++++++--------------------- rtengine/perspectivecorrection.h | 26 ++--- 4 files changed, 194 insertions(+), 142 deletions(-) diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index 6bce68d1a..b19cad59d 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -108,6 +108,7 @@ set(RTENGINESOURCEFILES myfile.cc panasonic_decoders.cc pdaflinesfilter.cc + perspectivecorrection.cc PF_correct_RT.cc pipettebuffer.cc pixelshift.cc diff --git a/rtengine/ashift_dt.c b/rtengine/ashift_dt.c index e25288589..ba2f62271 100644 --- a/rtengine/ashift_dt.c +++ b/rtengine/ashift_dt.c @@ -51,6 +51,7 @@ using namespace std; #define LENSSHIFT_RANGE_SOFT 1 // allowed min/max range for lensshift parameters with manual adjustment #define SHEAR_RANGE 0.2 // allowed min/max range for shear parameter #define SHEAR_RANGE_SOFT 0.5 // allowed min/max range for shear parameter with manual adjustment +#define CAMERA_ANGLE_RANGE_SOFT 80 #define MIN_LINE_LENGTH 5 // the minimum length of a line in pixels to be regarded as relevant #define MAX_TANGENTIAL_DEVIATION 30 // by how many degrees a line may deviate from the +/-180 and +/-90 to be regarded as relevant #define LSD_SCALE 0.99 // LSD: scaling factor for line detection @@ -97,6 +98,8 @@ using namespace std; // implemented by Michael F. Hutt. #include "ashift_nmsimplex.c" +#include "homogeneouscoordinates.h" + //----------------------------------------------------------------------------- // RT: BEGIN COMMENT @@ -292,6 +295,8 @@ typedef struct dt_iop_ashift_params_t float cr; float ct; float cb; + float camera_pitch; + float camera_yaw; } dt_iop_ashift_params_t; typedef struct dt_iop_ashift_line_t @@ -335,10 +340,14 @@ typedef struct dt_iop_ashift_fit_params_t float lensshift_v; float lensshift_h; float shear; + float camera_pitch; + float camera_yaw; float rotation_range; float lensshift_v_range; float lensshift_h_range; float shear_range; + float camera_pitch_range; + float camera_yaw_range; } dt_iop_ashift_fit_params_t; typedef struct dt_iop_ashift_cropfit_params_t @@ -384,6 +393,8 @@ typedef struct dt_iop_ashift_gui_data_t float lensshift_v_range; float lensshift_h_range; float shear_range; + float camera_pitch_range; + float camera_yaw_range; dt_iop_ashift_line_t *lines; int lines_in_width; int lines_in_height; @@ -639,9 +650,16 @@ static void print_roi(const dt_iop_roi_t *roi, const char *label) #define MAT3SWAP(a, b) { float (*tmp)[3] = (a); (a) = (b); (b) = tmp; } +/* static void homography(float *homograph, const float angle, const float shift_v, const float shift_h, const float shear, const float f_length_kb, const float orthocorr, const float aspect, const int width, const int height, dt_iop_ashift_homodir_t dir) +*/ +static void homography(float *homograph, const float angle, const float shift_v, + const float shift_h, const float shear, const float camera_pitch, const + float camera_yaw, const float f_length_kb, const float orthocorr, const + float aspect, const int width, const int height, dt_iop_ashift_homodir_t + dir) { // calculate homograph that combines all translations, rotations // and warping into one single matrix operation. @@ -652,7 +670,11 @@ static void homography(float *homograph, const float angle, const float shift_v, const float u = width; const float v = height; + const float rot = M_PI * angle / 180.0f; + const float pitch = M_PI * camera_pitch / 180.0f; + const float yaw = M_PI * camera_yaw / 180.0f; + /* const float phi = M_PI * angle / 180.0f; const float cosi = cos(phi); const float sini = sin(phi); @@ -675,16 +697,19 @@ static void homography(float *homograph, const float angle, const float shift_v, const float alpha_h = CLAMP(atan(rad_h), -1.5f, 1.5f); const float rt_h = sin(0.5f * alpha_h); const float r_h = fmax(0.1f, 2.0f * (vertifac - 1.0f) * rt_h * rt_h + 1.0f); + */ + const float f = f_length_kb * (sqrt(u*u + v*v) / sqrt(36.0*36.0 + 24.0*24.0)); // three intermediate buffers for matrix calculation ... - float m1[3][3], m2[3][3], m3[3][3]; + float m1[3][3]/*, m2[3][3]*/, m3[3][3]; // ... and some pointers to handle them more intuitively float (*mwork)[3] = m1; - float (*minput)[3] = m2; + //float (*minput)[3] = m2; float (*moutput)[3] = m3; + /* // Step 1: flip x and y coordinates (see above) memset(minput, 0, 9 * sizeof(float)); minput[0][1] = 1.0f; @@ -800,8 +825,43 @@ static void homography(float *homograph, const float angle, const float shift_v, MAT3SWAP(minput, moutput); // multiply mwork * minput -> moutput mat3mul((float *)moutput, (float *)mwork, (float *)minput); + */ + rtengine::homogeneous::Vector center; + center[0] = 0.0f; + center[1] = 0.0f; + center[2] = f; + center[3] = 1.0f; + using rtengine::operator*; + + // Location of image center after rotations. + const rtengine::homogeneous::Vector camera_center_yaw_pitch = + rtengine::homogeneous::rotationMatrix(pitch, rtengine::homogeneous::Axis::X) * + rtengine::homogeneous::rotationMatrix(yaw, rtengine::homogeneous::Axis::Y) * + center; + + const rtengine::homogeneous::Matrix matrix = + // Perspective correction. + rtengine::homogeneous::projectionMatrix(camera_center_yaw_pitch[2], rtengine::homogeneous::Axis::Z) * + rtengine::homogeneous::rotationMatrix(yaw, rtengine::homogeneous::Axis::Y) * + rtengine::homogeneous::rotationMatrix(pitch, rtengine::homogeneous::Axis::X) * + // Rotation. + rtengine::homogeneous::rotationMatrix(rot, rtengine::homogeneous::Axis::Z) * + // Lens/sensor shift and move to z == camera_focal_length. + rtengine::homogeneous::translationMatrix((0.01f * shift_h - 0.5f) * u, (-0.01f * shift_v - 0.5f) * v, f); + + m3[0][0] = matrix[0][0]; + m3[0][1] = matrix[0][1]; + m3[0][2] = matrix[0][3]; + m3[1][0] = matrix[1][0]; + m3[1][1] = matrix[1][1]; + m3[1][2] = matrix[1][3]; + m3[2][0] = matrix[3][0]; + m3[2][1] = matrix[3][1]; + m3[2][2] = matrix[3][3]; + + /* // Step 10: find x/y offsets and apply according correction so that // no negative coordinates occur in output vector float umin = FLT_MAX, vmin = FLT_MAX; @@ -830,6 +890,7 @@ static void homography(float *homograph, const float angle, const float shift_v, MAT3SWAP(minput, moutput); // multiply mwork * minput -> moutput mat3mul((float *)moutput, (float *)mwork, (float *)minput); + */ // on request we either keep the final matrix for forward conversions @@ -1920,10 +1981,16 @@ static double model_fitness(double *params, void *data) float lensshift_v = fit->lensshift_v; float lensshift_h = fit->lensshift_h; float shear = fit->shear; + float camera_pitch = fit->camera_pitch; + float camera_yaw = fit->camera_yaw; float rotation_range = fit->rotation_range; + /* float lensshift_v_range = fit->lensshift_v_range; float lensshift_h_range = fit->lensshift_h_range; float shear_range = fit->shear_range; + */ + float camera_pitch_range = fit->camera_pitch_range; + float camera_yaw_range = fit->camera_yaw_range; int pcount = 0; @@ -1934,6 +2001,7 @@ static double model_fitness(double *params, void *data) pcount++; } + /* if(isnan(lensshift_v)) { lensshift_v = ilogit(params[pcount], -lensshift_v_range, lensshift_v_range); @@ -1951,6 +2019,19 @@ static double model_fitness(double *params, void *data) shear = ilogit(params[pcount], -shear_range, shear_range); pcount++; } + */ + + if(isnan(camera_pitch)) + { + camera_pitch = ilogit(params[pcount], -camera_pitch_range, camera_pitch_range); + pcount++; + } + + if(isnan(camera_yaw)) + { + camera_yaw = ilogit(params[pcount], -camera_yaw_range, camera_yaw_range); + pcount++; + } assert(pcount == fit->params_count); @@ -1960,7 +2041,7 @@ static double model_fitness(double *params, void *data) // generate homograph out of the parameters float homograph[3][3]; - homography((float *)homograph, rotation, lensshift_v, lensshift_h, shear, f_length_kb, + homography((float *)homograph, rotation, lensshift_v, lensshift_h, shear, camera_pitch, camera_yaw, f_length_kb, orthocorr, aspect, width, height, ASHIFT_HOMOGRAPH_FORWARD); // accounting variables @@ -2018,8 +2099,12 @@ static double model_fitness(double *params, void *data) //double sum = sqrt(v + h) * 1.0e6; #ifdef ASHIFT_DEBUG + /* printf("fitness with rotation %f, lensshift_v %f, lensshift_h %f, shear %f -> lines %d, quality %10f\n", rotation, lensshift_v, lensshift_h, shear, count, sum); + */ + printf("fitness with rotation %f, camera_pitch %f, camera_yaw %f -> lines %d, quality %10f\n", + rotation, camera_pitch, camera_yaw, count, sum); #endif return sum; @@ -2050,10 +2135,14 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p fit.lensshift_v = p->lensshift_v; fit.lensshift_h = p->lensshift_h; fit.shear = p->shear; + fit.camera_pitch = p->camera_pitch; + fit.camera_yaw = p->camera_yaw; fit.rotation_range = g->rotation_range; fit.lensshift_v_range = g->lensshift_v_range; fit.lensshift_h_range = g->lensshift_h_range; fit.shear_range = g->shear_range; + fit.camera_pitch_range = g->camera_pitch_range; + fit.camera_yaw_range = g->camera_yaw_range; fit.linetype = ASHIFT_LINE_RELEVANT | ASHIFT_LINE_SELECTED; fit.linemask = ASHIFT_LINE_MASK; fit.params_count = 0; @@ -2086,6 +2175,7 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p fit.rotation = NAN; } + /* if(mdir & ASHIFT_FIT_LENS_VERT) { // we fit vertical lens shift @@ -2112,6 +2202,25 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p pcount++; fit.shear = NAN; } + */ + + if(mdir & ASHIFT_FIT_LENS_VERT) + { + // we fit pitch + fit.params_count++; + params[pcount] = logit(fit.camera_pitch, -fit.camera_pitch_range, fit.camera_pitch_range); + pcount++; + fit.camera_pitch = NAN; + } + + if(mdir & ASHIFT_FIT_LENS_HOR) + { + // we fit yaw + fit.params_count++; + params[pcount] = logit(fit.camera_yaw, -fit.camera_yaw_range, fit.camera_yaw_range); + pcount++; + fit.camera_yaw = NAN; + } if(mdir & ASHIFT_FIT_LINES_VERT) { @@ -2162,14 +2271,23 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p // fit was successful: now consolidate the results (order matters!!!) pcount = 0; fit.rotation = isnan(fit.rotation) ? ilogit(params[pcount++], -fit.rotation_range, fit.rotation_range) : fit.rotation; + /* fit.lensshift_v = isnan(fit.lensshift_v) ? ilogit(params[pcount++], -fit.lensshift_v_range, fit.lensshift_v_range) : fit.lensshift_v; fit.lensshift_h = isnan(fit.lensshift_h) ? ilogit(params[pcount++], -fit.lensshift_h_range, fit.lensshift_h_range) : fit.lensshift_h; fit.shear = isnan(fit.shear) ? ilogit(params[pcount++], -fit.shear_range, fit.shear_range) : fit.shear; + */ + fit.camera_pitch = isnan(fit.camera_pitch) ? ilogit(params[pcount++], -fit.camera_pitch_range, fit.camera_pitch_range) : fit.camera_pitch; + fit.camera_yaw = isnan(fit.camera_yaw) ? ilogit(params[pcount++], -fit.camera_yaw_range, fit.camera_yaw_range) : fit.camera_yaw; #ifdef ASHIFT_DEBUG + /* printf("params after optimization (%d iterations): rotation %f, lensshift_v %f, lensshift_h %f, shear %f\n", iter, fit.rotation, fit.lensshift_v, fit.lensshift_h, fit.shear); + */ + printf("params after optimization (%d iterations): rotation %f, camera_pitch %f, camera_yaw %f\n", + iter, fit.rotation, fit.camera_pitch, fit.camera_yaw); #endif + /* // sanity check: in case of extreme values the image gets distorted so strongly that it spans an insanely huge area. we check that // case and assume values that increase the image area by more than a factor of 4 as being insane. float homograph[3][3]; @@ -2202,12 +2320,17 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p #endif return NMS_INSANE; } + */ // now write the results into structure p p->rotation = fit.rotation; + /* p->lensshift_v = fit.lensshift_v; p->lensshift_h = fit.lensshift_h; p->shear = fit.shear; + */ + p->camera_pitch = fit.camera_pitch; + p->camera_yaw = fit.camera_yaw; return NMS_SUCCESS; } @@ -2291,6 +2414,10 @@ static void model_probe(dt_iop_module_t *module, dt_iop_ashift_params_t *p, dt_i #endif // if 0 //----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT (no crop support yet) +//----------------------------------------------------------------------------- +#if 0 // function to keep crop fitting parameters within constraints static void crop_constraint(double *params, int pcount) { @@ -2383,7 +2510,12 @@ static double crop_fitness(double *params, void *data) // and return -A to allow Nelder-Mead simplex to search for the minimum return -A; } +#endif // if 0 +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// RT: BEGIN COMMENT +#if 0 // strategy: for a given center of the crop area and a specific aspect angle // we calculate the largest crop area that still lies within the output image; // now we allow a Nelder-Mead simplex to search for the center coordinates @@ -2561,9 +2693,11 @@ failed: dt_control_log(_("automatic cropping failed")); return; } +#endif // if 0 +//----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -// RT: BEGIN COMMENT +// RT: BEGIN COMMENT (no crop support yet) //----------------------------------------------------------------------------- #if 0 // manually adjust crop area by shifting its center @@ -2804,8 +2938,10 @@ static int do_fit(dt_iop_module_t *module, dt_iop_ashift_params_t *p, dt_iop_ash g->fitting = 0; + /* // finally apply cropping do_crop(module, p); + */ return TRUE; @@ -3042,7 +3178,7 @@ int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_m } float ihomograph[3][3]; - homography((float *)ihomograph, d->rotation, d->lensshift_v, d->lensshift_h, d->shear, d->f_length_kb, + homography((float *)ihomograph, d->rotation, d->lensshift_v, d->lensshift_h, d->shear, d->f_length_kb, d->camera_pitch, d->camera_yaw, d->orthocorr, d->aspect, piece->buf_in.width, piece->buf_in.height, ASHIFT_HOMOGRAPH_INVERTED); // clipping offset diff --git a/rtengine/perspectivecorrection.cc b/rtengine/perspectivecorrection.cc index 724262b15..fe89b7f5f 100644 --- a/rtengine/perspectivecorrection.cc +++ b/rtengine/perspectivecorrection.cc @@ -55,6 +55,8 @@ #include #include "../rtgui/threadutils.h" +#include "colortemp.h" +#include "imagefloat.h" #include "settings.h" namespace rtengine { extern const Settings *settings; } @@ -97,76 +99,12 @@ inline int mat3inv(float *const dst, const float *const src) #include "ashift_dt.c" -procparams::PerspectiveParams import_meta(const procparams::PerspectiveParams &pp, const FramesMetaData *metadata) -{ - procparams::PerspectiveParams ret(pp); - if (metadata && ret.flength == 0) { - double f = metadata->getFocalLen(); - double f35 = metadata->getFocalLen35mm(); - if (f > 0 && f35 > 0) { - ret.flength = f; - ret.cropfactor = f35 / f; - } else if (f > 0) { - ret.flength = f; - } - } - return ret; -} - } // namespace -PerspectiveCorrection::PerspectiveCorrection(): - ok_(false), - scale_(1.0), - offx_(0.0), - offy_(0.0) -{ -} - - -void PerspectiveCorrection::init(int width, int height, const procparams::PerspectiveParams ¶ms, bool fill, const FramesMetaData *metadata) -{ - if (params.enabled) { - auto pp = import_meta(params, metadata); - homography((float *)ihomograph_, params.angle, params.vertical / 100.0, -params.horizontal / 100.0, params.shear / 100.0, params.flength * params.cropfactor, 100.f, params.aspect, width, height, ASHIFT_HOMOGRAPH_INVERTED); - - ok_ = true; - calc_scale(width, height, pp, fill); - } else { - ok_ = false; - } -} - - -inline void PerspectiveCorrection::correct(double &x, double &y, double scale, double offx, double offy) -{ - if (ok_) { - float pin[3], pout[3]; - pout[0] = x; - pout[1] = y; - pout[0] *= scale; - pout[1] *= scale; - pout[0] += offx; - pout[1] += offy; - pout[2] = 1.f; - mat3mulv(pin, (float *)ihomograph_, pout); - pin[0] /= pin[2]; - pin[1] /= pin[2]; - x = pin[0]; - y = pin[1]; - } -} - - -void PerspectiveCorrection::operator()(double &x, double &y) -{ - correct(x, y, scale_, offx_, offy_); -} - - namespace { +/* std::vector get_corners(int w, int h) { int x1 = 0, y1 = 0; @@ -180,9 +118,10 @@ std::vector get_corners(int w, int h) }; return corners; } +*/ void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g, - const procparams::PerspectiveParams *params) + const procparams::ProcParams *params) { dt_iop_ashift_params_t dp = { 0.0f, @@ -199,7 +138,9 @@ void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g, 0.0f, 1.0f, 0.0f, - 1.0f + 1.0f, + 0.0f, + 0.0f }; *p = dp; @@ -223,6 +164,8 @@ void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g, g->lensshift_v_range = LENSSHIFT_RANGE_SOFT; g->lensshift_h_range = LENSSHIFT_RANGE_SOFT; g->shear_range = SHEAR_RANGE_SOFT; + g->camera_pitch_range = CAMERA_ANGLE_RANGE_SOFT; + g->camera_yaw_range = CAMERA_ANGLE_RANGE_SOFT; g->lines_suppressed = 0; g->lines_version = 0; g->show_guides = 0; @@ -242,17 +185,18 @@ void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g, g->crop_cx = g->crop_cy = 1.0f; if (params) { - p->rotation = params->angle; - p->lensshift_v = params->vertical / 100.0; - p->lensshift_h = -params->horizontal / 100.0; - p->shear = params->shear / 100.0; - p->f_length = params->flength; - p->crop_factor = params->cropfactor; - p->aspect = params->aspect; + p->rotation = params->rotate.degree; + p->lensshift_v = params->perspective.camera_shift_vert; + p->lensshift_h = params->perspective.camera_shift_horiz; + p->f_length = params->perspective.camera_focal_length; + p->crop_factor = params->perspective.camera_crop_factor; + p->camera_pitch = params->perspective.camera_pitch; + p->camera_yaw = params->perspective.camera_yaw; } } +/* void get_view_size(int w, int h, const procparams::PerspectiveParams ¶ms, double &cw, double &ch) { double min_x = RT_INFINITY, max_x = -RT_INFINITY; @@ -278,47 +222,27 @@ void get_view_size(int w, int h, const procparams::PerspectiveParams ¶ms, do cw = max_x - min_x; ch = max_y - min_y; } +*/ } // namespace -void PerspectiveCorrection::calc_scale(int w, int h, const procparams::PerspectiveParams ¶ms, bool fill) +PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *src, bool corr_pitch, bool corr_yaw, const procparams::ProcParams *pparams, const FramesMetaData *metadata) { - double cw, ch; - get_view_size(w, h, params, cw, ch); - - if (!fill) { - scale_ = max(cw / double(w), ch / double(h)); - offx_ = (cw - w * scale_) * 0.5; - offy_ = (ch - h * scale_) * 0.5; - } else { - dt_iop_ashift_params_t p; - dt_iop_ashift_gui_data_t g; - init_dt_structures(&p, &g, ¶ms); - dt_iop_module_t module = { &g, false }; - g.buf_width = w; - g.buf_height = h; - p.cropmode = ASHIFT_CROP_ASPECT; - do_crop(&module, &p); - offx_ = p.cl * cw; - offy_ = p.ct * ch; - scale_ = (p.cr - p.cl) * cw/double(w); - } -} - - -procparams::PerspectiveParams PerspectiveCorrection::autocompute(ImageSource *src, Direction dir, const procparams::ProcParams *pparams, const FramesMetaData *metadata) -{ - auto pcp = import_meta(pparams->perspective, metadata); + auto pcp = procparams::PerspectiveParams(pparams->perspective); procparams::PerspectiveParams dflt; + /* pcp.horizontal = dflt.horizontal; pcp.vertical = dflt.vertical; pcp.angle = dflt.angle; pcp.shear = dflt.shear; + */ + pcp.camera_pitch = dflt.camera_pitch; + pcp.camera_yaw = dflt.camera_yaw; dt_iop_ashift_params_t p; dt_iop_ashift_gui_data_t g; - init_dt_structures(&p, &g, &pcp); + init_dt_structures(&p, &g, pparams); dt_iop_module_t module; module.gui_data = &g; module.is_raw = src->isRAW(); @@ -336,9 +260,11 @@ procparams::PerspectiveParams PerspectiveCorrection::autocompute(ImageSource *sr neutral.raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST); neutral.raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST); neutral.icm.outputProfile = ColorManagementParams::NoICMString; - src->getImage(src->getWB(), tr, img.get(), pp, neutral.exposure, neutral.raw); + src->getImage(src->getWB(), tr, img.get(), pp, neutral.toneCurve, neutral.raw); src->convertColorSpace(img.get(), pparams->icm, src->getWB()); + neutral.commonTrans.autofill = false; // Ensures crop factor is correct. + // TODO: Ensure image borders of rotated image do not get detected as lines. neutral.rotate = pparams->rotate; neutral.distortion = pparams->distortion; neutral.lensProf = pparams->lensProf; @@ -371,16 +297,12 @@ procparams::PerspectiveParams PerspectiveCorrection::autocompute(ImageSource *sr } dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE; - switch (dir) { - case HORIZONTAL: - fitaxis = ASHIFT_FIT_HORIZONTALLY; - break; - case VERTICAL: - fitaxis = ASHIFT_FIT_VERTICALLY; - break; - default: + if (corr_pitch && corr_yaw) { fitaxis = ASHIFT_FIT_BOTH_SHEAR; - break; + } else if (corr_pitch) { + fitaxis = ASHIFT_FIT_VERTICALLY; + } else if (corr_yaw) { + fitaxis = ASHIFT_FIT_HORIZONTALLY; } // reset the pseudo-random seed for repeatability -- ashift_dt uses rand() @@ -388,7 +310,11 @@ procparams::PerspectiveParams PerspectiveCorrection::autocompute(ImageSource *sr srand(1); auto res = do_get_structure(&module, &p, ASHIFT_ENHANCE_EDGES) && do_fit(&module, &p, fitaxis); - procparams::PerspectiveParams retval = pparams->perspective; + Params retval = { + .angle = p.rotation, + .pitch = p.camera_pitch, + .yaw = p.camera_yaw + }; // cleanup the gui if (g.lines) free(g.lines); @@ -396,16 +322,16 @@ procparams::PerspectiveParams PerspectiveCorrection::autocompute(ImageSource *sr if (g.points_idx) free(g.points_idx); free(g.buf); - if (res) { - retval.horizontal = -p.lensshift_h * 100; - retval.vertical = p.lensshift_v * 100; - retval.angle = p.rotation; - retval.shear = p.shear * 100; + if (!res) { + retval.angle = pparams->rotate.degree; + retval.pitch = pparams->perspective.camera_pitch; + retval.yaw = pparams->perspective.camera_yaw; } return retval; } +/* void PerspectiveCorrection::autocrop(int width, int height, bool fixratio, const procparams::PerspectiveParams ¶ms, const FramesMetaData *metadata, int &x, int &y, int &w, int &h) { auto pp = import_meta(params, metadata); @@ -429,5 +355,6 @@ void PerspectiveCorrection::autocrop(int width, int height, bool fixratio, const w = (p.cr - p.cl) * cw; h = (p.cb - p.ct) * ch; } +*/ } // namespace rtengine diff --git a/rtengine/perspectivecorrection.h b/rtengine/perspectivecorrection.h index 1b52941b2..bf7cfa08d 100644 --- a/rtengine/perspectivecorrection.h +++ b/rtengine/perspectivecorrection.h @@ -28,28 +28,16 @@ namespace rtengine { class PerspectiveCorrection { public: - PerspectiveCorrection(); - void init(int width, int height, const procparams::PerspectiveParams ¶ms, bool fill, const FramesMetaData *meta); - void operator()(double &x, double &y); - - enum Direction { - HORIZONTAL, - VERTICAL, - BOTH + struct Params + { + double angle; + double pitch; + double yaw; }; - static procparams::PerspectiveParams autocompute(ImageSource *src, Direction dir, const procparams::ProcParams *pparams, const FramesMetaData *metadata); - static void autocrop(int width, int height, bool fixratio, const procparams::PerspectiveParams ¶ms, const FramesMetaData *metadata, int &x, int &y, int &w, int &h); + static Params autocompute(ImageSource *src, bool corr_pitch, bool corr_yaw, const procparams::ProcParams *pparams, const FramesMetaData *metadata); -private: - void correct(double &x, double &y, double scale, double offx, double offy); - void calc_scale(int w, int h, const procparams::PerspectiveParams ¶ms, bool fill); - - bool ok_; - double scale_; - double offx_; - double offy_; - float ihomograph_[3][3]; + //static void autocrop(int width, int height, bool fixratio, const procparams::PerspectiveParams ¶ms, const FramesMetaData *metadata, int &x, int &y, int &w, int &h); }; } // namespace rtengine From 72dfa1b242e3e60be852cc7c2e20c49cc3c18265 Mon Sep 17 00:00:00 2001 From: Lawrence Date: Sat, 18 Jan 2020 14:32:03 -0800 Subject: [PATCH 10/20] Add auto perspective correction to GUI Add three buttons for correcting pitch, yaw, or both. Horizontal and/or vertical tilt adjusters are updated with the automatically computed values. --- .../svg/perspective-horizontal-vertical.svg | 116 ++++++++++++++++++ rtgui/lensgeomlistener.h | 1 + rtgui/perspective.cc | 57 +++++++++ rtgui/perspective.h | 10 ++ rtgui/toolpanelcoord.cc | 22 ++++ rtgui/toolpanelcoord.h | 1 + 6 files changed, 207 insertions(+) create mode 100644 rtdata/images/svg/perspective-horizontal-vertical.svg diff --git a/rtdata/images/svg/perspective-horizontal-vertical.svg b/rtdata/images/svg/perspective-horizontal-vertical.svg new file mode 100644 index 000000000..0c5046879 --- /dev/null +++ b/rtdata/images/svg/perspective-horizontal-vertical.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + image/svg+xml + + + + + Maciej Dworak + + + + + + + + RawTherapee icon. + + + + + + + + + + + + + + + + + diff --git a/rtgui/lensgeomlistener.h b/rtgui/lensgeomlistener.h index 7bfa0fb45..810b7ed98 100644 --- a/rtgui/lensgeomlistener.h +++ b/rtgui/lensgeomlistener.h @@ -25,4 +25,5 @@ public: virtual void straightenRequested () = 0; virtual void autoCropRequested () = 0; virtual double autoDistorRequested () = 0; + virtual void autoPerspRequested (bool corr_pitch, bool corr_yaw, double& rot, double& pitch, double& yaw) = 0; }; diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index e895c08c5..6fa524c45 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -28,11 +28,17 @@ using namespace rtengine::procparams; PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M("TP_PERSPECTIVE_LABEL")) { + lens_geom_listener = nullptr; + Gtk::Image* ipersHL = Gtk::manage (new RTImage ("perspective-horizontal-left-small.png")); 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* ipers_auto_pitch = Gtk::manage (new RTImage ("perspective-vertical-bottom.png")); + Gtk::Image* ipers_auto_yaw = Gtk::manage (new RTImage ("perspective-horizontal-left.png")); + Gtk::Image* ipers_auto_pitch_yaw = Gtk::manage (new RTImage ("perspective-horizontal-vertical.png")); + Gtk::Image* ipers_cam_yaw_left = Gtk::manage (new RTImage ("perspective-horizontal-left-small.png")); Gtk::Image* ipers_cam_yaw_right = Gtk::manage (new RTImage ("perspective-horizontal-right-small.png")); Gtk::Image* ipers_cam_pitch_left = Gtk::manage (new RTImage ("perspective-vertical-bottom-small.png")); @@ -89,6 +95,22 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" -85, 85, 0.1, 0, ipers_cam_yaw_left, ipers_cam_yaw_right)); camera_yaw->setAdjusterListener (this); + auto_pitch = Gtk::manage (new Gtk::Button ()); + auto_pitch->set_image(*ipers_auto_pitch); + auto_pitch->signal_pressed().connect( sigc::bind(sigc::mem_fun(*this, &PerspCorrection::autoCorrectionPressed), auto_pitch) ); + + auto_yaw = Gtk::manage (new Gtk::Button ()); + auto_yaw->set_image(*ipers_auto_yaw); + auto_yaw->signal_pressed().connect( sigc::bind(sigc::mem_fun(*this, &PerspCorrection::autoCorrectionPressed), auto_yaw) ); + + auto_pitch_yaw = Gtk::manage (new Gtk::Button ()); + auto_pitch_yaw->set_image(*ipers_auto_pitch_yaw); + auto_pitch_yaw->signal_pressed().connect( sigc::bind(sigc::mem_fun(*this, &PerspCorrection::autoCorrectionPressed), auto_pitch_yaw) ); + + Gtk::HBox* auto_hbox = Gtk::manage (new Gtk::HBox()); + Gtk::Label* auto_label = Gtk::manage (new Gtk::Label (M("GENERAL_AUTO") + ": ")); + auto_hbox->pack_start(*auto_label, Gtk::PACK_SHRINK); + Gtk::Frame* pca_frame = Gtk::manage (new Gtk::Frame (M("TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME"))); pca_frame->set_label_align(0.025, 0.5); @@ -122,12 +144,17 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" simple->pack_start (*horiz); simple->pack_start (*vert); + auto_hbox->pack_start (*auto_pitch); + auto_hbox->pack_start (*auto_yaw); + auto_hbox->pack_start (*auto_pitch_yaw); + camera_vbox->pack_start (*camera_focal_length); camera_vbox->pack_start (*camera_crop_factor); camera_vbox->pack_start (*camera_shift_horiz); camera_vbox->pack_start (*camera_shift_vert); camera_vbox->pack_start (*camera_pitch); camera_vbox->pack_start (*camera_yaw); + camera_vbox->pack_start (*auto_hbox); camera_frame->add(*camera_vbox); camera_based->pack_start(*camera_frame); @@ -354,6 +381,32 @@ void PerspCorrection::adjusterChanged(Adjuster* a, double newval) } } +void PerspCorrection::autoCorrectionPressed(Gtk::Button* b) +{ + if (!lens_geom_listener) { + return; + } + + double rot = 0; + double pitch = 0; + double yaw = 0; + + if (b == auto_pitch) { + lens_geom_listener->autoPerspRequested(true, false, rot, pitch, yaw); + } else if (b == auto_yaw) { + lens_geom_listener->autoPerspRequested(false, true, rot, pitch, yaw); + } else if (b == auto_pitch_yaw) { + lens_geom_listener->autoPerspRequested(true, true, rot, pitch, yaw); + } + + disableListener(); + camera_pitch->setValue(pitch); + camera_yaw->setValue(yaw); + enableListener(); + + adjusterChanged(camera_pitch, pitch); +} + void PerspCorrection::methodChanged (void) { @@ -431,5 +484,9 @@ void PerspCorrection::setBatchMode (bool batchMode) projection_shift_vert->showEditedCB (); projection_yaw->showEditedCB (); + auto_pitch->set_sensitive(false); + auto_yaw->set_sensitive(false); + auto_pitch_yaw->set_sensitive(false); + method->append (M("GENERAL_UNCHANGED")); } diff --git a/rtgui/perspective.h b/rtgui/perspective.h index 9d19c84b0..fadead7e9 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -21,6 +21,7 @@ #include #include "adjuster.h" +#include "lensgeomlistener.h" #include "toolpanel.h" class PerspCorrection final : @@ -34,6 +35,9 @@ protected: Gtk::VBox* simple; Adjuster* horiz; Adjuster* vert; + Gtk::Button* auto_pitch; + Gtk::Button* auto_yaw; + Gtk::Button* auto_pitch_yaw; Gtk::VBox* camera_based; Adjuster* camera_crop_factor; Adjuster* camera_focal_length; @@ -47,6 +51,7 @@ protected: Adjuster* projection_shift_horiz; Adjuster* projection_shift_vert; Adjuster* projection_yaw; + LensGeomListener* lens_geom_listener; public: @@ -58,7 +63,12 @@ public: void setBatchMode (bool batchMode) override; void adjusterChanged (Adjuster* a, double newval) override; + void autoCorrectionPressed (Gtk::Button* b); void methodChanged (void); void setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool camera_angle_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add); + void setLensGeomListener (LensGeomListener* listener) + { + lens_geom_listener = listener; + } void trimValues (rtengine::procparams::ProcParams* pp) override; }; diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 57f61a09c..a62bc5610 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -26,6 +26,7 @@ #include "../rtengine/dfmanager.h" #include "../rtengine/ffmanager.h" #include "../rtengine/improcfun.h" +#include "../rtengine/perspectivecorrection.h" #include "../rtengine/procevents.h" #include "../rtengine/refreshmap.h" @@ -258,6 +259,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit flatfield->setFFProvider (this); lensgeom->setLensGeomListener (this); rotate->setLensGeomListener (this); + perspective->setLensGeomListener (this); distortion->setLensGeomListener (this); crop->setCropPanelListener (this); icm->setICMPanelListener (this); @@ -844,6 +846,26 @@ void ToolPanelCoordinator::straightenRequested () toolBar->setTool (TMStraighten); } +void ToolPanelCoordinator::autoPerspRequested (bool corr_pitch, bool corr_yaw, double& rot, double& pitch, double& yaw) +{ + if (!(ipc && (corr_pitch || corr_yaw))) { + return; + } + + rtengine::ImageSource *src = dynamic_cast(ipc->getInitialImage()); + if (!src) { + return; + } + + rtengine::procparams::ProcParams params; + ipc->getParams(¶ms); + + auto res = rtengine::PerspectiveCorrection::autocompute(src, corr_pitch, corr_yaw, ¶ms, src->getMetaData()); + rot = res.angle; + pitch = res.pitch; + yaw = res.yaw; +} + double ToolPanelCoordinator::autoDistorRequested () { if (!ipc) { diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 4313f6d12..53b27f158 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -301,6 +301,7 @@ public: // rotatelistener interface void straightenRequested () override; void autoCropRequested () override; + void autoPerspRequested (bool corr_pitch, bool corr_yaw, double& rot, double& pitch, double& yaw) override; double autoDistorRequested () override; // spotwblistener interface From 025d11bb22de1fa51a7c63e46bb9c516531ac966 Mon Sep 17 00:00:00 2001 From: Lawrence Date: Sat, 18 Jan 2020 15:26:52 -0800 Subject: [PATCH 11/20] Make auto perspective centered at 0 darktable/ART searches for the optimal value of a parameter starting with the currently set value. They scan within a range of that value. This commit changes the starting value to 0 for rotation, pitch, and yaw. It makes the search range balanced. --- rtengine/ashift_dt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtengine/ashift_dt.c b/rtengine/ashift_dt.c index ba2f62271..d5367d7a5 100644 --- a/rtengine/ashift_dt.c +++ b/rtengine/ashift_dt.c @@ -2170,7 +2170,7 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p { // we fit rotation fit.params_count++; - params[pcount] = logit(fit.rotation, -fit.rotation_range, fit.rotation_range); + params[pcount] = logit(0, -fit.rotation_range, fit.rotation_range); pcount++; fit.rotation = NAN; } @@ -2208,7 +2208,7 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p { // we fit pitch fit.params_count++; - params[pcount] = logit(fit.camera_pitch, -fit.camera_pitch_range, fit.camera_pitch_range); + params[pcount] = logit(0, -fit.camera_pitch_range, fit.camera_pitch_range); pcount++; fit.camera_pitch = NAN; } @@ -2217,7 +2217,7 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p { // we fit yaw fit.params_count++; - params[pcount] = logit(fit.camera_yaw, -fit.camera_yaw_range, fit.camera_yaw_range); + params[pcount] = logit(0, -fit.camera_yaw_range, fit.camera_yaw_range); pcount++; fit.camera_yaw = NAN; } From 7395b26db4311509646c32fd72e3496087a71ccc Mon Sep 17 00:00:00 2001 From: Lawrence Date: Sat, 18 Jan 2020 17:13:24 -0800 Subject: [PATCH 12/20] Add rotation to camera-based perspective tool This rotation is different from the Rotate tool and the post-correction adjustment rotation because it is applied between camera shift and camera angle. It is equivalent to correcting the camera's roll and is the same as the rotation calculated by automatic perspective correction. --- rtdata/languages/default | 1 + rtengine/ashift_dt.c | 2 +- rtengine/iptransform.cc | 23 +++++++++++++++-------- rtengine/perspectivecorrection.cc | 22 ++++++++++++---------- rtengine/procparams.cc | 4 ++++ rtengine/procparams.h | 1 + rtgui/paramsedited.cc | 6 ++++++ rtgui/paramsedited.h | 1 + rtgui/perspective.cc | 21 +++++++++++++++++++-- rtgui/perspective.h | 1 + 10 files changed, 61 insertions(+), 21 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 82431fb43..95b8980b3 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1815,6 +1815,7 @@ TP_PERSPECTIVE_CAMERA_CROP_FACTOR;Crop factor TP_PERSPECTIVE_CAMERA_FRAME;Correction TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH;Focal length TP_PERSPECTIVE_CAMERA_PITCH;Vertical +TP_PERSPECTIVE_CAMERA_ROLL;Rotation TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL;Horizontal shift TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL;Vertical shift TP_PERSPECTIVE_CAMERA_YAW;Horizontal diff --git a/rtengine/ashift_dt.c b/rtengine/ashift_dt.c index d5367d7a5..b63ef147f 100644 --- a/rtengine/ashift_dt.c +++ b/rtengine/ashift_dt.c @@ -670,7 +670,7 @@ static void homography(float *homograph, const float angle, const float shift_v, const float u = width; const float v = height; - const float rot = M_PI * angle / 180.0f; + const float rot = -M_PI * angle / 180.0f; const float pitch = M_PI * camera_pitch / 180.0f; const float yaw = M_PI * camera_yaw / 180.0f; diff --git a/rtengine/iptransform.cc b/rtengine/iptransform.cc index b8083c1d6..600dca033 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -212,6 +212,7 @@ namespace rtengine * @param camera_focal_length Camera's focal length. * @param camera_shift_horiz Camera lens's shift to the right. * @param camera_shift_vert Camera lens's shift upwards. + * @param camera_roll Camera's roll in radians. Counter-clockwise is positive. * @param camera_pitch Camera's pitch in radians. Up is positive. * @param camera_yaw Camera's yaw in radians. Right is positive. * Up is positive. @@ -227,10 +228,11 @@ namespace rtengine * @param projection_scale Scale factor of perspective-corrected image. */ homogeneous::Matrix perspectiveMatrix(double camera_focal_length, double - camera_shift_horiz, double camera_shift_vert, double camera_pitch, - double camera_yaw, double projection_yaw, double projection_pitch, - double projection_rotate, double projection_shift_horiz, double - projection_shift_vert, double projection_scale) + camera_shift_horiz, double camera_shift_vert, double camera_roll, double + camera_pitch, double camera_yaw, double projection_yaw, double + projection_pitch, double projection_rotate, double + projection_shift_horiz, double projection_shift_vert, double + projection_scale) { const double projection_scale_inverse = 1.0 / projection_scale; homogeneous::Vector center; @@ -254,6 +256,8 @@ homogeneous::Matrix perspectiveMatrix(double camera_focal_length, double // Lens/sensor shift and move to z == camera_focal_length. homogeneous::translationMatrix(-camera_shift_horiz, -camera_shift_vert, -camera_focal_length) * + // Camera roll. + homogeneous::rotationMatrix(camera_roll, homogeneous::Axis::Z) * // Perspective correction. homogeneous::projectionMatrix(camera_focal_length, homogeneous::Axis::Z) * homogeneous::rotationMatrix(-camera_pitch, homogeneous::Axis::X) * @@ -337,6 +341,7 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, 12.0*12.0)); const double p_camera_yaw = params->perspective.camera_yaw / 180.0 * rtengine::RT_PI; const double p_camera_pitch = params->perspective.camera_pitch / 180.0 * rtengine::RT_PI; + const double p_camera_roll = params->perspective.camera_roll * rtengine::RT_PI_180; const double p_camera_shift_horiz = oW / 100.0 * params->perspective.camera_shift_horiz; const double p_camera_shift_vert = oH / -100.0 * params->perspective.camera_shift_vert; const double p_projection_shift_horiz = oW / 100.0 * params->perspective.projection_shift_horiz; @@ -346,8 +351,8 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, const double p_projection_pitch = -params->perspective.projection_pitch * rtengine::RT_PI_180; const double p_projection_scale = params->perspective.projection_scale; const homogeneous::Matrix p_matrix = perspectiveMatrix(f, - p_camera_shift_horiz, p_camera_shift_vert, p_camera_pitch, - p_camera_yaw, p_projection_yaw, p_projection_pitch, + p_camera_shift_horiz, p_camera_shift_vert, p_camera_roll, + p_camera_pitch, p_camera_yaw, p_projection_yaw, p_projection_pitch, p_projection_rotate, p_projection_shift_horiz, p_projection_shift_vert, p_projection_scale); @@ -1044,6 +1049,7 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I 12.0*12.0)); const double p_camera_yaw = params->perspective.camera_yaw / 180.0 * rtengine::RT_PI; const double p_camera_pitch = params->perspective.camera_pitch / 180.0 * rtengine::RT_PI; + const double p_camera_roll = params->perspective.camera_roll * rtengine::RT_PI_180; const double p_camera_shift_horiz = oW / 100.0 * params->perspective.camera_shift_horiz; const double p_camera_shift_vert = oH / -100.0 * params->perspective.camera_shift_vert; const double p_projection_shift_horiz = oW / 100.0 * params->perspective.projection_shift_horiz; @@ -1053,8 +1059,8 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I const double p_projection_pitch = -params->perspective.projection_pitch * rtengine::RT_PI_180; const double p_projection_scale = params->perspective.projection_scale; const homogeneous::Matrix p_matrix = perspectiveMatrix(f, - p_camera_shift_horiz, p_camera_shift_vert, p_camera_pitch, - p_camera_yaw, p_projection_yaw, p_projection_pitch, + p_camera_shift_horiz, p_camera_shift_vert, p_camera_roll, + p_camera_pitch, p_camera_yaw, p_projection_yaw, p_projection_pitch, p_projection_rotate, p_projection_shift_horiz, p_projection_shift_vert, p_projection_scale); @@ -1298,6 +1304,7 @@ bool ImProcFunctions::needsPerspective () const (params->perspective.horizontal || params->perspective.vertical) ) || ( (params->perspective.method == "camera_based") && ( params->perspective.camera_pitch || + params->perspective.camera_roll || params->perspective.camera_shift_horiz || params->perspective.camera_shift_vert || params->perspective.camera_yaw || diff --git a/rtengine/perspectivecorrection.cc b/rtengine/perspectivecorrection.cc index fe89b7f5f..5f2ce3aa5 100644 --- a/rtengine/perspectivecorrection.cc +++ b/rtengine/perspectivecorrection.cc @@ -45,6 +45,7 @@ #include "perspectivecorrection.h" #include "improcfun.h" +#include "procparams.h" #include "rt_math.h" #include #include @@ -121,7 +122,7 @@ std::vector get_corners(int w, int h) */ void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g, - const procparams::ProcParams *params) + const procparams::PerspectiveParams *params) { dt_iop_ashift_params_t dp = { 0.0f, @@ -185,13 +186,13 @@ void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g, g->crop_cx = g->crop_cy = 1.0f; if (params) { - p->rotation = params->rotate.degree; - p->lensshift_v = params->perspective.camera_shift_vert; - p->lensshift_h = params->perspective.camera_shift_horiz; - p->f_length = params->perspective.camera_focal_length; - p->crop_factor = params->perspective.camera_crop_factor; - p->camera_pitch = params->perspective.camera_pitch; - p->camera_yaw = params->perspective.camera_yaw; + p->rotation = params->camera_roll; + p->lensshift_v = params->camera_shift_vert; + p->lensshift_h = params->camera_shift_horiz; + p->f_length = params->camera_focal_length; + p->crop_factor = params->camera_crop_factor; + p->camera_pitch = params->camera_pitch; + p->camera_yaw = params->camera_yaw; } } @@ -238,11 +239,12 @@ PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *sr pcp.shear = dflt.shear; */ pcp.camera_pitch = dflt.camera_pitch; + pcp.camera_roll = dflt.camera_roll; pcp.camera_yaw = dflt.camera_yaw; dt_iop_ashift_params_t p; dt_iop_ashift_gui_data_t g; - init_dt_structures(&p, &g, pparams); + init_dt_structures(&p, &g, &pparams->perspective); dt_iop_module_t module; module.gui_data = &g; module.is_raw = src->isRAW(); @@ -323,7 +325,7 @@ PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *sr free(g.buf); if (!res) { - retval.angle = pparams->rotate.degree; + retval.angle = pparams->perspective.camera_roll; retval.pitch = pparams->perspective.camera_pitch; retval.yaw = pparams->perspective.camera_yaw; } diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 18be5346b..8934bf977 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1856,6 +1856,7 @@ PerspectiveParams::PerspectiveParams() : camera_crop_factor(1.0), camera_focal_length(24.0), camera_pitch(0.0), + camera_roll(0.0), camera_shift_horiz(0.0), camera_shift_vert(0.0), camera_yaw(0.0), @@ -1877,6 +1878,7 @@ bool PerspectiveParams::operator ==(const PerspectiveParams& other) const && camera_focal_length == other.camera_focal_length && camera_crop_factor == other.camera_crop_factor && camera_pitch == other.camera_pitch + && camera_roll == other.camera_roll && camera_shift_horiz == other.camera_shift_horiz && camera_shift_vert == other.camera_shift_vert && camera_yaw == other.camera_yaw @@ -3373,6 +3375,7 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->perspective.camera_crop_factor, "Perspective", "CameraCropFactor", perspective.camera_crop_factor, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_focal_length, "Perspective", "CameraFocalLength", perspective.camera_focal_length, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_pitch, "Perspective", "CameraPitch", perspective.camera_pitch, keyFile); + saveToKeyfile(!pedited || pedited->perspective.camera_roll, "Perspective", "CameraRoll", perspective.camera_roll, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_shift_horiz, "Perspective", "CameraShiftHorizontal", perspective.camera_shift_horiz, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_shift_vert, "Perspective", "CameraShiftVertical", perspective.camera_shift_vert, keyFile); saveToKeyfile(!pedited || pedited->perspective.camera_yaw, "Perspective", "CameraYaw", perspective.camera_yaw, keyFile); @@ -4464,6 +4467,7 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) assignFromKeyfile(keyFile, "Perspective", "CameraShiftHorizontal", pedited, perspective.camera_shift_horiz, pedited->perspective.camera_shift_horiz); assignFromKeyfile(keyFile, "Perspective", "CameraShiftVertical", pedited, perspective.camera_shift_vert, pedited->perspective.camera_shift_vert); assignFromKeyfile(keyFile, "Perspective", "CameraPitch", pedited, perspective.camera_pitch, pedited->perspective.camera_pitch); + assignFromKeyfile(keyFile, "Perspective", "CameraRoll", pedited, perspective.camera_roll, pedited->perspective.camera_roll); assignFromKeyfile(keyFile, "Perspective", "CameraCropFactor", pedited, perspective.camera_crop_factor, pedited->perspective.camera_crop_factor); assignFromKeyfile(keyFile, "Perspective", "CameraFocalLength", pedited, perspective.camera_focal_length, pedited->perspective.camera_focal_length); assignFromKeyfile(keyFile, "Perspective", "CameraYaw", pedited, perspective.camera_yaw, pedited->perspective.camera_yaw); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 3803f5a34..06d397a54 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -911,6 +911,7 @@ struct PerspectiveParams { double camera_crop_factor; double camera_focal_length; double camera_pitch; + double camera_roll; double camera_shift_horiz; double camera_shift_vert; double camera_yaw; diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index d21df0741..b78033796 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -340,6 +340,7 @@ void ParamsEdited::set(bool v) perspective.camera_crop_factor = v; perspective.camera_focal_length = v; perspective.camera_pitch = v; + perspective.camera_roll = v; perspective.camera_shift_horiz = v; perspective.camera_shift_vert = v; perspective.camera_yaw = v; @@ -936,6 +937,7 @@ void ParamsEdited::initFrom(const std::vector& perspective.camera_crop_factor = perspective.camera_crop_factor && p.perspective.camera_crop_factor == other.perspective.camera_crop_factor; perspective.camera_focal_length = perspective.camera_focal_length && p.perspective.camera_focal_length == other.perspective.camera_focal_length; perspective.camera_pitch = perspective.camera_pitch && p.perspective.camera_pitch == other.perspective.camera_pitch; + perspective.camera_roll = perspective.camera_roll && p.perspective.camera_roll == other.perspective.camera_roll; perspective.camera_shift_horiz = perspective.camera_shift_horiz && p.perspective.camera_shift_horiz == other.perspective.camera_shift_horiz; perspective.camera_shift_vert = perspective.camera_shift_vert && p.perspective.camera_shift_vert == other.perspective.camera_shift_vert; perspective.projection_pitch = perspective.projection_pitch && p.perspective.projection_pitch == other.perspective.projection_pitch; @@ -2359,6 +2361,10 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.perspective.camera_pitch = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_ANGLE] ? toEdit.perspective.camera_pitch + mods.perspective.camera_pitch : mods.perspective.camera_pitch; } + if (perspective.camera_roll) { + toEdit.perspective.camera_roll = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_ANGLE] ? toEdit.perspective.camera_roll + mods.perspective.camera_roll : mods.perspective.camera_roll; + } + if (perspective.camera_shift_horiz) { toEdit.perspective.camera_shift_horiz = dontforceSet && options.baBehav[ADDSET_PERSP_CAM_SHIFT] ? toEdit.perspective.camera_shift_horiz + mods.perspective.camera_shift_horiz : mods.perspective.camera_shift_horiz; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 29422b9d0..b7ae96de1 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -400,6 +400,7 @@ struct PerspectiveParamsEdited { bool camera_crop_factor; bool camera_focal_length; bool camera_pitch; + bool camera_roll; bool camera_shift_horiz; bool camera_shift_vert; bool camera_yaw; diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index 6fa524c45..f3b6eea2c 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -87,6 +87,9 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" camera_shift_vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), -100, 100, 0.01, 0)); camera_shift_vert->setAdjusterListener (this); + camera_roll = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_ROLL"), -45, 45, 0.01, 0)); + camera_roll->setAdjusterListener (this); + camera_pitch = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_PITCH"), -85, 85, 0.1, 0, ipers_cam_pitch_left, ipers_cam_pitch_right)); camera_pitch->setAdjusterListener (this); @@ -152,6 +155,7 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" camera_vbox->pack_start (*camera_crop_factor); camera_vbox->pack_start (*camera_shift_horiz); camera_vbox->pack_start (*camera_shift_vert); + camera_vbox->pack_start (*camera_roll); camera_vbox->pack_start (*camera_pitch); camera_vbox->pack_start (*camera_yaw); camera_vbox->pack_start (*auto_hbox); @@ -195,6 +199,7 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) camera_crop_factor->setEditedState (pedited->perspective.camera_crop_factor ? Edited : UnEdited); camera_focal_length->setEditedState (pedited->perspective.camera_focal_length ? Edited : UnEdited); camera_pitch->setEditedState (pedited->perspective.camera_pitch ? Edited : UnEdited); + camera_roll->setEditedState (pedited->perspective.camera_roll ? Edited : UnEdited); camera_shift_horiz->setEditedState (pedited->perspective.camera_shift_horiz ? Edited : UnEdited); camera_shift_vert->setEditedState (pedited->perspective.camera_shift_vert ? Edited : UnEdited); camera_yaw->setEditedState (pedited->perspective.camera_yaw ? Edited : UnEdited); @@ -211,6 +216,7 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) camera_crop_factor->setValue (pp->perspective.camera_crop_factor); camera_focal_length->setValue (pp->perspective.camera_focal_length); camera_pitch->setValue (pp->perspective.camera_pitch); + camera_roll->setValue (pp->perspective.camera_roll); camera_shift_horiz->setValue (pp->perspective.camera_shift_horiz); camera_shift_vert->setValue (pp->perspective.camera_shift_vert); camera_yaw->setValue (pp->perspective.camera_yaw); @@ -240,6 +246,7 @@ void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited) pp->perspective.camera_crop_factor= camera_crop_factor->getValue (); pp->perspective.camera_focal_length = camera_focal_length->getValue (); pp->perspective.camera_pitch = camera_pitch->getValue (); + pp->perspective.camera_roll = camera_roll->getValue (); pp->perspective.camera_shift_horiz = camera_shift_horiz->getValue (); pp->perspective.camera_shift_vert = camera_shift_vert->getValue (); pp->perspective.camera_yaw = camera_yaw->getValue (); @@ -263,6 +270,7 @@ void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited) pedited->perspective.camera_crop_factor= camera_crop_factor->getEditedState (); pedited->perspective.camera_focal_length = camera_focal_length->getEditedState (); pedited->perspective.camera_pitch = camera_pitch->getEditedState(); + pedited->perspective.camera_roll = camera_roll->getEditedState(); pedited->perspective.camera_shift_horiz = camera_shift_horiz->getEditedState(); pedited->perspective.camera_shift_vert = camera_shift_vert->getEditedState(); pedited->perspective.camera_yaw = camera_yaw->getEditedState(); @@ -283,6 +291,7 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit camera_crop_factor->setDefault (defParams->perspective.camera_crop_factor); camera_focal_length->setDefault (defParams->perspective.camera_focal_length); camera_pitch->setDefault (defParams->perspective.camera_pitch); + camera_roll->setDefault (defParams->perspective.camera_roll); camera_shift_horiz->setDefault (defParams->perspective.camera_shift_horiz); camera_shift_vert->setDefault (defParams->perspective.camera_shift_vert); camera_yaw->setDefault (defParams->perspective.camera_yaw); @@ -299,6 +308,7 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit camera_crop_factor->setDefaultEditedState (pedited->perspective.camera_crop_factor ? Edited : UnEdited); camera_focal_length->setDefaultEditedState (pedited->perspective.camera_focal_length ? Edited : UnEdited); camera_pitch->setDefaultEditedState (pedited->perspective.camera_pitch ? Edited : UnEdited); + camera_roll->setDefaultEditedState (pedited->perspective.camera_roll ? Edited : UnEdited); camera_shift_horiz->setDefaultEditedState (pedited->perspective.camera_shift_horiz ? Edited : UnEdited); camera_shift_vert->setDefaultEditedState (pedited->perspective.camera_shift_vert ? Edited : UnEdited); camera_yaw->setDefaultEditedState (pedited->perspective.camera_yaw ? Edited : UnEdited); @@ -314,6 +324,7 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit camera_crop_factor->setDefaultEditedState (Irrelevant); camera_focal_length->setDefaultEditedState (Irrelevant); camera_pitch->setDefaultEditedState (Irrelevant); + camera_roll->setDefaultEditedState (Irrelevant); camera_shift_horiz->setDefaultEditedState (Irrelevant); camera_shift_vert->setDefaultEditedState (Irrelevant); camera_yaw->setDefaultEditedState (Irrelevant); @@ -350,9 +361,11 @@ void PerspCorrection::adjusterChanged(Adjuster* a, double newval) camera_shift_horiz->getValue(), M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), camera_shift_vert->getValue())); - } else if (a == camera_pitch || a == camera_yaw) { + } else if (a == camera_pitch || a == camera_roll|| a == camera_yaw) { listener->panelChanged (EvPerspCamAngle, - Glib::ustring::compose("%1=%2\n%3=%4", + Glib::ustring::compose("%1=%2\n%3=%4\n%5=%6", + M("TP_PERSPECTIVE_CAMERA_ROLL"), + camera_roll->getValue(), M("TP_PERSPECTIVE_CAMERA_YAW"), camera_yaw->getValue(), M("TP_PERSPECTIVE_CAMERA_PITCH"), @@ -401,6 +414,7 @@ void PerspCorrection::autoCorrectionPressed(Gtk::Button* b) disableListener(); camera_pitch->setValue(pitch); + camera_roll->setValue(rot); camera_yaw->setValue(yaw); enableListener(); @@ -435,6 +449,7 @@ void PerspCorrection::setAdjusterBehavior (bool badd, bool camera_focal_length_a camera_crop_factor->setAddMode(camera_focal_length_add); camera_focal_length->setAddMode(camera_focal_length_add); camera_pitch->setAddMode(camera_angle_add); + camera_roll->setAddMode(camera_angle_add); camera_shift_horiz->setAddMode(camera_shift_add); camera_shift_vert->setAddMode(camera_shift_add); camera_yaw->setAddMode(camera_angle_add); @@ -454,6 +469,7 @@ void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp) camera_crop_factor->trimValue(pp->perspective.camera_crop_factor); camera_focal_length->trimValue(pp->perspective.camera_focal_length); camera_pitch->trimValue(pp->perspective.camera_pitch); + camera_roll->trimValue(pp->perspective.camera_roll); camera_shift_horiz->trimValue(pp->perspective.camera_shift_horiz); camera_shift_vert->trimValue(pp->perspective.camera_shift_vert); camera_yaw->trimValue(pp->perspective.camera_yaw); @@ -474,6 +490,7 @@ void PerspCorrection::setBatchMode (bool batchMode) camera_crop_factor->showEditedCB (); camera_focal_length->showEditedCB (); camera_pitch->showEditedCB (); + camera_roll->showEditedCB (); camera_shift_horiz->showEditedCB (); camera_shift_vert->showEditedCB (); camera_yaw->showEditedCB (); diff --git a/rtgui/perspective.h b/rtgui/perspective.h index fadead7e9..a7a2da7ed 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -42,6 +42,7 @@ protected: Adjuster* camera_crop_factor; Adjuster* camera_focal_length; Adjuster* camera_pitch; + Adjuster* camera_roll; Adjuster* camera_shift_horiz; Adjuster* camera_shift_vert; Adjuster* camera_yaw; From 65e76df2794a522e33cf2ceff4decd46f9f6de6c Mon Sep 17 00:00:00 2001 From: Lawrence Date: Mon, 20 Jan 2020 22:06:27 -0800 Subject: [PATCH 13/20] Fix LGTM multiplication alerts --- rtengine/ashift_dt.c | 10 +++++----- rtengine/ashift_lsd.c | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rtengine/ashift_dt.c b/rtengine/ashift_dt.c index b63ef147f..3894a1c1b 100644 --- a/rtengine/ashift_dt.c +++ b/rtengine/ashift_dt.c @@ -1462,8 +1462,8 @@ static int line_detect(float *in, const int width, const int height, const int x ashift_lines[lct].length = sqrt((px2 - px1) * (px2 - px1) + (py2 - py1) * (py2 - py1)); ashift_lines[lct].width = lsd_lines[n * 7 + 4] / scale; - // ... and weight (= length * width * angle precision) - const float weight = ashift_lines[lct].length * ashift_lines[lct].width * lsd_lines[n * 7 + 5]; + // ... and weight (= angle precision * length * width) + const float weight = lsd_lines[n * 7 + 5] * ashift_lines[lct].length * ashift_lines[lct].width; ashift_lines[lct].weight = weight; @@ -2083,10 +2083,10 @@ static double model_fitness(double *params, void *data) float s = vec3scalar(L, A); // sum up weighted s^2 for both directions individually - sumsq_v += isvertical ? s * s * lines[n].weight : 0.0; + sumsq_v += isvertical ? (double)s * s * lines[n].weight : 0.0; weight_v += isvertical ? lines[n].weight : 0.0; count_v += isvertical ? 1 : 0; - sumsq_h += !isvertical ? s * s * lines[n].weight : 0.0; + sumsq_h += !isvertical ? (double)s * s * lines[n].weight : 0.0; weight_h += !isvertical ? lines[n].weight : 0.0; count_h += !isvertical ? 1 : 0; count++; @@ -2128,7 +2128,7 @@ static dt_iop_ashift_nmsresult_t nmsfit(dt_iop_module_t *module, dt_iop_ashift_p fit.lines_count = g->lines_count; fit.width = g->lines_in_width; fit.height = g->lines_in_height; - fit.f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? DEFAULT_F_LENGTH : p->f_length * p->crop_factor; + fit.f_length_kb = (p->mode == ASHIFT_MODE_GENERIC) ? (float)DEFAULT_F_LENGTH : p->f_length * p->crop_factor; fit.orthocorr = (p->mode == ASHIFT_MODE_GENERIC) ? 0.0f : p->orthocorr; fit.aspect = (p->mode == ASHIFT_MODE_GENERIC) ? 1.0f : p->aspect; fit.rotation = p->rotation; diff --git a/rtengine/ashift_lsd.c b/rtengine/ashift_lsd.c index 8f4f672b1..a884da437 100644 --- a/rtengine/ashift_lsd.c +++ b/rtengine/ashift_lsd.c @@ -319,7 +319,7 @@ static ntuple_list new_ntuple_list(unsigned int dim) n_tuple->dim = dim; /* get memory for tuples */ - n_tuple->values = (double *) malloc( dim*n_tuple->max_size * sizeof(double) ); + n_tuple->values = (double *) malloc( sizeof(double) * dim*n_tuple->max_size ); if( n_tuple->values == NULL ) error("not enough memory."); return n_tuple; @@ -339,7 +339,7 @@ static void enlarge_ntuple_list(ntuple_list n_tuple) /* realloc memory */ n_tuple->values = (double *) realloc( (void *) n_tuple->values, - n_tuple->dim * n_tuple->max_size * sizeof(double) ); + sizeof(double) * n_tuple->dim * n_tuple->max_size ); if( n_tuple->values == NULL ) error("not enough memory."); } From 9a594474d9a599068ec94090dc380f92aa903827 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Sun, 23 Feb 2020 21:21:36 -0800 Subject: [PATCH 14/20] Add auto focal length for perspective in editor If the focal length and/or crop factor for the camera-based perspective correction tool are not edited, they will be set using the focal length information found in the image metadata. This only applies to the editor (batch editor and CLI still default to 24 for the focal length and 1 for the crop factor). --- rtengine/iptransform.cc | 14 ++++++----- rtengine/procparams.cc | 4 +-- rtgui/perspective.cc | 54 +++++++++++++++++++++++++++++++++++++++-- rtgui/perspective.h | 4 +++ rtgui/toolpanelcoord.cc | 1 + 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/rtengine/iptransform.cc b/rtengine/iptransform.cc index 0c900c998..577f02970 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -467,9 +467,10 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, 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); // Camera-based. - const double f = params->perspective.camera_focal_length * - params->perspective.camera_crop_factor * (maxRadius / sqrt(18.0*18.0 + - 12.0*12.0)); + const double f = + ((params->perspective.camera_focal_length > 0) ? params->perspective.camera_focal_length : 24.0) + * ((params->perspective.camera_crop_factor > 0) ? params->perspective.camera_crop_factor : 1.0) + * (maxRadius / sqrt(18.0*18.0 + 12.0*12.0)); const double p_camera_yaw = params->perspective.camera_yaw / 180.0 * rtengine::RT_PI; const double p_camera_pitch = params->perspective.camera_pitch / 180.0 * rtengine::RT_PI; const double p_camera_roll = params->perspective.camera_roll * rtengine::RT_PI_180; @@ -1165,9 +1166,10 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I const double hpcospt = (hpdeg >= 0 ? 1.0 : -1.0) * cos(hpteta); const double hptanpt = tan(hpteta); // Camera-based. - const double f = params->perspective.camera_focal_length * - params->perspective.camera_crop_factor * (maxRadius / sqrt(18.0*18.0 + - 12.0*12.0)); + const double f = + ((params->perspective.camera_focal_length > 0) ? params->perspective.camera_focal_length : 24.0) + * ((params->perspective.camera_crop_factor > 0) ? params->perspective.camera_crop_factor : 1.0) + * (maxRadius / sqrt(18.0*18.0 + 12.0*12.0)); const double p_camera_yaw = params->perspective.camera_yaw / 180.0 * rtengine::RT_PI; const double p_camera_pitch = params->perspective.camera_pitch / 180.0 * rtengine::RT_PI; const double p_camera_roll = params->perspective.camera_roll * rtengine::RT_PI_180; diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 686577d98..0f39a9a16 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1854,8 +1854,8 @@ PerspectiveParams::PerspectiveParams() : method("simple"), horizontal(0.0), vertical(0.0), - camera_crop_factor(1.0), - camera_focal_length(24.0), + camera_crop_factor(0.0), + camera_focal_length(0.0), camera_pitch(0.0), camera_roll(0.0), camera_shift_horiz(0.0), diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index f3b6eea2c..7736f72ec 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -213,8 +213,7 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) horiz->setValue (pp->perspective.horizontal); vert->setValue (pp->perspective.vertical); - camera_crop_factor->setValue (pp->perspective.camera_crop_factor); - camera_focal_length->setValue (pp->perspective.camera_focal_length); + setFocalLengthValue (pp, metadata); camera_pitch->setValue (pp->perspective.camera_pitch); camera_roll->setValue (pp->perspective.camera_roll); camera_shift_horiz->setValue (pp->perspective.camera_shift_horiz); @@ -461,6 +460,11 @@ void PerspCorrection::setAdjusterBehavior (bool badd, bool camera_focal_length_a projection_yaw->setAddMode(projection_angle_add); } +void PerspCorrection::setMetadata (const rtengine::FramesMetaData* metadata) +{ + this->metadata = metadata; +} + void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp) { @@ -507,3 +511,49 @@ void PerspCorrection::setBatchMode (bool batchMode) method->append (M("GENERAL_UNCHANGED")); } + +void PerspCorrection::setFocalLengthValue (const ProcParams* pparams, const FramesMetaData* metadata) +{ + const double pp_crop_factor = pparams->perspective.camera_crop_factor; + const double pp_focal_length = pparams->perspective.camera_focal_length; + double default_crop_factor = 1.0; + double default_focal_length = 24.0; + + // Defaults from metadata. + if (metadata && (pp_crop_factor <= 0 || pp_focal_length <= 0)) { + const double fl = metadata->getFocalLen(); + const double fl35 = metadata->getFocalLen35mm(); + + if (fl <= 0) { + if (fl35 <= 0) { + // No focal length data. + } else { + // 35mm focal length available. + default_focal_length = fl35; + } + } else { + if (fl35 <= 0) { + // Focal length available. + default_focal_length = fl; + } else { + // Focal length and 35mm equivalent available. + default_focal_length = fl; + default_crop_factor = fl35 / fl; + } + } + } + + // Change value if those from the ProcParams are invalid. + if (pp_crop_factor > 0) { + camera_crop_factor->setValue(pparams->perspective.camera_crop_factor); + } else { + camera_crop_factor->setDefault(default_crop_factor); + camera_crop_factor->setValue(default_crop_factor); + } + if (pp_focal_length > 0) { + camera_focal_length->setValue(pparams->perspective.camera_focal_length); + } else { + camera_focal_length->setDefault(default_focal_length); + camera_focal_length->setValue(default_focal_length); + } +} diff --git a/rtgui/perspective.h b/rtgui/perspective.h index a7a2da7ed..c1dd20ee4 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -53,6 +53,9 @@ protected: Adjuster* projection_shift_vert; Adjuster* projection_yaw; LensGeomListener* lens_geom_listener; + const rtengine::FramesMetaData* metadata; + + void setFocalLengthValue (const rtengine::procparams::ProcParams* pparams, const rtengine::FramesMetaData* metadata); public: @@ -71,5 +74,6 @@ public: { lens_geom_listener = listener; } + void setMetadata (const rtengine::FramesMetaData* metadata); void trimValues (rtengine::procparams::ProcParams* pp) override; }; diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index a62bc5610..77cb5643b 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -615,6 +615,7 @@ void ToolPanelCoordinator::initImage (rtengine::StagedImageProcessor* ipc_, bool icm->setRawMeta (raw, (const rtengine::FramesData*)pMetaData); lensProf->setRawMeta (raw, pMetaData); + perspective->setMetadata (pMetaData); } From 90d7b391ba9d1eb58f9405cc534f9e970f3367f0 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Wed, 26 Feb 2020 12:16:16 -0800 Subject: [PATCH 15/20] Change perspective correction adjuster limits Reduce the limits to more sensible values for now. They can be safely increased later without impacting backward-compatibility. --- rtgui/perspective.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index 7736f72ec..a9f1ef995 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -81,21 +81,21 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" camera_crop_factor = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_CROP_FACTOR"), 0.1, 30, 0.01, 1)); camera_crop_factor->setAdjusterListener (this); - camera_shift_horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL"), -100, 100, 0.01, 0)); + camera_shift_horiz = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL"), -50, 50, 0.01, 0)); camera_shift_horiz->setAdjusterListener (this); - camera_shift_vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), -100, 100, 0.01, 0)); + camera_shift_vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), -50, 50, 0.01, 0)); camera_shift_vert->setAdjusterListener (this); camera_roll = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_ROLL"), -45, 45, 0.01, 0)); camera_roll->setAdjusterListener (this); camera_pitch = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_PITCH"), - -85, 85, 0.1, 0, ipers_cam_pitch_left, ipers_cam_pitch_right)); + -60, 60, 0.1, 0, ipers_cam_pitch_left, ipers_cam_pitch_right)); camera_pitch->setAdjusterListener (this); camera_yaw = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_CAMERA_YAW"), - -85, 85, 0.1, 0, ipers_cam_yaw_left, ipers_cam_yaw_right)); + -60, 60, 0.1, 0, ipers_cam_yaw_left, ipers_cam_yaw_right)); camera_yaw->setAdjusterListener (this); auto_pitch = Gtk::manage (new Gtk::Button ()); @@ -126,7 +126,7 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" projection_shift_vert = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL"), -100, 100, 0.01, 0)); projection_shift_vert->setAdjusterListener (this); - projection_rotate = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_ROTATE"), -180, 180, 0.01, 0, ipers_rotate_left, ipers_rotate_right)); + projection_rotate = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_ROTATE"), -45, 45, 0.01, 0, ipers_rotate_left, ipers_rotate_right)); projection_rotate->setAdjusterListener (this); projection_scale = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_SCALE"), 0.5, 2, 0.01, 1)); @@ -138,10 +138,10 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" Gtk::VBox* recovery_vbox = Gtk::manage (new Gtk::VBox()); - projection_pitch = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_PITCH"), -85, 85, 0.1, 0, ipers_proj_pitch_left, ipers_proj_pitch_right)); + projection_pitch = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_PITCH"), -60, 60, 0.1, 0, ipers_proj_pitch_left, ipers_proj_pitch_right)); projection_pitch->setAdjusterListener (this); - projection_yaw = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_YAW"), -85, 85, 0.1, 0, ipers_proj_yaw_left, ipers_proj_yaw_right)); + projection_yaw = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_YAW"), -60, 60, 0.1, 0, ipers_proj_yaw_left, ipers_proj_yaw_right)); projection_yaw->setAdjusterListener (this); simple->pack_start (*horiz); From ab0c9c6cf0c6c4c49e89f366c9afae310617b4ce Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Wed, 26 Feb 2020 16:46:32 -0800 Subject: [PATCH 16/20] Correct string in batch processing preferences A string for perspective correction lacked the camera rotation. --- rtgui/preferences.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 5e3b2e7af..740b81ebd 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -346,7 +346,7 @@ Gtk::Widget* Preferences::getBatchProcPanel () appendBehavList (mi, M ("TP_PERSPECTIVE_METHOD_SIMPLE") + " - " + M ("TP_PERSPECTIVE_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_VERTICAL"), ADDSET_PERSPECTIVE, false); appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FOCAL_LENGTH") + ", " + M ("TP_PERSPECTIVE_CAMERA_CROP_FACTOR"), ADDSET_PERSP_CAM_FOCAL_LENGTH, false); appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FRAME") + " - " + M ("TP_PERSPECTIVE_CAMERA_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_CAMERA_SHIFT_VERTICAL"), ADDSET_PERSP_CAM_SHIFT, false); - appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FRAME") + " - " + M ("TP_PERSPECTIVE_CAMERA_YAW") + ", " + M ("TP_PERSPECTIVE_CAMERA_PITCH"), ADDSET_PERSP_CAM_ANGLE, false); + appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FRAME") + " - " + M ("TP_PERSPECTIVE_CAMERA_ROLL") + ", " + M ("TP_PERSPECTIVE_CAMERA_YAW") + ", " + M ("TP_PERSPECTIVE_CAMERA_PITCH"), ADDSET_PERSP_CAM_ANGLE, false); appendBehavList (mi, M ("TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME") + " - " + M ("TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL"), ADDSET_PERSP_PROJ_SHIFT, false); appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_ROTATE"), ADDSET_PERSP_PROJ_ROTATE, false); appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_SCALE"), ADDSET_PERSP_PROJ_SCALE, false); From 357cf5b96a6968a0bc27eebfc93ecab0ef47790a Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Wed, 26 Feb 2020 22:52:55 -0800 Subject: [PATCH 17/20] Remove scaling from perspective correction Scaling is applied at the end of all geometric transformations, so it would be more appropriate outside of the perspective correction tool. Such an implementation would also work better with the auto-fit feature. --- rtdata/languages/default | 8 +++----- rtengine/iptransform.cc | 5 ++--- rtengine/procevents.h | 7 +++---- rtengine/procparams.cc | 4 ---- rtengine/procparams.h | 1 - rtengine/refreshmap.cc | 1 - rtgui/addsetids.h | 1 - rtgui/batchtoolpanelcoord.cc | 5 ++--- rtgui/paramsedited.cc | 6 ------ rtgui/paramsedited.h | 1 - rtgui/perspective.cc | 20 +------------------- rtgui/perspective.h | 3 +-- rtgui/preferences.cc | 1 - 13 files changed, 12 insertions(+), 51 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index e6bbe8957..db90aea22 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -732,10 +732,9 @@ HISTORY_MSG_496;Perspective - Camera HISTORY_MSG_497;Perspective - Camera HISTORY_MSG_498;Perspective - Recovery HISTORY_MSG_499;Perspective - PCA - Rotation -HISTORY_MSG_500;Perspective - PCA - Scale -HISTORY_MSG_501;Perspective - PCA -HISTORY_MSG_502;Perspective - Camera -HISTORY_MSG_503;Perspective - Method +HISTORY_MSG_500;Perspective - PCA +HISTORY_MSG_501;Perspective - Camera +HISTORY_MSG_502;Perspective - Method HISTORY_MSG_CLAMPOOG;Clip out-of-gamut colors HISTORY_MSG_COLORTONING_LABGRID_VALUE;CT - Color correction HISTORY_MSG_COLORTONING_LABREGION_AB;CT - Color correction @@ -1831,7 +1830,6 @@ TP_PERSPECTIVE_METHOD_CAMERA_BASED;Camera-based TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME;Post-correction adjustment TP_PERSPECTIVE_PROJECTION_PITCH;Vertical TP_PERSPECTIVE_PROJECTION_ROTATE;Rotation -TP_PERSPECTIVE_PROJECTION_SCALE;Scale TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL;Horizontal shift TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL;Vertical shift TP_PERSPECTIVE_PROJECTION_YAW;Horizontal diff --git a/rtengine/iptransform.cc b/rtengine/iptransform.cc index 577f02970..dc095735f 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -481,7 +481,7 @@ bool ImProcFunctions::transCoord (int W, int H, const std::vector &src, const double p_projection_rotate = params->perspective.projection_rotate * rtengine::RT_PI_180; const double p_projection_yaw = -params->perspective.projection_yaw * rtengine::RT_PI_180; const double p_projection_pitch = -params->perspective.projection_pitch * rtengine::RT_PI_180; - const double p_projection_scale = params->perspective.projection_scale; + const double p_projection_scale = 1; const homogeneous::Matrix p_matrix = perspectiveMatrix(f, p_camera_shift_horiz, p_camera_shift_vert, p_camera_roll, p_camera_pitch, p_camera_yaw, p_projection_yaw, p_projection_pitch, @@ -1180,7 +1180,7 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I const double p_projection_rotate = params->perspective.projection_rotate * rtengine::RT_PI_180; const double p_projection_yaw = -params->perspective.projection_yaw * rtengine::RT_PI_180; const double p_projection_pitch = -params->perspective.projection_pitch * rtengine::RT_PI_180; - const double p_projection_scale = params->perspective.projection_scale; + const double p_projection_scale = 1; const homogeneous::Matrix p_matrix = perspectiveMatrix(f, p_camera_shift_horiz, p_camera_shift_vert, p_camera_roll, p_camera_pitch, p_camera_yaw, p_projection_yaw, p_projection_pitch, @@ -1487,7 +1487,6 @@ bool ImProcFunctions::needsPerspective () const params->perspective.camera_yaw || params->perspective.projection_pitch || params->perspective.projection_rotate || - params->perspective.projection_scale != 1.0 || params->perspective.projection_shift_horiz || params->perspective.projection_shift_vert || params->perspective.projection_yaw) ); diff --git a/rtengine/procevents.h b/rtengine/procevents.h index 9ab3e0395..bd8b06650 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -522,10 +522,9 @@ enum ProcEventCode { EvPerspCamShift = 496, EvPerspProjAngle = 497, EvPerspProjRotate = 498, - EvPerspProjScale = 499, - EvPerspProjShift = 500, - EvPerspCamAngle = 501, - EvPerspMethod = 502, + EvPerspProjShift = 499, + EvPerspCamAngle = 500, + EvPerspMethod = 501, NUMOFEVENTS diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 0f39a9a16..bb55cf41d 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1863,7 +1863,6 @@ PerspectiveParams::PerspectiveParams() : camera_yaw(0.0), projection_pitch(0.0), projection_rotate(0.0), - projection_scale(1.0), projection_shift_horiz(0.0), projection_shift_vert(0.0), projection_yaw(0.0) @@ -1885,7 +1884,6 @@ bool PerspectiveParams::operator ==(const PerspectiveParams& other) const && camera_yaw == other.camera_yaw && projection_shift_horiz == other.projection_shift_horiz && projection_shift_vert == other.projection_shift_vert - && projection_scale == other.projection_scale && projection_rotate == other.projection_rotate && projection_pitch == other.projection_pitch && projection_yaw == other.projection_yaw; @@ -3384,7 +3382,6 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->perspective.projection_shift_horiz, "Perspective", "ProjectionShiftHorizontal", perspective.projection_shift_horiz, keyFile); saveToKeyfile(!pedited || pedited->perspective.projection_pitch, "Perspective", "ProjectionPitch", perspective.projection_pitch, keyFile); saveToKeyfile(!pedited || pedited->perspective.projection_rotate, "Perspective", "ProjectionRotate", perspective.projection_rotate, keyFile); - saveToKeyfile(!pedited || pedited->perspective.projection_scale, "Perspective", "ProjectionScale", perspective.projection_scale, keyFile); saveToKeyfile(!pedited || pedited->perspective.projection_shift_horiz, "Perspective", "ProjectionShiftHorizontal", perspective.projection_shift_horiz, keyFile); saveToKeyfile(!pedited || pedited->perspective.projection_shift_vert, "Perspective", "ProjectionShiftVertical", perspective.projection_shift_vert, keyFile); saveToKeyfile(!pedited || pedited->perspective.projection_yaw, "Perspective", "ProjectionYaw", perspective.projection_yaw, keyFile); @@ -4480,7 +4477,6 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) assignFromKeyfile(keyFile, "Perspective", "CameraYaw", pedited, perspective.camera_yaw, pedited->perspective.camera_yaw); assignFromKeyfile(keyFile, "Perspective", "ProjectionPitch", pedited, perspective.projection_pitch, pedited->perspective.projection_pitch); assignFromKeyfile(keyFile, "Perspective", "ProjectionRotate", pedited, perspective.projection_rotate, pedited->perspective.projection_rotate); - assignFromKeyfile(keyFile, "Perspective", "ProjectionScale", pedited, perspective.projection_scale, pedited->perspective.projection_scale); assignFromKeyfile(keyFile, "Perspective", "ProjectionShiftHorizontal", pedited, perspective.projection_shift_horiz, pedited->perspective.projection_shift_horiz); assignFromKeyfile(keyFile, "Perspective", "ProjectionShiftVertical", pedited, perspective.projection_shift_vert, pedited->perspective.projection_shift_vert); assignFromKeyfile(keyFile, "Perspective", "ProjectionYaw", pedited, perspective.projection_yaw, pedited->perspective.projection_yaw); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index ffebe89b9..1472a5059 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -918,7 +918,6 @@ struct PerspectiveParams { double camera_yaw; double projection_pitch; double projection_rotate; - double projection_scale; double projection_shift_horiz; double projection_shift_vert; double projection_yaw; diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index e480f91ce..29384b652 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -526,7 +526,6 @@ int refreshmap[rtengine::NUMOFEVENTS] = { TRANSFORM, // EvPerspCamShift TRANSFORM, // EvPerspProjAngle TRANSFORM, // EvPerspProjRotate - TRANSFORM, // EvPerspProjScale TRANSFORM, // EvPerspProjShift TRANSFORM, // EvPerspCamAngle TRANSFORM // EvPerspMethod diff --git a/rtgui/addsetids.h b/rtgui/addsetids.h index 2a02274eb..8e7833a33 100644 --- a/rtgui/addsetids.h +++ b/rtgui/addsetids.h @@ -23,7 +23,6 @@ enum { ADDSET_PERSP_CAM_SHIFT, ADDSET_PERSP_PROJ_ANGLE, ADDSET_PERSP_PROJ_ROTATE, - ADDSET_PERSP_PROJ_SCALE, ADDSET_PERSP_PROJ_SHIFT, ADDSET_CA, ADDSET_VIGN_AMOUNT, diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index c6865a82d..f21d82f25 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -151,7 +151,7 @@ void BatchToolPanelCoordinator::initSession () rotate->setAdjusterBehavior (false); resize->setAdjusterBehavior (false); distortion->setAdjusterBehavior (false); - perspective->setAdjusterBehavior (false, false, false, false, false, false, false, false); + perspective->setAdjusterBehavior (false, false, false, false, false, false, false); gradient->setAdjusterBehavior (false, false, false, false); pcvignette->setAdjusterBehavior (false, false, false); cacorrection->setAdjusterBehavior (false); @@ -196,7 +196,7 @@ void BatchToolPanelCoordinator::initSession () rotate->setAdjusterBehavior (options.baBehav[ADDSET_ROTATE_DEGREE]); resize->setAdjusterBehavior (options.baBehav[ADDSET_RESIZE_SCALE]); distortion->setAdjusterBehavior (options.baBehav[ADDSET_DIST_AMOUNT]); - perspective->setAdjusterBehavior (options.baBehav[ADDSET_PERSPECTIVE], options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH], options.baBehav[ADDSET_PERSP_CAM_SHIFT], options.baBehav[ADDSET_PERSP_CAM_ANGLE], options.baBehav[ADDSET_PERSP_PROJ_ANGLE], options.baBehav[ADDSET_PERSP_PROJ_SHIFT], options.baBehav[ADDSET_PERSP_PROJ_ROTATE], options.baBehav[ADDSET_PERSP_PROJ_SCALE]); + perspective->setAdjusterBehavior (options.baBehav[ADDSET_PERSPECTIVE], options.baBehav[ADDSET_PERSP_CAM_FOCAL_LENGTH], options.baBehav[ADDSET_PERSP_CAM_SHIFT], options.baBehav[ADDSET_PERSP_CAM_ANGLE], options.baBehav[ADDSET_PERSP_PROJ_ANGLE], options.baBehav[ADDSET_PERSP_PROJ_SHIFT], options.baBehav[ADDSET_PERSP_PROJ_ROTATE]); gradient->setAdjusterBehavior (options.baBehav[ADDSET_GRADIENT_DEGREE], options.baBehav[ADDSET_GRADIENT_FEATHER], options.baBehav[ADDSET_GRADIENT_STRENGTH], options.baBehav[ADDSET_GRADIENT_CENTER]); pcvignette->setAdjusterBehavior (options.baBehav[ADDSET_PCVIGNETTE_STRENGTH], options.baBehav[ADDSET_PCVIGNETTE_FEATHER], options.baBehav[ADDSET_PCVIGNETTE_ROUNDNESS]); cacorrection->setAdjusterBehavior (options.baBehav[ADDSET_CA]); @@ -314,7 +314,6 @@ void BatchToolPanelCoordinator::initSession () if (options.baBehav[ADDSET_PERSP_PROJ_ANGLE]) { pparams.perspective.projection_yaw = pparams.perspective.projection_pitch = 0; } if (options.baBehav[ADDSET_PERSP_PROJ_SHIFT]) { pparams.perspective.projection_shift_horiz = pparams.perspective.projection_shift_vert = 0; } if (options.baBehav[ADDSET_PERSP_PROJ_ROTATE]) { pparams.perspective.projection_rotate = 0; } - if (options.baBehav[ADDSET_PERSP_PROJ_SCALE]) { pparams.perspective.projection_scale = 0; } if (options.baBehav[ADDSET_GRADIENT_DEGREE]) { pparams.gradient.degree = 0; } if (options.baBehav[ADDSET_GRADIENT_FEATHER]) { pparams.gradient.feather = 0; } if (options.baBehav[ADDSET_GRADIENT_STRENGTH]) { pparams.gradient.strength = 0; } diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 39cbf082b..9d11d8aad 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -347,7 +347,6 @@ void ParamsEdited::set(bool v) perspective.camera_yaw = v; perspective.projection_pitch = v; perspective.projection_rotate = v; - perspective.projection_scale = v; perspective.projection_shift_horiz = v; perspective.projection_shift_vert = v; perspective.projection_yaw = v; @@ -945,7 +944,6 @@ void ParamsEdited::initFrom(const std::vector& perspective.projection_pitch = perspective.projection_pitch && p.perspective.projection_pitch == other.perspective.projection_pitch; perspective.camera_yaw = perspective.camera_yaw && p.perspective.camera_yaw == other.perspective.camera_yaw; perspective.projection_rotate = perspective.projection_rotate && p.perspective.projection_rotate == other.perspective.projection_rotate; - perspective.projection_scale = perspective.projection_scale && p.perspective.projection_scale == other.perspective.projection_scale; perspective.projection_shift_horiz = perspective.projection_shift_horiz && p.perspective.projection_shift_horiz == other.perspective.projection_shift_horiz; perspective.projection_shift_vert = perspective.projection_shift_vert && p.perspective.projection_shift_vert == other.perspective.projection_shift_vert; perspective.projection_yaw = perspective.projection_yaw && p.perspective.projection_yaw == other.perspective.projection_yaw; @@ -2391,10 +2389,6 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.perspective.projection_rotate = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_ROTATE] ? toEdit.perspective.projection_rotate + mods.perspective.projection_rotate : mods.perspective.projection_rotate; } - if (perspective.projection_scale) { - toEdit.perspective.projection_scale = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_SCALE] ? toEdit.perspective.projection_scale + mods.perspective.projection_scale : mods.perspective.projection_scale; - } - if (perspective.projection_shift_horiz) { toEdit.perspective.projection_shift_horiz = dontforceSet && options.baBehav[ADDSET_PERSP_PROJ_SHIFT] ? toEdit.perspective.projection_shift_horiz + mods.perspective.projection_shift_horiz : mods.perspective.projection_shift_horiz; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 2a920a4f3..9c9f23939 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -407,7 +407,6 @@ struct PerspectiveParamsEdited { bool camera_yaw; bool projection_pitch; bool projection_rotate; - bool projection_scale; bool projection_shift_horiz; bool projection_shift_vert; bool projection_yaw; diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index a9f1ef995..42175616f 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -129,9 +129,6 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" projection_rotate = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_ROTATE"), -45, 45, 0.01, 0, ipers_rotate_left, ipers_rotate_right)); projection_rotate->setAdjusterListener (this); - projection_scale = Gtk::manage (new Adjuster (M("TP_PERSPECTIVE_PROJECTION_SCALE"), 0.5, 2, 0.01, 1)); - projection_scale->setAdjusterListener (this); - Gtk::Frame* recovery_frame = Gtk::manage (new Gtk::Frame (M("TP_PERSPECTIVE_RECOVERY_FRAME"))); recovery_frame->set_label_align(0.025, 0.5); @@ -165,7 +162,6 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" pca_vbox->pack_start (*projection_shift_horiz); pca_vbox->pack_start (*projection_shift_vert); pca_vbox->pack_start (*projection_rotate); - pca_vbox->pack_start (*projection_scale); pca_frame->add(*pca_vbox); camera_based->pack_start(*pca_frame); @@ -181,7 +177,6 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" vert->setLogScale(2, 0); camera_focal_length->setLogScale(4000, 0.5); camera_crop_factor->setLogScale(300, 0.1); - projection_scale->setLogScale(4, 0.5); method->signal_changed().connect(sigc::mem_fun(*this, &PerspCorrection::methodChanged)); @@ -205,7 +200,6 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) camera_yaw->setEditedState (pedited->perspective.camera_yaw ? Edited : UnEdited); projection_pitch->setEditedState (pedited->perspective.projection_pitch ? Edited : UnEdited); projection_rotate->setEditedState (pedited->perspective.projection_rotate ? Edited : UnEdited); - projection_scale->setEditedState (pedited->perspective.projection_scale ? Edited : UnEdited); projection_shift_horiz->setEditedState (pedited->perspective.projection_shift_horiz ? Edited : UnEdited); projection_shift_vert->setEditedState (pedited->perspective.projection_shift_vert ? Edited : UnEdited); projection_yaw->setEditedState (pedited->perspective.projection_yaw ? Edited : UnEdited); @@ -221,7 +215,6 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) camera_yaw->setValue (pp->perspective.camera_yaw); projection_pitch->setValue (pp->perspective.projection_pitch); projection_rotate->setValue (pp->perspective.projection_rotate); - projection_scale->setValue (pp->perspective.projection_scale); projection_shift_horiz->setValue (pp->perspective.projection_shift_horiz); projection_shift_vert->setValue (pp->perspective.projection_shift_vert); projection_yaw->setValue (pp->perspective.projection_yaw); @@ -251,7 +244,6 @@ void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited) pp->perspective.camera_yaw = camera_yaw->getValue (); pp->perspective.projection_pitch = projection_pitch->getValue (); pp->perspective.projection_rotate = projection_rotate->getValue (); - pp->perspective.projection_scale = projection_scale->getValue (); pp->perspective.projection_shift_horiz = projection_shift_horiz->getValue (); pp->perspective.projection_shift_vert = projection_shift_vert->getValue (); pp->perspective.projection_yaw = projection_yaw->getValue (); @@ -275,7 +267,6 @@ void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited) pedited->perspective.camera_yaw = camera_yaw->getEditedState(); pedited->perspective.projection_pitch = projection_pitch->getEditedState(); pedited->perspective.projection_rotate = projection_rotate->getEditedState(); - pedited->perspective.projection_scale = projection_scale->getEditedState(); pedited->perspective.projection_shift_horiz = projection_shift_horiz->getEditedState(); pedited->perspective.projection_shift_vert = projection_shift_vert->getEditedState(); pedited->perspective.projection_yaw = projection_yaw->getEditedState(); @@ -296,7 +287,6 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit camera_yaw->setDefault (defParams->perspective.camera_yaw); projection_pitch->setDefault (defParams->perspective.projection_pitch); projection_rotate->setDefault (defParams->perspective.projection_rotate); - projection_scale->setDefault (defParams->perspective.projection_scale); projection_shift_horiz->setDefault (defParams->perspective.projection_shift_horiz); projection_shift_vert->setDefault (defParams->perspective.projection_shift_vert); projection_yaw->setDefault (defParams->perspective.projection_yaw); @@ -313,7 +303,6 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit camera_yaw->setDefaultEditedState (pedited->perspective.camera_yaw ? Edited : UnEdited); projection_pitch->setDefaultEditedState (pedited->perspective.projection_pitch ? Edited : UnEdited); projection_rotate->setDefaultEditedState (pedited->perspective.projection_rotate ? Edited : UnEdited); - projection_scale->setDefaultEditedState (pedited->perspective.projection_scale ? Edited : UnEdited); projection_shift_horiz->setDefaultEditedState (pedited->perspective.projection_shift_horiz ? Edited : UnEdited); projection_shift_vert->setDefaultEditedState (pedited->perspective.projection_shift_vert ? Edited : UnEdited); projection_yaw->setDefaultEditedState (pedited->perspective.projection_yaw ? Edited : UnEdited); @@ -329,7 +318,6 @@ void PerspCorrection::setDefaults (const ProcParams* defParams, const ParamsEdit camera_yaw->setDefaultEditedState (Irrelevant); projection_pitch->setDefaultEditedState (Irrelevant); projection_rotate->setDefaultEditedState (Irrelevant); - projection_scale->setDefaultEditedState (Irrelevant); projection_shift_horiz->setDefaultEditedState (Irrelevant); projection_shift_vert->setDefaultEditedState (Irrelevant); projection_yaw->setDefaultEditedState (Irrelevant); @@ -379,9 +367,6 @@ void PerspCorrection::adjusterChanged(Adjuster* a, double newval) } else if (a == projection_rotate) { listener->panelChanged (EvPerspProjRotate, Glib::ustring::format(projection_rotate->getValue())); - } else if (a == projection_scale) { - listener->panelChanged (EvPerspProjScale, - Glib::ustring::format(projection_scale->getValue())); } else if (a == projection_pitch || a == projection_yaw) { listener->panelChanged (EvPerspProjAngle, Glib::ustring::compose("%1=%2\n%3=%4", @@ -440,7 +425,7 @@ void PerspCorrection::methodChanged (void) } -void PerspCorrection::setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool camera_angle_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add) +void PerspCorrection::setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool camera_angle_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add) { horiz->setAddMode(badd); @@ -454,7 +439,6 @@ void PerspCorrection::setAdjusterBehavior (bool badd, bool camera_focal_length_a camera_yaw->setAddMode(camera_angle_add); projection_pitch->setAddMode(projection_angle_add); projection_rotate->setAddMode(projection_rotate_add); - projection_scale->setAddMode(projection_scale_add); projection_shift_horiz->setAddMode(projection_shift_add); projection_shift_vert->setAddMode(projection_shift_add); projection_yaw->setAddMode(projection_angle_add); @@ -479,7 +463,6 @@ void PerspCorrection::trimValues (rtengine::procparams::ProcParams* pp) camera_yaw->trimValue(pp->perspective.camera_yaw); projection_pitch->trimValue(pp->perspective.projection_pitch); projection_rotate->trimValue(pp->perspective.projection_rotate); - projection_scale->trimValue(pp->perspective.projection_scale); projection_shift_horiz->trimValue(pp->perspective.projection_shift_horiz); projection_shift_vert->trimValue(pp->perspective.projection_shift_vert); projection_yaw->trimValue(pp->perspective.projection_yaw); @@ -500,7 +483,6 @@ void PerspCorrection::setBatchMode (bool batchMode) camera_yaw->showEditedCB (); projection_pitch->showEditedCB (); projection_rotate->showEditedCB (); - projection_scale->showEditedCB (); projection_shift_horiz->showEditedCB (); projection_shift_vert->showEditedCB (); projection_yaw->showEditedCB (); diff --git a/rtgui/perspective.h b/rtgui/perspective.h index c1dd20ee4..e09a766de 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -48,7 +48,6 @@ protected: Adjuster* camera_yaw; Adjuster* projection_pitch; Adjuster* projection_rotate; - Adjuster* projection_scale; Adjuster* projection_shift_horiz; Adjuster* projection_shift_vert; Adjuster* projection_yaw; @@ -69,7 +68,7 @@ public: void adjusterChanged (Adjuster* a, double newval) override; void autoCorrectionPressed (Gtk::Button* b); void methodChanged (void); - void setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool camera_angle_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add, bool projection_scale_add); + void setAdjusterBehavior (bool badd, bool camera_focal_length_add, bool camera_shift_add, bool camera_angle_add, bool projection_angle_add, bool projection_shift_add, bool projection_rotate_add); void setLensGeomListener (LensGeomListener* listener) { lens_geom_listener = listener; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 740b81ebd..2b36b792f 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -349,7 +349,6 @@ Gtk::Widget* Preferences::getBatchProcPanel () appendBehavList (mi, M ("TP_PERSPECTIVE_CAMERA_FRAME") + " - " + M ("TP_PERSPECTIVE_CAMERA_ROLL") + ", " + M ("TP_PERSPECTIVE_CAMERA_YAW") + ", " + M ("TP_PERSPECTIVE_CAMERA_PITCH"), ADDSET_PERSP_CAM_ANGLE, false); appendBehavList (mi, M ("TP_PERSPECTIVE_POST_CORRECTION_ADJUSTMENT_FRAME") + " - " + M ("TP_PERSPECTIVE_PROJECTION_SHIFT_HORIZONTAL") + ", " + M ("TP_PERSPECTIVE_PROJECTION_SHIFT_VERTICAL"), ADDSET_PERSP_PROJ_SHIFT, false); appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_ROTATE"), ADDSET_PERSP_PROJ_ROTATE, false); - appendBehavList (mi, M ("TP_PERSPECTIVE_PROJECTION_SCALE"), ADDSET_PERSP_PROJ_SCALE, false); appendBehavList (mi, M ("TP_PERSPECTIVE_RECOVERY_FRAME") + " - " + M ("TP_PERSPECTIVE_PROJECTION_YAW") + ", " + M ("TP_PERSPECTIVE_PROJECTION_PITCH"), ADDSET_PERSP_PROJ_ANGLE, false); mi = behModel->append (); From 852b3270e1fbbef1d3d2b170de226c6369b8b732 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Mon, 4 May 2020 18:15:27 -0700 Subject: [PATCH 18/20] Make history message key names more descriptive Use keys that are more descriptive than "HISTORY_MSG_###". --- rtdata/languages/default | 14 +++++++------- rtengine/procevents.h | 7 ------- rtengine/refreshmap.cc | 9 +-------- rtgui/perspective.cc | 9 +++++++++ rtgui/perspective.h | 7 +++++++ 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index b22b00e52..e1b4399f6 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -728,13 +728,6 @@ HISTORY_MSG_491;White Balance HISTORY_MSG_492;RGB Curves HISTORY_MSG_493;L*a*b* Adjustments HISTORY_MSG_494;Capture Sharpening -HISTORY_MSG_496;Perspective - Camera -HISTORY_MSG_497;Perspective - Camera -HISTORY_MSG_498;Perspective - Recovery -HISTORY_MSG_499;Perspective - PCA - Rotation -HISTORY_MSG_500;Perspective - PCA -HISTORY_MSG_501;Perspective - Camera -HISTORY_MSG_502;Perspective - Method HISTORY_MSG_CAT02PRESET;Cat02 automatic preset HISTORY_MSG_CLAMPOOG;Clip out-of-gamut colors HISTORY_MSG_COLORTONING_LABGRID_VALUE;CT - Color correction @@ -783,6 +776,13 @@ HISTORY_MSG_PDSHARPEN_CONTRAST;CS - Contrast threshold HISTORY_MSG_PDSHARPEN_ITERATIONS;CS - Iterations HISTORY_MSG_PDSHARPEN_RADIUS;CS - Radius HISTORY_MSG_PDSHARPEN_RADIUS_BOOST;CS - Corner radius boost +HISTORY_MSG_PERSP_CAM_ANGLE;Perspective - Camera +HISTORY_MSG_PERSP_CAM_FL;Perspective - Camera +HISTORY_MSG_PERSP_CAM_SHIFT;Perspective - Camera +HISTORY_MSG_PERSP_METHOD;Perspective - Method +HISTORY_MSG_PERSP_PROJ_ANGLE;Perspective - Recovery +HISTORY_MSG_PERSP_PROJ_ROTATE;Perspective - PCA rotation +HISTORY_MSG_PERSP_PROJ_SHIFT;Perspective - PCA HISTORY_MSG_PIXELSHIFT_DEMOSAIC;PS - Demosaic method for motion HISTORY_MSG_PREPROCESS_LINEDENOISE_DIRECTION;Line noise filter direction HISTORY_MSG_PREPROCESS_PDAFLINESFILTER;PDAF lines filter diff --git a/rtengine/procevents.h b/rtengine/procevents.h index bd8b06650..1b00d4218 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -518,13 +518,6 @@ enum ProcEventCode { EvLEnabled = 492, EvPdShrEnabled = 493, EvPdShrMaskToggled = 494, - EvPerspCamFocalLength = 495, - EvPerspCamShift = 496, - EvPerspProjAngle = 497, - EvPerspProjRotate = 498, - EvPerspProjShift = 499, - EvPerspCamAngle = 500, - EvPerspMethod = 501, NUMOFEVENTS diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index 29384b652..b77eac29c 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -521,14 +521,7 @@ int refreshmap[rtengine::NUMOFEVENTS] = { RGBCURVE, // EvRGBEnabled LUMINANCECURVE, // EvLEnabled DEMOSAIC, // EvPdShrEnabled - CAPTURESHARPEN, // EvPdShrMaskToggled - TRANSFORM, // EvPerspCamFocalLength - TRANSFORM, // EvPerspCamShift - TRANSFORM, // EvPerspProjAngle - TRANSFORM, // EvPerspProjRotate - TRANSFORM, // EvPerspProjShift - TRANSFORM, // EvPerspCamAngle - TRANSFORM // EvPerspMethod + CAPTURESHARPEN // EvPdShrMaskToggled }; diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index 42175616f..0607a7604 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -16,6 +16,7 @@ * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ +#include "eventmapper.h" #include "perspective.h" #include "rtimage.h" @@ -28,6 +29,14 @@ using namespace rtengine::procparams; PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M("TP_PERSPECTIVE_LABEL")) { + auto mapper = ProcEventMapper::getInstance(); + EvPerspCamAngle = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_CAM_ANGLE"); + EvPerspCamFocalLength = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_CAM_FL"); + EvPerspCamShift = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_CAM_SHIFT"); + EvPerspMethod = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_METHOD"); + EvPerspProjAngle = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_PROJ_ANGLE"); + EvPerspProjRotate = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_PROJ_ROTATE"); + EvPerspProjShift = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_PROJ_SHIFT"); lens_geom_listener = nullptr; Gtk::Image* ipersHL = Gtk::manage (new RTImage ("perspective-horizontal-left-small.png")); diff --git a/rtgui/perspective.h b/rtgui/perspective.h index e09a766de..1be392f7c 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -51,6 +51,13 @@ protected: Adjuster* projection_shift_horiz; Adjuster* projection_shift_vert; Adjuster* projection_yaw; + rtengine::ProcEvent EvPerspCamFocalLength; + rtengine::ProcEvent EvPerspCamShift; + rtengine::ProcEvent EvPerspCamAngle; + rtengine::ProcEvent EvPerspMethod; + rtengine::ProcEvent EvPerspProjShift; + rtengine::ProcEvent EvPerspProjRotate; + rtengine::ProcEvent EvPerspProjAngle; LensGeomListener* lens_geom_listener; const rtengine::FramesMetaData* metadata; From 7b4504e34f2c6b235b5e1f8dbc2682bd8ccdfdd8 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Fri, 12 Jun 2020 10:49:11 -0700 Subject: [PATCH 19/20] Add pointer initializer to avoid segfaults PerspCorrection::metadata was not initialized, which could cause a segfault. --- rtgui/perspective.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index 0607a7604..bdba2dcc1 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -38,6 +38,7 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" EvPerspProjRotate = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_PROJ_ROTATE"); EvPerspProjShift = mapper->newEvent(TRANSFORM, "HISTORY_MSG_PERSP_PROJ_SHIFT"); lens_geom_listener = nullptr; + metadata = nullptr; Gtk::Image* ipersHL = Gtk::manage (new RTImage ("perspective-horizontal-left-small.png")); Gtk::Image* ipersHR = Gtk::manage (new RTImage ("perspective-horizontal-right-small.png")); From e507416a2af140fef50f70d3ff88a584f1fbb0d5 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Fri, 19 Jun 2020 12:21:32 -0700 Subject: [PATCH 20/20] Update AUTHORS.txt --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 6ec33e1da..aae24ff63 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -21,6 +21,7 @@ Development contributors, in last name alphabetical order: Steve Herrell Philippe HupĂ© Wolfgang Kuehnel + Lawrence Lee Guokai Ma Emil Martinec Wyatt Olson