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