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