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