Revision control

++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qsvgfilter_p.h"
++
++ #include "qsvggraphics_p.h"
++ #include "qsvgnode_p.h"
++ #include "qsvgdocument_p.h"
++ #include "qpainter.h"
++
++ #include <QLoggingCategory>
++ #include <QtGui/qimageiohandler.h>
++ #include <QtGui/qimagereader.h>
++ #include <QVector4D>
++
++ #include <memory>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::Literals::StringLiterals;
++
++ QSvgFeFilterPrimitive::QSvgFeFilterPrimitive(QSvgNode *parent, const QString &input,
++ const QString &result, const QSvgRectF &rect)
++ : QSvgStructureNode(parent)
++ , m_input(input)
++ , m_result(result)
++ , m_rect(rect)
++ {
++
++ }
++
++ QSvgFeFilterPrimitive::~QSvgFeFilterPrimitive()
++ = default;
++
++ bool QSvgFeFilterPrimitive::shouldDrawNode(QPainter *, QSvgExtraStates &) const
++ {
++ return false;
++ }
++
++ QRectF QSvgFeFilterPrimitive::localSubRegion(const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes) const
++ {
++ // 15.7.3 Filter primitive subregion
++
++ QRectF clipRect = m_rect.resolveRelativeLengths(itemBounds, primitiveUnits);
++
++ // the default subregion is 0%,0%,100%,100%, where as a special-case the percentages are
++ // relative to the dimensions of the filter region, thus making the the default filter primitive
++ // subregion equal to the filter region.
++ if (m_rect.unitX() == QtSvg::UnitTypes::unknown)
++ clipRect.setX(filterBounds.x());
++ if (m_rect.unitY() == QtSvg::UnitTypes::unknown)
++ clipRect.setY(filterBounds.y());
++ if (m_rect.unitW() == QtSvg::UnitTypes::unknown)
++ clipRect.setWidth(filterBounds.width());
++ if (m_rect.unitH() == QtSvg::UnitTypes::unknown)
++ clipRect.setHeight(filterBounds.height());
++
++ clipRect = clipRect.intersected(filterBounds);
++
++ return clipRect;
++ }
++
++ QRectF QSvgFeFilterPrimitive::globalSubRegion(QPainter *p,
++ const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
++ {
++ return p->transform().mapRect(localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
++ }
++
++ void QSvgFeFilterPrimitive::clipToTransformedBounds(QImage *buffer, QPainter *p, const QRectF &localRect) const
++ {
++ QPainter painter(buffer);
++ painter.setRenderHints(p->renderHints());
++ painter.translate(-buffer->offset());
++ QPainterPath clipPath;
++ clipPath.setFillRule(Qt::OddEvenFill);
++ clipPath.addRect(QRect(buffer->offset(), buffer->size()).adjusted(-10, -10, 20, 20));
++ clipPath.addPolygon(p->transform().map(QPolygonF(localRect)));
++ painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
++ painter.fillPath(clipPath, Qt::transparent);
++ }
++
++ bool QSvgFeFilterPrimitive::requiresSourceAlpha() const
++ {
++ return m_input == QLatin1StringView("SourceAlpha");
++ }
++
++ const QSvgFeFilterPrimitive *QSvgFeFilterPrimitive::castToFilterPrimitive(const QSvgNode *node)
++ {
++ if (node->type() == QSvgNode::FeMerge ||
++ node->type() == QSvgNode::FeColormatrix ||
++ node->type() == QSvgNode::FeGaussianblur ||
++ node->type() == QSvgNode::FeOffset ||
++ node->type() == QSvgNode::FeComposite ||
++ node->type() == QSvgNode::FeFlood ||
++ node->type() == QSvgNode::FeBlend ) {
++ return static_cast<const QSvgFeFilterPrimitive*>(node);
++ } else {
++ return nullptr;
++ }
++ }
++
++ typedef QGenericMatrix<3, 3, qreal> Matrix3x3;
++
++ QSvgFeColorMatrix::QSvgFeColorMatrix(QSvgNode *parent, const QString &input, const QString &result,
++ const QSvgRectF &rect, ColorShiftType type,
++ const Matrix &matrix)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ , m_matrix(matrix)
++ {
++ //Magic numbers see SVG 1.1(Second edition)
++ constexpr qreal v1[] = {0.213, 0.213, 0.213,
++ 0.715, 0.715, 0.715,
++ 0.072, 0.072, 0.072};
++ static const Matrix3x3 m1(v1);
++
++ constexpr qreal v2[] = { 0.787, -0.213, -0.213,
++ -0.715, 0.285, -0.715,
++ -0.072, -0.072, 0.928};
++ static const Matrix3x3 m2(v2);
++
++ constexpr qreal v3[] = {-0.213, 0.143, -0.787,
++ -0.715, 0.140, 0.715,
++ 0.928, -0.283, 0.072};
++ static const Matrix3x3 m3(v3);
++
++ if (type == ColorShiftType::Saturate) {
++ qreal s = qBound(0., m_matrix.data()[0], 1.);
++
++ m_matrix.fill(0);
++
++ const Matrix3x3 m = m1 + m2 * s;
++
++ for (int j = 0; j < 3; ++j)
++ for (int i = 0; i < 3; ++i)
++ m_matrix.data()[i+j*5] = m.data()[i+j*3];
++ m_matrix.data()[3+3*5] = 1;
++
++ } else if (type == ColorShiftType::HueRotate){
++ qreal angle = m_matrix.data()[0]/180.*M_PI;
++ qreal s = sin(angle);
++ qreal c = cos(angle);
++
++ m_matrix.fill(0);
++
++ const Matrix3x3 m = m1 + m2 * c + m3 * s;
++
++ for (int j = 0; j < 3; ++j)
++ for (int i = 0; i < 3; ++i)
++ m_matrix.data()[i+j*5] = m.data()[i+j*3];
++ m_matrix.data()[3+3*5] = 1;
++ } else if (type == ColorShiftType::LuminanceToAlpha){
++ m_matrix.fill(0);
++
++ m_matrix.data()[0+3*5] = 0.2125;
++ m_matrix.data()[1+3*5] = 0.7154;
++ m_matrix.data()[2+3*5] = 0.0721;
++ }
++ }
++
++ QSvgFeColorMatrix::~QSvgFeColorMatrix()
++ = default;
++
++ QSvgNode::Type QSvgFeColorMatrix::type() const
++ {
++ return QSvgNode::FeColormatrix;
++ }
++
++ QImage QSvgFeColorMatrix::apply(const QMap<QString, QImage> &sources, QPainter *p,
++ const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
++ {
++ if (!sources.contains(m_input))
++ return QImage();
++ QImage source = sources[m_input];
++
++ QRect clipRectGlob = globalSubRegion(p, itemBounds, filterBounds, primitiveUnits, filterUnits).toRect();
++ if (clipRectGlob.isEmpty())
++ return QImage();
++
++ QImage result;
++ if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
++ qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
++ return QImage();
++ }
++ result.setOffset(clipRectGlob.topLeft());
++ result.fill(Qt::transparent);
++
++ Q_ASSERT(source.depth() == 32);
++
++ const Matrix transposedMatrix = m_matrix.transposed();
++
++ for (int i = 0; i < result.height(); i++) {
++ int sourceI = i - source.offset().y() + result.offset().y();
++
++ if (sourceI < 0 || sourceI >= source.height())
++ continue;
++
++ QRgb *sourceLine = reinterpret_cast<QRgb *>(source.scanLine(sourceI));
++ QRgb *resultLine = reinterpret_cast<QRgb *>(result.scanLine(i));
++
++ for (int j = 0; j < result.width(); j++) {
++ int sourceJ = j - source.offset().x() + result.offset().x();
++
++ if (sourceJ < 0 || sourceJ >= source.width())
++ continue;
++
++ QRgb sourceColor = qUnpremultiply(sourceLine[sourceJ]);
++ const qreal values[] = {qreal(qRed(sourceColor)),
++ qreal(qGreen(sourceColor)),
++ qreal(qBlue(sourceColor)),
++ qreal(qAlpha(sourceColor)),
++ 255.};
++ const QGenericMatrix<1, 5, qreal> sourceVector(values);
++ const QGenericMatrix<1, 5, qreal> resultVector = transposedMatrix * sourceVector;
++
++ QRgb rgba = qRgba(qBound(0, int(resultVector(0, 0)), 255),
++ qBound(0, int(resultVector(1, 0)), 255),
++ qBound(0, int(resultVector(2, 0)), 255),
++ qBound(0, int(resultVector(3, 0)), 255));
++ resultLine[j] = qPremultiply(rgba);
++ }
++ }
++
++ clipToTransformedBounds(&result, p, localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
++ return result;
++ }
++
++ QSvgFeGaussianBlur::QSvgFeGaussianBlur(QSvgNode *parent, const QString &input,
++ const QString &result, const QSvgRectF &rect,
++ qreal stdDeviationX, qreal stdDeviationY, EdgeMode edgemode)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ , m_stdDeviationX(stdDeviationX)
++ , m_stdDeviationY(stdDeviationY)
++ , m_edgemode(edgemode)
++ {
++ Q_UNUSED(m_edgemode);
++ }
++
++ QSvgFeGaussianBlur::~QSvgFeGaussianBlur()
++ = default;
++
++ QSvgNode::Type QSvgFeGaussianBlur::type() const
++ {
++ return QSvgNode::FeGaussianblur;
++ }
++
++ class ColorValues {
++ // This class is not designed for general use. It is is only meant to improve the calling code's
++ // readability by replacing a two-dimensial array of integers with a one-dimensial array of
++ // objects which perform repeated calculations on the single values by just one call.
++ public:
++ ColorValues() = default;
++ explicit ColorValues(QRgb rgb) : b(qBlue(rgb)), g(qGreen(rgb)), r(qRed(rgb)), a(qAlpha(rgb)){}
++
++ ColorValues &operator+=(const ColorValues &other)
++ {
++ b += other.b;
++ g += other.g;
++ r += other.r;
++ a += other.a;
++ return *this;
++ }
++
++ ColorValues operator+(const ColorValues &other)
++ {
++ ColorValues result(*this);
++ result+=(other);
++ return result;
++ }
++
++ ColorValues &operator-=(const ColorValues &other)
++ {
++ b -= other.b;
++ g -= other.g;
++ r -= other.r;
++ a -= other.a;
++ return *this;
++ }
++
++ ColorValues operator-(const ColorValues &other)
++ {
++ ColorValues result(*this);
++ result -= other;
++ return result;
++ }
++
++ ColorValues operator/=(uint64_t div)
++ {
++ // This does neither avoid nor handle division by zero. It
++ // relies on the calling code to never pass a zero value.
++ b /= div;
++ g /= div;
++ r /= div;
++ a /= div;
++ return *this;
++ }
++
++ QRgb toRgb()
++ {
++ return qRgba(r, g, b, a);
++ }
++
++ private:
++ uint64_t b = 0;
++ uint64_t g = 0;
++ uint64_t r = 0;
++ uint64_t a = 0;
++ };
++
++ QImage QSvgFeGaussianBlur::apply(const QMap<QString, QImage> &sources, QPainter *p,
++ const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
++ {
++ if (!sources.contains(m_input))
++ return QImage();
++ QImage source = sources[m_input];
++ Q_ASSERT(source.depth() == 32);
++
++ if (m_stdDeviationX == 0 && m_stdDeviationY == 0)
++ return source;
++
++ const qreal scaleX = qHypot(p->transform().m11(), p->transform().m21());
++ const qreal scaleY = qHypot(p->transform().m12(), p->transform().m22());
++
++ qreal sigma_x = scaleX * m_stdDeviationX;
++ qreal sigma_y = scaleY * m_stdDeviationY;
++ if (primitiveUnits == QtSvg::UnitTypes::objectBoundingBox) {
++ sigma_x *= itemBounds.width();
++ sigma_y *= itemBounds.height();
++ }
++
++ constexpr double sd = 3. * M_SQRT1_2 / M_2_SQRTPI; // 3 * sqrt(2 * pi) / 4
++ const int dx = floor(sigma_x * sd + 0.5);
++ const int dy = floor(sigma_y * sd + 0.5);
++
++ const QTransform scaleXr = QTransform::fromScale(scaleX, scaleY);
++ const QTransform restXr = scaleXr.inverted() * p->transform();
++
++ QRect clipRectGlob = scaleXr.mapRect(localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits)).toRect();
++ if (clipRectGlob.isEmpty())
++ return QImage();
++
++ constexpr auto warningMessage = "The requested filter buffer is too big, ignoring"_L1;
++ QImage tempSource;
++ if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &tempSource)) {
++ qCWarning(lcSvgDraw) << warningMessage;
++ return QImage();
++ }
++ tempSource.setOffset(clipRectGlob.topLeft());
++ tempSource.fill(Qt::transparent);
++ QPainter copyPainter(&tempSource);
++ copyPainter.translate(-tempSource.offset());
++ copyPainter.setTransform(restXr.inverted(), true);
++ copyPainter.drawImage(source.offset(), source);
++ copyPainter.end();
++
++ const qint64 bufferSize = qint64(tempSource.width()) * tempSource.height();
++ if (const int allocationLimitMb = QImageReader::allocationLimit()) {
++ // Allocation limit in bytes
++ const qint64 allocationLimit = qint64(allocationLimitMb) << 20;
++ // Buffer size in bytes including the size of ColorValues.
++ const qint64 bufferSizeBytes = qint64(bufferSize) * sizeof(ColorValues);
++ if (bufferSizeBytes > allocationLimit) {
++ qCWarning(lcSvgDraw) << warningMessage;
++ return QImage();
++ }
++ }
++
++ { // start a scope to reduce the allocation's lifetime
++ const auto buffer = std::make_unique<ColorValues[]>(bufferSize);
++
++ const int sourceHeight = tempSource.height();
++ const int sourceWidth = tempSource.width();
++ QRgb *rawImage = reinterpret_cast<QRgb *>(tempSource.bits());
++
++ // Three successive box-blurs build a piece-wise quadratic convolution kernel,
++ // which approximates the Gaussian kernel
++ for (int m = 0; m < 3; m++) {
++ // if d is odd, use three box-blurs of size 'd', centered on the output pixel.
++ // if d is even, two box-blurs of size 'd' (the first one centered on the pixel boundary
++ // between the output pixel and the one to the left, the second one centered on the
++ // pixel boundary between the output pixel and the one to the right) and one box blur of
++ // size 'd+1' centered on the output pixel.
++ auto adjustD = [](int d, int iteration) {
++ d = qMax(1, d); // Treat d == 0 just like d == 1
++ std::pair<int, int> result;
++ if (d % 2 == 1)
++ result = {d / 2 + 1, d / 2};
++ else if (iteration == 0)
++ result = {d / 2 + 1, d / 2 - 1};
++ else if (iteration == 1)
++ result = {d / 2, d / 2};
++ else
++ result = {d / 2 + 1, d / 2};
++ Q_ASSERT(result.first + result.second > 0);
++ return result;
++ };
++
++ const auto [dxleft, dxright] = adjustD(dx, m);
++ const auto [dytop, dybottom] = adjustD(dy, m);
++
++ // Generating the partial sum of color values from the top left corner
++ // These sums can be combined to yield the partial sum of any rectangular subregion
++ for (int j = 0; j < sourceHeight; j++) {
++ for (int i = 0; i < sourceWidth; i++) {
++ ColorValues colorValues(rawImage[i + j * sourceWidth]);
++ if (i > 0)
++ colorValues += buffer[(i - 1) + j * sourceWidth];
++ if (j > 0)
++ colorValues += buffer[i + (j - 1) * sourceWidth];
++ if (i > 0 && j > 0)
++ colorValues -= buffer[(i - 1) + (j - 1) * sourceWidth];
++ buffer[i + j * sourceWidth] = colorValues;
++ }
++ }
++
++ for (int j = 0; j < sourceHeight; j++) {
++ const int j1 = qMax(0, j - dytop);
++ const int j2 = qMin(sourceHeight - 1, j + dybottom);
++ for (int i = 0; i < sourceWidth; i++) {
++ const int i1 = qMax(0, i - dxleft);
++ const int i2 = qMin(sourceWidth - 1, i + dxright);
++ ColorValues colorValues = buffer[i2 + j2 * sourceWidth]
++ - buffer[i1 + j2 * sourceWidth]
++ - buffer[i2 + j1 * sourceWidth]
++ + buffer[i1 + j1 * sourceWidth];
++ colorValues /= uint64_t(dxleft + dxright) * uint64_t(dytop + dybottom);
++ rawImage[i + j * sourceWidth] = colorValues.toRgb();
++ }
++ }
++ }
++ }
++
++ QRectF trueClipRectGlob = globalSubRegion(p, itemBounds, filterBounds, primitiveUnits, filterUnits);
++
++ QImage result;
++ if (!QImageIOHandler::allocateImage(trueClipRectGlob.toRect().size(), QImage::Format_ARGB32_Premultiplied, &result)) {
++ qCWarning(lcSvgDraw) << warningMessage;
++ return QImage();
++ }
++ result.setOffset(trueClipRectGlob.toRect().topLeft());
++ result.fill(Qt::transparent);
++ QPainter transformPainter(&result);
++ transformPainter.setRenderHint(QPainter::Antialiasing, true);
++
++ transformPainter.translate(-result.offset());
++ transformPainter.setTransform(restXr, true);
++ transformPainter.drawImage(clipRectGlob.topLeft(), tempSource);
++ transformPainter.end();
++
++ clipToTransformedBounds(&result, p, localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
++ return result;
++ }
++
++ QSvgFeOffset::QSvgFeOffset(QSvgNode *parent, const QString &input, const QString &result,
++ const QSvgRectF &rect, qreal dx, qreal dy)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ , m_dx(dx)
++ , m_dy(dy)
++ {
++
++ }
++
++ QSvgFeOffset::~QSvgFeOffset()
++ = default;
++
++ QSvgNode::Type QSvgFeOffset::type() const
++ {
++ return QSvgNode::FeOffset;
++ }
++
++ QImage QSvgFeOffset::apply(const QMap<QString, QImage> &sources, QPainter *p,
++ const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
++ {
++ if (!sources.contains(m_input))
++ return QImage();
++
++ const QImage &source = sources[m_input];
++
++ QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
++ QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
++
++ QPoint offset(m_dx, m_dy);
++ if (primitiveUnits == QtSvg::UnitTypes::objectBoundingBox) {
++ offset = QPoint(m_dx * itemBounds.width(),
++ m_dy * itemBounds.height());
++ }
++ offset = p->transform().map(offset) - p->transform().map(QPoint(0, 0));
++
++ if (clipRectGlob.isEmpty())
++ return QImage();
++
++ QImage result;
++ if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
++ qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
++ return QImage();
++ }
++ result.setOffset(clipRectGlob.topLeft());
++ result.fill(Qt::transparent);
++
++ QPainter copyPainter(&result);
++ copyPainter.drawImage(source.offset()
++ - result.offset() + offset, source);
++ copyPainter.end();
++
++ clipToTransformedBounds(&result, p, clipRect);
++ return result;
++ }
++
++
++ QSvgFeMerge::QSvgFeMerge(QSvgNode *parent, const QString &input,
++ const QString &result, const QSvgRectF &rect)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ {
++
++ }
++
++ QSvgFeMerge::~QSvgFeMerge()
++ = default;
++
++ QSvgNode::Type QSvgFeMerge::type() const
++ {
++ return QSvgNode::FeMerge;
++ }
++
++ QImage QSvgFeMerge::apply(const QMap<QString, QImage> &sources, QPainter *p,
++ const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
++ {
++ QList<QImage> mergeNodeResults;
++ for (const auto &node : renderers()) {
++ if (node->type() == QSvgNode::FeMergenode) {
++ const QSvgFeMergeNode &filter = static_cast<const QSvgFeMergeNode&>(*node);
++ mergeNodeResults.append(filter.apply(sources, p, itemBounds, filterBounds, primitiveUnits, filterUnits));
++ }
++ }
++
++ QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
++ QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
++ if (clipRectGlob.isEmpty())
++ return QImage();
++
++ QImage result;
++ if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
++ qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
++ return QImage();
++ }
++ result.setOffset(clipRectGlob.topLeft());
++ result.fill(Qt::transparent);
++
++ QPainter proxyPainter(&result);
++ for (const QImage &i : mergeNodeResults) {
++ proxyPainter.drawImage(QRect(i.offset() - result.offset(), i.size()), i);
++ }
++ proxyPainter.end();
++
++ clipToTransformedBounds(&result, p, clipRect);
++ return result;
++ }
++
++ bool QSvgFeMerge::requiresSourceAlpha() const
++ {
++ for (const auto &node : renderers()) {
++ if (node->type() == QSvgNode::FeMergenode) {
++ const QSvgFeMergeNode &filter = static_cast<const QSvgFeMergeNode&>(*node);
++ if (filter.requiresSourceAlpha())
++ return true;
++ }
++ }
++ return false;
++ }
++
++ QSvgFeMergeNode::QSvgFeMergeNode(QSvgNode *parent, const QString &input,
++ const QString &result, const QSvgRectF &rect)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ {
++
++ }
++
++ QSvgFeMergeNode::~QSvgFeMergeNode()
++ = default;
++
++ QSvgNode::Type QSvgFeMergeNode::type() const
++ {
++ return QSvgNode::FeMergenode;
++ }
++
++ QImage QSvgFeMergeNode::apply(const QMap<QString, QImage> &sources, QPainter *,
++ const QRectF &, const QRectF &, QtSvg::UnitTypes, QtSvg::UnitTypes) const
++ {
++ return sources.value(m_input);
++ }
++
++ QSvgFeComposite::QSvgFeComposite(QSvgNode *parent, const QString &input, const QString &result,
++ const QSvgRectF &rect, const QString &input2, Operator op,
++ const QVector4D &k)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ , m_input2(input2)
++ , m_operator(op)
++ , m_k(k)
++ {
++
++ }
++
++ QSvgFeComposite::~QSvgFeComposite()
++ = default;
++
++ QSvgNode::Type QSvgFeComposite::type() const
++ {
++ return QSvgNode::FeComposite;
++ }
++
++ QImage QSvgFeComposite::apply(const QMap<QString, QImage> &sources, QPainter *p,
++ const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
++ {
++ if (!sources.contains(m_input))
++ return QImage();
++ if (!sources.contains(m_input2))
++ return QImage();
++ QImage source1 = sources[m_input];
++ QImage source2 = sources[m_input2];
++ Q_ASSERT(source1.depth() == 32);
++ Q_ASSERT(source2.depth() == 32);
++
++ QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
++ QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
++
++ if (clipRectGlob.isEmpty())
++ return QImage();
++
++ QImage result;
++ if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
++ qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
++ return QImage();
++ }
++ result.setOffset(clipRectGlob.topLeft());
++ result.fill(Qt::transparent);
++
++ if (m_operator == Operator::Arithmetic) {
++ const qreal k1 = m_k.x();
++ const qreal k2 = m_k.y();
++ const qreal k3 = m_k.z();
++ const qreal k4 = m_k.w();
++
++ for (int j = 0; j < result.height(); j++) {
++ int jj1 = j - source1.offset().y() + result.offset().y();
++ int jj2 = j - source2.offset().y() + result.offset().y();
++
++ QRgb *resultLine = reinterpret_cast<QRgb *>(result.scanLine(j));
++ QRgb *source1Line = nullptr;
++ QRgb *source2Line = nullptr;
++
++ if (jj1 >= 0 && jj1 < source1.size().height())
++ source1Line = reinterpret_cast<QRgb *>(source1.scanLine(jj1));
++ if (jj2 >= 0 && jj2 < source2.size().height())
++ source2Line = reinterpret_cast<QRgb *>(source2.scanLine(jj2));
++
++ for (int i = 0; i < result.width(); i++) {
++ int ii1 = i - source1.offset().x() + result.offset().x();
++ int ii2 = i - source2.offset().x() + result.offset().x();
++
++ QVector4D s1 = QVector4D(0, 0, 0, 0);
++ QVector4D s2 = QVector4D(0, 0, 0, 0);
++
++ if (ii1 >= 0 && ii1 < source1.size().width() && source1Line) {
++ QRgb pixel1 = source1Line[ii1];
++ s1 = QVector4D(qRed(pixel1),
++ qGreen(pixel1),
++ qBlue(pixel1),
++ qAlpha(pixel1));
++ }
++
++ if (ii2 >= 0 && ii2 < source2.size().width() && source2Line) {
++ QRgb pixel2 = source2Line[ii2];
++ s2 = QVector4D(qRed(pixel2),
++ qGreen(pixel2),
++ qBlue(pixel2),
++ qAlpha(pixel2));
++ }
++
++ int r = k1 * s1.x() * s2.x() / 255. + k2 * s1.x() + k3 * s2.x() + k4 * 255.;
++ int g = k1 * s1.y() * s2.y() / 255. + k2 * s1.y() + k3 * s2.y() + k4 * 255.;
++ int b = k1 * s1.z() * s2.z() / 255. + k2 * s1.z() + k3 * s2.z() + k4 * 255.;
++ int a = k1 * s1.w() * s2.w() / 255. + k2 * s1.w() + k3 * s2.w() + k4 * 255.;
++
++ a = qBound(0, a, 255);
++ resultLine[i] = qRgba(qBound(0, r, a),
++ qBound(0, g, a),
++ qBound(0, b, a),
++ a);
++ }
++ }
++ } else {
++ QPainter proxyPainter(&result);
++ proxyPainter.drawImage(QRect(source1.offset() - result.offset(), source1.size()), source1);
++
++ switch (m_operator) {
++ case Operator::In:
++ proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
++ break;
++ case Operator::Out:
++ proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
++ break;
++ case Operator::Xor:
++ proxyPainter.setCompositionMode(QPainter::CompositionMode_Xor);
++ break;
++ case Operator::Lighter:
++ proxyPainter.setCompositionMode(QPainter::CompositionMode_Lighten);
++ break;
++ case Operator::Atop:
++ proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationAtop);
++ break;
++ case Operator::Over:
++ proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
++ break;
++ case Operator::Arithmetic: // handled above
++ Q_UNREACHABLE();
++ break;
++ }
++ proxyPainter.drawImage(QRect(source2.offset()-result.offset(), source2.size()), source2);
++ proxyPainter.end();
++ }
++
++ clipToTransformedBounds(&result, p, clipRect);
++ return result;
++ }
++
++ bool QSvgFeComposite::requiresSourceAlpha() const
++ {
++ if (QSvgFeFilterPrimitive::requiresSourceAlpha())
++ return true;
++ return m_input2 == QLatin1StringView("SourceAlpha");
++ }
++
++
++ QSvgFeFlood::QSvgFeFlood(QSvgNode *parent, const QString &input, const QString &result,
++ const QSvgRectF &rect, const QColor &color)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ , m_color(color)
++ {
++
++ }
++
++ QSvgFeFlood::~QSvgFeFlood()
++ = default;
++
++ QSvgNode::Type QSvgFeFlood::type() const
++ {
++ return QSvgNode::FeFlood;
++ }
++
++ QImage QSvgFeFlood::apply(const QMap<QString, QImage> &,
++ QPainter *p, const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
++ {
++
++ QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
++ QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
++
++ QImage result;
++ if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
++ qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
++ return QImage();
++ }
++ result.setOffset(clipRectGlob.topLeft());
++ result.fill(m_color);
++
++ clipToTransformedBounds(&result, p, clipRect);
++ return result;
++ }
++
++ QSvgFeBlend::QSvgFeBlend(QSvgNode *parent, const QString &input, const QString &result,
++ const QSvgRectF &rect, const QString &input2, Mode mode)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ , m_input2(input2)
++ , m_mode(mode)
++ {
++
++ }
++
++ QSvgFeBlend::~QSvgFeBlend()
++ = default;
++
++ QSvgNode::Type QSvgFeBlend::type() const
++ {
++ return QSvgNode::FeBlend;
++ }
++
++ QImage QSvgFeBlend::apply(const QMap<QString, QImage> &sources, QPainter *p,
++ const QRectF &itemBounds, const QRectF &filterBounds,
++ QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const
++ {
++ if (!sources.contains(m_input))
++ return QImage();
++ if (!sources.contains(m_input2))
++ return QImage();
++ QImage source1 = sources[m_input];
++ QImage source2 = sources[m_input2];
++ Q_ASSERT(source1.depth() == 32);
++ Q_ASSERT(source2.depth() == 32);
++
++ QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
++ QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
++
++ QImage result;
++ if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
++ qCWarning(lcSvgDraw) << "The requested filter buffer is too big, ignoring";
++ return QImage();
++ }
++ result.setOffset(clipRectGlob.topLeft());
++ result.fill(Qt::transparent);
++
++ for (int j = 0; j < result.height(); j++) {
++ int jj1 = j - source1.offset().y() + result.offset().y();
++ int jj2 = j - source2.offset().y() + result.offset().y();
++
++ QRgb *resultLine = reinterpret_cast<QRgb *>(result.scanLine(j));
++ QRgb *source1Line = nullptr;
++ QRgb *source2Line = nullptr;
++
++ if (jj1 >= 0 && jj1 < source1.size().height())
++ source1Line = reinterpret_cast<QRgb *>(source1.scanLine(jj1));
++ if (jj2 >= 0 && jj2 < source2.size().height())
++ source2Line = reinterpret_cast<QRgb *>(source2.scanLine(jj2));
++
++ for (int i = 0; i < result.width(); i++) {
++ int ii1 = i - source1.offset().x() + result.offset().x();
++ int ii2 = i - source2.offset().x() + result.offset().x();
++
++ QRgb pixel1 = (ii1 >= 0 && ii1 < source1.size().width() && source1Line) ?
++ source1Line[ii1] : qRgba(0, 0, 0, 0);
++ QRgb pixel2 = (ii2 >= 0 && ii2 < source2.size().width() && source2Line) ?
++ source2Line[ii2] : qRgba(0, 0, 0, 0);
++
++ qreal r = 0, g = 0, b = 0;
++ qreal red1 = qRed(pixel1);
++ qreal red2 = qRed(pixel2);
++ qreal green1 = qGreen(pixel1);
++ qreal green2 = qGreen(pixel2);
++ qreal blue1 = qBlue(pixel1);
++ qreal blue2 = qBlue(pixel2);
++ qreal alpha1 = qAlpha(pixel1) / 255.;
++ qreal alpha2 = qAlpha(pixel2) / 255.;
++ qreal a = 255 - (1 - alpha1) * (1 - alpha2) * 255.;
++
++ switch (m_mode) {
++ case Mode::Normal:
++ r = (1 - alpha1) * red2 + red1;
++ g = (1 - alpha1) * green2 + green1;
++ b = (1 - alpha1) * blue2 + blue1;
++ break;
++ case Mode::Multiply:
++ r = (1 - alpha1) * red2 + (1 - alpha2) * red1 + red1 * red2 / 255.;
++ g = (1 - alpha1) * green2 + (1 - alpha2) * green1 + green1 * green2 / 255.;
++ b = (1 - alpha1) * blue2 + (1 - alpha2) * blue1 + blue1 * blue2 / 255.;
++ break;
++ case Mode::Screen:
++ r = red2 + red1 - red1 * red2 / 255.;
++ g = green2 + green1 - green1 * green2 / 255.;
++ b = blue2 + blue1 - blue1 * blue2 / 255.;
++ break;
++ case Mode::Darken:
++ r = qMin((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2);
++ g = qMin((1 - alpha1) * green2 + green1, (1 - alpha2) * green1 + green2);
++ b = qMin((1 - alpha1) * blue2 + blue1, (1 - alpha2) * blue1 + blue2);
++ break;
++ case Mode::Lighten:
++ r = qMax((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2);
++ g = qMax((1 - alpha1) * green2 + green1, (1 - alpha2) * green1 + green2);
++ b = qMax((1 - alpha1) * blue2 + blue1, (1 - alpha2) * blue1 + blue2);
++ break;
++ }
++ a = qBound(0., a, 255.);
++ resultLine[i] = qRgba(qBound(0., r, a),
++ qBound(0., g, a),
++ qBound(0., b, a),
++ a);
++ }
++ }
++ clipToTransformedBounds(&result, p, clipRect);
++ return result;
++ }
++
++ bool QSvgFeBlend::requiresSourceAlpha() const
++ {
++ if (QSvgFeFilterPrimitive::requiresSourceAlpha())
++ return true;
++ return m_input2 == QLatin1StringView("SourceAlpha");
++ }
++
++ QSvgFeUnsupported::QSvgFeUnsupported(QSvgNode *parent, const QString &input, const QString &result,
++ const QSvgRectF &rect)
++ : QSvgFeFilterPrimitive(parent, input, result, rect)
++ {
++ }
++
++ QSvgFeUnsupported::~QSvgFeUnsupported()
++ = default;
++
++ QSvgNode::Type QSvgFeUnsupported::type() const
++ {
++ return QSvgNode::FeUnsupported;
++ }
++
++ QImage QSvgFeUnsupported::apply(const QMap<QString, QImage> &,
++ QPainter *, const QRectF &, const QRectF &,
++ QtSvg::UnitTypes, QtSvg::UnitTypes) const
++ {
++ qCDebug(lcSvgDraw) << "Unsupported filter primitive should not be applied.";
++ return QImage();
++ }
++
++ QT_END_NAMESPACE