From 6b7c1871b9bd14e0bd6aa854953b46c032969033 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Mon, 8 Jun 2020 18:21:12 -0700 Subject: [PATCH] Implement basic perspective control lines (WIP) --- rtengine/iptransform.cc | 7 +- rtengine/perspectivecorrection.cc | 125 +++++--- rtengine/perspectivecorrection.h | 14 +- rtengine/procparams.cc | 2 + rtengine/procparams.h | 1 + rtgui/cropwindow.cc | 7 +- rtgui/editcallbacks.h | 6 +- rtgui/lensgeomlistener.h | 11 +- rtgui/perspective.cc | 464 ++++++++++++++++++++++++++++++ rtgui/perspective.h | 98 +++++++ rtgui/toolpanelcoord.cc | 16 +- rtgui/toolpanelcoord.h | 3 +- 12 files changed, 703 insertions(+), 51 deletions(-) diff --git a/rtengine/iptransform.cc b/rtengine/iptransform.cc index eb823d711..aa22aac56 100644 --- a/rtengine/iptransform.cc +++ b/rtengine/iptransform.cc @@ -454,7 +454,7 @@ 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); - double ascale = ascaleDef > 0 ? ascaleDef : (params->commonTrans.autofill ? getTransformAutoFill (oW, oH, pLCPMap) : 1.0); + double ascale = ascaleDef > 0 ? ascaleDef : (params->commonTrans.autofill && params->perspective.render ? getTransformAutoFill (oW, oH, pLCPMap) : 1.0); // auxiliary variables for perspective correction // Simple. @@ -1185,7 +1185,7 @@ void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, I p_projection_rotate, p_projection_shift_horiz, p_projection_shift_vert, p_projection_scale); - const double ascale = params->commonTrans.autofill ? getTransformAutoFill(oW, oH, pLCPMap) : 1.0; + const double ascale = params->commonTrans.autofill && params->perspective.render ? getTransformAutoFill(oW, oH, pLCPMap) : 1.0; const bool darkening = (params->vignetting.amount <= 0.0); const bool useLog = params->commonTrans.method == "log" && highQuality; @@ -1477,7 +1477,8 @@ bool ImProcFunctions::needsPerspective () const { return ( (params->perspective.method == "simple") && (params->perspective.horizontal || params->perspective.vertical) ) - || ( (params->perspective.method == "camera_based") && ( + || ( (params->perspective.method == "camera_based") && + params->perspective.render && ( params->perspective.camera_pitch || params->perspective.camera_roll || params->perspective.camera_shift_horiz || diff --git a/rtengine/perspectivecorrection.cc b/rtengine/perspectivecorrection.cc index 5fa7b32f3..2c8d9e9f2 100644 --- a/rtengine/perspectivecorrection.cc +++ b/rtengine/perspectivecorrection.cc @@ -225,10 +225,44 @@ void get_view_size(int w, int h, const procparams::PerspectiveParams ¶ms, do } */ +/** + * Allocates a new array and populates it with ashift lines corresponding to the + * provided control lines. + */ +dt_iop_ashift_line_t* toAshiftLines(const ControlLine *lines, size_t count) +{ + auto retval = (dt_iop_ashift_line_t*)malloc(count * sizeof(dt_iop_ashift_line_t)); + + for (size_t i = 0; i < count; i++) { + const float x1 = lines[i].x1; + const float y1 = lines[i].y1; + const float x2 = lines[i].x2; + const float y2 = lines[i].y2; + retval[i].p1[0] = x1; + retval[i].p1[1] = y1; + retval[i].p1[2] = 1.0f; + retval[i].p2[0] = x2; + retval[i].p2[1] = y2; + retval[i].p2[2] = 1.0f; + retval[i].length = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + retval[i].width = 1.0f; + retval[i].weight = retval[i].length; + if (lines[i].type == ControlLine::HORIZONTAL) { + retval[i].type = ASHIFT_LINE_HORIZONTAL_SELECTED; + } else if (lines[i].type == ControlLine::VERTICAL) { + retval[i].type = ASHIFT_LINE_VERTICAL_SELECTED; + } else { + retval[i].type = ASHIFT_LINE_IRRELEVANT; + } + } + + return retval; +} + } // namespace -PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *src, bool corr_pitch, bool corr_yaw, const procparams::ProcParams *pparams, const FramesMetaData *metadata) +PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *src, bool corr_pitch, bool corr_yaw, const procparams::ProcParams *pparams, const FramesMetaData *metadata, const ControlLine *control_lines, size_t control_lines_count) { auto pcp = procparams::PerspectiveParams(pparams->perspective); procparams::PerspectiveParams dflt; @@ -252,49 +286,51 @@ PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *sr 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)); + if (control_lines == nullptr) { + 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.toneCurve, neutral.raw); - src->convertColorSpace(img.get(), pparams->icm, src->getWB()); + 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.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; - ImProcFunctions ipf(&neutral, true); - if (ipf.needsTransform(w, h, src->getRotateDegree(), src->getMetaData())) { - 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); - } + 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; + ImProcFunctions ipf(&neutral, true); + if (ipf.needsTransform(w, h, src->getRotateDegree(), src->getMetaData())) { + 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; + // allocate the gui buffer + g.buf = static_cast(malloc(sizeof(float) * w * h * 4)); + g.buf_width = w; + g.buf_height = h; + + img->normalizeFloatTo1(); - 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; + 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; + } } } @@ -311,7 +347,20 @@ PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *sr // internally! srand(1); - auto res = do_get_structure(&module, &p, ASHIFT_ENHANCE_EDGES) && do_fit(&module, &p, fitaxis); + bool res; + if (control_lines == nullptr) { + res = do_get_structure(&module, &p, ASHIFT_ENHANCE_EDGES) && do_fit(&module, &p, fitaxis); + } else { + dt_iop_ashift_gui_data_t *g = module.gui_data; + g->lines_count = control_lines_count; + g->lines = toAshiftLines(control_lines, control_lines_count); + g->lines_in_height = fh; + g->lines_in_width = fw; + // A hack. + g->horizontal_count = 4; + g->vertical_count = 4; + res = do_fit(&module, &p, fitaxis); + } Params retval = { .angle = p.rotation, .pitch = p.camera_pitch, diff --git a/rtengine/perspectivecorrection.h b/rtengine/perspectivecorrection.h index bf7cfa08d..94a63a42c 100644 --- a/rtengine/perspectivecorrection.h +++ b/rtengine/perspectivecorrection.h @@ -26,6 +26,18 @@ namespace rtengine { +class ControlLine +{ +public: + enum Type + { + HORIZONTAL, + VERTICAL + }; + float x1, y1, x2, y2; + Type type; +}; + class PerspectiveCorrection { public: struct Params @@ -35,7 +47,7 @@ public: double yaw; }; - static Params autocompute(ImageSource *src, bool corr_pitch, bool corr_yaw, const procparams::ProcParams *pparams, const FramesMetaData *metadata); + static Params autocompute(ImageSource *src, bool corr_pitch, bool corr_yaw, const procparams::ProcParams *pparams, const FramesMetaData *metadata, const ControlLine *control_lines = nullptr, size_t control_lines_count = 0); //static void autocrop(int width, int height, bool fixratio, const procparams::PerspectiveParams ¶ms, const FramesMetaData *metadata, int &x, int &y, int &w, int &h); }; diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index af48d027a..e00e50186 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1860,6 +1860,7 @@ LensProfParams::LcMode LensProfParams::getMethodNumber(const Glib::ustring& mode PerspectiveParams::PerspectiveParams() : method("simple"), + render(true), horizontal(0.0), vertical(0.0), camera_crop_factor(0.0), @@ -1881,6 +1882,7 @@ bool PerspectiveParams::operator ==(const PerspectiveParams& other) const { return method == other.method + && render == other.render && horizontal == other.horizontal && vertical == other.vertical && camera_focal_length == other.camera_focal_length diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 8a95032e8..b6d2b9c3a 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -911,6 +911,7 @@ struct LensProfParams { */ struct PerspectiveParams { Glib::ustring method; + bool render; double horizontal; double vertical; double camera_crop_factor; diff --git a/rtgui/cropwindow.cc b/rtgui/cropwindow.cc index d87876cec..975bac15c 100644 --- a/rtgui/cropwindow.cc +++ b/rtgui/cropwindow.cc @@ -429,6 +429,8 @@ void CropWindow::buttonPress (int button, int type, int bstate, int x, int y) state = SEditPick1; pickedObject = iarea->getObject(); pickModifierKey = bstate; + } else { + state = SCropImgMove; } press_x = x; press_y = y; @@ -764,7 +766,10 @@ void CropWindow::buttonRelease (int button, int num, int bstate, int x, int y) iarea->setObject(ObjectMOBuffer::getObjectID(cropPos)); - bool elemPicked = iarea->getObject() == pickedObject && bstate == pickModifierKey; + int buttonMask = ((state == SEditPick1) ? GDK_BUTTON1_MASK : 0) + | ((state == SEditPick2) ? GDK_BUTTON2_MASK : 0) + | ((state == SEditPick3) ? GDK_BUTTON3_MASK : 0); + bool elemPicked = iarea->getObject() == pickedObject && bstate == (pickModifierKey | buttonMask); if (state == SEditPick1) { needRedraw = editSubscriber->pick1 (elemPicked); diff --git a/rtgui/editcallbacks.h b/rtgui/editcallbacks.h index ee357c2f7..c2efcf53e 100644 --- a/rtgui/editcallbacks.h +++ b/rtgui/editcallbacks.h @@ -129,19 +129,19 @@ public: @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. @return true if the preview has to be redrawn, false otherwise */ - bool pick1 (bool picked); + virtual bool pick1 (bool picked); /** @brief Triggered when the user is releasing mouse button 2 while in action==ES_ACTION_PICKING mode @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. @return true if the preview has to be redrawn, false otherwise */ - bool pick2 (bool picked); + virtual bool pick2 (bool picked); /** @brief Triggered when the user is releasing mouse button 3 while in action==ES_ACTION_PICKING mode @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. @return true if the preview has to be redrawn, false otherwise */ - bool pick3 (bool picked); + virtual bool pick3 (bool picked); /** @brief Get the geometry to be shown to the user */ const std::vector& getVisibleGeometry (); diff --git a/rtgui/lensgeomlistener.h b/rtgui/lensgeomlistener.h index 810b7ed98..9bbaf125a 100644 --- a/rtgui/lensgeomlistener.h +++ b/rtgui/lensgeomlistener.h @@ -18,6 +18,14 @@ */ #pragma once +#include + +namespace rtengine +{ +class ControlLine; +class ProcEvent; +} + class LensGeomListener { public: @@ -25,5 +33,6 @@ 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; + virtual void autoPerspRequested (bool corr_pitch, bool corr_yaw, double& rot, double& pitch, double& yaw, const rtengine::ControlLine *lines = nullptr, size_t line_count = 0) = 0; + virtual void updateTransformPreviewRequested (rtengine::ProcEvent event, bool render_perspective) = 0; }; diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index 0607a7604..1b1672914 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -37,6 +37,7 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" 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"); + EvPerspRender = mapper->newEvent(TRANSFORM); lens_geom_listener = nullptr; Gtk::Image* ipersHL = Gtk::manage (new RTImage ("perspective-horizontal-left-small.png")); @@ -107,6 +108,31 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" -60, 60, 0.1, 0, ipers_cam_yaw_left, ipers_cam_yaw_right)); camera_yaw->setAdjusterListener (this); + // Begin control lines interface. + lines_button_h = Gtk::manage (new Gtk::ToggleButton()); + lines_button_h->signal_toggled().connect(sigc::bind(sigc::mem_fun( + *this, &::PerspCorrection::linesButtonPressed), lines_button_h)); + + lines_button_v = Gtk::manage (new Gtk::ToggleButton()); + lines_button_v->signal_toggled().connect(sigc::bind(sigc::mem_fun( + *this, &::PerspCorrection::linesButtonPressed), lines_button_v)); + + lines_button_edit = Gtk::manage (new Gtk::ToggleButton()); + lines_button_edit->signal_toggled().connect(sigc::mem_fun( + *this, &::PerspCorrection::linesEditButtonPressed)); + + lines = new ControlLineManager(); + lines->callbacks = new LinesCallbacks(this, lines); + + img_ctrl_lines_apply = NULL; + img_ctrl_lines_edit = NULL; + + Gtk::HBox* control_lines_box = Gtk::manage (new Gtk::HBox()); + control_lines_box->pack_start(*lines_button_v); + control_lines_box->pack_start(*lines_button_h); + control_lines_box->pack_start(*lines_button_edit); + // End control lines interface. + 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) ); @@ -164,6 +190,7 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" camera_vbox->pack_start (*camera_roll); camera_vbox->pack_start (*camera_pitch); camera_vbox->pack_start (*camera_yaw); + camera_vbox->pack_start (*control_lines_box); camera_vbox->pack_start (*auto_hbox); camera_frame->add(*camera_vbox); camera_based->pack_start(*camera_frame); @@ -192,6 +219,18 @@ PerspCorrection::PerspCorrection () : FoldableToolPanel(this, "perspective", M(" show_all(); } +PerspCorrection::~PerspCorrection() +{ + delete lines->callbacks; + delete lines; + if (img_ctrl_lines_apply) { + delete img_ctrl_lines_apply; + } + if (img_ctrl_lines_edit) { + delete img_ctrl_lines_edit; + } +} + void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) { @@ -242,6 +281,8 @@ void PerspCorrection::read (const ProcParams* pp, const ParamsEdited* pedited) void PerspCorrection::write (ProcParams* pp, ParamsEdited* pedited) { + pp->perspective.render = render; + pp->perspective.horizontal = horiz->getValue (); pp->perspective.vertical = vert->getValue (); pp->perspective.camera_crop_factor= camera_crop_factor->getValue (); @@ -387,6 +428,39 @@ void PerspCorrection::adjusterChanged(Adjuster* a, double newval) } } +void PerspCorrection::applyControlLines(void) +{ + if (!lens_geom_listener) { + return; + } + + auto control_lines = lines->toControlLines(); + int h_count = 0, v_count = 0; + double rot = 0; + double pitch = 0; + double yaw = 0; + + for (unsigned int i = 0; i < lines->size(); i++) { + if (control_lines[i].type == rtengine::ControlLine::HORIZONTAL) { + h_count++; + } else if (control_lines[i].type == rtengine::ControlLine::VERTICAL) { + v_count++; + } + } + lens_geom_listener->autoPerspRequested(v_count > 1, h_count > 1, rot, pitch, + yaw, control_lines, lines->size()); + + free(control_lines); + + disableListener(); + camera_pitch->setValue(pitch); + camera_roll->setValue(rot); + camera_yaw->setValue(yaw); + enableListener(); + + adjusterChanged(camera_pitch, pitch); +} + void PerspCorrection::autoCorrectionPressed(Gtk::Button* b) { if (!lens_geom_listener) { @@ -548,3 +622,393 @@ void PerspCorrection::setFocalLengthValue (const ProcParams* pparams, const Fram camera_focal_length->setValue(default_focal_length); } } + +void PerspCorrection::switchOffEditMode(ControlLineManager* lines) +{ + lines_button_h->set_active(false); + lines_button_v->set_active(false); + lines_button_edit->set_active(false); +} + +void PerspCorrection::setEditProvider(EditDataProvider* provider) +{ + lines->setEditProvider(provider); +} + +void PerspCorrection::linesButtonPressed(Gtk::ToggleButton* button) +{ + lines->setLinesState(lines_button_h->get_active(), lines_button_v->get_active()); + + if (!button->get_active()) { + return; + } + + if (button == lines_button_h) { + lines->draw_line_type = rtengine::ControlLine::HORIZONTAL; + if (lines_button_v->get_active()) { + lines_button_v->set_active(false); + } + } else if (button == lines_button_v) { + lines->draw_line_type = rtengine::ControlLine::VERTICAL; + if (lines_button_h->get_active()) { + lines_button_h->set_active(false); + } + } + + if (!lines_button_edit->get_active()) { + lines_button_edit->set_active(true); + } + + lines->setDrawMode(true); +} + +void PerspCorrection::linesEditButtonPressed(void) +{ + if (lines_button_edit->get_active()) { // Enter edit mode. + lines->setActive(true); + if (img_ctrl_lines_apply) { + lines_button_edit->set_image(*img_ctrl_lines_apply); + } + render = false; + lines->setLinesState(lines_button_h->get_active(), lines_button_v->get_active()); + if (lens_geom_listener) { + lens_geom_listener->updateTransformPreviewRequested(EvPerspRender, false); + } + } else { // Leave edit mode. + render = true; + lines->setDrawMode(false); + lines->setActive(false); + if (img_ctrl_lines_edit) { + lines_button_edit->set_image(*img_ctrl_lines_edit); + } + lines_button_h->set_active(false); + lines_button_v->set_active(false); + applyControlLines(); + } +} + +ControlLineManager::ControlLineManager(): + EditSubscriber(ET_OBJECTS), + cursor(CSCrosshair), + prev_obj(-1), + selected_object(-1) +{ + canvas_area = new Rectangle(); + canvas_area->filled = true; + canvas_area->topLeft = Coord(0, 0); + mouseOverGeometry.push_back(canvas_area); +} + +ControlLineManager::~ControlLineManager() +{ + for (auto i = mouseOverGeometry.begin(); i != mouseOverGeometry.end(); i++) { + delete *i; + } + for (auto i = control_lines.begin(); i != control_lines.end(); i++) { + delete *i; + } +} + +Geometry::State ControlLineManager::calcLineState(const ::ControlLine& line) const +{ + if (line.type == rtengine::ControlLine::HORIZONTAL && active_h) { + return Geometry::NORMAL; + } else if (line.type == rtengine::ControlLine::VERTICAL && active_v) { + return Geometry::NORMAL; + } + return Geometry::INSENSITIVE; +} + +void ControlLineManager::setActive(bool active) +{ + EditDataProvider* provider = getEditProvider(); + + if (!provider || (this == provider->getCurrSubscriber()) == active) { + return; + } + + if (active) { + subscribe(); + + int ih, iw; + provider->getImageSize(iw, ih); + canvas_area->bottomRight = Coord(iw, ih); + } else { + unsubscribe(); + } +} + +void ControlLineManager::setDrawMode(bool draw) +{ + draw_mode = draw; +} + +void ControlLineManager::setLinesState(bool horiz_active, bool vert_active) +{ + active_h = horiz_active; + active_v = vert_active; + + for (auto line = control_lines.begin(); line != control_lines.end(); line++) { + auto state = calcLineState(**line); + (*line)->begin->state = state; + (*line)->end->state = state; + (*line)->line->state = state; + } +} + +size_t ControlLineManager::size(void) const +{ + return control_lines.size(); +} + +bool ControlLineManager::button1Pressed(int modifierKey) +{ + EditDataProvider* dataProvider = getEditProvider(); + + if (!dataProvider) { + return false; + } + + drag_delta = Coord(0, 0); + + const int object = dataProvider->getObject(); + if (object > 0) { // A control line. + selected_object = object; + action = Action::DRAGGING; + } else if (draw_mode && (modifierKey & GDK_CONTROL_MASK)) { // Add new line. + addLine(dataProvider->posImage, dataProvider->posImage); + selected_object = mouseOverGeometry.size() - 1; // Select endpoint. + action = Action::DRAGGING; + } + + return false; +} + +bool ControlLineManager::button1Released(void) +{ + action = Action::NONE; + selected_object = -1; + return false; +} + +bool ControlLineManager::button3Pressed(int modifierKey) +{ + EditDataProvider* provider = getEditProvider(); + + action = Action::NONE; + + if (!provider || provider->getObject() < 1) { + return false; + } + + action = Action::PICKING; + return false; +} + +bool ControlLineManager::pick3(bool picked) +{ + if (!picked) { + return false; + } + + EditDataProvider* provider = getEditProvider(); + + if (!provider) { + return false; + } + + removeLine((provider->getObject() - 1) / 3); + return false; +} + +bool ControlLineManager::drag1(int modifierKey) +{ + EditDataProvider* provider = getEditProvider(); + + if (!provider || selected_object < 1) { + return false; + } + + ::ControlLine* control_line = control_lines[(selected_object - 1) / 3]; + int component = selected_object % 3; // 0 == end, 1 == line, 2 == begin + Coord mouse = provider->posImage + provider->deltaImage; + Coord delta = provider->deltaImage - drag_delta; + int ih, iw; + provider->getImageSize(iw, ih); + + switch (component) { + case (0): // end + control_line->end->center = mouse; + control_line->end->center.clip(iw, ih); + control_line->line->end = control_line->end->center; + control_line->end->state = Geometry::DRAGGED; + break; + case (1): { // line + // Constrain delta so the end stays above the image. + Coord new_delta = control_line->end->center + delta; + new_delta.clip(iw, ih); + new_delta -= control_line->end->center; + // Constrain delta so the beginning stays above the image. + new_delta += control_line->begin->center; + new_delta.clip(iw, ih); + new_delta -= control_line->begin->center; + // Move all objects in the control line. + control_line->end->center += new_delta; + control_line->begin->center += new_delta; + control_line->line->end = control_line->end->center; + control_line->line->begin = control_line->begin->center; + drag_delta += new_delta; + control_line->line->state = Geometry::DRAGGED; + break; + } + case (2): // begin + control_line->begin->center = mouse; + control_line->begin->center.clip(iw, ih); + control_line->line->begin = control_line->begin->center; + control_line->begin->state = Geometry::DRAGGED; + break; + } + + return false; +} + +CursorShape ControlLineManager::getCursor(int objectID) const +{ + return cursor; +} + +bool ControlLineManager::mouseOver(int modifierKey) +{ + EditDataProvider* provider = getEditProvider(); + + if (!provider) { + return false; + } + + int cur_obj = provider->getObject(); + + if (cur_obj == 0) { // Canvas + if (draw_mode && modifierKey & GDK_CONTROL_MASK) { + cursor = CSPlus; + } else { + cursor = CSCrosshair; + } + } else if (cur_obj < 0) { // Nothing + cursor = CSArrow; + } else { // Object + visibleGeometry[cur_obj - 1]->state = Geometry::PRELIGHT; + cursor = CSMove2D; + } + + if (prev_obj != cur_obj && prev_obj > 0) { + auto state = calcLineState(*control_lines[(prev_obj - 1) / 3]); + visibleGeometry[prev_obj - 1]->state = state; + } + + prev_obj = cur_obj; + + return false; +} + +void ControlLineManager::switchOffEditMode(void) +{ + if (callbacks) { + callbacks->switchOffEditMode(); + } +} + +void ControlLineManager::setEditProvider(EditDataProvider* provider) +{ + EditSubscriber::setEditProvider(provider); +} + +void ControlLineManager::addLine(Coord begin, Coord end) +{ + constexpr int line_width = 2; + constexpr int handle_radius = 6; + Line* line; + Circle *begin_c, *end_c; + + line = new Line(); + line->datum = Geometry::IMAGE; + line->innerLineWidth = line_width; + line->begin = begin; + line->end = end; + + begin_c = new Circle(); + begin_c->datum = Geometry::IMAGE; + begin_c->filled = true; + begin_c->radius = handle_radius; + begin_c->center = begin; + + end_c = new Circle(); + end_c->datum = Geometry::IMAGE; + end_c->filled = true; + end_c->radius = handle_radius; + end_c->center = begin; + + EditSubscriber::visibleGeometry.push_back(line); + EditSubscriber::visibleGeometry.push_back(begin_c); + EditSubscriber::visibleGeometry.push_back(end_c); + + EditSubscriber::mouseOverGeometry.push_back(line); + EditSubscriber::mouseOverGeometry.push_back(begin_c); + EditSubscriber::mouseOverGeometry.push_back(end_c); + + ::ControlLine* control_line = new ::ControlLine(); + control_line->begin = begin_c; + control_line->end = end_c; + control_line->line = line; + control_line->type = draw_line_type; + control_lines.push_back(control_line); +} + +void ControlLineManager::removeLine(size_t line_id) +{ + if (line_id >= control_lines.size()) { + return; + } + + ::ControlLine* line = control_lines[line_id]; + delete line->begin; + delete line->end; + delete line->line; + delete line; + control_lines.erase(control_lines.begin() + line_id); + visibleGeometry.erase(visibleGeometry.begin() + 3 * line_id, + visibleGeometry.begin() + 3 * line_id + 3); + mouseOverGeometry.erase(mouseOverGeometry.begin() + 3 * line_id + 1, + mouseOverGeometry.begin() + 3 * line_id + 4); +} + +rtengine::ControlLine* ControlLineManager::toControlLines(void) const +{ + auto retval = (rtengine::ControlLine*)malloc(control_lines.size() * sizeof(rtengine::ControlLine)); + + for (unsigned int i = 0; i < control_lines.size(); i++) { + retval[i].x1 = control_lines[i]->begin->center.x; + retval[i].y1 = control_lines[i]->begin->center.y; + retval[i].x2 = control_lines[i]->end->center.x; + retval[i].y2 = control_lines[i]->end->center.y; + retval[i].type = control_lines[i]->type; + } + + return retval; +} + +LinesCallbacks::LinesCallbacks(PerspCorrection* tool, ControlLineManager* lines): + lines(lines), + tool(tool) +{ +} + +LinesCallbacks::~LinesCallbacks() +{ +} + +void LinesCallbacks::switchOffEditMode(void) +{ + if (tool) { + tool->switchOffEditMode(lines); + } +} diff --git a/rtgui/perspective.h b/rtgui/perspective.h index 1be392f7c..8a9933b19 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -21,8 +21,80 @@ #include #include "adjuster.h" +#include "editcallbacks.h" +#include "editwidgets.h" #include "lensgeomlistener.h" #include "toolpanel.h" +#include "../rtengine/coord.h" +#include "../rtengine/perspectivecorrection.h" + +struct ControlLine +{ + Line* line; + Circle *begin, *end; + rtengine::ControlLine::Type type; +}; + +class ControlLineManager: EditSubscriber +{ + +protected: + /** Determine how horizontal and vertical lines are displayed. */ + bool active_h, active_v; + /** Hidden object for capturing mouse events. */ + Rectangle* canvas_area; + rtengine::Coord drag_delta; + std::vector control_lines; + CursorShape cursor; + bool draw_mode; + int prev_obj; + int selected_object; + + void addLine (rtengine::Coord begin, rtengine::Coord end); + Geometry::State calcLineState(const ControlLine& line) const; + void removeLine (size_t line_id); + +public: + class Callbacks + { + public: + virtual ~Callbacks() {}; + /** Called when the EditSubscriber's switchOffEditMode is called. */ + virtual void switchOffEditMode (void) {}; + }; + + /** Callbacks to invoke. */ + Callbacks* callbacks; + /** Type of line for newly drawn lines. */ + rtengine::ControlLine::Type draw_line_type; + + ControlLineManager(); + ~ControlLineManager(); + + /** Sets whether or not the lines are visible and interact-able. */ + void setActive (bool active); + /** Set whether or not lines can be drawn and deleted. */ + void setDrawMode (bool draw); + void setEditProvider (EditDataProvider* provider); + /** Determines how each line type is displayed. */ + void setLinesState (bool horiz_active, bool vert_active); + /** Returns the number of lines. */ + size_t size (void) const; + /** + * Allocates a new array and populates it with copies of the control lines. + */ + rtengine::ControlLine* toControlLines (void) const; + + // EditSubscriber overrides + bool button1Pressed (int modifierKey) override; + bool button1Released (void) override; + bool button3Pressed (int modifierKey) override; + bool pick3 (bool picked) override; + bool drag1 (int modifierKey) override; + CursorShape getCursor (int objectID) const override; + bool mouseOver (int modifierKey) override; + void switchOffEditMode (void) override; +}; class PerspCorrection final : public ToolParamBlock, @@ -31,6 +103,7 @@ class PerspCorrection final : { protected: + bool render = true; MyComboBoxText* method; Gtk::VBox* simple; Adjuster* horiz; @@ -46,6 +119,12 @@ protected: Adjuster* camera_shift_horiz; Adjuster* camera_shift_vert; Adjuster* camera_yaw; + Gtk::Image* img_ctrl_lines_edit; + Gtk::Image* img_ctrl_lines_apply; + ControlLineManager* lines; + Gtk::ToggleButton* lines_button_edit; + Gtk::ToggleButton* lines_button_h; + Gtk::ToggleButton* lines_button_v; Adjuster* projection_pitch; Adjuster* projection_rotate; Adjuster* projection_shift_horiz; @@ -58,14 +137,17 @@ protected: rtengine::ProcEvent EvPerspProjShift; rtengine::ProcEvent EvPerspProjRotate; rtengine::ProcEvent EvPerspProjAngle; + rtengine::ProcEvent EvPerspRender; LensGeomListener* lens_geom_listener; const rtengine::FramesMetaData* metadata; + void applyControlLines (void); void setFocalLengthValue (const rtengine::procparams::ProcParams* pparams, const rtengine::FramesMetaData* metadata); public: PerspCorrection (); + ~PerspCorrection (); void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr) override; void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr) override; @@ -74,12 +156,28 @@ public: void adjusterChanged (Adjuster* a, double newval) override; void autoCorrectionPressed (Gtk::Button* b); + void linesButtonPressed (Gtk::ToggleButton* button); + void linesEditButtonPressed (void); 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); + void setEditProvider (EditDataProvider* provider) override; void setLensGeomListener (LensGeomListener* listener) { lens_geom_listener = listener; } void setMetadata (const rtengine::FramesMetaData* metadata); + void switchOffEditMode (ControlLineManager* lines); void trimValues (rtengine::procparams::ProcParams* pp) override; }; + +class LinesCallbacks: public ControlLineManager::Callbacks +{ +protected: + ControlLineManager* lines; + PerspCorrection* tool; + +public: + LinesCallbacks(PerspCorrection* tool, ControlLineManager* lines); + ~LinesCallbacks(); + void switchOffEditMode (void) override; +}; diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index dadc56d44..d02dd0237 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -860,7 +860,7 @@ void ToolPanelCoordinator::straightenRequested () toolBar->setTool (TMStraighten); } -void ToolPanelCoordinator::autoPerspRequested (bool corr_pitch, bool corr_yaw, double& rot, double& pitch, double& yaw) +void ToolPanelCoordinator::autoPerspRequested (bool corr_pitch, bool corr_yaw, double& rot, double& pitch, double& yaw, const rtengine::ControlLine *lines, size_t line_count) { if (!(ipc && (corr_pitch || corr_yaw))) { return; @@ -874,7 +874,7 @@ void ToolPanelCoordinator::autoPerspRequested (bool corr_pitch, bool corr_yaw, d rtengine::procparams::ProcParams params; ipc->getParams(¶ms); - auto res = rtengine::PerspectiveCorrection::autocompute(src, corr_pitch, corr_yaw, ¶ms, src->getMetaData()); + auto res = rtengine::PerspectiveCorrection::autocompute(src, corr_pitch, corr_yaw, ¶ms, src->getMetaData(), lines, line_count); rot = res.angle; pitch = res.pitch; yaw = res.yaw; @@ -889,6 +889,16 @@ double ToolPanelCoordinator::autoDistorRequested () return rtengine::ImProcFunctions::getAutoDistor (ipc->getInitialImage()->getFileName(), 400); } +void ToolPanelCoordinator::updateTransformPreviewRequested(rtengine::ProcEvent event, bool render_perspective) +{ + if (!ipc) { + return; + } + + ipc->beginUpdateParams()->perspective.render = render_perspective; + ipc->endUpdateParams(event); +} + void ToolPanelCoordinator::spotWBRequested (int size) { @@ -1101,4 +1111,4 @@ bool ToolPanelCoordinator::getFilmNegativeExponents(rtengine::Coord spotA, rteng bool ToolPanelCoordinator::getRawSpotValues(rtengine::Coord spot, int spotSize, std::array& rawValues) { return ipc && ipc->getRawSpotValues(spot.x, spot.y, spotSize, rawValues); -} \ No newline at end of file +} diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 3861052cd..fe65a0745 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -302,8 +302,9 @@ public: // rotatelistener interface void straightenRequested () override; void autoCropRequested () override; - void autoPerspRequested (bool corr_pitch, bool corr_yaw, double& rot, double& pitch, double& yaw) override; + void autoPerspRequested (bool corr_pitch, bool corr_yaw, double& rot, double& pitch, double& yaw, const rtengine::ControlLine *lines = nullptr, size_t line_count = 0) override; double autoDistorRequested () override; + void updateTransformPreviewRequested (rtengine::ProcEvent event, bool render_perspective) override; // spotwblistener interface void spotWBRequested (int size) override;