File indexing completed on 2025-01-26 04:04:56
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Sharaf Zaman <sharafzaz121@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "SvgMeshArray.h" 0007 0008 #include <KoPathSegment.h> 0009 #include <kis_global.h> 0010 0011 SvgMeshArray::SvgMeshArray() 0012 { 0013 } 0014 0015 SvgMeshArray::SvgMeshArray(const SvgMeshArray& other) 0016 { 0017 for (const auto& row: other.m_array) { 0018 newRow(); 0019 for (const auto& patch: row) { 0020 m_array.last().append(new SvgMeshPatch(*patch)); 0021 } 0022 } 0023 } 0024 0025 SvgMeshArray::~SvgMeshArray() 0026 { 0027 for (auto& row: m_array) { 0028 for (auto& patch: row) { 0029 delete patch; 0030 } 0031 } 0032 } 0033 0034 void SvgMeshArray::newRow() 0035 { 0036 m_array << QVector<SvgMeshPatch*>(); 0037 } 0038 0039 void SvgMeshArray::createDefaultMesh(const int nrows, 0040 const int ncols, 0041 const QColor color, 0042 const QSizeF size) 0043 { 0044 // individual patch size should be: 0045 qreal patchWidth = size.width() / ncols; 0046 qreal patchHeight = size.height() / nrows; 0047 0048 // normalize 0049 patchWidth /= size.width(); 0050 patchHeight /= size.height(); 0051 0052 QRectF start(0, 0, patchWidth, patchHeight); 0053 0054 QColor colors[2] = {Qt::white, color}; 0055 0056 for (int irow = 0; irow < nrows; ++irow) { 0057 newRow(); 0058 0059 for (int icol = 0; icol < ncols; ++icol) { 0060 SvgMeshPatch *patch = new SvgMeshPatch(start.topLeft()); 0061 // alternate between colors 0062 int index = (irow + icol) % 2; 0063 0064 patch->addStopLinear({start.topLeft(), start.topRight()}, 0065 colors[index], 0066 SvgMeshPatch::Top); 0067 0068 index = (index + 1) % 2; 0069 patch->addStopLinear({start.topRight(), start.bottomRight()}, 0070 colors[index], 0071 SvgMeshPatch::Right); 0072 0073 index = (index + 1) % 2; 0074 patch->addStopLinear({start.bottomRight(), start.bottomLeft()}, 0075 colors[index], 0076 SvgMeshPatch::Bottom); 0077 0078 index = (index + 1) % 2; 0079 patch->addStopLinear({start.bottomLeft(), start.topLeft()}, 0080 colors[index], 0081 SvgMeshPatch::Left); 0082 0083 m_array.last().append(patch); 0084 0085 // TopRight of the previous patch in this row 0086 start.setX(patch->getStop(SvgMeshPatch::Right).point.x()); 0087 start.setWidth(patchWidth); 0088 } 0089 0090 // BottomLeft of the patch is the starting point for new row 0091 start.setTopLeft(m_array.last().first()->getStop(SvgMeshPatch::Left).point); 0092 start.setSize({patchWidth, patchHeight}); 0093 } 0094 } 0095 0096 bool SvgMeshArray::addPatch(QList<QPair<QString, QColor>> stops, const QPointF initialPoint) 0097 { 0098 // This is function is full of edge-case landmines, please run TestMeshArray after any changes 0099 if (stops.size() > 4 || stops.size() < 2) 0100 return false; 0101 0102 SvgMeshPatch *patch = new SvgMeshPatch(initialPoint); 0103 0104 m_array.last().append(patch); 0105 0106 int irow = m_array.size() - 1; 0107 int icol = m_array.last().size() - 1; 0108 0109 if (irow == 0 && icol == 0) { 0110 patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Top); 0111 stops.removeFirst(); 0112 } else if (irow == 0) { 0113 // For first row, parse patches 0114 patch->addStop(stops[0].first, getColor(SvgMeshPatch::Right, irow, icol - 1), SvgMeshPatch::Top); 0115 stops.removeFirst(); 0116 } else { 0117 // path is already defined for rows >= 1 0118 QColor color = getStop(SvgMeshPatch::Left, irow - 1, icol).color; 0119 0120 std::array<QPointF, 4> points = getPath(SvgMeshPatch::Bottom, irow - 1, icol); 0121 std::reverse(points.begin(), points.end()); 0122 0123 patch->addStop(points, color, SvgMeshPatch::Top); 0124 } 0125 0126 if (irow > 0) { 0127 patch->addStop(stops[0].first, getColor(SvgMeshPatch::Bottom, irow - 1, icol), SvgMeshPatch::Right); 0128 stops.removeFirst(); 0129 } else { 0130 patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Right); 0131 stops.removeFirst(); 0132 } 0133 0134 if (icol > 0) { 0135 patch->addStop( 0136 stops[0].first, 0137 stops[0].second, 0138 SvgMeshPatch::Bottom, 0139 true, getStop(SvgMeshPatch::Bottom, irow, icol - 1).point); 0140 stops.removeFirst(); 0141 } else { 0142 patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Bottom); 0143 stops.removeFirst(); 0144 } 0145 0146 // last stop 0147 if (icol == 0) { 0148 // if stop is in the 0th column, parse path 0149 patch->addStop( 0150 stops[0].first, 0151 stops[0].second, 0152 SvgMeshPatch::Left, 0153 true, getStop(SvgMeshPatch::Top, irow, icol).point); 0154 stops.removeFirst(); 0155 } else { 0156 QColor color = getStop(SvgMeshPatch::Bottom, irow, icol - 1).color; 0157 0158 // reuse Right side of the previous patch 0159 std::array<QPointF, 4> points = getPath(SvgMeshPatch::Right, irow, icol - 1); 0160 std::reverse(points.begin(), points.end()); 0161 0162 patch->addStop(points, color, SvgMeshPatch::Left); 0163 } 0164 return true; 0165 } 0166 0167 SvgMeshStop SvgMeshArray::getStop(const SvgMeshPatch::Type edge, const int row, const int col) const 0168 { 0169 KIS_ASSERT(row < m_array.size() && col < m_array[row].size() 0170 && row >= 0 && col >= 0); 0171 0172 SvgMeshPatch *patch = m_array[row][col]; 0173 SvgMeshStop node = patch->getStop(edge); 0174 0175 if (node.isValid()) { 0176 return node; 0177 } 0178 0179 switch (patch->countPoints()) { 0180 case 3: 0181 case 2: 0182 if (edge == SvgMeshPatch::Top) 0183 return getStop(SvgMeshPatch::Left, row - 1, col); 0184 else if (edge == SvgMeshPatch::Left) 0185 return getStop(SvgMeshPatch::Bottom, row, col - 1); 0186 } 0187 Q_ASSERT(false); 0188 return SvgMeshStop(); 0189 } 0190 0191 SvgMeshStop SvgMeshArray::getStop(const SvgMeshPosition &pos) const 0192 { 0193 return getStop(pos.segmentType, pos.row, pos.col); 0194 } 0195 0196 std::array<QPointF, 4> SvgMeshArray::getPath(const SvgMeshPatch::Type edge, const int row, const int col) const 0197 { 0198 KIS_ASSERT(row < m_array.size() && col < m_array[row].size() 0199 && row >= 0 && col >= 0); 0200 0201 return m_array[row][col]->getSegment(edge); 0202 } 0203 0204 SvgMeshPath SvgMeshArray::getPath(const SvgMeshPosition &pos) const 0205 { 0206 return getPath(pos.segmentType, pos.row, pos.col); 0207 } 0208 0209 SvgMeshPatch* SvgMeshArray::getPatch(const int row, const int col) const 0210 { 0211 KIS_ASSERT(row < m_array.size() && col < m_array[row].size() 0212 && row >= 0 && col >= 0); 0213 0214 return m_array[row][col]; 0215 } 0216 0217 int SvgMeshArray::numRows() const 0218 { 0219 return m_array.size(); 0220 } 0221 0222 int SvgMeshArray::numColumns() const 0223 { 0224 if (m_array.isEmpty()) 0225 return 0; 0226 return m_array.first().size(); 0227 } 0228 0229 void SvgMeshArray::setTransform(const QTransform& matrix) 0230 { 0231 for (auto& row: m_array) { 0232 for (auto& patch: row) { 0233 patch->setTransform(matrix); 0234 } 0235 } 0236 } 0237 0238 QRectF SvgMeshArray::boundingRect() const 0239 { 0240 KIS_ASSERT(numRows() > 0 && numColumns() > 0); 0241 0242 QPointF topLeft = m_array[0][0]->boundingRect().topLeft(); 0243 QPointF bottomRight = m_array.last().last()->boundingRect().bottomRight(); 0244 0245 // mesharray may be backwards, in which case we might get the right most value 0246 // but we need topLeft for things to work as expected 0247 for (int i = 0; i < numRows(); ++i) { 0248 for (int j = 0; j < numColumns(); ++j) { 0249 QPointF left = m_array[i][j]->boundingRect().topLeft(); 0250 if (left.x() < topLeft.x()) { 0251 topLeft.rx() = left.x(); 0252 } 0253 if ( left.y() < topLeft.y()) { 0254 topLeft.ry() = left.y(); 0255 } 0256 0257 QPointF right = m_array[i][j]->boundingRect().bottomRight(); 0258 if (bottomRight.x() < right.x()) { 0259 bottomRight.rx() = right.x(); 0260 } 0261 if (bottomRight.y() < right.y()) { 0262 bottomRight.ry() = right.y(); 0263 } 0264 } 0265 } 0266 0267 // return extremas 0268 return QRectF(topLeft, bottomRight); 0269 } 0270 0271 QVector<SvgMeshPosition> SvgMeshArray::getConnectedPaths(const SvgMeshPosition &position) const 0272 { 0273 QVector<SvgMeshPosition> positions; 0274 0275 int row = position.row; 0276 int col = position.col; 0277 SvgMeshPatch::Type type = position.segmentType; 0278 0279 SvgMeshPatch::Type nextType = static_cast<SvgMeshPatch::Type>(type + 1); 0280 SvgMeshPatch::Type previousType = static_cast<SvgMeshPatch::Type>((SvgMeshPatch::Size + type - 1) % SvgMeshPatch::Size); 0281 0282 if (type == SvgMeshPatch::Top) { 0283 if (row == 0) { 0284 if (col > 0) { 0285 positions << SvgMeshPosition {row, col - 1, type}; 0286 } 0287 } else { 0288 if (col > 0) { 0289 positions << SvgMeshPosition {row, col - 1, type}; 0290 positions << SvgMeshPosition {row - 1, col - 1, nextType}; 0291 } 0292 positions << SvgMeshPosition {row - 1, col, previousType}; 0293 } 0294 } else if (type == SvgMeshPatch::Right && row > 0) { 0295 positions << SvgMeshPosition {row - 1, col, type}; 0296 0297 } else if (type == SvgMeshPatch::Left && col > 0) { 0298 positions << SvgMeshPosition {row, col - 1, previousType}; 0299 } 0300 0301 positions << SvgMeshPosition {row, col, previousType}; 0302 positions << SvgMeshPosition {row, col, type}; 0303 0304 return positions; 0305 } 0306 0307 void SvgMeshArray::modifyHandle(const SvgMeshPosition &position, 0308 const std::array<QPointF, 4> &newPath) 0309 { 0310 std::array<QPointF, 4> reversed = newPath; 0311 std::reverse(reversed.begin(), reversed.end()); 0312 0313 if (position.segmentType == SvgMeshPatch::Top && position.row > 0) { 0314 // modify the shared side 0315 m_array[position.row - 1][position.col]->modifyPath(SvgMeshPatch::Bottom, reversed); 0316 0317 } else if (position.segmentType == SvgMeshPatch::Left && position.col > 0) { 0318 // modify the shared side as well 0319 m_array[position.row][position.col - 1]->modifyPath(SvgMeshPatch::Right, reversed); 0320 } 0321 m_array[position.row][position.col]->modifyPath(position.segmentType, newPath); 0322 } 0323 0324 void SvgMeshArray::modifyCorner(const SvgMeshPosition &position, 0325 const QPointF &newPos) 0326 { 0327 QVector<SvgMeshPosition> paths = getSharedPaths(position); 0328 0329 QPointF delta = m_array[position.row][position.col]->getStop(position.segmentType).point - newPos; 0330 0331 for (const auto &path: paths) { 0332 m_array[path.row][path.col]->modifyCorner(path.segmentType, delta); 0333 } 0334 } 0335 0336 void SvgMeshArray::modifyColor(const SvgMeshPosition &position, const QColor &color) 0337 { 0338 QVector<SvgMeshPosition> paths = getSharedPaths(position); 0339 0340 for (const auto &path: paths) { 0341 m_array[path.row][path.col]->setStopColor(path.segmentType, color); 0342 } 0343 } 0344 0345 QVector<SvgMeshPosition> SvgMeshArray::getSharedPaths(const SvgMeshPosition &position) const 0346 { 0347 QVector<SvgMeshPosition> positions; 0348 0349 int row = position.row; 0350 int col = position.col; 0351 SvgMeshPatch::Type type = position.segmentType; 0352 0353 SvgMeshPatch::Type nextType = static_cast<SvgMeshPatch::Type>(type + 1); 0354 SvgMeshPatch::Type previousType = static_cast<SvgMeshPatch::Type>((SvgMeshPatch::Size + type - 1) % SvgMeshPatch::Size); 0355 0356 if (type == SvgMeshPatch::Top) { 0357 if (row == 0) { 0358 if (col > 0) { 0359 positions << SvgMeshPosition {row, col - 1, nextType}; 0360 } 0361 } else { 0362 if (col > 0) { 0363 positions << SvgMeshPosition {row, col - 1, nextType}; 0364 positions << SvgMeshPosition {row - 1, col - 1, SvgMeshPatch::Bottom}; 0365 } 0366 positions << SvgMeshPosition {row - 1, col, previousType}; 0367 } 0368 } else if (type == SvgMeshPatch::Right && row > 0) { 0369 positions << SvgMeshPosition {row - 1, col, nextType}; 0370 0371 } else if (type == SvgMeshPatch::Left && col > 0) { 0372 positions << SvgMeshPosition {row, col - 1, previousType}; 0373 } 0374 0375 positions << SvgMeshPosition {row, col, type}; 0376 0377 return positions; 0378 } 0379 0380 QColor SvgMeshArray::getColor(SvgMeshPatch::Type edge, int row, int col) const 0381 { 0382 return getStop(edge, row, col).color; 0383 } 0384