SvgMeshPatch.cpp 20.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 *  Copyright (c) 2007 Jan Hambrecht <jaham@gmx.net>
 *  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 "SvgMeshPatch.h"

21
#include <array>
22
23
#include <math.h>
#include <QDebug>
24
#include <kis_global.h>
25
26


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
inline QPointF lerp(const QPointF& p1, const QPointF& p2, qreal t)
{
    return (1 - t) * p1 + t * p2;
}

void deCasteljau(const std::array<QPointF, 4>& points,
                 qreal t, QPointF *p1, QPointF *p2,
                 QPointF *p3, QPointF *p4, QPointF *p5)
{
    QPointF q[4];

    q[0] = points[0];
    q[1] = points[1];
    q[2] = points[2];
    q[3] = points[3];

    // points of the new segment after the split point
    QPointF p[3];

    // the De Casteljau algorithm
    for (unsigned short j = 1; j <= 3; ++j) {
        for (unsigned short i = 0; i <= 3 - j; ++i) {
            q[i] = (1.0 - t) * q[i] + t * q[i + 1];
        }
        p[j - 1] = q[0];
    }

    if (p1)
        *p1 = p[0];
    if (p2)
        *p2 = p[1];
    if (p3)
        *p3 = p[2];
    if (p4)
        *p4 = q[1];
    if (p5)
        *p5 = q[2];
}

QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitAt(const std::array<QPointF, 4>& points, qreal t)
{
    QPointF newCP2, newCP1, splitP, splitCP1, splitCP2;
    deCasteljau(points, t, &newCP2, &splitCP1, &splitP, &splitCP2, &newCP1);
    return {{points[0], newCP2, splitCP1, splitP},
            {splitP, splitCP2, newCP1, points[3]}};
}

74
SvgMeshPatch::SvgMeshPatch(QPointF startingPoint)
75
76
    : m_newPath(true)
    , m_startingPoint(startingPoint)
77
    , m_parametricCoords({QPointF(0, 0), {1, 0}, {1, 1}, {0, 1}})
78
79
80
{
}

81
SvgMeshPatch::SvgMeshPatch(const SvgMeshPatch& other)
82
83
84
    : m_newPath(other.m_newPath)
    , m_startingPoint(other.m_startingPoint)
    , m_nodes(other.m_nodes)
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
    , controlPoints(other.controlPoints)
    , m_parametricCoords({QPointF(0, 0), {1, 0}, {1, 1}, {0, 1}})
{
}

void SvgMeshPatch::moveTo(const QPointF& p)
{
    controlPoints[counter][0] = p;
}

void SvgMeshPatch::lineTo(const QPointF& p)
{
    controlPoints[counter][1] = lerp(controlPoints[counter][0], p, 1.0 / 3);
    controlPoints[counter][2] = lerp(controlPoints[counter][0], p, 2.0 / 3);
    controlPoints[counter][3] = p;
    counter++;
    if (counter < Size)
        controlPoints[counter][0] = p;
}

void SvgMeshPatch::curveTo(const QPointF& c1, const QPointF& c2, const QPointF& p)
106
{
107
108
109
110
111
112
    controlPoints[counter][1] = c1;
    controlPoints[counter][2] = c2;
    controlPoints[counter][3] = p;
    counter++;
    if (counter < Size)
        controlPoints[counter][0] = p;
113
114
}

115
SvgMeshStop SvgMeshPatch::getStop(SvgMeshPatch::Type type) const
116
{
117
118
    return m_nodes[type];
}
119

120
121
122
123
124
QPointF SvgMeshPatch::segmentPointAt(Type type, qreal t) const
{
    QPointF p;
    deCasteljau(controlPoints[type], t, 0, 0, &p, 0, 0);
    return p;
125
126
}

127
QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> SvgMeshPatch::segmentSplitAt(Type type, qreal t) const
128
{
129
    return splitAt(controlPoints[type], t);
130
131
}

