1 /*
2  *  Copyright (c) 2020 Sharaf Zaman <sharafzaz121@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 #include "SvgMeshArray.h"
19 
20 #include <KoPathSegment.h>
21 #include <kis_global.h>
22 
SvgMeshArray()23 SvgMeshArray::SvgMeshArray()
24 {
25 }
26 
SvgMeshArray(const SvgMeshArray & other)27 SvgMeshArray::SvgMeshArray(const SvgMeshArray& other)
28 {
29     for (const auto& row: other.m_array) {
30         newRow();
31         for (const auto& patch: row) {
32             m_array.last().append(new SvgMeshPatch(*patch));
33         }
34     }
35 }
36 
~SvgMeshArray()37 SvgMeshArray::~SvgMeshArray()
38 {
39     for (auto& row: m_array) {
40         for (auto& patch: row) {
41             delete patch;
42         }
43     }
44 }
45 
newRow()46 void SvgMeshArray::newRow()
47 {
48     m_array << QVector<SvgMeshPatch*>();
49 }
50 
createDefaultMesh(const int nrows,const int ncols,const QColor color,const QSizeF size)51 void SvgMeshArray::createDefaultMesh(const int nrows,
52                                      const int ncols,
53                                      const QColor color,
54                                      const QSizeF size)
55 {
56     // individual patch size should be:
57     qreal patchWidth  = size.width()  / ncols;
58     qreal patchHeight = size.height() / nrows;
59 
60     // normalize
61     patchWidth  /= size.width();
62     patchHeight /= size.height();
63 
64     QRectF start(0, 0, patchWidth, patchHeight);
65 
66     QColor colors[2] = {Qt::white, color};
67 
68     for (int irow = 0; irow < nrows; ++irow) {
69         newRow();
70 
71         for (int icol = 0; icol < ncols; ++icol) {
72             SvgMeshPatch *patch = new SvgMeshPatch(start.topLeft());
73             // alternate between colors
74             int index = (irow + icol) % 2;
75 
76             patch->addStopLinear({start.topLeft(), start.topRight()},
77                                  colors[index],
78                                  SvgMeshPatch::Top);
79 
80             index = (index + 1) % 2;
81             patch->addStopLinear({start.topRight(), start.bottomRight()},
82                                  colors[index],
83                                  SvgMeshPatch::Right);
84 
85             index = (index + 1) % 2;
86             patch->addStopLinear({start.bottomRight(), start.bottomLeft()},
87                                  colors[index],
88                                  SvgMeshPatch::Bottom);
89 
90             index = (index + 1) % 2;
91             patch->addStopLinear({start.bottomLeft(), start.topLeft()},
92                                  colors[index],
93                                  SvgMeshPatch::Left);
94 
95             m_array.last().append(patch);
96 
97             // TopRight of the previous patch in this row
98             start.setX(patch->getStop(SvgMeshPatch::Right).point.x());
99             start.setWidth(patchWidth);
100         }
101 
102         // BottomLeft of the patch is the starting point for new row
103         start.setTopLeft(m_array.last().first()->getStop(SvgMeshPatch::Left).point);
104         start.setSize({patchWidth, patchHeight});
105     }
106 }
107 
addPatch(QList<QPair<QString,QColor>> stops,const QPointF initialPoint)108 bool SvgMeshArray::addPatch(QList<QPair<QString, QColor>> stops, const QPointF initialPoint)
109 {
110     // This is function is full of edge-case landmines, please run TestMeshArray after any changes
111     if (stops.size() > 4 || stops.size() < 2)
112         return false;
113 
114     SvgMeshPatch *patch = new SvgMeshPatch(initialPoint);
115 
116      m_array.last().append(patch);
117 
118     int irow = m_array.size() - 1;
119     int icol = m_array.last().size() - 1;
120 
121     if (irow == 0 && icol == 0) {
122         patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Top);
123         stops.removeFirst();
124     } else if (irow == 0) {
125         // For first row, parse patches
126         patch->addStop(stops[0].first, getColor(SvgMeshPatch::Right, irow, icol - 1), SvgMeshPatch::Top);
127         stops.removeFirst();
128     } else {
129         // path is already defined for rows >= 1
130         QColor color = getStop(SvgMeshPatch::Left, irow - 1, icol).color;
131 
132         std::array<QPointF, 4> points = getPath(SvgMeshPatch::Bottom, irow - 1, icol);
133         std::reverse(points.begin(), points.end());
134 
135         patch->addStop(points, color, SvgMeshPatch::Top);
136     }
137 
138     if (irow > 0) {
139         patch->addStop(stops[0].first, getColor(SvgMeshPatch::Bottom, irow - 1, icol), SvgMeshPatch::Right);
140         stops.removeFirst();
141     } else {
142         patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Right);
143         stops.removeFirst();
144     }
145 
146     if (icol > 0) {
147         patch->addStop(
148                 stops[0].first,
149                 stops[0].second,
150                 SvgMeshPatch::Bottom,
151                 true, getStop(SvgMeshPatch::Bottom, irow, icol - 1).point);
152         stops.removeFirst();
153     } else {
154         patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Bottom);
155         stops.removeFirst();
156     }
157 
158     // last stop
159     if (icol == 0) {
160         // if stop is in the 0th column, parse path
161         patch->addStop(
162                 stops[0].first,
163                 stops[0].second,
164                 SvgMeshPatch::Left,
165                 true, getStop(SvgMeshPatch::Top, irow, icol).point);
166         stops.removeFirst();
167     } else {
168         QColor color = getStop(SvgMeshPatch::Bottom, irow, icol - 1).color;
169 
170         // reuse Right side of the previous patch
171         std::array<QPointF, 4> points = getPath(SvgMeshPatch::Right, irow, icol - 1);
172         std::reverse(points.begin(), points.end());
173 
174         patch->addStop(points, color, SvgMeshPatch::Left);
175     }
176     return true;
177 }
178 
getStop(const SvgMeshPatch::Type edge,const int row,const int col) const179 SvgMeshStop SvgMeshArray::getStop(const SvgMeshPatch::Type edge, const int row, const int col) const
180 {
181     KIS_ASSERT(row < m_array.size() && col < m_array[row].size()
182             && row >= 0 && col >= 0);
183 
184     SvgMeshPatch *patch = m_array[row][col];
185     SvgMeshStop node = patch->getStop(edge);
186 
187     if (node.isValid()) {
188         return node;
189     }
190 
191     switch (patch->countPoints()) {
192     case 3:
193     case 2:
194         if (edge == SvgMeshPatch::Top)
195             return getStop(SvgMeshPatch::Left, row - 1, col);
196         else if (edge == SvgMeshPatch::Left)
197             return getStop(SvgMeshPatch::Bottom, row, col - 1);
198     }
199     Q_ASSERT(false);
200     return SvgMeshStop();
201 }
202 
getStop(const SvgMeshPosition & pos) const203 SvgMeshStop SvgMeshArray::getStop(const SvgMeshPosition &pos) const
204 {
205     return getStop(pos.segmentType, pos.row, pos.col);
206 }
207 
getPath(const SvgMeshPatch::Type edge,const int row,const int col) const208 std::array<QPointF, 4> SvgMeshArray::getPath(const SvgMeshPatch::Type edge, const int row, const int col) const
209 {
210     KIS_ASSERT(row < m_array.size() && col < m_array[row].size()
211             && row >= 0 && col >= 0);
212 
213     return m_array[row][col]->getSegment(edge);
214 }
215 
getPath(const SvgMeshPosition & pos) const216 SvgMeshPath SvgMeshArray::getPath(const SvgMeshPosition &pos) const
217 {
218     return getPath(pos.segmentType, pos.row, pos.col);
219 }
220 
getPatch(const int row,const int col) const221 SvgMeshPatch* SvgMeshArray::getPatch(const int row, const int col) const
222 {
223     KIS_ASSERT(row < m_array.size() && col < m_array[row].size()
224             && row >= 0 && col >= 0);
225 
226     return m_array[row][col];
227 }
228 
numRows() const229 int SvgMeshArray::numRows() const
230 {
231     return m_array.size();
232 }
233 
numColumns() const234 int SvgMeshArray::numColumns() const
235 {
236     if (m_array.isEmpty())
237         return 0;
238     return m_array.first().size();
239 }
240 
setTransform(const QTransform & matrix)241 void SvgMeshArray::setTransform(const QTransform& matrix)
242 {
243     for (auto& row: m_array) {
244         for (auto& patch: row) {
245             patch->setTransform(matrix);
246         }
247     }
248 }
249 
boundingRect() const250 QRectF SvgMeshArray::boundingRect() const
251 {
252     KIS_ASSERT(numRows() > 0 && numColumns() > 0);
253 
254     QPointF topLeft = m_array[0][0]->boundingRect().topLeft();
255     QPointF bottomRight = m_array.last().last()->boundingRect().bottomRight();
256 
257     // mesharray may be backwards, in which case we might get the right most value
258     // but we need topLeft for things to work as expected
259     for (int i = 0; i < numRows(); ++i) {
260         for (int j = 0; j < numColumns(); ++j) {
261             QPointF left  = m_array[i][j]->boundingRect().topLeft();
262             if (left.x() < topLeft.x()) {
263                 topLeft.rx() = left.x();
264             }
265             if ( left.y() < topLeft.y()) {
266                 topLeft.ry() = left.y();
267             }
268 
269             QPointF right = m_array[i][j]->boundingRect().bottomRight();
270             if (bottomRight.x() < right.x()) {
271                 bottomRight.rx() = right.x();
272             }
273             if (bottomRight.y() < right.y()) {
274                 bottomRight.ry() = right.y();
275             }
276         }
277     }
278 
279     // return extremas
280     return QRectF(topLeft, bottomRight);
281 }
282 
getConnectedPaths(const SvgMeshPosition & position) const283 QVector<SvgMeshPosition> SvgMeshArray::getConnectedPaths(const SvgMeshPosition &position) const
284 {
285     QVector<SvgMeshPosition> positions;
286 
287     int row = position.row;
288     int col = position.col;
289     SvgMeshPatch::Type type = position.segmentType;
290 
291     SvgMeshPatch::Type nextType = static_cast<SvgMeshPatch::Type>(type + 1);
292     SvgMeshPatch::Type previousType = static_cast<SvgMeshPatch::Type>((SvgMeshPatch::Size + type - 1) % SvgMeshPatch::Size);
293 
294     if (type == SvgMeshPatch::Top) {
295         if (row == 0) {
296             if (col > 0) {
297                 positions << SvgMeshPosition {row, col - 1, type};
298             }
299         } else {
300             if (col > 0) {
301                 positions << SvgMeshPosition {row, col - 1, type};
302                 positions << SvgMeshPosition {row - 1, col - 1, nextType};
303             }
304             positions << SvgMeshPosition {row - 1, col, previousType};
305         }
306     } else if (type == SvgMeshPatch::Right && row > 0) {
307         positions << SvgMeshPosition {row - 1, col, type};
308 
309     } else if (type == SvgMeshPatch::Left && col > 0) {
310         positions << SvgMeshPosition {row, col - 1, previousType};
311     }
312 
313     positions << SvgMeshPosition {row, col, previousType};
314     positions << SvgMeshPosition {row, col, type};
315 
316     return positions;
317 }
318 
modifyHandle(const SvgMeshPosition & position,const std::array<QPointF,4> & newPath)319 void SvgMeshArray::modifyHandle(const SvgMeshPosition &position,
320                                 const std::array<QPointF, 4> &newPath)
321 {
322     std::array<QPointF, 4> reversed = newPath;
323     std::reverse(reversed.begin(), reversed.end());
324 
325     if (position.segmentType == SvgMeshPatch::Top && position.row > 0) {
326         // modify the shared side
327         m_array[position.row - 1][position.col]->modifyPath(SvgMeshPatch::Bottom, reversed);
328 
329     } else if (position.segmentType == SvgMeshPatch::Left && position.col > 0) {
330         // modify the shared side as well
331         m_array[position.row][position.col - 1]->modifyPath(SvgMeshPatch::Right, reversed);
332     }
333     m_array[position.row][position.col]->modifyPath(position.segmentType, newPath);
334 }
335 
modifyCorner(const SvgMeshPosition & position,const QPointF & newPos)336 void SvgMeshArray::modifyCorner(const SvgMeshPosition &position,
337                                 const QPointF &newPos)
338 {
339     QVector<SvgMeshPosition> paths = getSharedPaths(position);
340 
341     QPointF delta = m_array[position.row][position.col]->getStop(position.segmentType).point - newPos;
342 
343     for (const auto &path: paths) {
344         m_array[path.row][path.col]->modifyCorner(path.segmentType, delta);
345     }
346 }
347 
modifyColor(const SvgMeshPosition & position,const QColor & color)348 void SvgMeshArray::modifyColor(const SvgMeshPosition &position, const QColor &color)
349 {
350     QVector<SvgMeshPosition> paths = getSharedPaths(position);
351 
352     for (const auto &path: paths) {
353         m_array[path.row][path.col]->setStopColor(path.segmentType, color);
354     }
355 }
356 
getSharedPaths(const SvgMeshPosition & position) const357 QVector<SvgMeshPosition> SvgMeshArray::getSharedPaths(const SvgMeshPosition &position) const
358 {
359     QVector<SvgMeshPosition> positions;
360 
361     int row = position.row;
362     int col = position.col;
363     SvgMeshPatch::Type type = position.segmentType;
364 
365     SvgMeshPatch::Type nextType = static_cast<SvgMeshPatch::Type>(type + 1);
366     SvgMeshPatch::Type previousType = static_cast<SvgMeshPatch::Type>((SvgMeshPatch::Size + type - 1) % SvgMeshPatch::Size);
367 
368     if (type == SvgMeshPatch::Top) {
369         if (row == 0) {
370             if (col > 0) {
371                 positions << SvgMeshPosition {row, col - 1, nextType};
372             }
373         } else {
374             if (col > 0) {
375                 positions << SvgMeshPosition {row, col - 1, nextType};
376                 positions << SvgMeshPosition {row - 1, col - 1, SvgMeshPatch::Bottom};
377             }
378             positions << SvgMeshPosition {row - 1, col, previousType};
379         }
380     } else if (type == SvgMeshPatch::Right && row > 0) {
381         positions << SvgMeshPosition {row - 1, col, nextType};
382 
383     } else if (type == SvgMeshPatch::Left && col > 0) {
384         positions << SvgMeshPosition {row, col - 1, previousType};
385     }
386 
387     positions << SvgMeshPosition {row, col, type};
388 
389     return positions;
390 }
391 
getColor(SvgMeshPatch::Type edge,int row,int col) const392 QColor SvgMeshArray::getColor(SvgMeshPatch::Type edge, int row, int col) const
393 {
394     return getStop(edge, row, col).color;
395 }
396 
397