SvgMeshArray.cpp 12.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
 *  Copyright (c) 2020 Sharaf Zaman <sharafzaz121@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
#include "SvgMeshArray.h"
19
20

#include <KoPathSegment.h>
Sharaf Zaman's avatar
Sharaf Zaman committed
21
#include <kis_global.h>
22
23
24
25
26

SvgMeshArray::SvgMeshArray()
{
}

27
SvgMeshArray::SvgMeshArray(const SvgMeshArray& other)
28
{
29
30
31
32
33
34
    for (const auto& row: other.m_array) {
        newRow();
        for (const auto& patch: row) {
            m_array.last().append(new SvgMeshPatch(*patch));
        }
    }
35
36
}

37
38
39
40
41
42
43
44
45
46
47
48
49
SvgMeshArray::~SvgMeshArray()
{
    for (auto& row: m_array) {
        for (auto& patch: row) {
            delete patch;
        }
    }
}

void SvgMeshArray::newRow()
{
    m_array << QVector<SvgMeshPatch*>();
}
50

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void SvgMeshArray::createDefaultMesh(const int nrows,
                                     const int ncols,
                                     const QColor color,
                                     const QSizeF size)
{
    // individual patch size should be:
    qreal patchWidth  = size.width()  / ncols;
    qreal patchHeight = size.height() / nrows;

    // normalize
    patchWidth  /= size.width();
    patchHeight /= size.height();

    QRectF start(0, 0, patchWidth, patchHeight);

    QColor colors[2] = {Qt::white, color};

    for (int irow = 0; irow < nrows; ++irow) {
        newRow();

        for (int icol = 0; icol < ncols; ++icol) {
            SvgMeshPatch *patch = new SvgMeshPatch(start.topLeft());
            // alternate between colors
            int index = (irow + icol) % 2;

76
77
78
            patch->addStopLinear({start.topLeft(), start.topRight()},
                                 colors[index],
                                 SvgMeshPatch::Top);
79
80

            index = (index + 1) % 2;
81
82
83
            patch->addStopLinear({start.topRight(), start.bottomRight()},
                                 colors[index],
                                 SvgMeshPatch::Right);
84
85

            index = (index + 1) % 2;
86
87
88
            patch->addStopLinear({start.bottomRight(), start.bottomLeft()},
                                 colors[index],
                                 SvgMeshPatch::Bottom);
89
90

            index = (index + 1) % 2;
91
92
93
            patch->addStopLinear({start.bottomLeft(), start.topLeft()},
                                 colors[index],
                                 SvgMeshPatch::Left);
94
95
96
97
98
99
100
101
102
103
104
105
106
107

            m_array.last().append(patch);

            // TopRight of the previous patch in this row
            start.setX(patch->getStop(SvgMeshPatch::Right).point.x());
            start.setWidth(patchWidth);
        }

        // BottomLeft of the patch is the starting point for new row
        start.setTopLeft(m_array.last().first()->getStop(SvgMeshPatch::Left).point);
        start.setSize({patchWidth, patchHeight});
    }
}

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
112
113
114
115
    if (stops.size() > 4 || stops.size() < 2)
        return false;

    SvgMeshPatch *patch = new SvgMeshPatch(initialPoint);

Sharaf Zaman's avatar
Sharaf Zaman committed
116
     m_array.last().append(patch);
117
118
119
120

    int irow = m_array.size() - 1;
    int icol = m_array.last().size() - 1;

Sharaf Zaman's avatar
Sharaf Zaman committed
121
122
123
124
    if (irow == 0 && icol == 0) {
        patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Top);
        stops.removeFirst();
    } else if (irow == 0) {
125
        // For first row, parse patches
126
        patch->addStop(stops[0].first, getColor(SvgMeshPatch::Right, irow, icol - 1), SvgMeshPatch::Top);
Sharaf Zaman's avatar
Sharaf Zaman committed
127
128
129
130
        stops.removeFirst();
    } else {
        // path is already defined for rows >= 1
        QColor color = getStop(SvgMeshPatch::Left, irow - 1, icol).color;
131

132
        std::array<QPointF, 4> points = getPath(SvgMeshPatch::Bottom, irow - 1, icol);
Sharaf Zaman's avatar
Sharaf Zaman committed
133
        std::reverse(points.begin(), points.end());
134

Sharaf Zaman's avatar
Sharaf Zaman committed
135
        patch->addStop(points, color, SvgMeshPatch::Top);
136
    }
137

138
139
140
141
142
143
144
    if (irow > 0) {
        patch->addStop(stops[0].first, getColor(SvgMeshPatch::Bottom, irow - 1, icol), SvgMeshPatch::Right);
        stops.removeFirst();
    } else {
        patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Right);
        stops.removeFirst();
    }
