Krita Source Code Documentation
Loading...
Searching...
No Matches
KoMeshPatchesRenderer.h
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2020 Sharaf Zaman <sharafzaz121@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7#ifndef KOMESHPATCHESRENDERER_H
8#define KOMESHPATCHESRENDERER_H
9
10#include <QImage>
11#include <QPainter>
12#include <QPainterPath>
13#include <QVector>
14#include <QRectF>
15
17#include <KoMixColorsOp.h>
18#include <SvgMeshArray.h>
19#include <SvgMeshGradient.h>
20
22public:
23
26
27 void configure(QRectF gradientRect, const QTransform& painterTransform) {
28
29 // NOTE: This is a necessary step to prevent loss of quality, because painterTransform is scaled.
30
31 // we wish to scale the patch, but not translate, because patch should stay inside
32 // the boundingRect
33 QTransform painterTransformShifted = painterTransform *
34 QTransform::fromTranslate(painterTransform.dx(), painterTransform.dy()).inverted();
35
36 // we are applying transformation on a Unit rect, so we can extract scaling info only
37 QRectF unitRectScaled = painterTransformShifted.mapRect(QRectF(gradientRect.topLeft(), QSize(1, 1)));
38 QTransform scaledTransform = QTransform::fromScale(unitRectScaled.width(), unitRectScaled.height());
39
40 // boundingRect of the scaled version
41 QRectF scaledGradientRect = scaledTransform.mapRect(gradientRect);
42
43 m_patch = QImage(scaledGradientRect.size().toSize(), QImage::Format_ARGB32);
44 m_patch.fill(Qt::transparent);
45
46 m_patchPainter.begin(&m_patch);
47
48 // this ensures that the patch renders inside the boundingRect
49 m_patchPainter.translate(-scaledGradientRect.topLeft());
50
51 // upscale the patch to the same scaling factor as the painterTransform
52 m_patchPainter.setTransform(scaledTransform, true);
53 m_patchPainter.setCompositionMode(QPainter::CompositionMode_Source);
54 }
55
56 void fillPatch(const SvgMeshPatch *patch,
58 const SvgMeshArray *mesharray = nullptr,
59 const int row = -1,
60 const int col = -1) {
61
62 QColor color0 = patch->getStop(SvgMeshPatch::Top).color;
63 QColor color1 = patch->getStop(SvgMeshPatch::Right).color;
64 QColor color2 = patch->getStop(SvgMeshPatch::Bottom).color;
65 QColor color3 = patch->getStop(SvgMeshPatch::Left).color;
66
68
69 quint8 c[4][4];
70 cs->fromQColor(color0, c[0]);
71 cs->fromQColor(color1, c[1]);
72 cs->fromQColor(color2, c[2]);
73 cs->fromQColor(color3, c[3]);
74
75 bool verticalDiv = patch->isDivisibleVertically();
76 bool horizontalDiv = patch->isDivisibleHorizontally();
77 bool colorVariationExists = checkColorVariance(c);
78
79 if (colorVariationExists && (verticalDiv || horizontalDiv)) {
80 QVector<QColor> colors;
81 if (type == SvgMeshGradient::BICUBIC) {
82
83 // it is a parent patch, so calculate coefficients aka alpha
84 if (mesharray) {
85 calculateAlpha(mesharray, row, col, patch);
86 }
87
88 colors = getColorsBicubic(patch);
89 } else {
90 colors = getColorsBilinear(patch);
91 }
92
93 if (verticalDiv && horizontalDiv) {
95 patches.reserve(4);
96
97 patch->subdivide(patches, colors);
98 for (const auto& p: patches) {
99 fillPatch(p, type);
100 delete p;
101 }
102 } else if (verticalDiv) {
104 patches.reserve(2);
105
106 patch->subdivideVertically(patches, colors);
107 for (const auto& p: patches) {
108 fillPatch(p, type);
109 delete p;
110 }
111 } else if (horizontalDiv) {
113 patches.reserve(2);
114
115 patch->subdivideHorizontally(patches, colors);
116 for (const auto& p: patches) {
117 fillPatch(p, type);
118 delete p;
119 }
120 }
121
122 } else {
123 const QPainterPath outline = patch->getPath();
124 const QRectF patchRect = outline.boundingRect();
125
126 quint8 mixed[4];
127 cs->mixColorsOp()->mixColors(c[0], 4, mixed);
128
129 QColor average;
130 cs->toQColor(mixed, &average);
131
132 QPen pen(average);
133 pen.setWidth(0);
134 m_patchPainter.setPen(pen);
135
136 if (patchRect.width() <= 1 && patchRect.height() <= 1) {
137 m_patchPainter.drawPoint(patchRect.topLeft());
138 m_patchPainter.fillPath(outline, average);
139
140 } else {
141 m_patchPainter.setBrush(average);
142 m_patchPainter.drawPath(outline);
143 }
144 }
145 }
146
147 /*
148 * returns false if the variation is below tolerance.
149 */
150 bool checkColorVariance(quint8 c[4][4])
151 {
153 const quint8 tolerance = 0;
154
155 for (int i = 0; i < 3; ++i) {
156 if (cs->difference(c[i], c[i + 1]) > tolerance) {
157 return true;
158 }
159
160 if (c[i][3] != c[i + 1][3] && cs->differenceA(c[i], c[i + 1]) > tolerance) {
161 return true;
162 }
163 }
164
165 return false;
166 }
167
169 {
170 QVector<qreal> v3(4, 0);
171 for (int i = 0; i < 4; ++i) {
172 v3[i] = v1[i] - v2[i];
173 }
174 return v3;
175 }
176
177
179 {
180 QVector<qreal> v3(4, 0);
181 for (int i = 0; i < 4; ++i) {
182 v3[i] = v1[i] * n;
183 }
184 return v3;
185 }
186
188 {
189 return {c.redF(), c.greenF(), c.blueF(), c.alphaF()};
190 }
191
192 qreal getValue(const QVector<qreal>& alpha, const QPointF p)
193 {
194 KIS_ASSERT(alpha.size() == 16);
195 qreal x = p.x(), y = p.y();
196 qreal result = 0;
197
198 qreal xx = x * x;
199 qreal xxx = xx * x;
200 qreal yy = y * y;
201 qreal yyy = yy * y;
202
203 result += alpha[0];
204 result += alpha[1] * x;
205 result += alpha[2] * xx;
206 result += alpha[3] * xxx;
207
208 result += alpha[4] * y;
209 result += alpha[5] * y * x;
210 result += alpha[6] * y * xx;
211 result += alpha[7] * y * xxx;
212
213 result += alpha[ 8] * yy;
214 result += alpha[ 9] * yy * x;
215 result += alpha[10] * yy * xx;
216 result += alpha[11] * yy * xxx;
217
218 result += alpha[12] * yyy;
219 result += alpha[13] * yyy * x;
220 result += alpha[14] * yyy * xx;
221 result += alpha[15] * yyy * xxx;
222
223 return result;
224 }
225
226 QColor getColorUsingAlpha(const QVector<QVector<qreal>>& alpha, QPointF p)
227 {
228 qreal r = getValue(alpha[0], p);
229 qreal g = getValue(alpha[1], p);
230 qreal b = getValue(alpha[2], p);
231 qreal a = getValue(alpha[3], p);
232
233 // Clamp
234 r = r > 1.0 ? 1.0 : r;
235 g = g > 1.0 ? 1.0 : g;
236 b = b > 1.0 ? 1.0 : b;
237 a = a > 1.0 ? 1.0 : a;
238
239 r = r < 0.0 ? 0.0 : r;
240 g = g < 0.0 ? 0.0 : g;
241 b = b < 0.0 ? 0.0 : b;
242 a = a < 0.0 ? 0.0 : a;
243
244 QColor result;
245 result.setRgbF(r, g, b);
246 result.setAlphaF(a);
247 return result;
248 }
249
252 {
253 const static qreal A[16][16] = {
254 { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
255 { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
256 {-3, 3, 0, 0, -2,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
257 { 2,-2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
258 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
259 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
260 { 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2,-1, 0, 0},
261 { 0, 0, 0, 0, 0, 0, 0, 0, 2,-2, 0, 0, 1, 1, 0, 0},
262 {-3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0, 0, 0, 0, 0},
263 { 0, 0, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0},
264 { 9,-9,-9, 9, 6, 3,-6,-3, 6,-6, 3,-3, 4, 2, 2, 1},
265 {-6, 6, 6,-6, -3,-3, 3, 3, -4, 4,-2, 2, -2,-2,-1,-1},
266 { 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0},
267 { 0, 0, 0, 0, 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0},
268 {-6, 6, 6,-6, -4,-2, 4, 2, -3, 3,-3, 3, -2,-1,-2,-1},
269 { 4,-4,-4, 4, 2, 2,-2,-2, 2,-2, 2,-2, 1, 1, 1, 1}};
270
271
272 QVector<qreal> alpha(16, 0);
273 for (int i = 0; i < 16; ++i) {
274 alpha[i] = 0;
275 for (int j = 0; j < 16; ++j) {
276 alpha[i] += A[i][j] * X[j];
277 }
278 }
279
280 return alpha;
281 }
282
283 QVector<qreal> secant(const SvgMeshStop& stop1, const SvgMeshStop& stop2)
284 {
285 qreal distance = QLineF(stop1.point, stop2.point).length();
286
287 if (distance == 0.0) { // NaN
288 return {0.0, 0.0, 0.0, 0.0};
289 }
290
291 qreal c1[4] = {stop1.color.redF(), stop1.color.greenF(), stop1.color.blueF(), stop1.color.alphaF()};
292 qreal c2[4] = {stop2.color.redF(), stop2.color.greenF(), stop2.color.blueF(), stop2.color.alphaF()};
293
294 QVector<qreal> result(4, 0.0);
295
296 for (int i = 0; i < 4; ++i) {
297 result[i] = (c2[i] - c1[i]) / distance;
298 }
299
300 return result;
301 }
302
304 const SvgMeshStop& stop1,
305 const SvgMeshStop& stop2)
306 {
307 QVector<qreal> delta0 = secant(stop0, stop1);
308 QVector<qreal> delta1 = secant(stop1, stop2);
309
310 QVector<qreal> result(4);
311
312 for (int i = 0; i < 4; ++i) {
313 qreal slope = (delta0[i] + delta1[i]) / 2;
314
315 // if sign changes
316 if ((delta0[i] * delta1[i]) < 0 && delta0[i] > 0) {
317 slope = 0;
318 } else if (abs(slope) > 3 * abs(delta0[i])) {
319 slope = 3 * delta0[i];
320 } else if (abs(slope) > 3 * abs(delta1[i])) {
321 slope = 3 * delta1[i];
322 }
323
324 result.push_back(slope);
325 }
326 return result;
327 }
328
331 const SvgMeshPatch* patch1,
332 const SvgMeshPatch* patch2)
333 {
336
341
344
345 QVector<qreal> d11 = derivative(f10, f11, f12);
346 QVector<qreal> d12 = derivative(f11, f12, f13);
347 QVector<qreal> d21 = derivative(f20, f21, f22);
348 QVector<qreal> d22 = derivative(f21, f22, f23);
349
350 return {d11, d12, d21, d22};
351 }
352
355 const SvgMeshPatch* patch1,
356 const SvgMeshPatch* patch2)
357 {
360
365
368
369 QVector<qreal> d11 = derivative(f01, f11, f21);
370 QVector<qreal> d12 = derivative(f02, f12, f22);
371 QVector<qreal> d21 = derivative(f11, f21, f31);
372 QVector<qreal> d22 = derivative(f12, f22, f32);
373
374 return {d11, d12, d21, d22};
375 }
376
379 const SvgMeshPatch* patch1)
380 {
385
388
389 QVector<qreal> d01 = derivative(f00, f01, f02);
390 QVector<qreal> d11 = derivative(f10, f11, f12);
391 QVector<qreal> d00 = difference(multiply(secant(f00, f01), 2), d01);
392 QVector<qreal> d10 = difference(multiply(secant(f10, f11), 2), d11);
393
394 return {d00, d01, d10, d11};
395 }
396
399 const SvgMeshPatch* patch1)
400 {
405
408
409 QVector<qreal> d10 = derivative(f00, f10, f20);
410 QVector<qreal> d11 = derivative(f01, f11, f21);
411 QVector<qreal> d00 = difference(multiply(secant(f00, f10), 2), d10);
412 QVector<qreal> d01 = difference(multiply(secant(f01, f11), 2), d11);
413
414 return {d00, d01, d10, d11};
415 }
416
417 // Derivative in the X direction. The edge has to be in right-most column.
419 const SvgMeshPatch* patch0)
420 {
425
428
429 QVector<qreal> d02 = derivative(f01, f02, f03);
430 QVector<qreal> d12 = derivative(f11, f12, f13);
431 QVector<qreal> d03 = difference(multiply(secant(f02, f03), 2), d02);
432 QVector<qreal> d13 = difference(multiply(secant(f12, f13), 2), d12);
433
434 return {d02, d03, d12, d13};
435 }
436
437 // Derivative in the Y direction. The edge has to be the bottom row
439 const SvgMeshPatch* patch0)
440 {
445
448
449 QVector<qreal> d22 = derivative(f12, f22, f32);
450 QVector<qreal> d23 = derivative(f13, f23, f33);
451 QVector<qreal> d32 = difference(multiply(secant(f22, f32), 2), d22);
452 QVector<qreal> d33 = difference(multiply(secant(f23, f33), 2), d23);
453
454 return {d22, d23, d32, d33};
455 }
456
457 // TODO: Make this private
458 void calculateAlpha(const SvgMeshArray* mesharray, const int row, const int col, const SvgMeshPatch* patch)
459 {
460 // This is the convention which is being followed:
461 //
462 // f00, f03, f30, f33 are corners and their respective derivatives are represented as
463 // d00, d03, d30, d33
464 //
465 // +-----> U/x (row)
466 // |
467 // | f00-------f01-------f02-------f03
468 // V | | | |
469 // V/y (col) | | | |
470 // | | | |
471 // f10-------f11-------f12-------f13
472 // | | | |
473 // | | | |
474 // | | | |
475 // f20-------f21-------f22-------f23
476 // | | | |
477 // | | | |
478 // | | | |
479 // f30-------f31-------f32-------f33
480 //
481
486
489
490 // dx
491 if (!mesharray || mesharray->numColumns() < 2) {
492
493 // NOTE: they're zero here: from trial and error
494 dx[0] = multiply(secant(f11, f12), 2);
495 dx[2] = multiply(secant(f21, f22), 2);
496 dx[1] = dx[3] = {0, 0, 0, 0};
497
498 } else if (col == 0) {
499 dx = derivativeEdgeBeginX(patch, mesharray->getPatch(row, col + 1));
500
501 } else if (col == mesharray->numColumns() - 1) {
502 dx = derivativeEdgeEndX(mesharray->getPatch(row, col),
503 mesharray->getPatch(row, col - 1));
504 } else {
505 dx = derivativeX(mesharray->getPatch(row, col - 1),
506 mesharray->getPatch(row, col),
507 mesharray->getPatch(row, col + 1));
508 }
509
510 // dy
511 if (!mesharray || mesharray->numRows() < 2) {
512
513 // NOTE: they're zero here: from trial and error
514 dy[0] = multiply(secant(f11, f21), 2);
515 dy[1] = multiply(secant(f12, f22), 2);
516 dy[2] = dy[3] = {0, 0, 0, 0};
517
518 } else if (row == 0) {
519 dy = derivativeEdgeBeginY(patch, mesharray->getPatch(row + 1, col));
520
521 } else if (row == mesharray->numRows() - 1) {
522 dy = derivativeEdgeEndY(mesharray->getPatch(row, col),
523 mesharray->getPatch(row - 1, col));
524 } else {
525 dy = derivativeY(mesharray->getPatch(row - 1, col),
526 mesharray->getPatch(row, col),
527 mesharray->getPatch(row + 1, col));
528 }
529
530 QVector<QVector<qreal>> c = {split(f11.color), split(f12.color), split(f21.color), split(f22.color)};
531 QVector<QVector<qreal>> alpha(4, QVector<qreal>(16, 0));
532
533 qreal width01 = QLineF(f11.point, f12.point).length();
534 qreal width23 = QLineF(f21.point, f22.point).length();
535
536 qreal height01 = QLineF(f11.point, f21.point).length();
537 qreal height23 = QLineF(f12.point, f22.point).length();
538
539 for (int i = 0; i < 4; ++i) {
541 c[0][i],
542 c[1][i],
543 c[2][i],
544 c[3][i],
545
546 dx[0][i] * width01,
547 dx[1][i] * width01,
548 dx[2][i] * width23,
549 dx[3][i] * width23,
550
551 dy[0][i] * height01,
552 dy[1][i] * height23,
553 dy[2][i] * height01,
554 dy[3][i] * height23,
555
556 // Specs says not to care about cross derivatives
557 0,
558 0,
559 0,
560 0};
561
562 alpha[i] = getAlpha(X);
563 }
564
565 m_alpha = alpha;
566 }
567
569 {
570 QPointF midTop = patch->getMidpointParametric(SvgMeshPatch::Top);
571 QPointF midRight = patch->getMidpointParametric(SvgMeshPatch::Right);
572 QPointF midBottom = patch->getMidpointParametric(SvgMeshPatch::Bottom);
573 QPointF midLeft = patch->getMidpointParametric(SvgMeshPatch::Left);
574 QPointF center = (midTop + midBottom) / 2;
575
576 QVector<QColor> result(5);
577 result[0] = getColorUsingAlpha(m_alpha, midTop);
578 result[1] = getColorUsingAlpha(m_alpha, midRight);
579 result[2] = getColorUsingAlpha(m_alpha, midBottom);
580 result[3] = getColorUsingAlpha(m_alpha, midLeft);
581 result[4] = getColorUsingAlpha(m_alpha, center);
582
583 return result;
584 }
585
586 QColor midPointColor(QColor first, QColor second)
587 {
588 qreal a = (first.alphaF() + second.alphaF()) / 2;
589 qreal r = (first.redF() + second.redF()) / 2;
590 qreal g = (first.greenF() + second.greenF()) / 2;
591 qreal b = (first.blueF() + second.blueF()) / 2;
592
593 QColor c;
594 c.setRgbF(r, g, b, a);
595 return c;
596 }
597
599 {
600 QVector<QColor> result(5);
601
602 QColor c1 = patch->getStop(SvgMeshPatch::Top).color;
603 QColor c2 = patch->getStop(SvgMeshPatch::Right).color;
604 QColor c3 = patch->getStop(SvgMeshPatch::Bottom).color;
605 QColor c4 = patch->getStop(SvgMeshPatch::Left).color;
606
607 result[0] = midPointColor(c1, c2);
608 result[1] = midPointColor(c2, c3);
609 result[2] = midPointColor(c3, c4);
610 result[3] = midPointColor(c4, c1);
611 result[4] = midPointColor(midPointColor(result[0], result[2]), midPointColor(result[1], result[3]));
612
613 return result;
614 }
615
616 QImage* patchImage() {
617 return &m_patch;
618 }
619
620private:
621 QImage m_patch;
623 // TODO: make them local
625};
626
627#endif // KOMESHPATCHESRENDERER_H
const Params2D p
qreal distance(const QPointF &p1, const QPointF &p2)
virtual quint8 difference(const quint8 *src1, const quint8 *src2) const =0
virtual void toQColor(const quint8 *src, QColor *c) const =0
virtual void fromQColor(const QColor &color, quint8 *dst) const =0
virtual quint8 differenceA(const quint8 *src1, const quint8 *src2) const =0
KoMixColorsOp * mixColorsOp
virtual void mixColors(const quint8 *const *colors, const qint16 *weights, int nColors, quint8 *dst, int weightSum=255) const =0
int numRows() const
int numColumns() const
SvgMeshPatch * getPatch(const int row, const int col) const
QPainterPath getPath() const
Get full (closed) meshpath.
QPointF getMidpointParametric(Type type) const
returns the midPoint in parametric space
void subdivideVertically(QVector< SvgMeshPatch * > &subdivided, const QVector< QColor > &colors) const
void subdivideHorizontally(QVector< SvgMeshPatch * > &subdivided, const QVector< QColor > &colors) const
bool isDivisibleHorizontally() const
void subdivide(QVector< SvgMeshPatch * > &subdivided, const QVector< QColor > &colors) const
SvgMeshStop getStop(Type type) const
returns the starting point of the stop
bool isDivisibleVertically() const
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())
void calculateAlpha(const SvgMeshArray *mesharray, const int row, const int col, const SvgMeshPatch *patch)
void configure(QRectF gradientRect, const QTransform &painterTransform)
QVector< QVector< qreal > > derivativeY(const SvgMeshPatch *patch0, const SvgMeshPatch *patch1, const SvgMeshPatch *patch2)
Derivative in the Y direction, but the patch should not be on an edge.
QColor midPointColor(QColor first, QColor second)
QColor getColorUsingAlpha(const QVector< QVector< qreal > > &alpha, QPointF p)
QVector< qreal > derivative(const SvgMeshStop &stop0, const SvgMeshStop &stop1, const SvgMeshStop &stop2)
QVector< qreal > multiply(const QVector< qreal > &v1, qreal n)
qreal getValue(const QVector< qreal > &alpha, const QPointF p)
QVector< QVector< qreal > > derivativeEdgeBeginY(const SvgMeshPatch *patch0, const SvgMeshPatch *patch1)
Derivative in the Y direction. The edge has to be in top row.
QVector< QVector< qreal > > derivativeEdgeEndY(const SvgMeshPatch *patch1, const SvgMeshPatch *patch0)
QVector< QVector< qreal > > derivativeEdgeEndX(const SvgMeshPatch *patch1, const SvgMeshPatch *patch0)
QVector< QVector< qreal > > derivativeEdgeBeginX(const SvgMeshPatch *patch0, const SvgMeshPatch *patch1)
Derivative in the X direction. The edge has to be in left-most column.
QVector< qreal > split(QColor c)
QVector< qreal > difference(const QVector< qreal > &v1, const QVector< qreal > &v2)
QVector< qreal > secant(const SvgMeshStop &stop1, const SvgMeshStop &stop2)
QVector< QColor > getColorsBilinear(const SvgMeshPatch *patch)
QVector< QVector< qreal > > m_alpha
QVector< QColor > getColorsBicubic(const SvgMeshPatch *patch)
bool checkColorVariance(quint8 c[4][4])
QVector< QVector< qreal > > derivativeX(const SvgMeshPatch *patch0, const SvgMeshPatch *patch1, const SvgMeshPatch *patch2)
Derivative in the X direction, but the patch should not be on an edge.
void fillPatch(const SvgMeshPatch *patch, SvgMeshGradient::Shading type, const SvgMeshArray *mesharray=nullptr, const int row=-1, const int col=-1)
QVector< qreal > getAlpha(const QVector< qreal > &X)
Naming convention adopted from: https://en.wikipedia.org/wiki/Bicubic_interpolation#Computation.
QPointF point