Krita Source Code Documentation
Loading...
Searching...
No Matches
SvgMeshArray.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Sharaf Zaman <sharafzaz121@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "SvgMeshArray.h"
7
8#include <KoPathSegment.h>
9#include <kis_global.h>
10
14
16{
17 for (const auto& row: other.m_array) {
18 newRow();
19 for (const auto& patch: row) {
20 m_array.last().append(new SvgMeshPatch(*patch));
21 }
22 }
23}
24
26{
27 for (auto& row: m_array) {
28 for (auto& patch: row) {
29 delete patch;
30 }
31 }
32}
33
35{
36 m_array << QVector<SvgMeshPatch*>();
37}
38
40 const int ncols,
41 const QColor color,
42 const QSizeF size)
43{
44 // individual patch size should be:
45 qreal patchWidth = size.width() / ncols;
46 qreal patchHeight = size.height() / nrows;
47
48 // normalize
49 patchWidth /= size.width();
50 patchHeight /= size.height();
51
52 QRectF start(0, 0, patchWidth, patchHeight);
53
54 QColor colors[2] = {Qt::white, color};
55
56 for (int irow = 0; irow < nrows; ++irow) {
57 newRow();
58
59 for (int icol = 0; icol < ncols; ++icol) {
60 SvgMeshPatch *patch = new SvgMeshPatch(start.topLeft());
61 // alternate between colors
62 int index = (irow + icol) % 2;
63
64 patch->addStopLinear({start.topLeft(), start.topRight()},
65 colors[index],
67
68 index = (index + 1) % 2;
69 patch->addStopLinear({start.topRight(), start.bottomRight()},
70 colors[index],
72
73 index = (index + 1) % 2;
74 patch->addStopLinear({start.bottomRight(), start.bottomLeft()},
75 colors[index],
77
78 index = (index + 1) % 2;
79 patch->addStopLinear({start.bottomLeft(), start.topLeft()},
80 colors[index],
82
83 m_array.last().append(patch);
84
85 // TopRight of the previous patch in this row
86 start.setX(patch->getStop(SvgMeshPatch::Right).point.x());
87 start.setWidth(patchWidth);
88 }
89
90 // BottomLeft of the patch is the starting point for new row
91 start.setTopLeft(m_array.last().first()->getStop(SvgMeshPatch::Left).point);
92 start.setSize({patchWidth, patchHeight});
93 }
94}
95
96bool SvgMeshArray::addPatch(QList<QPair<QString, QColor>> stops, const QPointF initialPoint)
97{
98 // This is function is full of edge-case landmines, please run TestMeshArray after any changes
99 if (stops.size() > 4 || stops.size() < 2)
100 return false;
101
102 SvgMeshPatch *patch = new SvgMeshPatch(initialPoint);
103
104 m_array.last().append(patch);
105
106 int irow = m_array.size() - 1;
107 int icol = m_array.last().size() - 1;
108
109 if (irow == 0 && icol == 0) {
110 patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Top);
111 stops.removeFirst();
112 } else if (irow == 0) {
113 // For first row, parse patches
114 patch->addStop(stops[0].first, getColor(SvgMeshPatch::Right, irow, icol - 1), SvgMeshPatch::Top);
115 stops.removeFirst();
116 } else {
117 // path is already defined for rows >= 1
118 QColor color = getStop(SvgMeshPatch::Left, irow - 1, icol).color;
119
120 std::array<QPointF, 4> points = getPath(SvgMeshPatch::Bottom, irow - 1, icol);
121 std::reverse(points.begin(), points.end());
122
123 patch->addStop(points, color, SvgMeshPatch::Top);
124 }
125
126 if (irow > 0) {
127 patch->addStop(stops[0].first, getColor(SvgMeshPatch::Bottom, irow - 1, icol), SvgMeshPatch::Right);
128 stops.removeFirst();
129 } else {
130 patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Right);
131 stops.removeFirst();
132 }
133
134 if (icol > 0) {
135 patch->addStop(
136 stops[0].first,
137 stops[0].second,
139 true, getStop(SvgMeshPatch::Bottom, irow, icol - 1).point);
140 stops.removeFirst();
141 } else {
142 patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Bottom);
143 stops.removeFirst();
144 }
145
146 // last stop
147 if (icol == 0) {
148 // if stop is in the 0th column, parse path
149 patch->addStop(
150 stops[0].first,
151 stops[0].second,
153 true, getStop(SvgMeshPatch::Top, irow, icol).point);
154 stops.removeFirst();
155 } else {
156 QColor color = getStop(SvgMeshPatch::Bottom, irow, icol - 1).color;
157
158 // reuse Right side of the previous patch
159 std::array<QPointF, 4> points = getPath(SvgMeshPatch::Right, irow, icol - 1);
160 std::reverse(points.begin(), points.end());
161
162 patch->addStop(points, color, SvgMeshPatch::Left);
163 }
164 return true;
165}
166
167SvgMeshStop SvgMeshArray::getStop(const SvgMeshPatch::Type edge, const int row, const int col) const
168{
169 KIS_ASSERT(row < m_array.size() && col < m_array[row].size()
170 && row >= 0 && col >= 0);
171
172 SvgMeshPatch *patch = m_array[row][col];
173 SvgMeshStop node = patch->getStop(edge);
174
175 if (node.isValid()) {
176 return node;
177 }
178
179 switch (patch->countPoints()) {
180 case 3:
181 case 2:
182 if (edge == SvgMeshPatch::Top)
183 return getStop(SvgMeshPatch::Left, row - 1, col);
184 else if (edge == SvgMeshPatch::Left)
185 return getStop(SvgMeshPatch::Bottom, row, col - 1);
186 }
187 Q_ASSERT(false);
188 return SvgMeshStop();
189}
190
192{
193 return getStop(pos.segmentType, pos.row, pos.col);
194}
195
196std::array<QPointF, 4> SvgMeshArray::getPath(const SvgMeshPatch::Type edge, const int row, const int col) const
197{
198 KIS_ASSERT(row < m_array.size() && col < m_array[row].size()
199 && row >= 0 && col >= 0);
200
201 return m_array[row][col]->getSegment(edge);
202}
203
205{
206 return getPath(pos.segmentType, pos.row, pos.col);
207}
208
209SvgMeshPatch* SvgMeshArray::getPatch(const int row, const int col) const
210{
211 KIS_ASSERT(row < m_array.size() && col < m_array[row].size()
212 && row >= 0 && col >= 0);
213
214 return m_array[row][col];
215}
216
218{
219 return m_array.size();
220}
221
223{
224 if (m_array.isEmpty())
225 return 0;
226 return m_array.first().size();
227}
228
229void SvgMeshArray::setTransform(const QTransform& matrix)
230{
231 for (auto& row: m_array) {
232 for (auto& patch: row) {
233 patch->setTransform(matrix);
234 }
235 }
236}
237
239{
240 KIS_ASSERT(numRows() > 0 && numColumns() > 0);
241
242 QPointF topLeft = m_array[0][0]->boundingRect().topLeft();
243 QPointF bottomRight = m_array.last().last()->boundingRect().bottomRight();
244
245 // mesharray may be backwards, in which case we might get the right most value
246 // but we need topLeft for things to work as expected
247 for (int i = 0; i < numRows(); ++i) {
248 for (int j = 0; j < numColumns(); ++j) {
249 QPointF left = m_array[i][j]->boundingRect().topLeft();
250 if (left.x() < topLeft.x()) {
251 topLeft.rx() = left.x();
252 }
253 if ( left.y() < topLeft.y()) {
254 topLeft.ry() = left.y();
255 }
256
257 QPointF right = m_array[i][j]->boundingRect().bottomRight();
258 if (bottomRight.x() < right.x()) {
259 bottomRight.rx() = right.x();
260 }
261 if (bottomRight.y() < right.y()) {
262 bottomRight.ry() = right.y();
263 }
264 }
265 }
266
267 // return extremas
268 return QRectF(topLeft, bottomRight);
269}
270
272{
273 QVector<SvgMeshPosition> positions;
274
275 int row = position.row;
276 int col = position.col;
277 SvgMeshPatch::Type type = position.segmentType;
278
279 SvgMeshPatch::Type nextType = static_cast<SvgMeshPatch::Type>(type + 1);
280 SvgMeshPatch::Type previousType = static_cast<SvgMeshPatch::Type>((SvgMeshPatch::Size + type - 1) % SvgMeshPatch::Size);
281
282 if (type == SvgMeshPatch::Top) {
283 if (row == 0) {
284 if (col > 0) {
285 positions << SvgMeshPosition {row, col - 1, type};
286 }
287 } else {
288 if (col > 0) {
289 positions << SvgMeshPosition {row, col - 1, type};
290 positions << SvgMeshPosition {row - 1, col - 1, nextType};
291 }
292 positions << SvgMeshPosition {row - 1, col, previousType};
293 }
294 } else if (type == SvgMeshPatch::Right && row > 0) {
295 positions << SvgMeshPosition {row - 1, col, type};
296
297 } else if (type == SvgMeshPatch::Left && col > 0) {
298 positions << SvgMeshPosition {row, col - 1, previousType};
299 }
300
301 positions << SvgMeshPosition {row, col, previousType};
302 positions << SvgMeshPosition {row, col, type};
303
304 return positions;
305}
306
308 const std::array<QPointF, 4> &newPath)
309{
310 std::array<QPointF, 4> reversed = newPath;
311 std::reverse(reversed.begin(), reversed.end());
312
313 if (position.segmentType == SvgMeshPatch::Top && position.row > 0) {
314 // modify the shared side
315 m_array[position.row - 1][position.col]->modifyPath(SvgMeshPatch::Bottom, reversed);
316
317 } else if (position.segmentType == SvgMeshPatch::Left && position.col > 0) {
318 // modify the shared side as well
319 m_array[position.row][position.col - 1]->modifyPath(SvgMeshPatch::Right, reversed);
320 }
321 m_array[position.row][position.col]->modifyPath(position.segmentType, newPath);
322}
323
325 const QPointF &newPos)
326{
328
329 QPointF delta = m_array[position.row][position.col]->getStop(position.segmentType).point - newPos;
330
331 for (const auto &path: paths) {
332 m_array[path.row][path.col]->modifyCorner(path.segmentType, delta);
333 }
334}
335
336void SvgMeshArray::modifyColor(const SvgMeshPosition &position, const QColor &color)
337{
339
340 for (const auto &path: paths) {
341 m_array[path.row][path.col]->setStopColor(path.segmentType, color);
342 }
343}
344
346{
347 QVector<SvgMeshPosition> positions;
348
349 int row = position.row;
350 int col = position.col;
351 SvgMeshPatch::Type type = position.segmentType;
352
353 SvgMeshPatch::Type nextType = static_cast<SvgMeshPatch::Type>(type + 1);
354 SvgMeshPatch::Type previousType = static_cast<SvgMeshPatch::Type>((SvgMeshPatch::Size + type - 1) % SvgMeshPatch::Size);
355
356 if (type == SvgMeshPatch::Top) {
357 if (row == 0) {
358 if (col > 0) {
359 positions << SvgMeshPosition {row, col - 1, nextType};
360 }
361 } else {
362 if (col > 0) {
363 positions << SvgMeshPosition {row, col - 1, nextType};
364 positions << SvgMeshPosition {row - 1, col - 1, SvgMeshPatch::Bottom};
365 }
366 positions << SvgMeshPosition {row - 1, col, previousType};
367 }
368 } else if (type == SvgMeshPatch::Right && row > 0) {
369 positions << SvgMeshPosition {row - 1, col, nextType};
370
371 } else if (type == SvgMeshPatch::Left && col > 0) {
372 positions << SvgMeshPosition {row, col - 1, previousType};
373 }
374
375 positions << SvgMeshPosition {row, col, type};
376
377 return positions;
378}
379
380QColor SvgMeshArray::getColor(SvgMeshPatch::Type edge, int row, int col) const
381{
382 return getStop(edge, row, col).color;
383}
384
std::array< QPointF, 4 > SvgMeshPath
int numRows() const
QVector< QVector< SvgMeshPatch * > > m_array
where each vector is a meshrow
QVector< SvgMeshPosition > getConnectedPaths(const SvgMeshPosition &position) const
Return the paths connected to the corner. Can be thought of as edges connected to a vertex.
QColor getColor(SvgMeshPatch::Type edge, int row, int col) const
void modifyCorner(const SvgMeshPosition &position, const QPointF &newPos)
void createDefaultMesh(const int nrows, const int ncols, const QColor color, const QSizeF size)
creates a default mesh in OBB coordinates (because it's easier and more logical in this case)
QRectF boundingRect() const
void modifyColor(const SvgMeshPosition &position, const QColor &color)
void setTransform(const QTransform &matrix)
bool addPatch(QList< QPair< QString, QColor > > stops, const QPointF initialPoint)
void modifyHandle(const SvgMeshPosition &position, const std::array< QPointF, 4 > &newPath)
std::array< QPointF, 4 > getPath(const SvgMeshPatch::Type edge, const int row, const int col) const
Get the Path Points for a segment of the meshpatch.
int numColumns() const
SvgMeshPatch * getPatch(const int row, const int col) const
SvgMeshStop getStop(const SvgMeshPatch::Type edge, const int row, const int col) const
Get the point of a node in mesharray.
QVector< SvgMeshPosition > getSharedPaths(const SvgMeshPosition &position) const
Type
Position of stop in the patch.
int countPoints() const
void addStop(const QString &pathStr, QColor color, Type edge, bool pathIncomplete=false, QPointF lastPoint=QPointF())
void addStopLinear(const std::array< QPointF, 2 > &pathPoints, QColor color, Type edge)
Adds linear path to the shape.
SvgMeshStop getStop(Type type) const
returns the starting point of the stop
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
SvgMeshPatch::Type segmentType
bool isValid() const
QPointF point