diff --git a/plugins/tools/selectiontools/KisMagneticWorker.cc b/plugins/tools/selectiontools/KisMagneticWorker.cc index b12a867723..24e122a942 100644 --- a/plugins/tools/selectiontools/KisMagneticWorker.cc +++ b/plugins/tools/selectiontools/KisMagneticWorker.cc @@ -1,227 +1,229 @@ /* * Copyright (c) 2019 Kuntal Majumder * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisMagneticWorker.h" #include #include #include #include #include #include "KisMagneticGraph.h" struct DistanceMap { typedef VertexDescriptor key_type; typedef double data_type; typedef std::pair value_type; DistanceMap(double const& dval) : m_default(dval) { } data_type &operator[](key_type const& k) { if (m.find(k) == m.end()) m[k] = m_default; return m[k]; } private: std::map m; data_type const m_default; }; struct PredecessorMap{ PredecessorMap() { } PredecessorMap(PredecessorMap const& that): m_map(that.m_map) { } typedef VertexDescriptor key_type; typedef VertexDescriptor value_type; typedef VertexDescriptor & reference_type; typedef boost::read_write_property_map_tag category; VertexDescriptor &operator[](VertexDescriptor v){ return m_map[v]; } std::map m_map; }; VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v){ typename std::map::const_iterator found = m.m_map.find(v); return found != m.m_map.end() ? found->second : v; } void put(PredecessorMap &m, VertexDescriptor key, VertexDescriptor value){ m.m_map[key] = value; } double EuclideanDistance(VertexDescriptor p1, VertexDescriptor p2){ return std::sqrt(std::pow(p1.y-p2.y, 2) + std::pow(p1.x-p2.x, 2)); } class AStarHeuristic : public boost::astar_heuristic { private: VertexDescriptor m_goal; double coeff_a, coeff_b; public: AStarHeuristic(VertexDescriptor goal, double a, double b): m_goal(goal), coeff_a(a), coeff_b(b) { } AStarHeuristic(VertexDescriptor goal): m_goal(goal), coeff_a(0.5), coeff_b(0.5) { } double operator()(VertexDescriptor v){ return EuclideanDistance(v,m_goal); } }; struct GoalFound {}; class AStarGoalVisitor : public boost::default_astar_visitor { public: AStarGoalVisitor(VertexDescriptor goal) : m_goal(goal) { } void examine_vertex(VertexDescriptor u, KisMagneticGraph const &g) { Q_UNUSED(g) if(u == m_goal){ throw GoalFound(); } } private: VertexDescriptor m_goal; }; struct WeightMap{ typedef std::pair key_type; typedef double data_type; typedef std::pair value_type; WeightMap() { } WeightMap(KisMagneticGraph g): m_graph(g) { } data_type& operator[](key_type const& k) { if (m_map.find(k) == m_map.end()) { double edge_gradient = (m_graph.getIntensity(k.first) + m_graph.getIntensity(k.second))/2; m_map[k] = EuclideanDistance(k.first, k.second) * (edge_gradient + 1); } return m_map[k]; } private: std::map m_map; KisMagneticGraph m_graph; }; QRect KisMagneticWorker::calculateRect(QPoint p1, QPoint p2, int radius) const { // I am sure there is a simpler version of it which exists but well // Calculates a bounding rectangle based on the radius, // -------------------------------- // | ------------------- | // | | A* | | // | | | | // | | *B| | // | ------------------- | // -------------------------------- double slope = (p2.y() - p1.y())/(p2.x() - p1.x()); QPoint a,b,c,d; if(slope != 0){ slope = -1/slope; double denom = 2 * std::sqrt(slope*slope+1); double numer = radius/denom; double fac1 = numer/denom; denom = 2 * denom; numer = 3 * slope * numer; double fac2 = numer/denom; a = QPoint(p1.x() - fac1, p1.y() - fac2); b = QPoint(p1.x() + fac1, p1.y() + fac2); c = QPoint(p2.x() - fac1, p2.y() - fac2); d = QPoint(p2.x() + fac1, p2.y() + fac2); }else{ double fac = radius/2; a = QPoint(p1.x() - fac, p1.y() - fac); b = QPoint(p1.x() + fac, p1.y() + fac); c = QPoint(p2.x() - fac, p2.y() - fac); d = QPoint(p2.x() + fac, p2.y() + fac); } QPolygon p(QVector{a,b,c,d}); return p.boundingRect(); } QVector KisMagneticWorker::computeEdge(KisPaintDeviceSP dev, int radius, QPoint begin, QPoint end) { - QRect rect = calculateRect(begin, end, radius); + //QRect rect = calculateRect(begin, end, radius); + QRect rect = QPolygon(QVector{begin, end}).boundingRect(); + rect.setSize(rect.size()*10); KisGaussianKernel::applyLoG(dev, rect, 2, 1.0, QBitArray(), 0); KisLazyFillTools::normalizeAndInvertAlpha8Device(dev, rect); VertexDescriptor goal(end); VertexDescriptor start(begin); KisMagneticGraph g(dev, rect); // How many maps does it require? // Take a look here, if it doesn't make sense, https://www.boost.org/doc/libs/1_70_0/libs/graph/doc/astar_search.html PredecessorMap pmap; DistanceMap dmap(std::numeric_limits::max()); dmap[start] = 0; std::map rmap; std::map cmap; std::map imap; WeightMap wmap(g); AStarHeuristic heuristic(goal); QVector result; try{ boost::astar_search_no_init( g, start, heuristic ,boost::visitor(AStarGoalVisitor(goal)) .distance_map(boost::associative_property_map(dmap)) .predecessor_map(boost::ref(pmap)) .weight_map(boost::associative_property_map(wmap)) .vertex_index_map(boost::associative_property_map>(imap)) .rank_map(boost::associative_property_map>(rmap)) .color_map(boost::associative_property_map>(cmap)) .distance_combine(std::plus()) .distance_compare(std::less()) ); }catch(GoalFound const&){ for(VertexDescriptor u=goal; u!=start; u = pmap[u]){ result.push_back(QPointF(u.x,u.y)); } } result.push_back(QPoint(start.x,start.y)); return result; } diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc index 8d4421d30d..f41e357c77 100644 --- a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc @@ -1,273 +1,260 @@ /* * Copyright (c) 2019 Kuntal Majumder * * This program 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 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisToolSelectMagnetic.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_painter.h" #include #include "canvas/kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_algebra_2d.h" +#include "KisMagneticWorker.h" + #define FEEDBACK_LINE_WIDTH 2 KisToolSelectMagnetic::KisToolSelectMagnetic(KoCanvasBase *canvas) : KisToolSelect(canvas, KisCursor::load("tool_magnetic_selection_cursor.svg", 16, 16), i18n("Magnetic Selection")), - m_continuedMode(false) + m_continuedMode(false), m_complete(true) { } KisToolSelectMagnetic::~KisToolSelectMagnetic() { } void KisToolSelectMagnetic::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control) { m_continuedMode = true; } KisToolSelect::keyPressEvent(event); } void KisToolSelectMagnetic::keyReleaseEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control || !(event->modifiers() & Qt::ControlModifier)) { m_continuedMode = false; if (mode() != PAINT_MODE && !m_points.isEmpty()) { finishSelectionAction(); } } KisToolSelect::keyReleaseEvent(event); } void KisToolSelectMagnetic::mouseMoveEvent(KoPointerEvent *event) { KisToolSelect::mouseMoveEvent(event); - if (selectionDragInProgress()) return; - + if(m_complete){ + finishSelectionAction(); + return; + } m_lastCursorPos = convertToPixelCoord(event); + auto current = QPoint(m_lastCursorPos.x(), m_lastCursorPos.y()); + //qDebug() << current; + KisMagneticWorker worker; + m_points = worker.computeEdge(image()->projection(), 2, m_lastAnchor, current); + m_paintPath = QPainterPath(); + m_paintPath.moveTo(pixelToView(m_points[m_points.count()-1])); + for(int i=m_points.count()-2; i>0;i--){ + m_paintPath.lineTo(pixelToView(m_points[i])); + } + + updateFeedback(); + if (m_continuedMode && mode() != PAINT_MODE) { updateContinuedMode(); } } void KisToolSelectMagnetic::beginPrimaryAction(KoPointerEvent *event) { - KisToolSelectBase::beginPrimaryAction(event); - if (selectionDragInProgress()) return; - - if (!selectionEditable()) { - event->ignore(); - return; - } - setMode(KisTool::PAINT_MODE); - - if (m_continuedMode && !m_points.isEmpty()) { - m_paintPath.lineTo(pixelToView(convertToPixelCoord(event))); - } else { - m_paintPath.moveTo(pixelToView(convertToPixelCoord(event))); - } - - m_points.append(convertToPixelCoord(event)); + auto temp = convertToPixelCoord(event); + m_lastAnchor = QPoint(temp.x(), temp.y()); + qDebug() << m_lastAnchor; + m_complete = !m_complete; //just for testing } void KisToolSelectMagnetic::continuePrimaryAction(KoPointerEvent *event) { KisToolSelectBase::continuePrimaryAction(event); - if (selectionDragInProgress()) return; - - CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); - - QPointF point = convertToPixelCoord(event); - m_paintPath.lineTo(pixelToView(point)); - m_points.append(point); - updateFeedback(); - - } void KisToolSelectMagnetic::endPrimaryAction(KoPointerEvent *event) { - const bool hadMoveInProgress = selectionDragInProgress(); KisToolSelectBase::endPrimaryAction(event); - if (hadMoveInProgress) return; - - CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); - setMode(KisTool::HOVER_MODE); - - if (!m_continuedMode) { - finishSelectionAction(); - } } void KisToolSelectMagnetic::finishSelectionAction() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); + setMode(KisTool::HOVER_MODE); QRectF boundingViewRect = pixelToView(KisAlgebra2D::accumulateBounds(m_points)); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Outline")); if (m_points.count() > 2 && !helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) { QApplication::setOverrideCursor(KisCursor::waitCursor()); const SelectionMode mode = helper.tryOverrideSelectionMode(kisCanvas->viewManager()->selection(), selectionMode(), selectionAction()); if (mode == PIXEL_SELECTION) { KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setAntiAliasPolygonFill(antiAliasSelection()); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setStrokeStyle(KisPainter::StrokeStyleNone); painter.paintPolygon(m_points); QPainterPath cache; cache.addPolygon(m_points); cache.closeSubpath(); tmpSel->setOutlineCache(cache); helper.selectPixelSelection(tmpSel, selectionAction()); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_points[0])); for (int i = 1; i < m_points.count(); i++) path->lineTo(resolutionMatrix.map(m_points[i])); path->close(); path->normalize(); helper.addSelectionShape(path, selectionAction()); } QApplication::restoreOverrideCursor(); } m_points.clear(); m_paintPath = QPainterPath(); } void KisToolSelectMagnetic::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if ((mode() == KisTool::PAINT_MODE || m_continuedMode) && !m_points.isEmpty()) { QPainterPath outline = m_paintPath; if (m_continuedMode && mode() != KisTool::PAINT_MODE) { outline.lineTo(pixelToView(m_lastCursorPos)); } paintToolOutline(&gc, outline); } } void KisToolSelectMagnetic::updateFeedback() { if (m_points.count() > 1) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectMagnetic::updateContinuedMode() { if (!m_points.isEmpty()) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectMagnetic::deactivate() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); m_continuedMode = false; + m_complete = true; KisTool::deactivate(); } void KisToolSelectMagnetic::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_outline_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_outline_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.h b/plugins/tools/selectiontools/KisToolSelectMagnetic.h index a2505d5a3b..9329b53e36 100644 --- a/plugins/tools/selectiontools/KisToolSelectMagnetic.h +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.h @@ -1,88 +1,90 @@ /* * Copyright (c) 2019 Kuntal Majumder * * This program 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 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_SELECT_MAGNETIC_H_ #define KIS_TOOL_SELECT_MAGNETIC_H_ #include #include "KisSelectionToolFactoryBase.h" #include #include class QPainterPath; class KisToolSelectMagnetic : public KisToolSelect { Q_OBJECT public: KisToolSelectMagnetic(KoCanvasBase *canvas); ~KisToolSelectMagnetic() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void paint(QPainter& gc, const KoViewConverter &converter) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; void resetCursorStyle(); public Q_SLOTS: void deactivate() override; protected: using KisToolSelectBase::m_widgetHelper; private: void finishSelectionAction(); void updateFeedback(); void updateContinuedMode(); void updateCanvas(); QPainterPath m_paintPath; vQPointF m_points; bool m_continuedMode; QPointF m_lastCursorPos; + QPoint m_lastAnchor; + bool m_complete; }; class KisToolSelectMagneticFactory : public KisSelectionToolFactoryBase { public: KisToolSelectMagneticFactory() : KisSelectionToolFactoryBase("KisToolSelectMagnetic") { setToolTip(i18n("Magnetic Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_magnetic_selection")); setPriority(8); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectMagneticFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectMagnetic(canvas); } }; #endif //__selecttoolmagnetic_h__