Revision control
Copy as Markdown
// 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