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