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