Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsvgfilter.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
6#include "qsvgfilter_p.h"
7
9#include "qsvgnode_p.h"
10#include "qsvgdocument_p.h"
11#include "qpainter.h"
12
13#include <QLoggingCategory>
14#include <QtGui/qimageiohandler.h>
15#include <QVector4D>
16
18
19QSvgFeFilterPrimitive::QSvgFeFilterPrimitive(QSvgNode *parent, const QString &input,
20 const QString &result, const QSvgRectF &rect)
21 : QSvgStructureNode(parent)
22 , m_input(input)
23 , m_result(result)
24 , m_rect(rect)
25{
26
27}
28
29QSvgFeFilterPrimitive::~QSvgFeFilterPrimitive()
30 = default;
31
32bool QSvgFeFilterPrimitive::shouldDrawNode(QPainter *, QSvgExtraStates &) const
33{
34 return false;
35}
36
37QRectF QSvgFeFilterPrimitive::localSubRegion(const QRectF &itemBounds, const QRectF &filterBounds,
38 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes) const
39{
40 // 15.7.3 Filter primitive subregion
41 // https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion
42
43 QRectF clipRect = m_rect.resolveRelativeLengths(itemBounds, primitiveUnits);
44
45 // the default subregion is 0%,0%,100%,100%, where as a special-case the percentages are
46 // relative to the dimensions of the filter region, thus making the the default filter primitive
47 // subregion equal to the filter region.
48 if (m_rect.unitX() == QtSvg::UnitTypes::unknown)
49 clipRect.setX(filterBounds.x());
50 if (m_rect.unitY() == QtSvg::UnitTypes::unknown)
51 clipRect.setY(filterBounds.y());
52 if (m_rect.unitW() == QtSvg::UnitTypes::unknown)
53 clipRect.setWidth(filterBounds.width());
54 if (m_rect.unitH() == QtSvg::UnitTypes::unknown)
55 clipRect.setHeight(filterBounds.height());
56
57 clipRect = clipRect.intersected(filterBounds);
58
59 return clipRect;
60}
61
62QRectF QSvgFeFilterPrimitive::globalSubRegion(QPainter *p,
63 const QRectF &itemBounds, const QRectF &filterBounds,
64 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
65{
66 return p->transform().mapRect(localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
67}
68
69void QSvgFeFilterPrimitive::clipToTransformedBounds(QImage *buffer, QPainter *p, const QRectF &localRect) const
70{
71 QPainter painter(buffer);
72 painter.setRenderHints(p->renderHints());
73 painter.translate(-buffer->offset());
74 QPainterPath clipPath;
75 clipPath.setFillRule(Qt::OddEvenFill);
76 clipPath.addRect(QRect(buffer->offset(), buffer->size()).adjusted(-10, -10, 20, 20));
77 clipPath.addPolygon(p->transform().map(QPolygonF(localRect)));
78 painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
79 painter.fillPath(clipPath, Qt::transparent);
80}
81
82bool QSvgFeFilterPrimitive::requiresSourceAlpha() const
83{
84 return m_input == QLatin1StringView("SourceAlpha");
85}
86
87const QSvgFeFilterPrimitive *QSvgFeFilterPrimitive::castToFilterPrimitive(const QSvgNode *node)
88{
89 if (node->type() == QSvgNode::FeMerge ||
90 node->type() == QSvgNode::FeColormatrix ||
91 node->type() == QSvgNode::FeGaussianblur ||
92 node->type() == QSvgNode::FeOffset ||
93 node->type() == QSvgNode::FeComposite ||
94 node->type() == QSvgNode::FeFlood ||
95 node->type() == QSvgNode::FeBlend ) {
96 return static_cast<const QSvgFeFilterPrimitive*>(node);
97 } else {
98 return nullptr;
99 }
100}
101
103
104QSvgFeColorMatrix::QSvgFeColorMatrix(QSvgNode *parent, const QString &input, const QString &result,
105 const QSvgRectF &rect, ColorShiftType type,
106 const Matrix &matrix)
107 : QSvgFeFilterPrimitive(parent, input, result, rect)
108 , m_matrix(matrix)
109{
110 //Magic numbers see SVG 1.1(Second edition)
111 constexpr qreal v1[] = {0.213, 0.213, 0.213,
112 0.715, 0.715, 0.715,
113 0.072, 0.072, 0.072};
114 static const Matrix3x3 m1(v1);
115
116 constexpr qreal v2[] = { 0.787, -0.213, -0.213,
117 -0.715, 0.285, -0.715,
118 -0.072, -0.072, 0.928};
119 static const Matrix3x3 m2(v2);
120
121 constexpr qreal v3[] = {-0.213, 0.143, -0.787,
122 -0.715, 0.140, 0.715,
123 0.928, -0.283, 0.072};
124 static const Matrix3x3 m3(v3);
125
126 if (type == ColorShiftType::Saturate) {
127 qreal s = qBound(0., m_matrix.data()[0], 1.);
128
129 m_matrix.fill(0);
130
131 const Matrix3x3 m = m1 + m2 * s;
132
133 for (int j = 0; j < 3; ++j)
134 for (int i = 0; i < 3; ++i)
135 m_matrix.data()[i+j*5] = m.data()[i+j*3];
136 m_matrix.data()[3+3*5] = 1;
137
138 } else if (type == ColorShiftType::HueRotate){
139 qreal angle = m_matrix.data()[0]/180.*M_PI;
140 qreal s = sin(angle);
141 qreal c = cos(angle);
142
143 m_matrix.fill(0);
144
145 const Matrix3x3 m = m1 + m2 * c + m3 * s;
146
147 for (int j = 0; j < 3; ++j)
148 for (int i = 0; i < 3; ++i)
149 m_matrix.data()[i+j*5] = m.data()[i+j*3];
150 m_matrix.data()[3+3*5] = 1;
151 } else if (type == ColorShiftType::LuminanceToAlpha){
152 m_matrix.fill(0);
153
154 m_matrix.data()[0+3*5] = 0.2125;
155 m_matrix.data()[1+3*5] = 0.7154;
156 m_matrix.data()[2+3*5] = 0.0721;
157 }
158}
159
160QSvgFeColorMatrix::~QSvgFeColorMatrix()
161 = default;
162
163QSvgNode::Type QSvgFeColorMatrix::type() const
164{
165 return QSvgNode::FeColormatrix;
166}
167
168QImage QSvgFeColorMatrix::apply(const QMap<QString, QImage> &sources, QPainter *p,
169 const QRectF &itemBounds, const QRectF &filterBounds,
170 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
171{
172 if (!sources.contains(m_input))
173 return QImage();
174 QImage source = sources[m_input];
175
176 QRect clipRectGlob = globalSubRegion(p, itemBounds, filterBounds, primitiveUnits, filterUnits).toRect();
177 if (clipRectGlob.isEmpty())
178 return QImage();
179
180 QImage result;
181 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
182 qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
183 return QImage();
184 }
185 result.setOffset(clipRectGlob.topLeft());
186 result.fill(Qt::transparent);
187
188 Q_ASSERT(source.depth() == 32);
189
190 const Matrix transposedMatrix = m_matrix.transposed();
191
192 for (int i = 0; i < result.height(); i++) {
193 int sourceI = i - source.offset().y() + result.offset().y();
194
195 if (sourceI < 0 || sourceI >= source.height())
196 continue;
197
198 QRgb *sourceLine = reinterpret_cast<QRgb *>(source.scanLine(sourceI));
199 QRgb *resultLine = reinterpret_cast<QRgb *>(result.scanLine(i));
200
201 for (int j = 0; j < result.width(); j++) {
202 int sourceJ = j - source.offset().x() + result.offset().x();
203
204 if (sourceJ < 0 || sourceJ >= source.width())
205 continue;
206
207 QRgb sourceColor = qUnpremultiply(sourceLine[sourceJ]);
208 const qreal values[] = {qreal(qRed(sourceColor)),
209 qreal(qGreen(sourceColor)),
210 qreal(qBlue(sourceColor)),
211 qreal(qAlpha(sourceColor)),
212 255.};
213 const QGenericMatrix<1, 5, qreal> sourceVector(values);
214 const QGenericMatrix<1, 5, qreal> resultVector = transposedMatrix * sourceVector;
215
216 QRgb rgba = qRgba(qBound(0, int(resultVector(0, 0)), 255),
217 qBound(0, int(resultVector(1, 0)), 255),
218 qBound(0, int(resultVector(2, 0)), 255),
219 qBound(0, int(resultVector(3, 0)), 255));
220 resultLine[j] = qPremultiply(rgba);
221 }
222 }
223
224 clipToTransformedBounds(&result, p, localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
225 return result;
226}
227
228QSvgFeGaussianBlur::QSvgFeGaussianBlur(QSvgNode *parent, const QString &input,
229 const QString &result, const QSvgRectF &rect,
230 qreal stdDeviationX, qreal stdDeviationY, EdgeMode edgemode)
231 : QSvgFeFilterPrimitive(parent, input, result, rect)
232 , m_stdDeviationX(stdDeviationX)
233 , m_stdDeviationY(stdDeviationY)
234 , m_edgemode(edgemode)
235{
236 Q_UNUSED(m_edgemode);
237}
238
239QSvgFeGaussianBlur::~QSvgFeGaussianBlur()
240 = default;
241
242QSvgNode::Type QSvgFeGaussianBlur::type() const
243{
244 return QSvgNode::FeGaussianblur;
245}
246
248 // This class is not designed for general use. It is is only meant to improve the calling code's
249 // readability by replacing a two-dimensial array of integers with a one-dimensial array of
250 // objects which perform repeated calculations on the single values by just one call.
251public:
252 ColorValues() = default;
253 explicit ColorValues(QRgb rgb) : b(qBlue(rgb)), g(qGreen(rgb)), r(qRed(rgb)), a(qAlpha(rgb)){}
254
256 {
257 b += other.b;
258 g += other.g;
259 r += other.r;
260 a += other.a;
261 return *this;
262 }
263
265 {
266 ColorValues result(*this);
267 result+=(other);
268 return result;
269 }
270
272 {
273 b -= other.b;
274 g -= other.g;
275 r -= other.r;
276 a -= other.a;
277 return *this;
278 }
279
281 {
282 ColorValues result(*this);
283 result -= other;
284 return result;
285 }
286
287 ColorValues operator/=(uint64_t div)
288 {
289 // This does neither avoid nor handle division by zero. It
290 // relies on the calling code to never pass a zero value.
291 b /= div;
292 g /= div;
293 r /= div;
294 a /= div;
295 return *this;
296 }
297
299 {
300 return qRgba(r, g, b, a);
301 }
302
303private:
304 uint64_t b = 0;
305 uint64_t g = 0;
306 uint64_t r = 0;
307 uint64_t a = 0;
308};
309
310QImage QSvgFeGaussianBlur::apply(const QMap<QString, QImage> &sources, QPainter *p,
311 const QRectF &itemBounds, const QRectF &filterBounds,
312 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
313{
314 if (!sources.contains(m_input))
315 return QImage();
316 QImage source = sources[m_input];
317 Q_ASSERT(source.depth() == 32);
318
319 if (m_stdDeviationX == 0 && m_stdDeviationY == 0)
320 return source;
321
322 const qreal scaleX = qHypot(p->transform().m11(), p->transform().m21());
323 const qreal scaleY = qHypot(p->transform().m12(), p->transform().m22());
324
325 qreal sigma_x = scaleX * m_stdDeviationX;
326 qreal sigma_y = scaleY * m_stdDeviationY;
327 if (primitiveUnits == QtSvg::UnitTypes::objectBoundingBox) {
328 sigma_x *= itemBounds.width();
329 sigma_y *= itemBounds.height();
330 }
331
332 constexpr double sd = 3. * M_SQRT1_2 / M_2_SQRTPI; // 3 * sqrt(2 * pi) / 4
333 const int dx = floor(sigma_x * sd + 0.5);
334 const int dy = floor(sigma_y * sd + 0.5);
335
336 const QTransform scaleXr = QTransform::fromScale(scaleX, scaleY);
337 const QTransform restXr = scaleXr.inverted() * p->transform();
338
339 QRect clipRectGlob = scaleXr.mapRect(localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits)).toRect();
340 if (clipRectGlob.isEmpty())
341 return QImage();
342
343 QImage tempSource;
344 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &tempSource)) {
345 qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
346 return QImage();
347 }
348 tempSource.setOffset(clipRectGlob.topLeft());
349 tempSource.fill(Qt::transparent);
350 QPainter copyPainter(&tempSource);
351 copyPainter.translate(-tempSource.offset());
352 copyPainter.setTransform(restXr.inverted(), true);
353 copyPainter.drawImage(source.offset(), source);
354 copyPainter.end();
355
356 QVarLengthArray<ColorValues, 32 * 32> buffer(tempSource.width() * tempSource.height());
357
358 const int sourceHeight = tempSource.height();
359 const int sourceWidth = tempSource.width();
360 QRgb *rawImage = reinterpret_cast<QRgb *>(tempSource.bits());
361
362 // https://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement:
363 // Three successive box-blurs build a piece-wise quadratic convolution kernel,
364 // which approximates the Gaussian kernel
365 for (int m = 0; m < 3; m++) {
366 // https://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement:
367 // if d is odd, use three box-blurs of size 'd', centered on the output pixel.
368 // if d is even, two box-blurs of size 'd' (the first one centered on the pixel boundary
369 // between the output pixel and the one to the left, the second one centered on the pixel
370 // boundary between the output pixel and the one to the right) and one box blur of size
371 // 'd+1' centered on the output pixel.
372 auto adjustD = [](int d, int iteration) {
373 d = qMax(1, d); // Treat d == 0 just like d == 1
374 std::pair<int, int> result;
375 if (d % 2 == 1)
376 result = {d / 2 + 1, d / 2};
377 else if (iteration == 0)
378 result = {d / 2 + 1, d / 2 - 1};
379 else if (iteration == 1)
380 result = {d / 2, d / 2};
381 else
382 result = {d / 2 + 1, d / 2};
383 Q_ASSERT(result.first + result.second > 0);
384 return result;
385 };
386
387 const auto [dxleft, dxright] = adjustD(dx, m);
388 const auto [dytop, dybottom] = adjustD(dy, m);
389
390 // Generating the partial sum of color values from the top left corner
391 // These sums can be combined to yield the partial sum of any rectangular subregion
392 for (int j = 0; j < sourceHeight; j++) {
393 for (int i = 0; i < sourceWidth; i++) {
394 ColorValues colorValues(rawImage[i + j * sourceWidth]);
395 if (i > 0)
396 colorValues += buffer[(i - 1) + j * sourceWidth];
397 if (j > 0)
398 colorValues += buffer[i + (j - 1) * sourceWidth];
399 if (i > 0 && j > 0)
400 colorValues -= buffer[(i - 1) + (j - 1) * sourceWidth];
401 buffer[i + j * sourceWidth] = colorValues;
402 }
403 }
404
405 for (int j = 0; j < sourceHeight; j++) {
406 const int j1 = qMax(0, j - dytop);
407 const int j2 = qMin(sourceHeight - 1, j + dybottom);
408 for (int i = 0; i < sourceWidth; i++) {
409 const int i1 = qMax(0, i - dxleft);
410 const int i2 = qMin(sourceWidth - 1, i + dxright);
411 ColorValues colorValues = buffer[i2 + j2 * sourceWidth]
412 - buffer[i1 + j2 * sourceWidth]
413 - buffer[i2 + j1 * sourceWidth]
414 + buffer[i1 + j1 * sourceWidth];
415 colorValues /= uint64_t(dxleft + dxright) * uint64_t(dytop + dybottom);
416 rawImage[i + j * sourceWidth] = colorValues.toRgb();
417 }
418 }
419 }
420
421 QRectF trueClipRectGlob = globalSubRegion(p, itemBounds, filterBounds, primitiveUnits, filterUnits);
422
423 QImage result;
424 if (!QImageIOHandler::allocateImage(trueClipRectGlob.toRect().size(), QImage::Format_ARGB32_Premultiplied, &result)) {
425 qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
426 return QImage();
427 }
428 result.setOffset(trueClipRectGlob.toRect().topLeft());
429 result.fill(Qt::transparent);
430 QPainter transformPainter(&result);
431 transformPainter.setRenderHint(QPainter::Antialiasing, true);
432
433 transformPainter.translate(-result.offset());
434 transformPainter.setTransform(restXr, true);
435 transformPainter.drawImage(clipRectGlob.topLeft(), tempSource);
436 transformPainter.end();
437
438 clipToTransformedBounds(&result, p, localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
439 return result;
440}
441
442QSvgFeOffset::QSvgFeOffset(QSvgNode *parent, const QString &input, const QString &result,
443 const QSvgRectF &rect, qreal dx, qreal dy)
444 : QSvgFeFilterPrimitive(parent, input, result, rect)
445 , m_dx(dx)
446 , m_dy(dy)
447{
448
449}
450
451QSvgFeOffset::~QSvgFeOffset()
452 = default;
453
454QSvgNode::Type QSvgFeOffset::type() const
455{
456 return QSvgNode::FeOffset;
457}
458
459QImage QSvgFeOffset::apply(const QMap<QString, QImage> &sources, QPainter *p,
460 const QRectF &itemBounds, const QRectF &filterBounds,
461 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
462{
463 if (!sources.contains(m_input))
464 return QImage();
465
466 const QImage &source = sources[m_input];
467
468 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
469 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
470
471 QPoint offset(m_dx, m_dy);
472 if (primitiveUnits == QtSvg::UnitTypes::objectBoundingBox) {
473 offset = QPoint(m_dx * itemBounds.width(),
474 m_dy * itemBounds.height());
475 }
476 offset = p->transform().map(offset) - p->transform().map(QPoint(0, 0));
477
478 if (clipRectGlob.isEmpty())
479 return QImage();
480
481 QImage result;
482 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
483 qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
484 return QImage();
485 }
486 result.setOffset(clipRectGlob.topLeft());
487 result.fill(Qt::transparent);
488
489 QPainter copyPainter(&result);
490 copyPainter.drawImage(source.offset()
491 - result.offset() + offset, source);
492 copyPainter.end();
493
494 clipToTransformedBounds(&result, p, clipRect);
495 return result;
496}
497
498
499QSvgFeMerge::QSvgFeMerge(QSvgNode *parent, const QString &input,
500 const QString &result, const QSvgRectF &rect)
501 : QSvgFeFilterPrimitive(parent, input, result, rect)
502{
503
504}
505
506QSvgFeMerge::~QSvgFeMerge()
507 = default;
508
509QSvgNode::Type QSvgFeMerge::type() const
510{
511 return QSvgNode::FeMerge;
512}
513
514QImage QSvgFeMerge::apply(const QMap<QString, QImage> &sources, QPainter *p,
515 const QRectF &itemBounds, const QRectF &filterBounds,
516 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
517{
518 QList<QImage> mergeNodeResults;
519 for (const auto &node : renderers()) {
520 if (node->type() == QSvgNode::FeMergenode) {
521 const QSvgFeMergeNode &filter = static_cast<const QSvgFeMergeNode&>(*node);
522 mergeNodeResults.append(filter.apply(sources, p, itemBounds, filterBounds, primitiveUnits, filterUnits));
523 }
524 }
525
526 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
527 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
528 if (clipRectGlob.isEmpty())
529 return QImage();
530
531 QImage result;
532 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
533 qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
534 return QImage();
535 }
536 result.setOffset(clipRectGlob.topLeft());
537 result.fill(Qt::transparent);
538
539 QPainter proxyPainter(&result);
540 for (const QImage &i : mergeNodeResults) {
541 proxyPainter.drawImage(QRect(i.offset() - result.offset(), i.size()), i);
542 }
543 proxyPainter.end();
544
545 clipToTransformedBounds(&result, p, clipRect);
546 return result;
547}
548
549bool QSvgFeMerge::requiresSourceAlpha() const
550{
551 for (const auto &node : renderers()) {
552 if (node->type() == QSvgNode::FeMergenode) {
553 const QSvgFeMergeNode &filter = static_cast<const QSvgFeMergeNode&>(*node);
554 if (filter.requiresSourceAlpha())
555 return true;
556 }
557 }
558 return false;
559}
560
561QSvgFeMergeNode::QSvgFeMergeNode(QSvgNode *parent, const QString &input,
562 const QString &result, const QSvgRectF &rect)
563 : QSvgFeFilterPrimitive(parent, input, result, rect)
564{
565
566}
567
568QSvgFeMergeNode::~QSvgFeMergeNode()
569 = default;
570
571QSvgNode::Type QSvgFeMergeNode::type() const
572{
573 return QSvgNode::FeMergenode;
574}
575
576QImage QSvgFeMergeNode::apply(const QMap<QString, QImage> &sources, QPainter *,
577 const QRectF &, const QRectF &, QtSvg::UnitTypes, QtSvg::UnitTypes) const
578{
579 return sources.value(m_input);
580}
581
582QSvgFeComposite::QSvgFeComposite(QSvgNode *parent, const QString &input, const QString &result,
583 const QSvgRectF &rect, const QString &input2, Operator op,
584 const QVector4D &k)
585 : QSvgFeFilterPrimitive(parent, input, result, rect)
586 , m_input2(input2)
587 , m_operator(op)
588 , m_k(k)
589{
590
591}
592
593QSvgFeComposite::~QSvgFeComposite()
594 = default;
595
596QSvgNode::Type QSvgFeComposite::type() const
597{
598 return QSvgNode::FeComposite;
599}
600
601QImage QSvgFeComposite::apply(const QMap<QString, QImage> &sources, QPainter *p,
602 const QRectF &itemBounds, const QRectF &filterBounds,
603 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
604{
605 if (!sources.contains(m_input))
606 return QImage();
607 if (!sources.contains(m_input2))
608 return QImage();
609 QImage source1 = sources[m_input];
610 QImage source2 = sources[m_input2];
611 Q_ASSERT(source1.depth() == 32);
612 Q_ASSERT(source2.depth() == 32);
613
614 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
615 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
616
617 if (clipRectGlob.isEmpty())
618 return QImage();
619
620 QImage result;
621 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
622 qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
623 return QImage();
624 }
625 result.setOffset(clipRectGlob.topLeft());
626 result.fill(Qt::transparent);
627
628 if (m_operator == Operator::Arithmetic) {
629 const qreal k1 = m_k.x();
630 const qreal k2 = m_k.y();
631 const qreal k3 = m_k.z();
632 const qreal k4 = m_k.w();
633
634 for (int j = 0; j < result.height(); j++) {
635 int jj1 = j - source1.offset().y() + result.offset().y();
636 int jj2 = j - source2.offset().y() + result.offset().y();
637
638 QRgb *resultLine = reinterpret_cast<QRgb *>(result.scanLine(j));
639 QRgb *source1Line = nullptr;
640 QRgb *source2Line = nullptr;
641
642 if (jj1 >= 0 && jj1 < source1.size().height())
643 source1Line = reinterpret_cast<QRgb *>(source1.scanLine(jj1));
644 if (jj2 >= 0 && jj2 < source2.size().height())
645 source2Line = reinterpret_cast<QRgb *>(source2.scanLine(jj2));
646
647 for (int i = 0; i < result.width(); i++) {
648 int ii1 = i - source1.offset().x() + result.offset().x();
649 int ii2 = i - source2.offset().x() + result.offset().x();
650
651 QVector4D s1 = QVector4D(0, 0, 0, 0);
652 QVector4D s2 = QVector4D(0, 0, 0, 0);
653
654 if (ii1 >= 0 && ii1 < source1.size().width() && source1Line) {
655 QRgb pixel1 = source1Line[ii1];
656 s1 = QVector4D(qRed(pixel1),
657 qGreen(pixel1),
658 qBlue(pixel1),
659 qAlpha(pixel1));
660 }
661
662 if (ii2 >= 0 && ii2 < source2.size().width() && source2Line) {
663 QRgb pixel2 = source2Line[ii2];
664 s2 = QVector4D(qRed(pixel2),
665 qGreen(pixel2),
666 qBlue(pixel2),
667 qAlpha(pixel2));
668 }
669
670 int r = k1 * s1.x() * s2.x() / 255. + k2 * s1.x() + k3 * s2.x() + k4 * 255.;
671 int g = k1 * s1.y() * s2.y() / 255. + k2 * s1.y() + k3 * s2.y() + k4 * 255.;
672 int b = k1 * s1.z() * s2.z() / 255. + k2 * s1.z() + k3 * s2.z() + k4 * 255.;
673 int a = k1 * s1.w() * s2.w() / 255. + k2 * s1.w() + k3 * s2.w() + k4 * 255.;
674
675 a = qBound(0, a, 255);
676 resultLine[i] = qRgba(qBound(0, r, a),
677 qBound(0, g, a),
678 qBound(0, b, a),
679 a);
680 }
681 }
682 } else {
683 QPainter proxyPainter(&result);
684 proxyPainter.drawImage(QRect(source1.offset() - result.offset(), source1.size()), source1);
685
686 switch (m_operator) {
687 case Operator::In:
688 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
689 break;
690 case Operator::Out:
691 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
692 break;
693 case Operator::Xor:
694 proxyPainter.setCompositionMode(QPainter::CompositionMode_Xor);
695 break;
696 case Operator::Lighter:
697 proxyPainter.setCompositionMode(QPainter::CompositionMode_Lighten);
698 break;
699 case Operator::Atop:
700 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationAtop);
701 break;
702 case Operator::Over:
703 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
704 break;
705 case Operator::Arithmetic: // handled above
706 Q_UNREACHABLE();
707 break;
708 }
709 proxyPainter.drawImage(QRect(source2.offset()-result.offset(), source2.size()), source2);
710 proxyPainter.end();
711 }
712
713 clipToTransformedBounds(&result, p, clipRect);
714 return result;
715}
716
717bool QSvgFeComposite::requiresSourceAlpha() const
718{
719 if (QSvgFeFilterPrimitive::requiresSourceAlpha())
720 return true;
721 return m_input2 == QLatin1StringView("SourceAlpha");
722}
723
724
725QSvgFeFlood::QSvgFeFlood(QSvgNode *parent, const QString &input, const QString &result,
726 const QSvgRectF &rect, const QColor &color)
727 : QSvgFeFilterPrimitive(parent, input, result, rect)
728 , m_color(color)
729{
730
731}
732
733QSvgFeFlood::~QSvgFeFlood()
734 = default;
735
736QSvgNode::Type QSvgFeFlood::type() const
737{
738 return QSvgNode::FeFlood;
739}
740
741QImage QSvgFeFlood::apply(const QMap<QString, QImage> &,
742 QPainter *p, const QRectF &itemBounds, const QRectF &filterBounds,
743 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
744{
745
746 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
747 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
748
749 QImage result;
750 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
751 qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
752 return QImage();
753 }
754 result.setOffset(clipRectGlob.topLeft());
755 result.fill(m_color);
756
757 clipToTransformedBounds(&result, p, clipRect);
758 return result;
759}
760
761QSvgFeBlend::QSvgFeBlend(QSvgNode *parent, const QString &input, const QString &result,
762 const QSvgRectF &rect, const QString &input2, Mode mode)
763 : QSvgFeFilterPrimitive(parent, input, result, rect)
764 , m_input2(input2)
765 , m_mode(mode)
766{
767
768}
769
770QSvgFeBlend::~QSvgFeBlend()
771 = default;
772
773QSvgNode::Type QSvgFeBlend::type() const
774{
775 return QSvgNode::FeBlend;
776}
777
778QImage QSvgFeBlend::apply(const QMap<QString, QImage> &sources, QPainter *p,
779 const QRectF &itemBounds, const QRectF &filterBounds,
780 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
781{
782 if (!sources.contains(m_input))
783 return QImage();
784 if (!sources.contains(m_input2))
785 return QImage();
786 QImage source1 = sources[m_input];
787 QImage source2 = sources[m_input2];
788 Q_ASSERT(source1.depth() == 32);
789 Q_ASSERT(source2.depth() == 32);
790
791 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
792 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
793
794 QImage result;
795 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
796 qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
797 return QImage();
798 }
799 result.setOffset(clipRectGlob.topLeft());
800 result.fill(Qt::transparent);
801
802 for (int j = 0; j < result.height(); j++) {
803 int jj1 = j - source1.offset().y() + result.offset().y();
804 int jj2 = j - source2.offset().y() + result.offset().y();
805
806 QRgb *resultLine = reinterpret_cast<QRgb *>(result.scanLine(j));
807 QRgb *source1Line = nullptr;
808 QRgb *source2Line = nullptr;
809
810 if (jj1 >= 0 && jj1 < source1.size().height())
811 source1Line = reinterpret_cast<QRgb *>(source1.scanLine(jj1));
812 if (jj2 >= 0 && jj2 < source2.size().height())
813 source2Line = reinterpret_cast<QRgb *>(source2.scanLine(jj2));
814
815 for (int i = 0; i < result.width(); i++) {
816 int ii1 = i - source1.offset().x() + result.offset().x();
817 int ii2 = i - source2.offset().x() + result.offset().x();
818
819 QRgb pixel1 = (ii1 >= 0 && ii1 < source1.size().width() && source1Line) ?
820 source1Line[ii1] : qRgba(0, 0, 0, 0);
821 QRgb pixel2 = (ii2 >= 0 && ii2 < source2.size().width() && source2Line) ?
822 source2Line[ii2] : qRgba(0, 0, 0, 0);
823
824 qreal r = 0, g = 0, b = 0;
825 qreal red1 = qRed(pixel1);
826 qreal red2 = qRed(pixel2);
827 qreal green1 = qGreen(pixel1);
828 qreal green2 = qGreen(pixel2);
829 qreal blue1 = qBlue(pixel1);
830 qreal blue2 = qBlue(pixel2);
831 qreal alpha1 = qAlpha(pixel1) / 255.;
832 qreal alpha2 = qAlpha(pixel2) / 255.;
833 qreal a = 255 - (1 - alpha1) * (1 - alpha2) * 255.;
834
835 switch (m_mode) {
836 case Mode::Normal:
837 r = (1 - alpha1) * red2 + red1;
838 g = (1 - alpha1) * green2 + green1;
839 b = (1 - alpha1) * blue2 + blue1;
840 break;
841 case Mode::Multiply:
842 r = (1 - alpha1) * red2 + (1 - alpha2) * red1 + red1 * red2 / 255.;
843 g = (1 - alpha1) * green2 + (1 - alpha2) * green1 + green1 * green2 / 255.;
844 b = (1 - alpha1) * blue2 + (1 - alpha2) * blue1 + blue1 * blue2 / 255.;
845 break;
846 case Mode::Screen:
847 r = red2 + red1 - red1 * red2 / 255.;
848 g = green2 + green1 - green1 * green2 / 255.;
849 b = blue2 + blue1 - blue1 * blue2 / 255.;
850 break;
851 case Mode::Darken:
852 r = qMin((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2);
853 g = qMin((1 - alpha1) * green2 + green1, (1 - alpha2) * green1 + green2);
854 b = qMin((1 - alpha1) * blue2 + blue1, (1 - alpha2) * blue1 + blue2);
855 break;
856 case Mode::Lighten:
857 r = qMax((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2);
858 g = qMax((1 - alpha1) * green2 + green1, (1 - alpha2) * green1 + green2);
859 b = qMax((1 - alpha1) * blue2 + blue1, (1 - alpha2) * blue1 + blue2);
860 break;
861 }
862 a = qBound(0., a, 255.);
863 resultLine[i] = qRgba(qBound(0., r, a),
864 qBound(0., g, a),
865 qBound(0., b, a),
866 a);
867 }
868 }
869 clipToTransformedBounds(&result, p, clipRect);
870 return result;
871}
872
873bool QSvgFeBlend::requiresSourceAlpha() const
874{
875 if (QSvgFeFilterPrimitive::requiresSourceAlpha())
876 return true;
877 return m_input2 == QLatin1StringView("SourceAlpha");
878}
879
880QSvgFeUnsupported::QSvgFeUnsupported(QSvgNode *parent, const QString &input, const QString &result,
881 const QSvgRectF &rect)
882 : QSvgFeFilterPrimitive(parent, input, result, rect)
883{
884}
885
886QSvgFeUnsupported::~QSvgFeUnsupported()
887 = default;
888
889QSvgNode::Type QSvgFeUnsupported::type() const
890{
891 return QSvgNode::FeUnsupported;
892}
893
894QImage QSvgFeUnsupported::apply(const QMap<QString, QImage> &,
895 QPainter *, const QRectF &, const QRectF &,
896 QtSvg::UnitTypes, QtSvg::UnitTypes) const
897{
898 qCDebug(lcSvgDraw) << "Unsupported filter primitive should not be applied.";
899 return QImage();
900}
901
902QT_END_NAMESPACE
ColorValues & operator-=(const ColorValues &other)
ColorValues & operator+=(const ColorValues &other)
ColorValues(QRgb rgb)
ColorValues()=default
ColorValues operator+(const ColorValues &other)
ColorValues operator/=(uint64_t div)
ColorValues operator-(const ColorValues &other)
Combined button and popup list for selecting options.
QGenericMatrix< 3, 3, qreal > Matrix3x3