File indexing completed on 2024-12-22 04:16:39

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Kuntal Majumder <hellozee@disroot.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.1-only
0005  */
0006 
0007 #include "KisMagneticWorker.h"
0008 
0009 #include <kis_gaussian_kernel.h>
0010 #include <lazybrush/kis_lazy_fill_tools.h>
0011 #include <kis_algebra_2d.h>
0012 #include <kis_painter.h>
0013 
0014 #include <QtCore>
0015 #include <QPolygon>
0016 #include <QPainter>
0017 #include <QPainterPath>
0018 
0019 #include <boost/graph/astar_search.hpp>
0020 #include <krita_utils.h>
0021 
0022 #include "KisMagneticGraph.h"
0023 
0024 struct DistanceMap {
0025     typedef VertexDescriptor key_type;
0026     typedef double data_type;
0027     typedef std::pair<key_type, data_type> value_type;
0028 
0029     explicit DistanceMap(double const &dval)
0030         : m_default(dval)
0031     { }
0032 
0033     data_type &operator [] (key_type const &k)
0034     {
0035         if (m.find(k) == m.end())
0036             m[k] = m_default;
0037         return m[k];
0038     }
0039 
0040 private:
0041     std::map<key_type, data_type> m;
0042     data_type const               m_default;
0043 };
0044 
0045 struct PredecessorMap {
0046     PredecessorMap() = default;
0047 
0048     PredecessorMap(PredecessorMap const &that) = default;
0049 
0050     typedef VertexDescriptor key_type;
0051     typedef VertexDescriptor value_type;
0052     typedef boost::read_write_property_map_tag category;
0053 
0054     VertexDescriptor &operator [] (VertexDescriptor v)
0055     {
0056         return m_map[v];
0057     }
0058 
0059     std::map<VertexDescriptor, VertexDescriptor> m_map;
0060 };
0061 
0062 VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v)
0063 {
0064     auto found = m.m_map.find(v);
0065     return found != m.m_map.end() ? found->second : v;
0066 }
0067 
0068 void put(PredecessorMap &m, VertexDescriptor key, VertexDescriptor value)
0069 {
0070     m.m_map[key] = value;
0071 }
0072 
0073 double EuclideanDistance(VertexDescriptor p1, VertexDescriptor p2)
0074 {
0075     return std::sqrt(std::pow(p1.y - p2.y, 2) + std::pow(p1.x - p2.x, 2));
0076 }
0077 
0078 class AStarHeuristic : public boost::astar_heuristic<KisMagneticGraph, double>
0079 {
0080 private:
0081     VertexDescriptor m_goal;
0082 
0083 public:
0084     explicit AStarHeuristic(VertexDescriptor goal) :
0085         m_goal(goal)
0086     { }
0087 
0088     double operator () (VertexDescriptor v)
0089     {
0090         return EuclideanDistance(v, m_goal);
0091     }
0092 };
0093 
0094 struct GoalFound { };
0095 
0096 class AStarGoalVisitor : public boost::default_astar_visitor
0097 {
0098 public:
0099     explicit AStarGoalVisitor(VertexDescriptor goal) : m_goal(goal){ }
0100 
0101     void examine_vertex(VertexDescriptor u, KisMagneticGraph const &g)
0102     {
0103         Q_UNUSED(g);
0104         if (u == m_goal) {
0105             throw GoalFound();
0106         }
0107     }
0108 
0109 private:
0110     VertexDescriptor m_goal;
0111 };
0112 
0113 struct WeightMap {
0114     typedef std::pair<VertexDescriptor, VertexDescriptor> key_type;
0115     typedef double data_type;
0116     typedef std::pair<key_type, data_type> value_type;
0117 
0118     WeightMap() = default;
0119 
0120     explicit WeightMap(const KisMagneticGraph &g) :
0121         m_graph(g)
0122     { }
0123 
0124     data_type &operator [] (key_type const &k)
0125     {
0126         if (m_map.find(k) == m_map.end()) {
0127             double edge_gradient = (m_graph.getIntensity(k.first) + m_graph.getIntensity(k.second)) / 2.0;
0128             m_map[k] = EuclideanDistance(k.first, k.second) + 255.0 - edge_gradient;
0129         }
0130         return m_map[k];
0131     }
0132 
0133 private:
0134     std::map<key_type, data_type> m_map;
0135     KisMagneticGraph              m_graph;
0136 };
0137 
0138 KisMagneticLazyTiles::KisMagneticLazyTiles(KisPaintDeviceSP dev)
0139 {
0140     m_dev = KisPainter::convertToAlphaAsGray(dev);
0141     QSize s = dev->defaultBounds()->bounds().size();
0142     m_tileSize    = KritaUtils::optimalPatchSize();
0143     m_tilesPerRow = (int) std::ceil((double) s.width() / (double) m_tileSize.width());
0144     int tilesPerColumn = (int) std::ceil((double) s.height() / (double) m_tileSize.height());
0145     m_dev->setDefaultBounds(dev->defaultBounds());
0146 
0147     for (int i = 0; i < tilesPerColumn; i++) {
0148         for (int j = 0; j < m_tilesPerRow; j++) {
0149             int width  = std::min(s.width() - j * m_tileSize.width(), m_tileSize.width());
0150             int height = std::min(s.height() - i * m_tileSize.height(), m_tileSize.height());
0151             QRect temp(j *m_tileSize.width(), i *m_tileSize.height(), width, height);
0152             m_tiles.push_back(temp);
0153         }
0154     }
0155     m_radiusRecord = QVector<qreal>(m_tiles.size(), -1);
0156 }
0157 
0158 void KisMagneticLazyTiles::filter(qreal radius, QRect &rect)
0159 {
0160     auto divide = [](QPoint p, QSize s){
0161                       return QPoint(p.x() / s.width(), p.y() / s.height());
0162                   };
0163 
0164     QPoint firstTile = divide(rect.topLeft(), m_tileSize);
0165     QPoint lastTile  = divide(rect.bottomRight(), m_tileSize);
0166     for (int i = firstTile.y(); i <= lastTile.y(); i++) {
0167         for (int j = firstTile.x(); j <= lastTile.x(); j++) {
0168             int currentTile = i * m_tilesPerRow + j;
0169             if (currentTile < m_tiles.size()
0170                     && currentTile < m_radiusRecord.size()
0171                     && radius != m_radiusRecord[currentTile]) {
0172                 QRect bounds = m_tiles[currentTile];
0173                 KisGaussianKernel::applyTightLoG(m_dev, bounds, radius, -1.0, QBitArray(), nullptr);
0174                 KisLazyFillTools::normalizeAlpha8Device(m_dev, bounds);
0175                 m_radiusRecord[currentTile] = radius;
0176             }
0177         }
0178     }
0179 }
0180 
0181 KisMagneticWorker::KisMagneticWorker(const KisPaintDeviceSP &dev) :
0182     m_lazyTileFilter(dev)
0183 { }
0184 
0185 QVector<QPointF> KisMagneticWorker::computeEdge(int bounds, QPoint begin, QPoint end, qreal radius)
0186 {
0187     QRect rect;
0188     KisAlgebra2D::accumulateBounds(QVector<QPoint> { begin, end }, &rect);
0189     rect = kisGrowRect(rect, bounds);
0190     m_lazyTileFilter.filter(radius, rect);
0191 
0192     VertexDescriptor goal(end);
0193     VertexDescriptor start(begin);
0194 
0195     m_graph = new KisMagneticGraph(m_lazyTileFilter.device(), rect);
0196 
0197     // How many maps does it require?
0198     // 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
0199     PredecessorMap pmap;
0200     DistanceMap dmap(std::numeric_limits<double>::max());
0201     dmap[start] = 0;
0202     std::map<VertexDescriptor, double> rmap;
0203     std::map<VertexDescriptor, boost::default_color_type> cmap;
0204     std::map<VertexDescriptor, double> imap;
0205     WeightMap wmap(*m_graph);
0206     AStarHeuristic heuristic(goal);
0207     QVector<QPointF> result;
0208 
0209     try {
0210         boost::astar_search_no_init(
0211             *m_graph, start, heuristic,
0212             boost::visitor(AStarGoalVisitor(goal))
0213             .distance_map(boost::associative_property_map<DistanceMap>(dmap))
0214             .predecessor_map(boost::ref(pmap))
0215             .weight_map(boost::associative_property_map<WeightMap>(wmap))
0216             .vertex_index_map(boost::associative_property_map<std::map<VertexDescriptor, double> >(imap))
0217             .rank_map(boost::associative_property_map<std::map<VertexDescriptor, double> >(rmap))
0218             .color_map(boost::associative_property_map<std::map<VertexDescriptor, boost::default_color_type> >
0219                            (cmap))
0220             .distance_combine(std::plus<double>())
0221             .distance_compare(std::less<double>())
0222             );
0223     } catch (GoalFound const &) {
0224         for (VertexDescriptor u = goal; u != start; u = pmap[u]) {
0225             result.push_front(QPointF(u.x, u.y));
0226         }
0227     }
0228 
0229     result.push_front(QPoint(start.x, start.y));
0230 
0231     return result;
0232 } // KisMagneticWorker::computeEdge
0233 
0234 qreal KisMagneticWorker::intensity(QPoint pt)
0235 {
0236     return m_graph->getIntensity(VertexDescriptor(pt));
0237 }
0238 
0239 void KisMagneticWorker::saveTheImage(vQPointF points)
0240 {
0241     QImage img = m_lazyTileFilter.device()->convertToQImage(nullptr, m_lazyTileFilter.device()->exactBounds());
0242 
0243     const QPointF offset = m_lazyTileFilter.device()->exactBounds().topLeft();
0244     for (QPointF &pt : points) {
0245         pt -= offset;
0246     }
0247 
0248     img.convertTo(QImage::Format_ARGB32);
0249     QPainter gc(&img);
0250 
0251     QPainterPath path;
0252 
0253     for (int i = 0; i < points.size(); i++) {
0254         if (i == 0) {
0255             path.moveTo(points[i]);
0256         } else {
0257             path.lineTo(points[i]);
0258         }
0259     }
0260 
0261     gc.setPen(Qt::blue);
0262     gc.drawPath(path);
0263 
0264     gc.setPen(Qt::green);
0265     gc.drawEllipse(points[0], 3, 3);
0266     gc.setPen(Qt::red);
0267     gc.drawEllipse(points[points.count() - 1], 2, 2);
0268 
0269     for (QRect &r : m_lazyTileFilter.tiles() ) {
0270         gc.drawRect(r);
0271     }
0272 
0273     img.save("result.png");
0274 } // KisMagneticWorker::saveTheImage