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