132
std::array<QPointF, 4> SvgMeshPatch::getSegment(Type type) const
Sharaf Zaman's avatar
Sharaf Zaman committed
133
{
134
    return controlPoints[type];
Sharaf Zaman's avatar
Sharaf Zaman committed
135
136
}

137
QPainterPath SvgMeshPatch::getPath() const
138
{
139
140
141
142
143
144
    QPainterPath path;
    path.moveTo(controlPoints[Top][0]);
    for (const auto& i: controlPoints) {
        path.cubicTo(i[1], i[2], i[3]);
    }
    return path;
145
146
}

147
QRectF SvgMeshPatch::boundingRect() const
Sharaf Zaman's avatar
Sharaf Zaman committed
148
{
149
150
151
152
153
154
    return getPath().boundingRect();
}

QSizeF SvgMeshPatch::size() const
{
    return boundingRect().size();
Sharaf Zaman's avatar
Sharaf Zaman committed
155
156
}

157
std::array<QPointF, 4> SvgMeshPatch::getMidCurve(bool isVertical) const
Sharaf Zaman's avatar
Sharaf Zaman committed
158
{
159
160
161
    std::array<QPointF, 4> p;
    std::array<QPointF, 4> curvedBoundary0;
    std::array<QPointF, 4> curvedBoundary1;
Sharaf Zaman's avatar
Sharaf Zaman committed
162
163
164
165
166

    QPointF midpointRuled0;
    QPointF midpointRuled1;

    if (isVertical) {
167
168
        curvedBoundary0 = getSegment(Right);
        curvedBoundary1 = getSegment(Left);
Sharaf Zaman's avatar
Sharaf Zaman committed
169

170
171
        midpointRuled0 = segmentPointAt(Top, 0.5);
        midpointRuled1 = segmentPointAt(Bottom, 0.5);
Sharaf Zaman's avatar
Sharaf Zaman committed
172
    } else {
173
174
        curvedBoundary0 = getSegment(Top);
        curvedBoundary1 = getSegment(Bottom);
Sharaf Zaman's avatar
Sharaf Zaman committed
175

176
177
        midpointRuled0 = segmentPointAt(Left, 0.5);
        midpointRuled1 = segmentPointAt(Right, 0.5);
Sharaf Zaman's avatar
Sharaf Zaman committed
178
179
180
181
182
183
    }

    // we have to reverse it, cB1 & cB2 are in opposite direction
    std::reverse(curvedBoundary1.begin(), curvedBoundary1.end());

    // Sum of two Bezier curve is a Bezier curve
Sharaf Zaman's avatar
Sharaf Zaman committed
184
    QVector<QPointF> midCurved = {
Sharaf Zaman's avatar
Sharaf Zaman committed
185
186
187
188
189
190
191
        (curvedBoundary0[0] + curvedBoundary1[0]) / 2,
        (curvedBoundary0[1] + curvedBoundary1[1]) / 2,
        (curvedBoundary0[2] + curvedBoundary1[2]) / 2,
        (curvedBoundary0[3] + curvedBoundary1[3]) / 2,
    };

    // line cutting the bilinear surface in middle
Sharaf Zaman's avatar
Sharaf Zaman committed
192
193
    QPointF x_2_1 = lerp(midpointRuled0, midpointRuled1, 1.0 / 3);
    QPointF x_2_2 = lerp(midpointRuled0, midpointRuled1, 2.0 / 3);
Sharaf Zaman's avatar
Sharaf Zaman committed
194
195

    // line cutting rulled surface in middle
Sharaf Zaman's avatar
Sharaf Zaman committed
196
197
    QPointF x_3_1 = lerp(midCurved[0], midCurved[3], 1.0 / 3);
    QPointF x_3_2 = lerp(midCurved[0], midCurved[3], 2.0 / 3);
Sharaf Zaman's avatar
Sharaf Zaman committed
198
199
200
201
202
203
204
205
206
207
208
209


    p[0] = midpointRuled0;

    // X_1 = x_1_1 + x_2_1 - x_3_1
    p[1] = midCurved[1] + x_2_1 - x_3_1;

    // X_2 = x_1_2 + x_2_2 - x_3_2
    p[2] = midCurved[2] + x_2_2 - x_3_2;

    p[3] = midpointRuled1;

210
    return p;
Sharaf Zaman's avatar
Sharaf Zaman committed
211
212
}

