diff --git a/plugins/tools/selectiontools/KisMagneticGraph.h b/plugins/tools/selectiontools/KisMagneticGraph.h index d668acdcee..a49b456ae6 100644 --- a/plugins/tools/selectiontools/KisMagneticGraph.h +++ b/plugins/tools/selectiontools/KisMagneticGraph.h @@ -1,277 +1,289 @@ /* * 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. */ #ifndef KISMAGNETICGRAPH_H #define KISMAGNETICGRAPH_H #include #include #include #include #include #include #include struct VertexDescriptor { long x, y; enum Direction { MIN = 0, N = MIN, S, E, W, NW, NE, SW, SE, NONE }; VertexDescriptor(long _x, long _y) : x(_x), y(_y) { } VertexDescriptor(QPoint pt) : x(pt.x()), y(pt.y()) { } VertexDescriptor() : x(0), y(0) { } bool operator == (VertexDescriptor const &rhs) const { return rhs.x == x && rhs.y == y; } bool operator == (QPoint const &rhs) const { return rhs.x() == x && rhs.y() == y; } bool operator != (VertexDescriptor const &rhs) const { return rhs.x != x || rhs.y != y; } bool operator < (VertexDescriptor const &rhs) const { return x < rhs.x || (x == rhs.x && y < rhs.y); } // returns one of the 8 neighboring pixel based on the direction // it gives out multiple warnings, but I am lazy, sorry VertexDescriptor neighbor(Direction direction) const { int dx = 0, dy = 0; switch (direction) { case W: + Q_FALLTHROUGH(); case SW: + Q_FALLTHROUGH(); case NW: dx = -1; break; case E: + Q_FALLTHROUGH(); case SE: + Q_FALLTHROUGH(); case NE: dx = 1; + default: + ; } switch (direction) { case N: + Q_FALLTHROUGH(); case NW: + Q_FALLTHROUGH(); case NE: dy = -1; break; case S: + Q_FALLTHROUGH(); case SW: + Q_FALLTHROUGH(); case SE: dy = 1; + default: + ; } VertexDescriptor const neighbor(x + dx, y + dy); return neighbor; - } + } // neighbor }; QDebug operator << (QDebug dbg, const VertexDescriptor &v) { dbg.nospace() << "(" << v.x << ", " << v.y << ")"; return dbg.space(); } struct neighbour_iterator; struct KisMagneticGraph { typedef KisMagneticGraph type; KisMagneticGraph(){ } KisMagneticGraph(KisPaintDeviceSP dev) : m_dev(dev) { m_randAccess = m_dev->createRandomAccessorNG(m_dev->exactBounds().x(), m_dev->exactBounds().y()); } KisMagneticGraph(KisPaintDeviceSP dev, QRect graphRect) : m_rect(graphRect), m_dev(dev) { m_randAccess = m_dev->createRandomAccessorNG(m_dev->exactBounds().x(), m_dev->exactBounds().y()); } typedef VertexDescriptor vertex_descriptor; typedef std::pair edge_descriptor; typedef boost::undirected_tag directed_category; typedef boost::disallow_parallel_edge_tag edge_parallel_category; typedef boost::incidence_graph_tag traversal_category; typedef neighbour_iterator out_edge_iterator; typedef unsigned degree_size_type; quint8 getIntensity(VertexDescriptor pt) { m_randAccess->moveTo(pt.x, pt.y); quint8 val = *(m_randAccess->rawData()); return val; } unsigned outDegree(VertexDescriptor pt) { // corners if (pt == m_rect.topLeft() || pt == m_rect.topRight() || pt == m_rect.bottomLeft() || pt == m_rect.bottomRight()) { if (m_rect.width() == 1 || m_rect.height() == 1) return 1; return 3; } // edges if (pt.x == m_rect.topLeft().x() || pt.y == m_rect.topLeft().y() || pt.x == m_rect.bottomRight().x() || pt.y == m_rect.bottomRight().y()) { if (m_rect.width() == 1 || m_rect.height() == 1) return 2; return 5; } return 8; } QRect m_rect; private: KisPaintDeviceSP m_dev; KisRandomAccessorSP m_randAccess; }; struct neighbour_iterator : public boost::iterator_facade , boost::forward_traversal_tag , std::pair > { neighbour_iterator(VertexDescriptor v, KisMagneticGraph g, VertexDescriptor::Direction d) : m_point(v), m_direction(d), m_graph(g) { } neighbour_iterator() { } std::pair operator * () const { std::pair const result = std::make_pair(m_point, m_point.neighbor(m_direction)); return result; } void operator ++ () { m_direction = static_cast(int(m_direction) + 1); VertexDescriptor next = m_point.neighbor(m_direction); if (m_direction == VertexDescriptor::NONE) { return; } if (!m_graph.m_rect.contains(next.x, next.y)) { operator ++ (); } } bool operator == (neighbour_iterator const& that) const { return m_point == that.m_point && m_direction == that.m_direction; } bool equal(neighbour_iterator const& that) const { return operator == (that); } void increment() { operator ++ (); } private: VertexDescriptor m_point; VertexDescriptor::Direction m_direction; KisMagneticGraph m_graph; }; // Requirements for an Incidence Graph, // https://www.boost.org/doc/libs/1_70_0/libs/graph/doc/IncidenceGraph.html namespace boost { template <> struct graph_traits { typedef typename KisMagneticGraph::vertex_descriptor vertex_descriptor; typedef typename KisMagneticGraph::edge_descriptor edge_descriptor; typedef typename KisMagneticGraph::out_edge_iterator out_edge_iterator; typedef typename KisMagneticGraph::directed_category directed_category; typedef typename KisMagneticGraph::edge_parallel_category edge_parallel_category; typedef typename KisMagneticGraph::traversal_category traversal_category; typedef typename KisMagneticGraph::degree_size_type degree_size_type; typedef void in_edge_iterator; typedef void vertex_iterator; typedef void vertices_size_type; typedef void edge_iterator; typedef void edges_size_type; }; } typename KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g) { Q_UNUSED(g) return e.first; } typename KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g) { Q_UNUSED(g) return e.second; } std::pair out_edges( typename KisMagneticGraph::vertex_descriptor v, KisMagneticGraph g) { return std::make_pair( KisMagneticGraph::out_edge_iterator(v, g, VertexDescriptor::Direction::MIN), KisMagneticGraph::out_edge_iterator(v, g, VertexDescriptor::Direction::NONE) ); } typename KisMagneticGraph::degree_size_type out_degree(typename KisMagneticGraph::vertex_descriptor v, KisMagneticGraph g) { return g.outDegree(v); } #endif // ifndef KISMAGNETICGRAPH_H diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc index 43aa9fc356..fc923c5991 100644 --- a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc @@ -1,373 +1,384 @@ /* * 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 #define FEEDBACK_LINE_WIDTH 2 KisToolSelectMagnetic::KisToolSelectMagnetic(KoCanvasBase *canvas) : KisToolSelect(canvas, KisCursor::load("tool_magnetic_selection_cursor.png", 5, 5), i18n("Magnetic Selection")), m_continuedMode(false), m_complete(true), m_radius(20), m_threshold(100), m_checkPoint(-1) { } 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); } // the cursor is still tracked even when no mousebutton is pressed void KisToolSelectMagnetic::mouseMoveEvent(KoPointerEvent *event) { KisToolSelect::mouseMoveEvent(event); if (m_complete) return; m_lastCursorPos = convertToPixelCoord(event); - QPoint current((int) m_lastCursorPos.x(), (int) m_lastCursorPos.y()); + QPoint current = m_lastCursorPos.toPoint(); + + if (m_anchorPoints.count() > 0 && m_snapBound.contains(m_lastAnchor)) { + //set a freaking cursor + //useCursor(KisCursor::load("tool_outline_selection_cursor_add.png", 6, 6)); + return; + } + vQPointF pointSet = m_worker.computeEdge(m_radius, m_lastAnchor, current); m_points.resize(m_checkPoint + 1); m_points.append(pointSet); int lastCheckPoint = m_checkPoint; for (int i = m_points.count() - 1; i > m_checkPoint; i--) { QPoint pointInQuestion(m_points[i].toPoint()); if (m_worker.intensity(pointInQuestion) >= m_threshold) { m_checkPoint = i; m_lastAnchor = pointInQuestion; break; } } for (int i = lastCheckPoint; i < m_checkPoint; i++) { int temp = m_checkPoint - i; if (temp % m_radius == 0 && temp != 0) { m_lastAnchor = m_points[i].toPoint(); m_anchorPoints.push_back(i); } } m_paintPath = QPainterPath(); m_paintPath.moveTo(pixelToView(m_points[0])); for (int i = 1; i < m_points.count(); i++) { m_paintPath.lineTo(pixelToView(m_points[i])); } updateFeedback(); if (m_continuedMode && mode() != PAINT_MODE) { updateContinuedMode(); } } // KisToolSelectMagnetic::mouseMoveEvent // press primary mouse button void KisToolSelectMagnetic::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); QPointF temp(convertToPixelCoord(event)); m_lastAnchor = QPoint((int) temp.x(), (int) temp.y()); m_checkPoint = m_points.count() - 1; if (m_anchorPoints.count() == 0) { m_snapBound = QRect(QPoint(0, 0), QSize(10, 10)); m_snapBound.moveCenter(m_lastAnchor); } if (m_anchorPoints.count() > 0 && m_snapBound.contains(m_lastAnchor)) { m_complete = true; finishSelectionAction(); return; } m_anchorPoints.push_back(m_checkPoint); m_complete = false; } // drag while primary mouse button is pressed void KisToolSelectMagnetic::continuePrimaryAction(KoPointerEvent *event) { KisToolSelectBase::continuePrimaryAction(event); } // release primary mouse button void KisToolSelectMagnetic::endPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::endPrimaryAction(event); } 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_anchorPoints.clear(); m_paintPath = QPainterPath(); } // KisToolSelectMagnetic::finishSelectionAction 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); Q_FOREACH (const int pt, m_anchorPoints) { + if(pt < 0){ + //no points are set + break; + } QRect tempRect(QPoint(0, 0), QSize(1, 1)); tempRect.moveTo(m_points[pt].toPoint()); gc.drawRect(pixelToView(tempRect)); } } } 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::activate(KoToolBase::ToolActivation activation, const QSet &shapes) { m_worker = KisMagneticWorker(image()->projection()); m_configGroup = KSharedConfig::openConfig()->group(toolId()); KisToolSelect::activate(activation, shapes); } 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::requestUndoDuringStroke() { if (m_complete) return; m_anchorPoints.pop_back(); m_lastAnchor = m_points[m_anchorPoints.last()].toPoint(); m_checkPoint = m_anchorPoints.last(); updateCanvasPixelRect(image()->bounds()); } void KisToolSelectMagnetic::requestStrokeEnd() { if (m_complete) return; m_complete = true; finishSelectionAction(); } void KisToolSelectMagnetic::requestStrokeCancellation() { setMode(KisTool::HOVER_MODE); m_complete = true; m_points.clear(); m_anchorPoints.clear(); m_paintPath = QPainterPath(); updateCanvasPixelRect(image()->bounds()); } QWidget * KisToolSelectMagnetic::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); QHBoxLayout *f1 = new QHBoxLayout(); QLabel *lblRad = new QLabel(i18n("Radius: "), selectionWidget); f1->addWidget(lblRad); KisSliderSpinBox *radInput = new KisSliderSpinBox(selectionWidget); radInput->setObjectName("radius"); radInput->setRange(20, 200); radInput->setSingleStep(10); f1->addWidget(radInput); connect(radInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetRadius(int))); QHBoxLayout *f2 = new QHBoxLayout(); QLabel *lblThreshold = new QLabel(i18n("Threshold: "), selectionWidget); f2->addWidget(lblThreshold); KisSliderSpinBox *threshInput = new KisSliderSpinBox(selectionWidget); threshInput->setObjectName("threshold"); threshInput->setRange(60, 200); threshInput->setSingleStep(10); f2->addWidget(threshInput); connect(threshInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetThreshold(int))); QVBoxLayout *l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); l->insertLayout(1, f1); l->insertLayout(2, f2); radInput->setValue(m_configGroup.readEntry("radius", 20)); threshInput->setValue(m_configGroup.readEntry("threshold", 100)); return selectionWidget; } // KisToolSelectMagnetic::createOptionWidget void KisToolSelectMagnetic::slotSetRadius(int r) { m_radius = r; m_configGroup.writeEntry("radius", r); } void KisToolSelectMagnetic::slotSetThreshold(int t) { m_threshold = t; m_configGroup.writeEntry("threshold", t); } 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(); } }