145
146
147
148
149
150
151
152
153
154

    if (icol > 0) {
        patch->addStop(
                stops[0].first,
                stops[0].second,
                SvgMeshPatch::Bottom,
                true, getStop(SvgMeshPatch::Bottom, irow, icol - 1).point);
        stops.removeFirst();
    } else {
        patch->addStop(stops[0].first, stops[0].second, SvgMeshPatch::Bottom);
155
156
157
158
159
160
        stops.removeFirst();
    }

    // last stop
    if (icol == 0) {
        // if stop is in the 0th column, parse path
161
162
163
164
165
        patch->addStop(
                stops[0].first,
                stops[0].second,
                SvgMeshPatch::Left,
                true, getStop(SvgMeshPatch::Top, irow, icol).point);
166
167
168
169
170
        stops.removeFirst();
    } else {
        QColor color = getStop(SvgMeshPatch::Bottom, irow, icol - 1).color;

        // reuse Right side of the previous patch
171
        std::array<QPointF, 4> points = getPath(SvgMeshPatch::Right, irow, icol - 1);
172
173
174
175
176
        std::reverse(points.begin(), points.end());

        patch->addStop(points, color, SvgMeshPatch::Left);
    }
    return true;
177
178
179
180
}

SvgMeshStop SvgMeshArray::getStop(const SvgMeshPatch::Type edge, const int row, const int col) const
{
Sharaf Zaman's avatar
Sharaf Zaman committed
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
188
    if (node.isValid()) {
        return node;
189
190
191
192
193
194
195
196
197
198
    }

    switch (patch->countPoints()) {
    case 3:
    case 2:
        if (edge == SvgMeshPatch::Top)
            return getStop(SvgMeshPatch::Left, row - 1, col);
        else if (edge == SvgMeshPatch::Left)
            return getStop(SvgMeshPatch::Bottom, row, col - 1);
    }
199
200
201
    assert(false);
}

202
203
204
205
206
SvgMeshStop SvgMeshArray::getStop(const SvgMeshPosition &pos) const
{
    return getStop(pos.segmentType, pos.row, pos.col);
}

207
std::array<QPointF, 4> SvgMeshArray::getPath(const SvgMeshPatch::Type edge, const int row, const int col) const
208
{
Sharaf Zaman's avatar
Sharaf Zaman committed
209
    KIS_ASSERT(row < m_array.size() && col < m_array[row].size()
210
            && row >= 0 && col >= 0);
211

212
    return m_array[row][col]->getSegment(edge);
213
214
}

215
216
217
218
219
SvgMeshPath SvgMeshArray::getPath(const SvgMeshPosition &pos) const
{
    return getPath(pos.segmentType, pos.row, pos.col);
}

220
221
222
223
224
225
226
227
SvgMeshPatch* SvgMeshArray::getPatch(const int row, const int col) const
{
    KIS_ASSERT(row < m_array.size() && col < m_array[row].size()
            && row >= 0 && col >= 0);

    return m_array[row][col];
}

228
229
230
231
232
233
234
235
236
237
238
int SvgMeshArray::numRows() const
{
    return m_array.size();
}

int SvgMeshArray::numColumns() const
{
    if (m_array.isEmpty())
        return 0;
    return m_array.first().size();
}
Sharaf Zaman's avatar
Sharaf Zaman committed
239

240
241
242
243
244
245
246
247
void SvgMeshArray::setTransform(const QTransform& matrix)
{
    for (auto& row: m_array) {
        for (auto& patch: row) {
            patch->setTransform(matrix);
        }
    }
}
248
249
250
251
252

QRectF SvgMeshArray::boundingRect() const
{
    KIS_ASSERT(numRows() > 0 && numColumns() > 0);

253
254
    QPointF topLeft = m_array[0][0]->boundingRect().topLeft();
    QPointF bottomRight = m_array.last().last()->boundingRect().bottomRight();
255

256
257
258
259
    // mesharray may be backwards, in which case we might get the right most value
    // but we need topLeft for things to work as expected
    for (int i = 0; i < numRows(); ++i) {
        for (int j = 0; j < numColumns(); ++j) {
260
261
262
            QPointF left  = m_array[i][j]->boundingRect().topLeft();
            if (left.x() < topLeft.x()) {
                topLeft.rx() = left.x();
263
            }
264
265
            if ( left.y() < topLeft.y()) {
                topLeft.ry() = left.y();
266
            }
267

268
269
270
271
272
273
274
275
            QPointF right = m_array[i][j]->boundingRect().bottomRight();
            if (bottomRight.x() < right.x()) {
                bottomRight.rx() = right.x();
            }
            if (bottomRight.y() < right.y()) {
                bottomRight.ry() = right.y();
            }
        }
276
277
    }

278
279
    // return extremas
    return QRectF(topLeft, bottomRight);
280
}
281

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
QVector<SvgMeshPosition> SvgMeshArray::getConnectedPaths(const SvgMeshPosition &position) const
{
    QVector<SvgMeshPosition> positions;

    int row = position.row;
    int col = position.col;
    SvgMeshPatch::Type type = position.segmentType;

    SvgMeshPatch::Type nextType = static_cast<SvgMeshPatch::Type>(type + 1);
    SvgMeshPatch::Type previousType = static_cast<SvgMeshPatch::Type>((SvgMeshPatch::Size + type - 1) % SvgMeshPatch::Size);

    if (type == SvgMeshPatch::Top) {
        if (row == 0) {
            if (col > 0) {
                positions << SvgMeshPosition {row, col - 1, type};
            }
        } else {
            if (col > 0) {
                positions << SvgMeshPosition {row, col - 1, type};
                positions << SvgMeshPosition {row - 1, col - 1, nextType};
            }
            positions << SvgMeshPosition {row - 1, col, previousType};
        }
    } else if (type == SvgMeshPatch::Right && row > 0) {
        positions << SvgMeshPosition {row - 1, col, type};

    } else if (type == SvgMeshPatch::Left && col > 0) {
        positions << SvgMeshPosition {row, col - 1, previousType};
    }

    positions << SvgMeshPosition {row, col, previousType};
    positions << SvgMeshPosition {row, col, type};

    return positions;
}