213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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
void SvgMeshPatch::subdivideHorizontally(QVector<SvgMeshPatch*>& subdivided,
                                         const QVector<QColor>& colors) const
{
    const QPair<SvgMeshPath, SvgMeshPath> splitRight = segmentSplitAt(Right, 0.5);
    const QPair<SvgMeshPath, SvgMeshPath> splitLeft  = segmentSplitAt(Left, 0.5);

    SvgMeshPath midHor = getMidCurve(/*isVertical = */ false);
    SvgMeshPath rMidHor = midHor;
    std::reverse(rMidHor.begin(), rMidHor.end());

    QColor c1 = getStop(Top).color;
    QColor c2 = getStop(Right).color;
    QColor c3 = getStop(Bottom).color;
    QColor c4 = getStop(Left).color;
    QColor midc23 = colors[1];
    QColor midc41 = colors[3];

    QPointF midRightParametric = getMidpointParametric(Right);
    QPointF midLeftParametric = getMidpointParametric(Left);

    SvgMeshPatch *patch = new SvgMeshPatch(getSegment(Top)[0]);
    patch->addStop(getSegment(Top), c1, Top);
    patch->addStop(splitRight.first, c2, Right);
    patch->addStop(rMidHor, midc23, Bottom);
    patch->addStop(splitLeft.second, midc41, Left);
    patch->m_parametricCoords = {
        m_parametricCoords[0],
        m_parametricCoords[1],
        midRightParametric,
        midLeftParametric
    };
    subdivided.append(patch);

    patch = new SvgMeshPatch(midHor[0]);
    patch->addStop(midHor, midc41, Top);
    patch->addStop(splitRight.second, midc23, Right);
    patch->addStop(getSegment(Bottom), c3, Bottom);
    patch->addStop(splitLeft.first,c4, Left);
    patch->m_parametricCoords = {
        midLeftParametric,
        midRightParametric,
        m_parametricCoords[2],
        m_parametricCoords[3]
    };
    subdivided.append(patch);
}

void SvgMeshPatch::subdivideVertically(QVector<SvgMeshPatch*>& subdivided,
                                       const QVector<QColor>& colors) const
{
    const QPair<SvgMeshPath, SvgMeshPath> splitTop    = segmentSplitAt(Top, 0.5);
    const QPair<SvgMeshPath, SvgMeshPath> splitBottom = segmentSplitAt(Bottom, 0.5);

    SvgMeshPath midVer = getMidCurve(/*isVertical = */ true);
    SvgMeshPath rMidVer = midVer;
    std::reverse(rMidVer.begin(), rMidVer.end());

    QColor c1 = getStop(Top).color;
    QColor c2 = getStop(Right).color;
    QColor c3 = getStop(Bottom).color;
    QColor c4 = getStop(Left).color;
    QColor midc12 = colors[0];
    QColor midc34 = colors[2];

    QPointF midTopParametric = getMidpointParametric(Top);
    QPointF midBottomParametric = getMidpointParametric(Bottom);

    SvgMeshPatch *patch = new SvgMeshPatch(splitTop.first[0]);
    patch->addStop(splitTop.first, c1, Top);
    patch->addStop(midVer, midc12, Right);
    patch->addStop(splitBottom.second, midc34, Bottom);
    patch->addStop(getSegment(Left), c4, Left);
    patch->m_parametricCoords = {
        m_parametricCoords[0],
        midTopParametric,
        midBottomParametric,
        m_parametricCoords[3]
    };
    subdivided.append(patch);

    patch = new SvgMeshPatch(splitTop.second[0]);
    patch->addStop(splitTop.second, midc12, Top);
    patch->addStop(getSegment(Right), c2, Right);
    patch->addStop(splitBottom.first, c3, Bottom);
    patch->addStop(rMidVer, midc34, Left);
    patch->m_parametricCoords = {
        midTopParametric,
        m_parametricCoords[1],
        m_parametricCoords[2],
        midBottomParametric
    };
    subdivided.append(patch);
}

