diff --git a/plugins/tools/selectiontools/KisMagneticWorker.cc b/plugins/tools/selectiontools/KisMagneticWorker.cc index ed4c987052..85026f5341 100644 --- a/plugins/tools/selectiontools/KisMagneticWorker.cc +++ b/plugins/tools/selectiontools/KisMagneticWorker.cc @@ -1,279 +1,284 @@ /* * 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 #include #include #include #include "KisMagneticGraph.h" struct DistanceMap { typedef VertexDescriptor key_type; typedef double data_type; typedef std::pair value_type; explicit 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() = default; PredecessorMap(PredecessorMap const &that) = default; typedef VertexDescriptor key_type; typedef VertexDescriptor value_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) { auto 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; public: explicit AStarHeuristic(VertexDescriptor goal) : m_goal(goal) { } double operator () (VertexDescriptor v) { return EuclideanDistance(v, m_goal); } }; struct GoalFound { }; class AStarGoalVisitor : public boost::default_astar_visitor { public: explicit 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() = default; explicit WeightMap(const 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) + 255.0 - edge_gradient; } return m_map[k]; } private: std::map m_map; KisMagneticGraph m_graph; }; KisMagneticLazyTiles::KisMagneticLazyTiles(KisPaintDeviceSP dev) { m_dev = KisPainter::convertToAlphaAsGray(dev); QSize s = m_dev->exactBounds().size(); m_tileSize = KritaUtils::optimalPatchSize(); m_tilesPerRow = std::ceil((double) s.width() / (double) m_tileSize.width()); int tilesPerColumn = std::ceil((double) s.height() / (double) m_tileSize.height()); m_dev->setDefaultBounds(dev->defaultBounds()); for (int i = 0; i < tilesPerColumn; i++) { for (int j = 0; j < m_tilesPerRow; j++) { int width = std::min(m_dev->exactBounds().width() - j * m_tileSize.width(), m_tileSize.width()); int height = std::min(m_dev->exactBounds().height() - i * m_tileSize.height(), m_tileSize.height()); QRect temp(j *m_tileSize.width(), i *m_tileSize.height(), width, height); m_tiles.push_back(temp); } } m_radiusRecord = QVector(m_tiles.size(), -1); } void KisMagneticLazyTiles::filter(qreal radius, QRect &rect) { auto divide = [](QPoint p, QSize s){ - return QPoint(p.x() / s.width(), p.y() / s.height()); - }; + return QPoint(p.x() / s.width(), p.y() / s.height()); + }; QPoint firstTile = divide(rect.topLeft(), m_tileSize); QPoint lastTile = divide(rect.bottomRight(), m_tileSize); for (int i = firstTile.y(); i <= lastTile.y(); i++) { for (int j = firstTile.x(); j <= lastTile.x(); j++) { int currentTile = i * m_tilesPerRow + j; if (radius != m_radiusRecord[currentTile]) { QRect bounds = m_tiles[currentTile]; KisGaussianKernel::applyTightLoG(m_dev, bounds, radius, -1.0, QBitArray(), nullptr); KisLazyFillTools::normalizeAlpha8Device(m_dev, bounds); m_radiusRecord[currentTile] = radius; } } } } KisMagneticWorker::KisMagneticWorker(const KisPaintDeviceSP &dev) : m_lazyTileFilter(dev) { } QVector KisMagneticWorker::computeEdge(int bounds, QPoint begin, QPoint end, qreal radius) { QRect rect; KisAlgebra2D::accumulateBounds(QVector { begin, end }, &rect); rect = kisGrowRect(rect, bounds); m_lazyTileFilter.filter(radius, rect); VertexDescriptor goal(end); VertexDescriptor start(begin); - KisMagneticGraph m_graph(m_lazyTileFilter.device(), rect); + m_graph = new KisMagneticGraph(m_lazyTileFilter.device(), 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(m_graph); + WeightMap wmap(*m_graph); AStarHeuristic heuristic(goal); QVector result; try { boost::astar_search_no_init( - m_graph, start, heuristic, + *m_graph, 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_front(QPointF(u.x, u.y)); } } result.push_front(QPoint(start.x, start.y)); return result; } // KisMagneticWorker::computeEdge +qreal KisMagneticWorker::intensity(QPoint pt) +{ + return m_graph->getIntensity(VertexDescriptor(pt)); +} + void KisMagneticWorker::saveTheImage(vQPointF points) { QImage img = m_lazyTileFilter.device()->convertToQImage(nullptr, m_lazyTileFilter.device()->exactBounds()); const QPointF offset = m_lazyTileFilter.device()->exactBounds().topLeft(); for (QPointF &pt : points) { pt -= offset; } img = img.convertToFormat(QImage::Format_ARGB32); QPainter gc(&img); QPainterPath path; for (int i = 0; i < points.size(); i++) { if (i == 0) { path.moveTo(points[i]); } else { path.lineTo(points[i]); } } gc.setPen(Qt::blue); gc.drawPath(path); gc.setPen(Qt::green); gc.drawEllipse(points[0], 3, 3); gc.setPen(Qt::red); gc.drawEllipse(points[points.count() - 1], 2, 2); - for(QRect &r : m_lazyTileFilter.tiles() ){ + for (QRect &r : m_lazyTileFilter.tiles() ) { gc.drawRect(r); } img.save("result.png"); } // KisMagneticWorker::saveTheImage diff --git a/plugins/tools/selectiontools/KisMagneticWorker.h b/plugins/tools/selectiontools/KisMagneticWorker.h index 887e4db4a2..3ceed4fdf6 100644 --- a/plugins/tools/selectiontools/KisMagneticWorker.h +++ b/plugins/tools/selectiontools/KisMagneticWorker.h @@ -1,50 +1,54 @@ /* * 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 KISMAGNETICWORKER_H #define KISMAGNETICWORKER_H #include #include -class KisMagneticLazyTiles{ +class KisMagneticGraph; + +class KisMagneticLazyTiles { private: QVector m_tiles; QVector m_radiusRecord; KisPaintDeviceSP m_dev; QSize m_tileSize; int m_tilesPerRow; public: KisMagneticLazyTiles(KisPaintDeviceSP dev); void filter(qreal radius, QRect &rect); - inline KisPaintDeviceSP device() { return m_dev; } - inline QVector tiles() { return m_tiles; } + inline KisPaintDeviceSP device(){ return m_dev; } + inline QVector tiles(){ return m_tiles; } }; class KRITASELECTIONTOOLS_EXPORT KisMagneticWorker { public: KisMagneticWorker(const KisPaintDeviceSP &dev); QVector computeEdge(int bounds, QPoint start, QPoint end, qreal radius); void saveTheImage(vQPointF points); + qreal intensity(QPoint pt); private: KisMagneticLazyTiles m_lazyTileFilter; + KisMagneticGraph *m_graph; }; #endif // ifndef KISMAGNETICWORKER_H diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc index 05ae008167..e38c2be595 100644 --- a/plugins/tools/selectiontools/KisToolSelectMagnetic.cc +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.cc @@ -1,586 +1,690 @@ /* * 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 "KisHandlePainterHelper.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_interactiveMode(false), m_complete(false), m_selected(false), m_finished(false), - m_worker(image()->projection()), m_threshold(70), m_searchRadius(30), m_filterRadius(3.0) + m_worker(image()->projection()), m_threshold(70), m_searchRadius(30), m_checkPoint(-1), m_filterRadius(3.0) { } void KisToolSelectMagnetic::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control) { m_continuedMode = true; } KisToolSelect::keyPressEvent(event); } +/* + * Calculates the checkpoints responsible to determining the last point from where + * the edge is calculated. + * Takes 3 point, min, median and max, searches for an edge point from median to max, if fails, + * searches for the same from median to min, if fails, median becomes that edge point. + */ +void KisToolSelectMagnetic::calculateCheckPoints() +{ + qreal totalDistance = 0.0; + int finalPoint = m_checkPoint + 2; + int midPoint = m_checkPoint + 1; + int minPoint = m_checkPoint; + qreal maxFactor = 2; + + for (; finalPoint < m_points.count(); finalPoint++) { + totalDistance += kisDistance(m_points[finalPoint], m_points[finalPoint - 1]); + + if (totalDistance <= m_anchorGap / 3) { + minPoint = finalPoint; + } + + if (totalDistance <= m_anchorGap) { + midPoint = finalPoint; + } + + if (totalDistance > maxFactor * m_anchorGap) { + break; + } + } + + if (totalDistance > maxFactor * m_anchorGap) { + bool foundSomething = false; + + for (int i = midPoint; i < finalPoint; i++) { + if (m_worker.intensity(m_points.at(i).toPoint()) >= m_threshold) { + m_checkPoint = i; + m_lastAnchor = m_points.at(i).toPoint(); + m_anchorPoints.push_back(m_lastAnchor); + foundSomething = true; + break; + } + } + + if (!foundSomething) { + for (int i = midPoint - 1; i >= minPoint; i--) { + if (m_worker.intensity(m_points.at(i).toPoint()) >= m_threshold) { + m_checkPoint = i; + m_lastAnchor = m_points.at(i).toPoint(); + m_anchorPoints.push_back(m_lastAnchor); + foundSomething = true; + break; + } + } + } + + if (!foundSomething && m_checkPoint >= 0) { + m_checkPoint = midPoint; + m_lastAnchor = m_points.at(m_checkPoint).toPoint(); + m_anchorPoints.push_back(m_lastAnchor); + foundSomething = true; + } + } + + totalDistance = 0.0; + + for (; finalPoint < m_points.count(); finalPoint++) { + totalDistance += kisDistance(m_points[finalPoint], m_points[m_checkPoint]); + + if (totalDistance > maxFactor * m_anchorGap) { + calculateCheckPoints(); + totalDistance = 0.0; + } + } +} // KisToolSelectMagnetic::calculateCheckPoints + 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); } vQPointF KisToolSelectMagnetic::computeEdgeWrapper(QPoint a, QPoint b) { return m_worker.computeEdge(m_searchRadius, a, b, m_filterRadius); } // the cursor is still tracked even when no mousebutton is pressed void KisToolSelectMagnetic::mouseMoveEvent(KoPointerEvent *event) { m_lastCursorPos = convertToPixelCoord(event); KisToolSelect::mouseMoveEvent(event); updatePaintPath(); } // KisToolSelectMagnetic::mouseMoveEvent // press primary mouse button void KisToolSelectMagnetic::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); QPointF temp(convertToPixelCoord(event)); if (!image()->bounds().contains(temp.toPoint())) { return; } - if (m_interactiveMode) { } + if (m_interactiveMode) { + m_lastAnchor = QPoint((int) temp.x(), (int) temp.y()); + m_checkPoint = m_points.count() == 0 ? 0 : m_points.count() - 1; + m_anchorPoints.push_back(m_lastAnchor); + m_complete = false; + updateCanvasPixelRect(image()->bounds()); + return; + } if (m_complete) { checkIfAnchorIsSelected(temp); return; } if (m_points.count() != 0) { vQPointF edge = computeEdgeWrapper(m_anchorPoints.last(), temp.toPoint()); m_points.append(edge); m_pointCollection.push_back(edge); if (m_snapBound.contains(temp)) { m_complete = true; return; } } else { m_points.push_back(temp); qreal zoomLevel = canvas()->viewConverter()->zoom(); int sides = (int) std::ceil(10.0 / zoomLevel); m_snapBound = QRectF(QPoint(0, 0), QSize(sides, sides)); m_snapBound.moveCenter(temp); emit setInteractiveModeEnable(false); } m_lastAnchor = temp.toPoint(); m_anchorPoints.push_back(m_lastAnchor); updateCanvasPixelRect(image()->bounds()); m_complete = false; updatePaintPath(); } // KisToolSelectMagnetic::beginPrimaryAction void KisToolSelectMagnetic::checkIfAnchorIsSelected(QPointF temp) { Q_FOREACH (const QPoint pt, m_anchorPoints) { qreal zoomLevel = canvas()->viewConverter()->zoom(); int sides = (int) std::ceil(10.0 / zoomLevel); QRect r = QRect(QPoint(0, 0), QSize(sides, sides)); r.moveCenter(pt); if (r.contains(temp.toPoint())) { m_selected = true; m_selectedAnchor = m_anchorPoints.lastIndexOf(pt); return; } } } void KisToolSelectMagnetic::beginPrimaryDoubleClickAction(KoPointerEvent *event) { if (m_complete) { QPointF temp = convertToPixelCoord(event); if (!image()->bounds().contains(temp.toPoint())) { return; } checkIfAnchorIsSelected(temp); if (m_selected) { int prev = m_selectedAnchor == 0 ? m_anchorPoints.count() - 1 : m_selectedAnchor - 1; QPoint previousAnchor = m_anchorPoints[prev]; QPoint nextAnchor = m_anchorPoints[(m_selectedAnchor + 1) % m_anchorPoints.count()]; m_anchorPoints.remove(m_selectedAnchor); m_pointCollection[prev] = computeEdgeWrapper(previousAnchor, nextAnchor); m_pointCollection.remove(m_selectedAnchor); if (m_selectedAnchor == 0) m_snapBound.moveCenter(m_anchorPoints.first()); m_selected = false; reEvaluatePoints(); return; } int pointA = 0, pointB = 1; double dist = std::numeric_limits::max(); int total = m_anchorPoints.count(); for (int i = 0; i < total; i++) { double distToCompare = kisDistance(m_anchorPoints[i], temp) + kisDistance(temp, m_anchorPoints[(i + 1) % total]); if (dist > distToCompare) { pointA = i; pointB = (i + 1) % total; dist = distToCompare; } } vQPointF path1 = computeEdgeWrapper(m_anchorPoints[pointA], temp.toPoint()); vQPointF path2 = computeEdgeWrapper(temp.toPoint(), m_anchorPoints[pointB]); m_pointCollection[pointA] = path1; m_pointCollection.insert(pointB, path2); m_anchorPoints.insert(pointB, temp.toPoint()); reEvaluatePoints(); } } // KisToolSelectMagnetic::beginPrimaryDoubleClickAction // drag while primary mouse button is pressed void KisToolSelectMagnetic::continuePrimaryAction(KoPointerEvent *event) { if (m_interactiveMode) { + QPoint current = m_lastCursorPos.toPoint(); + + vQPointF pointSet = computeEdgeWrapper(m_lastAnchor, current); + m_points.resize(m_checkPoint); + m_points.append(pointSet); + + calculateCheckPoints(); + + 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(); + } + return; } if (m_selected) { m_anchorPoints[m_selectedAnchor] = convertToPixelCoord(event).toPoint(); } KisToolSelectBase::continuePrimaryAction(event); } // release primary mouse button void KisToolSelectMagnetic::endPrimaryAction(KoPointerEvent *event) { if (m_interactiveMode) { finishSelectionAction(); + m_finished = false; return; } if (m_selected) { int prev = m_selectedAnchor == 0 ? m_anchorPoints.count() - 1 : m_selectedAnchor - 1; QPoint currentAnchor = m_anchorPoints[m_selectedAnchor]; QPoint previousAnchor = m_anchorPoints[prev]; QPoint nextAnchor = m_anchorPoints[(m_selectedAnchor + 1) % m_anchorPoints.count()]; if (!image()->bounds().contains(m_anchorPoints[m_selectedAnchor])) { m_anchorPoints.remove(m_selectedAnchor); m_pointCollection[prev] = computeEdgeWrapper(previousAnchor, nextAnchor); m_pointCollection.remove(m_selectedAnchor); if (m_selectedAnchor == 0) m_snapBound.moveCenter(m_anchorPoints.first()); } else { m_pointCollection[prev] = computeEdgeWrapper(previousAnchor, currentAnchor); m_pointCollection[m_selectedAnchor] = computeEdgeWrapper(currentAnchor, nextAnchor); } reEvaluatePoints(); } m_selected = false; KisToolSelectBase::endPrimaryAction(event); } // KisToolSelectMagnetic::endPrimaryAction void KisToolSelectMagnetic::reEvaluatePoints() { m_points.clear(); Q_FOREACH (const vQPointF vec, m_pointCollection) { m_points.append(vec); } updatePaintPath(); } void KisToolSelectMagnetic::finishSelectionAction() { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); setMode(KisTool::HOVER_MODE); m_complete = false; m_finished = true; // just for testing out // m_worker.saveTheImage(m_points); QRectF boundingViewRect = pixelToView(KisAlgebra2D::accumulateBounds(m_points)); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Magnetic Selection")); 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(); } resetVariables(); emit setInteractiveModeEnable(true); } // KisToolSelectMagnetic::finishSelectionAction void KisToolSelectMagnetic::resetVariables() { m_points.clear(); m_anchorPoints.clear(); m_pointCollection.clear(); m_paintPath = QPainterPath(); } void KisToolSelectMagnetic::updatePaintPath() { m_paintPath = QPainterPath(); if (m_points.size() > 0) { 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(); } updateCanvasPixelRect(image()->bounds()); } void KisToolSelectMagnetic::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); updatePaintPath(); 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); drawAnchors(gc); } } void KisToolSelectMagnetic::drawAnchors(QPainter &gc) { qreal zoomLevel = canvas()->viewConverter()->zoom(); int sides = (int) std::ceil(10.0 / zoomLevel); QSize realSize(sides, sides); m_snapBound = QRectF(QPoint(0, 0), realSize); m_snapBound.moveCenter(m_anchorPoints.first()); Q_FOREACH (const QPoint pt, m_anchorPoints) { KisHandlePainterHelper helper(&gc, handleRadius()); QRect r(QPoint(0, 0), QSize(sides, sides)); r.moveCenter(pt); if ((m_complete && r.contains(m_lastCursorPos.toPoint())) || (m_snapBound.contains(m_lastCursorPos) && pt == m_anchorPoints.first())) { helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles()); } else { helper.setHandleStyle(KisHandleStyle::primarySelection()); } helper.drawHandleRect(pixelToView(pt), 4, QPoint(0, 0)); } } 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()); connect(action("undo_polygon_selection"), SIGNAL(triggered()), SLOT(undoPoints()), Qt::UniqueConnection); 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; disconnect(action("undo_polygon_selection"), nullptr, this, nullptr); KisTool::deactivate(); } void KisToolSelectMagnetic::undoPoints() { if (m_complete) return; m_anchorPoints.pop_back(); m_pointCollection.pop_back(); reEvaluatePoints(); } void KisToolSelectMagnetic::requestStrokeEnd() { if (m_finished || m_anchorPoints.count() < 2) return; finishSelectionAction(); m_finished = false; } void KisToolSelectMagnetic::requestStrokeCancellation() { m_complete = false; m_points.clear(); m_anchorPoints.clear(); m_paintPath = QPainterPath(); updatePaintPath(); } QWidget * KisToolSelectMagnetic::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); QHBoxLayout *f1 = new QHBoxLayout(); QLabel *filterRadiusLabel = new QLabel(i18n("Filter Radius: "), selectionWidget); f1->addWidget(filterRadiusLabel); KisDoubleSliderSpinBox *filterRadiusInput = new KisDoubleSliderSpinBox(selectionWidget); filterRadiusInput->setObjectName("radius"); filterRadiusInput->setRange(2.5, 100.0, 2); filterRadiusInput->setSingleStep(0.5); filterRadiusInput->setToolTip("Radius of the filter for the detecting edges, might take some time to calculate"); f1->addWidget(filterRadiusInput); connect(filterRadiusInput, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetFilterRadius(qreal))); QHBoxLayout *f2 = new QHBoxLayout(); QLabel *thresholdLabel = new QLabel(i18n("Threshold: "), selectionWidget); f2->addWidget(thresholdLabel); KisSliderSpinBox *thresholdInput = new KisSliderSpinBox(selectionWidget); thresholdInput->setObjectName("threshold"); thresholdInput->setRange(1, 255); thresholdInput->setSingleStep(10); thresholdInput->setToolTip("Threshold for determining the minimum intensity of the edges"); f2->addWidget(thresholdInput); connect(thresholdInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetThreshold(int))); QHBoxLayout *f3 = new QHBoxLayout(); QLabel *searchRadiusLabel = new QLabel(i18n("Search Radius: "), selectionWidget); f3->addWidget(searchRadiusLabel); KisSliderSpinBox *searchRadiusInput = new KisSliderSpinBox(selectionWidget); searchRadiusInput->setObjectName("frequency"); searchRadiusInput->setRange(20, 200); searchRadiusInput->setSingleStep(10); searchRadiusInput->setToolTip("Extra area to be searched"); searchRadiusInput->setSuffix(" px"); f3->addWidget(searchRadiusInput); connect(searchRadiusInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetSearchRadius(int))); QHBoxLayout *f4 = new QHBoxLayout(); QCheckBox *interactiveModeCheckBox = new QCheckBox(i18n("Interactive Mode: "), selectionWidget); f4->addWidget(interactiveModeCheckBox); interactiveModeCheckBox->setCheckState(Qt::Unchecked); connect(interactiveModeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotSetInteractiveMode(int))); QHBoxLayout *f5 = new QHBoxLayout(); QLabel *anchorGapLabel = new QLabel(i18n("Anchor Gap: "), selectionWidget); f5->addWidget(anchorGapLabel); KisSliderSpinBox *anchorGapInput = new KisSliderSpinBox(selectionWidget); anchorGapInput->setObjectName("anchorgap"); anchorGapInput->setRange(20, 200); anchorGapInput->setSingleStep(10); anchorGapInput->setToolTip("Gap between 2 anchors in interative mode"); anchorGapInput->setSuffix(" px"); f5->addWidget(anchorGapInput); anchorGapInput->setEnabled(interactiveModeCheckBox->checkState()); connect(interactiveModeCheckBox, &QCheckBox::stateChanged, [anchorGapInput](int state){ anchorGapInput->setEnabled(state); }); connect(this, SIGNAL(setInteractiveModeEnable(bool)), interactiveModeCheckBox, SLOT(setEnabled(bool))); connect(anchorGapInput, SIGNAL(valueChanged(int)), this, SLOT(slotSetAnchorGap(int))); QVBoxLayout *l = dynamic_cast(selectionWidget->layout()); l->insertLayout(1, f1); l->insertLayout(2, f2); l->insertLayout(3, f3); l->insertLayout(4, f4); l->insertLayout(5, f5); filterRadiusInput->setValue(m_configGroup.readEntry("filterradius", 3.0)); thresholdInput->setValue(m_configGroup.readEntry("threshold", 100)); searchRadiusInput->setValue(m_configGroup.readEntry("searchradius", 30)); anchorGapInput->setValue(m_configGroup.readEntry("anchorgap", 20)); return selectionWidget; } // KisToolSelectMagnetic::createOptionWidget void KisToolSelectMagnetic::slotSetFilterRadius(qreal r) { m_filterRadius = r; m_configGroup.writeEntry("filterradius", r); } void KisToolSelectMagnetic::slotSetThreshold(int t) { m_threshold = t; m_configGroup.writeEntry("threshold", t); } void KisToolSelectMagnetic::slotSetSearchRadius(int r) { m_searchRadius = r; m_configGroup.writeEntry("searchradius", r); } void KisToolSelectMagnetic::slotSetAnchorGap(int g) { m_anchorGap = g; m_configGroup.writeEntry("anchorgap", g); } void KisToolSelectMagnetic::slotSetInteractiveMode(int i) { m_interactiveMode = i ? true : false; } void KisToolSelectMagnetic::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_magnetic_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_magnetic_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/plugins/tools/selectiontools/KisToolSelectMagnetic.h b/plugins/tools/selectiontools/KisToolSelectMagnetic.h index d18a73b431..e1a366923e 100644 --- a/plugins/tools/selectiontools/KisToolSelectMagnetic.h +++ b/plugins/tools/selectiontools/KisToolSelectMagnetic.h @@ -1,129 +1,130 @@ /* * 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 #include "KisMagneticWorker.h" class QPainterPath; class KisToolSelectMagnetic : public KisToolSelect { Q_OBJECT public: KisToolSelectMagnetic(KoCanvasBase *canvas); ~KisToolSelectMagnetic() override = default; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void paint(QPainter& gc, const KoViewConverter &converter) override; void beginPrimaryDoubleClickAction(KoPointerEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; void resetCursorStyle() override; void requestStrokeEnd() override; void requestStrokeCancellation() override; QWidget * createOptionWidget() override; public Q_SLOTS: void deactivate() override; void activate(KoToolBase::ToolActivation activation, const QSet &shapes) override; void undoPoints(); void slotSetFilterRadius(qreal); void slotSetThreshold(int); void slotSetSearchRadius(int); void slotSetAnchorGap(int); void slotSetInteractiveMode(int); Q_SIGNALS: void setInteractiveModeEnable(bool); protected: using KisToolSelectBase::m_widgetHelper; private: void finishSelectionAction(); void updateFeedback(); void updateContinuedMode(); void updateCanvas(); void updatePaintPath(); void resetVariables(); void drawAnchors(QPainter &gc); void checkIfAnchorIsSelected(QPointF pt); vQPointF computeEdgeWrapper(QPoint a, QPoint b); void reEvaluatePoints(); + void calculateCheckPoints(); QPainterPath m_paintPath; QVector m_points; QVector m_anchorPoints; bool m_continuedMode, m_interactiveMode; QPointF m_lastCursorPos; QPoint m_lastAnchor; bool m_complete, m_selected, m_finished; KisMagneticWorker m_worker; - int m_threshold, m_searchRadius, m_selectedAnchor, m_anchorGap; + int m_threshold, m_searchRadius, m_selectedAnchor, m_anchorGap, m_checkPoint; qreal m_filterRadius; QRectF m_snapBound; KConfigGroup m_configGroup; QVector m_pointCollection; }; 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); } QList createActionsImpl() override { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QList actions = KisSelectionToolFactoryBase::createActionsImpl(); actions << actionRegistry->makeQAction("undo_polygon_selection"); return actions; } }; #endif // __selecttoolmagnetic_h__