318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
void SvgMeshArray::modifyHandle(const SvgMeshPosition &position,
                                const std::array<QPointF, 4> &newPath)
{
    std::array<QPointF, 4> reversed = newPath;
    std::reverse(reversed.begin(), reversed.end());

    if (position.segmentType == SvgMeshPatch::Top && position.row > 0) {
        // modify the shared side
        m_array[position.row - 1][position.col]->modifyPath(SvgMeshPatch::Bottom, reversed);

    } else if (position.segmentType == SvgMeshPatch::Left && position.col > 0) {
        // modify the shared side as well
        m_array[position.row][position.col - 1]->modifyPath(SvgMeshPatch::Right, reversed);
    }
    m_array[position.row][position.col]->modifyPath(position.segmentType, newPath);
}

void SvgMeshArray::modifyCorner(const SvgMeshPosition &position,
                                const QPointF &newPos)
{
Sharaf Zaman's avatar
Sharaf Zaman committed
338
    QVector<SvgMeshPosition> paths = getSharedPaths(position);
339

Sharaf Zaman's avatar
Sharaf Zaman committed
340
    QPointF delta = m_array[position.row][position.col]->getStop(position.segmentType).point - newPos;
341

Sharaf Zaman's avatar
Sharaf Zaman committed
342
343
344
345
    for (const auto &path: paths) {
        m_array[path.row][path.col]->modifyCorner(path.segmentType, delta);
    }
}
346

Sharaf Zaman's avatar
Sharaf Zaman committed
347
348
349
void SvgMeshArray::modifyColor(const SvgMeshPosition &position, const QColor &color)
{
    QVector<SvgMeshPosition> paths = getSharedPaths(position);
350

Sharaf Zaman's avatar
Sharaf Zaman committed
351
352
    for (const auto &path: paths) {
        m_array[path.row][path.col]->setStopColor(path.segmentType, color);
353
354
355
    }
}

Sharaf Zaman's avatar
Sharaf Zaman committed
356
QVector<SvgMeshPosition> SvgMeshArray::getSharedPaths(const SvgMeshPosition &position) const
357
{
Sharaf Zaman's avatar
Sharaf Zaman committed
358
359
    QVector<SvgMeshPosition> positions;

360
361
    int row = position.row;
    int col = position.col;
Sharaf Zaman's avatar
Sharaf Zaman committed
362
    SvgMeshPatch::Type type = position.segmentType;
363
364
365
366
367
368
369

    SvgMeshPatch::Type nextType = static_cast<SvgMeshPatch::Type>(type + 1);
    SvgMeshPatch::Type previousType = static_cast<SvgMeshPatch::Type>((SvgMeshPatch::Size + type - 1) % SvgMeshPatch::Size);

    if (type == SvgMeshPatch::Top) {
        if (row == 0) {
            if (col > 0) {
Sharaf Zaman's avatar
Sharaf Zaman committed
370
                positions << SvgMeshPosition {row, col - 1, nextType};
371
372
373
            }
        } else {
            if (col > 0) {
Sharaf Zaman's avatar
Sharaf Zaman committed
374
375
                positions << SvgMeshPosition {row, col - 1, nextType};
                positions << SvgMeshPosition {row - 1, col - 1, SvgMeshPatch::Bottom};
376
            }
Sharaf Zaman's avatar
Sharaf Zaman committed
377
            positions << SvgMeshPosition {row - 1, col, previousType};
378
        }
Sharaf Zaman's avatar
Sharaf Zaman committed
379
380
    } else if (type == SvgMeshPatch::Right && row > 0) {
        positions << SvgMeshPosition {row - 1, col, nextType};
381

Sharaf Zaman's avatar
Sharaf Zaman committed
382
383
    } else if (type == SvgMeshPatch::Left && col > 0) {
        positions << SvgMeshPosition {row, col - 1, previousType};
384
385
    }

Sharaf Zaman's avatar
Sharaf Zaman committed
386
387
388
    positions << SvgMeshPosition {row, col, type};

    return positions;
389
390
}

Sharaf Zaman's avatar
Sharaf Zaman committed
391
392
393
394
395
QColor SvgMeshArray::getColor(SvgMeshPatch::Type edge, int row, int col) const
{
    return getStop(edge, row, col).color;
}