void SvgMeshPatch::subdivide(QVector<SvgMeshPatch*>& subdivided,
                             const QVector<QColor>& colors) const
Sharaf Zaman's avatar
Sharaf Zaman committed
309
{
310
311
    KIS_ASSERT(colors.size() == 5);

Sharaf Zaman's avatar
Sharaf Zaman committed
312
313
314
315
    // The orientation is left to right and top to bottom, which means
    // Eg. the first part of splitTop is TopLeft and the second part is TopRight
    // Similarly the first part of splitRight is RightTop, but the first part of
    // splitLeft is splitLeft.second (once again, in Top to Bottom  convention)
316
317
318
319
    const QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitTop    = segmentSplitAt(Top, 0.5);
    const QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitRight  = segmentSplitAt(Right, 0.5);
    const QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitBottom = segmentSplitAt(Bottom, 0.5);
    const QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitLeft   = segmentSplitAt(Left, 0.5);
Sharaf Zaman's avatar
Sharaf Zaman committed
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336

    // The way the curve and the colors at the corners are arranged before and after subdivision
    //
    //              midc12
    //       c1       +       c2
    //        +---------------+
    //        |       |       |
    //        |       | midVer|
    //        |       | <     |
    // midc41 +---------------+ midc23
    //        |  ^    |       |
    //        | midHor|       |
    //        |       |       |
    //        +---------------+
    //       c4       +       c3
    //              midc43
    //
337
338
339
340
341
    //             
    //  midHor --> left to right
    //  midVer --> top to bottom


342
343
    QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> midHor = splitAt(getMidCurve(/*isVertical = */ false), 0.5);
    QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> midVer = splitAt(getMidCurve(/*isVertical = */ true), 0.5);
Sharaf Zaman's avatar
Sharaf Zaman committed
344
345

    // middle curve is shared among the two, so we need both directions
346
    std::array<QPointF, 4> reversedMidHorFirst = midHor.first;
Sharaf Zaman's avatar
Sharaf Zaman committed
347
    std::reverse(reversedMidHorFirst.begin(), reversedMidHorFirst.end());
348
    std::array<QPointF, 4> reversedMidHorSecond = midHor.second;
Sharaf Zaman's avatar
Sharaf Zaman committed
349
350
    std::reverse(reversedMidHorSecond.begin(), reversedMidHorSecond.end());

351
    std::array<QPointF, 4> reversedMidVerFirst = midVer.first;
Sharaf Zaman's avatar
Sharaf Zaman committed
352
    std::reverse(reversedMidVerFirst.begin(), reversedMidVerFirst.end());
353
    std::array<QPointF, 4> reversedMidVerSecond = midVer.second;
Sharaf Zaman's avatar
Sharaf Zaman committed
354
355
    std::reverse(reversedMidVerSecond.begin(), reversedMidVerSecond.end());

356
357
358
359
    QColor c1 = getStop(Top).color;
    QColor c2 = getStop(Right).color;
    QColor c3 = getStop(Bottom).color;
    QColor c4 = getStop(Left).color;
360
361
362
363
364
    QColor midc12 = colors[0];
    QColor midc23 = colors[1];
    QColor midc34 = colors[2];
    QColor midc41 = colors[3];
    QColor center = colors[4];
Sharaf Zaman's avatar
Sharaf Zaman committed
365

366
367
368
369
370
371
372
    // mid points in parametric space
    QPointF midTopP     = getMidpointParametric(Top);
    QPointF midRightP   = getMidpointParametric(Right);
    QPointF midBottomP  = getMidpointParametric(Bottom);
    QPointF midLeftP    = getMidpointParametric(Left);
    QPointF centerP     = 0.5 * (midTopP + midBottomP);

Sharaf Zaman's avatar
Sharaf Zaman committed
373
    // patch 1: TopLeft/NorthWest
374
375
376
    SvgMeshPatch *patch = new SvgMeshPatch(splitTop.first[0]);
    patch->addStop(splitTop.first, c1, Top);
    patch->addStop(midVer.first, midc12, Right);
Sharaf Zaman's avatar
Sharaf Zaman committed
377
    patch->addStop(reversedMidHorFirst, center, Bottom);
378
    patch->addStop(splitLeft.second, midc41, Left);
379
380
381
382
383
384
    patch->m_parametricCoords = {
        m_parametricCoords[0],
        midTopP,
        centerP,
        midLeftP
    };
Sharaf Zaman's avatar
Sharaf Zaman committed
385
386
387
    subdivided.append(patch);

    // patch 2: TopRight/NorthRight
388
389
390
    patch = new SvgMeshPatch(splitTop.second[0]);
    patch->addStop(splitTop.second, midc12, Top);
    patch->addStop(splitRight.first, c2, Right);
Sharaf Zaman's avatar
Sharaf Zaman committed
391
392
    patch->addStop(reversedMidHorSecond, midc23, Bottom);
    patch->addStop(reversedMidVerFirst, center, Left);
393
394
395
396
397
398
    patch->m_parametricCoords = {
        midTopP,
        m_parametricCoords[1],
        midRightP,
        centerP
    };
Sharaf Zaman's avatar
Sharaf Zaman committed
399
400
401
    subdivided.append(patch);

    // patch 3: BottomLeft/SouthWest
402
403
404
405
406
    patch = new SvgMeshPatch(midHor.first[0]);
    patch->addStop(midHor.first, midc41, Top);
    patch->addStop(midVer.second, center, Right);
    patch->addStop(splitBottom.second, midc34, Bottom);
    patch->addStop(splitLeft.first, c4, Left);
407
408
409
410
411
412
    patch->m_parametricCoords = {
        midLeftP,
        centerP,
        midBottomP,
        m_parametricCoords[3]
    };
Sharaf Zaman's avatar
Sharaf Zaman committed
413
414
415
    subdivided.append(patch);

    // patch 4: BottomRight/SouthEast
416
417
418
419
    patch = new SvgMeshPatch(midHor.second[0]);
    patch->addStop(midHor.second, center, Top);
    patch->addStop(splitRight.second, midc23, Right);
    patch->addStop(splitBottom.first, c3, Bottom);
Sharaf Zaman's avatar
Sharaf Zaman committed
420
    patch->addStop(reversedMidVerSecond, midc34, Left);
421
422
423
424
425
426
    patch->m_parametricCoords = {
        centerP,
        midRightP,
        m_parametricCoords[2],
        midBottomP
    };
Sharaf Zaman's avatar
Sharaf Zaman committed
427
428
429
    subdivided.append(patch);
}

