diff --git a/libs/image/tests/kis_algebra_2d_test.cpp b/libs/image/tests/kis_algebra_2d_test.cpp index a153156f9f..c13f64f446 100644 --- a/libs/image/tests/kis_algebra_2d_test.cpp +++ b/libs/image/tests/kis_algebra_2d_test.cpp @@ -1,329 +1,330 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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 "kis_algebra_2d_test.h" #include #include "kis_algebra_2d.h" #include "kis_debug.h" namespace KisAlgebra2D { } void KisAlgebra2DTest::testHalfPlane() { { QPointF a(10,10); QPointF b(5,5); KisAlgebra2D::RightHalfPlane p(a, b); QVERIFY(p.value(QPointF(7, 5)) > 0); QVERIFY(p.value(QPointF(3, 5)) < 0); QVERIFY(p.value(QPointF(3, 3)) == 0); } { QPointF a(10,10); QPointF b(15,10); KisAlgebra2D::RightHalfPlane p(a, b); QCOMPARE(p.value(QPointF(3, 5)), -5.0); QCOMPARE(p.value(QPointF(500, 15)), 5.0); QCOMPARE(p.value(QPointF(1000, 10)), 0.0); QCOMPARE(p.valueSq(QPointF(3, 5)), -25.0); QCOMPARE(p.valueSq(QPointF(500, 15)), 25.0); QCOMPARE(p.valueSq(QPointF(1000, 10)), 0.0); QCOMPARE(p.pos(QPointF(3, 5)), -1); QCOMPARE(p.pos(QPointF(500, 15)), 1); QCOMPARE(p.pos(QPointF(1000, 10)), 0); } } void KisAlgebra2DTest::testOuterCircle() { QPointF a(10,10); KisAlgebra2D::OuterCircle p(a, 5); QVERIFY(p.value(QPointF(3, 5)) > 0); QVERIFY(p.value(QPointF(7, 7)) < 0); QVERIFY(p.value(QPointF(10, 5)) == 0); QCOMPARE(p.value(QPointF(10, 12)), -3.0); QCOMPARE(p.value(QPointF(10, 15)), 0.0); QCOMPARE(p.value(QPointF(10, 17)), 2.0); } void KisAlgebra2DTest::testQuadraticEquation() { int result = 0; qreal x1 = 0; qreal x2 = 0; result = KisAlgebra2D::quadraticEquation(3, -11, 6, &x1, &x2); QCOMPARE(result, 2); QCOMPARE(x2, 2.0 / 3.0); QCOMPARE(x1, 3.0); result = KisAlgebra2D::quadraticEquation(9, -12, 4, &x1, &x2); QCOMPARE(result, 1); QCOMPARE(x1, 2.0 / 3.0); result = KisAlgebra2D::quadraticEquation(9, -1, 4, &x1, &x2); QCOMPARE(result, 0); } void KisAlgebra2DTest::testIntersections() { QVector points; points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0, QPointF(20,10), 5.0); QCOMPARE(points.size(), 1); QCOMPARE(points[0], QPointF(15, 10)); points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0, QPointF(18,10), 5.0); QCOMPARE(points.size(), 2); QCOMPARE(points[0], QPointF(14, 13)); QCOMPARE(points[1], QPointF(14, 7)); points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0, QPointF(10,20), 5.0); QCOMPARE(points.size(), 1); QCOMPARE(points[0], QPointF(10, 15)); points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0, QPointF(10,18), 5.0); QCOMPARE(points.size(), 2); QCOMPARE(points[0], QPointF(7, 14)); QCOMPARE(points[1], QPointF(13, 14)); points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0, QPointF(17,17), 5.0); QCOMPARE(points.size(), 2); QCOMPARE(points[0], QPointF(13, 14)); QCOMPARE(points[1], QPointF(14, 13)); points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0, QPointF(10,100), 5.0); QCOMPARE(points.size(), 0); } void KisAlgebra2DTest::testWeirdIntersections() { QVector points; QPointF c1 = QPointF(5369.14,3537.98); QPointF c2 = QPointF(5370.24,3536.71); qreal r1 = 8.5; qreal r2 = 10; points = KisAlgebra2D::intersectTwoCircles(c1, r1, c2, r2); QCOMPARE(points.size(), 2); //QCOMPARE(points[0], QPointF(15, 10)); } void KisAlgebra2DTest::testMatrixDecomposition1() { QTransform Sh; Sh.shear(0.2, 0); QTransform R; R.rotate(30); const QTransform t0 = QTransform::fromScale(0.5, -0.6) * R * Sh * QTransform::fromTranslate(100, 200); KisAlgebra2D::DecomposedMatix matrix(t0); QCOMPARE(matrix.isValid(), true); QVERIFY(KisAlgebra2D::fuzzyMatrixCompare(matrix.transform(), t0, 1e-4)); } void KisAlgebra2DTest::testMatrixDecomposition2() { QPolygonF poly; poly << QPointF(-0.3,-0.3); poly << QPointF(1,0); poly << QPointF(0.8,0.8); poly << QPointF(0,1); QTransform t0; bool valid = QTransform::squareToQuad(poly, t0); QVERIFY(valid); KisAlgebra2D::DecomposedMatix matrix(t0); QCOMPARE(matrix.isValid(), true); QVERIFY(KisAlgebra2D::fuzzyMatrixCompare(matrix.transform(), t0, 1e-4)); } void KisAlgebra2DTest::testDivisionWithFloor() { QBENCHMARK { for (int a = -1000; a < 1000; a++) { for (int b = -1000; b < 1000; b++) { if (b == 0) continue; int refValue = qFloor(qreal(a) / b); int value = KisAlgebra2D::divideFloor(a, b); if (refValue != value) { qDebug() << ppVar(a) << ppVar(b); QCOMPARE(value, refValue); } } } } } #include +#include void KisAlgebra2DTest::testDrawEllipse() { QImage image(QSize(300,300), QImage::Format_ARGB32); image.fill(255); QPainter gc(&image); QTransform rot; rot.rotate(-30); QTransform shear; shear.shear(0.5, 0.3); const QTransform transform = rot * QTransform::fromTranslate(10, 30) * shear * QTransform::fromTranslate(150, 150); const qreal a = 100; const qreal b = 50; gc.setTransform(transform); gc.setPen(Qt::black); gc.drawEllipse(QPointF(0,0), a, b); gc.setPen(Qt::blue); gc.drawEllipse(QPointF(a, 0), 3, 3); gc.setPen(Qt::red); gc.drawEllipse(QPointF(0, b), 3, 3); QPointF newAxes; QTransform newTransform; std::tie(newAxes, newTransform) = KisAlgebra2D::transformEllipse(QPointF(a, b), transform); gc.setOpacity(50); gc.resetTransform(); gc.setTransform(newTransform); gc.setPen(QPen(Qt::blue, 2)); gc.drawEllipse(QPointF(0,0), newAxes.x(), newAxes.y()); gc.setPen(QPen(Qt::green, 2)); gc.drawEllipse(QPointF(newAxes.x(), 0), 5, 5); gc.setPen(Qt::yellow); gc.drawEllipse(QPointF(0, newAxes.y()), 5, 5); image.save("ellipse_result.png"); } void KisAlgebra2DTest::testNullRectProcessing() { QPainterPath line; line.moveTo(10,10); line.lineTo(110,10); const QRectF lineRect(line.boundingRect()); qDebug() << ppVar(lineRect) << ppVar(lineRect.isValid()) << ppVar(lineRect.isNull()); // test relative operations const QPointF relPoint = KisAlgebra2D::absoluteToRelative(QPointF(30, 10), lineRect); QCOMPARE(relPoint, QPointF(0.2, 0.0)); const QPointF absPoint = KisAlgebra2D::relativeToAbsolute(relPoint, lineRect); QCOMPARE(absPoint, QPointF(30.0, 10.0)); const QTransform relTransform = KisAlgebra2D::mapToRect(lineRect); QCOMPARE(relTransform.map(relPoint), absPoint); // test relative isotropic operations QCOMPARE(KisAlgebra2D::absoluteToRelative( KisAlgebra2D::relativeToAbsolute(0.2, lineRect), lineRect), 0.2); /// test transformations // translate QCOMPARE(QTransform::fromTranslate(10, 20).mapRect(lineRect), QRect(20, 30, 100, 0)); // scale QCOMPARE(QTransform::fromScale(2.0, 2.0).mapRect(lineRect), QRect(20, 20, 200, 0)); // rotate QTransform rot; rot.rotate(90); QCOMPARE(rot.mapRect(lineRect), QRect(-10, 10, 0, 100)); // shear-x QTransform shearX; shearX.shear(2.0, 0.0); QCOMPARE(shearX.mapRect(lineRect), QRect(30, 10, 100, 0)); // shear-y QTransform shearY; shearY.shear(0.0, 2.0); QCOMPARE(shearY.mapRect(lineRect), QRect(10, 30, 100, 200)); /// binary operations QCOMPARE(QRectF() | lineRect, lineRect); QCOMPARE(QRectF(10, 10, 40, 40) | lineRect, QRectF(10, 10, 100, 40)); QCOMPARE(QRectF(20, 20, 40, 40) & lineRect, QRectF()); QEXPECT_FAIL("", "Qt's konjunstion operator doesn't work with line-rects", Continue); QCOMPARE(QRectF(10, 10, 40, 40) & lineRect, QRectF(10, 10, 40, 0)); /// QPolygon's bounding rect QCOMPARE(QPolygonF(lineRect).boundingRect(), lineRect); } QTEST_MAIN(KisAlgebra2DTest) diff --git a/plugins/tools/selectiontools/KisMagneticWorker.cc b/plugins/tools/selectiontools/KisMagneticWorker.cc index 8a21ac8069..f190006eb3 100644 --- a/plugins/tools/selectiontools/KisMagneticWorker.cc +++ b/plugins/tools/selectiontools/KisMagneticWorker.cc @@ -1,282 +1,283 @@ /* * 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 #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 = dev->defaultBounds()->bounds().size(); m_tileSize = KritaUtils::optimalPatchSize(); m_tilesPerRow = (int) std::ceil((double) s.width() / (double) m_tileSize.width()); int tilesPerColumn = (int) 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(s.width() - j * m_tileSize.width(), m_tileSize.width()); int height = std::min(s.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()); }; 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); 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); AStarHeuristic heuristic(goal); QVector result; try { boost::astar_search_no_init( *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() ) { gc.drawRect(r); } img.save("result.png"); } // KisMagneticWorker::saveTheImage