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