430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
static qreal controlrectLen(const SvgMeshPath &path) {
    return QLineF(path[0], path[1]).length() +
        QLineF(path[1], path[2]).length() +
        QLineF(path[2], path[3]).length();
}

bool SvgMeshPatch::isDivisbleVertically() const
{
    // I arrived at this number by the virute called trial 'n error
    const qreal minlength = 1.7;
    const qreal line1 = QLineF(controlPoints[Top][0], controlPoints[Top][3]).length();
    const qreal control1 = controlrectLen(getSegment(Top));

    // a decent average, thanks to Khronos's forums
    if ((line1 + control1 / 2) < minlength) {
        return false;
    }

    const qreal line2 = QLineF(controlPoints[Bottom][0], controlPoints[Bottom][3]).length();
    const qreal control2 = controlrectLen(getSegment(Bottom));
    if ((line2 + control2 / 2) < minlength) {
        return false;
    }

    return true;
}

bool SvgMeshPatch::isDivisibleHorizontally() const
{
    // I arrived at this number by the virute called trial 'n error
    const qreal minlength = 1.7;

    // a decent average, thanks to Khronos's forums
    const qreal line1 = QLineF(controlPoints[Right][0], controlPoints[Right][3]).length();
    const qreal control1 = controlrectLen(getSegment(Right));
    if ((line1 + control1 / 2) < minlength) {
        return false;
    }

    const qreal line2 = QLineF(controlPoints[Left][0], controlPoints[Left][3]).length();
    const qreal control2 = controlrectLen(getSegment(Left));
    if ((line2 + control2 / 2) < minlength) {
        return false;
    }

    return true;
}

