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