Sharaf Zaman's avatar
Sharaf Zaman committed
478
479
480
481
482
void SvgMeshPatch::addStop(const QString& pathStr,
                           QColor color,
                           Type edge,
                           bool pathIncomplete,
                           QPointF lastPoint)
483
{
484
    SvgMeshStop node(color, m_startingPoint);
485
    m_nodes[edge] = node;
486

487
    m_startingPoint = parseMeshPath(pathStr, pathIncomplete, lastPoint);
488
}
489

490
void SvgMeshPatch::addStop(const std::array<QPointF, 4>& pathPoints, QColor color, Type edge)
491
{
492
493
    SvgMeshStop stop(color, pathPoints[0]);
    m_nodes[edge] = stop;
494

495
    if (edge == SvgMeshPatch::Top) {
496
        moveTo(pathPoints[0]);
497
498
        m_newPath = false;
    }
499

500
501
    curveTo(pathPoints[1], pathPoints[2], pathPoints[3]);
    m_startingPoint = pathPoints[3];
502
503
}

504
505

void SvgMeshPatch::addStopLinear(const std::array<QPointF, 2>& pathPoints, QColor color, Type edge)
506
{
507
508
509
510
511
512
    SvgMeshStop stop(color, pathPoints[0]);
    m_nodes[edge] = stop;

    if (edge == SvgMeshPatch::Top) {
        moveTo(pathPoints[0]);
        m_newPath = false;
513
    }
514
515
516

    lineTo(pathPoints[1]);
    m_startingPoint = pathPoints[1];
517
518
}

519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
void SvgMeshPatch::modifyPath(SvgMeshPatch::Type type, std::array<QPointF, 4> newPath)
{
    controlPoints[type] = newPath;
    m_nodes[type].point = newPath[0];
}

void SvgMeshPatch::modifyCorner(SvgMeshPatch::Type type, const QPointF &delta)
{
    controlPoints[type][0] -= delta;
    controlPoints[type][1] -= delta;
    m_nodes[type].point = controlPoints[type][0];

    controlPoints[(Size + type - 1) % Size][3] -= delta;
    controlPoints[(Size + type - 1) % Size][2] -= delta;
}

void SvgMeshPatch::setStopColor(SvgMeshPatch::Type type, const QColor &color)
{
    m_nodes[type].color = color;
}

540
541
542
void SvgMeshPatch::setTransform(const QTransform& matrix)
{
    m_startingPoint = matrix.map(m_startingPoint);
543
544
545
546
547
    for (int i = 0; i < Size; ++i) {
        m_nodes[i].point = matrix.map(m_nodes[i].point);
        for (int j = 0; j < 4; ++j) {
            controlPoints[i][j] = matrix.map(controlPoints[i][j]);
        }
548
549
550
    }
}

551
552
553
554
555
int SvgMeshPatch::countPoints() const
{
    return m_nodes.size();
}

556

557
QPointF SvgMeshPatch::parseMeshPath(const QString& s, bool pathIncomplete, const QPointF lastPoint)
558
559
560
561
562
563
564
565
566
567
568
569
{
    // bits and pieces from KoPathShapeLoader, see the copyright above
    if (!s.isEmpty()) {
        QString d = s;
        d.replace(',', ' ');
        d = d.simplified();

        const QByteArray buffer = d.toLatin1();
        const char *ptr = buffer.constData();
        qreal curx = m_startingPoint.x();
        qreal cury = m_startingPoint.y();
        qreal tox, toy, x1, y1, x2, y2;
570
        bool relative = false;
571
572
573
        char command = *(ptr++);

        if (m_newPath) {
574
            moveTo(m_startingPoint);
575
576
577
            m_newPath = false;
        }

578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
       while (*ptr == ' ')
           ++ptr;

       switch (command) {
       case 'l':
           relative = true;
           Q_FALLTHROUGH();
       case 'L': {
           ptr = getCoord(ptr, tox);
           ptr = getCoord(ptr, toy);

           if (relative) {
               tox = curx + tox;
               toy = cury + toy;
           }

594
595
596
597
598
           if (pathIncomplete) {
               tox = lastPoint.x();
               toy = lastPoint.y();
           }

599
           // we convert lines to cubic curve
600
           lineTo({tox, toy});
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
           break;
       }
       case 'c':
           relative = true;
           Q_FALLTHROUGH();
       case 'C': {
           ptr = getCoord(ptr, x1);
           ptr = getCoord(ptr, y1);
           ptr = getCoord(ptr, x2);
           ptr = getCoord(ptr, y2);
           ptr = getCoord(ptr, tox);
           ptr = getCoord(ptr, toy);

           if (relative) {
               x1  = curx + x1;
               y1  = cury + y1;
               x2  = curx + x2;
               y2  = cury + y2;
               tox = curx + tox;
               toy = cury + toy;
           }

623
624
625
           if (pathIncomplete) {
               tox = lastPoint.x();
               toy = lastPoint.y();
626
627
           }

628
           curveTo(QPointF(x1, y1), QPointF(x2, y2), QPointF(tox, toy));
629
630
631
632
633
634
635
636
637
           break;
       }

       default: {
           qWarning() << "SvgMeshPatch::parseMeshPath: Bad command \"" << command << "\"";
           return QPointF();
       }
       }
       return {tox, toy};
638
    }
639
    return QPointF();
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
}

const char* SvgMeshPatch::getCoord(const char* ptr, qreal& number)
{
    // copied from KoPathShapeLoader, see the copyright above
    int integer, exponent;
    qreal decimal, frac;
    int sign, expsign;

    exponent = 0;
    integer = 0;
    frac = 1.0;
    decimal = 0;
    sign = 1;
    expsign = 1;

    // read the sign
    if (*ptr == '+')
        ++ptr;
    else if (*ptr == '-') {
        ++ptr;
        sign = -1;
    }

    // read the integer part
    while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
        integer = (integer * 10) + *(ptr++) - '0';
    if (*ptr == '.') { // read the decimals
        ++ptr;
        while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
            decimal += (*(ptr++) - '0') * (frac *= 0.1);
    }

    if (*ptr == 'e' || *ptr == 'E') { // read the exponent part
        ++ptr;

        // read the sign of the exponent
        if (*ptr == '+')
            ++ptr;
        else if (*ptr == '-') {
            ++ptr;
            expsign = -1;
        }

        exponent = 0;
        while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') {
            exponent *= 10;
            exponent += *ptr - '0';
            ++ptr;
        }
    }
    number = integer + decimal;
    number *= sign * pow((qreal)10, qreal(expsign * exponent));

    // skip the following space
    if (*ptr == ' ')
        ++ptr;

    return ptr;
}