109QSvgFeColorMatrix::QSvgFeColorMatrix(QSvgNode *parent,
const QString &input,
const QString &result,
110 const QSvgRectF &rect, ColorShiftType type,
111 const Matrix &matrix)
112 : QSvgFeFilterPrimitive(parent, input, result, rect)
116 constexpr qreal v1[] = {0.213, 0.213, 0.213,
118 0.072, 0.072, 0.072};
119 static const Matrix3x3 m1(v1);
121 constexpr qreal v2[] = { 0.787, -0.213, -0.213,
122 -0.715, 0.285, -0.715,
123 -0.072, -0.072, 0.928};
124 static const Matrix3x3 m2(v2);
126 constexpr qreal v3[] = {-0.213, 0.143, -0.787,
127 -0.715, 0.140, 0.715,
128 0.928, -0.283, 0.072};
129 static const Matrix3x3 m3(v3);
131 if (type == ColorShiftType::Saturate) {
132 qreal s = qBound(0., m_matrix.data()[0], 1.);
136 const Matrix3x3 m = m1 + m2 * s;
138 for (
int j = 0; j < 3; ++j)
139 for (
int i = 0; i < 3; ++i)
140 m_matrix.data()[i+j*5] = m.data()[i+j*3];
141 m_matrix.data()[3+3*5] = 1;
143 }
else if (type == ColorShiftType::HueRotate){
144 qreal angle = m_matrix.data()[0]/180.*M_PI;
145 qreal s = sin(angle);
146 qreal c = cos(angle);
150 const Matrix3x3 m = m1 + m2 * c + m3 * s;
152 for (
int j = 0; j < 3; ++j)
153 for (
int i = 0; i < 3; ++i)
154 m_matrix.data()[i+j*5] = m.data()[i+j*3];
155 m_matrix.data()[3+3*5] = 1;
156 }
else if (type == ColorShiftType::LuminanceToAlpha){
159 m_matrix.data()[0+3*5] = 0.2125;
160 m_matrix.data()[1+3*5] = 0.7154;
161 m_matrix.data()[2+3*5] = 0.0721;
173QImage QSvgFeColorMatrix::apply(
const QMap<QString, QImage> &sources, QPainter *p,
174 const QRectF &itemBounds,
const QRectF &filterBounds,
175 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
177 if (!sources.contains(m_input))
179 QImage source = sources[m_input];
181 QRect clipRectGlob = globalSubRegion(p, itemBounds, filterBounds, primitiveUnits, filterUnits).toRect();
182 if (clipRectGlob.isEmpty())
186 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
187 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
190 result.setOffset(clipRectGlob.topLeft());
191 result.fill(Qt::transparent);
193 Q_ASSERT(source.depth() == 32);
195 const Matrix transposedMatrix = m_matrix.transposed();
197 for (
int i = 0; i < result.height(); i++) {
198 int sourceI = i - source.offset().y() + result.offset().y();
200 if (sourceI < 0 || sourceI >= source.height())
203 QRgb *sourceLine =
reinterpret_cast<QRgb *>(source.scanLine(sourceI));
204 QRgb *resultLine =
reinterpret_cast<QRgb *>(result.scanLine(i));
206 for (
int j = 0; j < result.width(); j++) {
207 int sourceJ = j - source.offset().x() + result.offset().x();
209 if (sourceJ < 0 || sourceJ >= source.width())
212 QRgb sourceColor = qUnpremultiply(sourceLine[sourceJ]);
213 const qreal values[] = {qreal(qRed(sourceColor)),
214 qreal(qGreen(sourceColor)),
215 qreal(qBlue(sourceColor)),
216 qreal(qAlpha(sourceColor)),
218 const QGenericMatrix<1, 5, qreal> sourceVector(values);
219 const QGenericMatrix<1, 5, qreal> resultVector = transposedMatrix * sourceVector;
221 QRgb rgba = qRgba(qBound(0,
int(resultVector(0, 0)), 255),
222 qBound(0,
int(resultVector(1, 0)), 255),
223 qBound(0,
int(resultVector(2, 0)), 255),
224 qBound(0,
int(resultVector(3, 0)), 255));
225 resultLine[j] = qPremultiply(rgba);
229 clipToTransformedBounds(&result, p, localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
315QImage QSvgFeGaussianBlur::apply(
const QMap<QString, QImage> &sources, QPainter *p,
316 const QRectF &itemBounds,
const QRectF &filterBounds,
317 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
319 if (!sources.contains(m_input))
321 QImage source = sources[m_input];
322 Q_ASSERT(source.depth() == 32);
324 if (m_stdDeviationX == 0 && m_stdDeviationY == 0)
327 const qreal scaleX = qHypot(p->transform().m11(), p->transform().m21());
328 const qreal scaleY = qHypot(p->transform().m12(), p->transform().m22());
330 qreal sigma_x = scaleX * m_stdDeviationX;
331 qreal sigma_y = scaleY * m_stdDeviationY;
332 if (primitiveUnits == QtSvg::UnitTypes::objectBoundingBox) {
333 sigma_x *= itemBounds.width();
334 sigma_y *= itemBounds.height();
337 constexpr double sd = 3. * M_SQRT1_2 / M_2_SQRTPI;
338 const int dx = floor(sigma_x * sd + 0.5);
339 const int dy = floor(sigma_y * sd + 0.5);
341 const QTransform scaleXr = QTransform::fromScale(scaleX, scaleY);
342 const QTransform restXr = scaleXr.inverted() * p->transform();
344 QRect clipRectGlob = scaleXr.mapRect(localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits)).toRect();
345 if (clipRectGlob.isEmpty())
348 constexpr auto warningMessage =
"The requested filter buffer is too big, ignoring"_L1;
350 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &tempSource)) {
351 qCWarning(lcSvgDraw) << warningMessage;
354 tempSource.setOffset(clipRectGlob.topLeft());
355 tempSource.fill(Qt::transparent);
356 QPainter copyPainter(&tempSource);
357 copyPainter.translate(-tempSource.offset());
358 copyPainter.setTransform(restXr.inverted(),
true);
359 copyPainter.drawImage(source.offset(), source);
362 const qint64 bufferSize = qint64(tempSource.width()) * tempSource.height();
363 if (
const int allocationLimitMb = QImageReader::allocationLimit()) {
365 const qint64 allocationLimit = qint64(allocationLimitMb) << 20;
367 const qint64 bufferSizeBytes = qint64(bufferSize) *
sizeof(ColorValues);
368 if (bufferSizeBytes > allocationLimit) {
369 qCWarning(lcSvgDraw) << warningMessage;
375 const auto buffer = std::make_unique<ColorValues[]>(bufferSize);
377 const int sourceHeight = tempSource.height();
378 const int sourceWidth = tempSource.width();
379 QRgb *rawImage =
reinterpret_cast<QRgb *>(tempSource.bits());
384 for (
int m = 0; m < 3; m++) {
391 auto adjustD = [](
int d,
int iteration) {
393 std::pair<
int,
int> result;
395 result = {d / 2 + 1, d / 2};
396 else if (iteration == 0)
397 result = {d / 2 + 1, d / 2 - 1};
398 else if (iteration == 1)
399 result = {d / 2, d / 2};
401 result = {d / 2 + 1, d / 2};
402 Q_ASSERT(result.first + result.second > 0);
406 const auto [dxleft, dxright] = adjustD(dx, m);
407 const auto [dytop, dybottom] = adjustD(dy, m);
411 for (
int j = 0; j < sourceHeight; j++) {
412 for (
int i = 0; i < sourceWidth; i++) {
413 ColorValues colorValues(rawImage[i + j * sourceWidth]);
415 colorValues += buffer[(i - 1) + j * sourceWidth];
417 colorValues += buffer[i + (j - 1) * sourceWidth];
419 colorValues -= buffer[(i - 1) + (j - 1) * sourceWidth];
420 buffer[i + j * sourceWidth] = colorValues;
424 for (
int j = 0; j < sourceHeight; j++) {
425 const int j1 = qMax(0, j - dytop);
426 const int j2 = qMin(sourceHeight - 1, j + dybottom);
427 for (
int i = 0; i < sourceWidth; i++) {
428 const int i1 = qMax(0, i - dxleft);
429 const int i2 = qMin(sourceWidth - 1, i + dxright);
430 ColorValues colorValues = buffer[i2 + j2 * sourceWidth]
431 - buffer[i1 + j2 * sourceWidth]
432 - buffer[i2 + j1 * sourceWidth]
433 + buffer[i1 + j1 * sourceWidth];
434 colorValues /= uint64_t(dxleft + dxright) * uint64_t(dytop + dybottom);
435 rawImage[i + j * sourceWidth] = colorValues.toRgb();
441 QRectF trueClipRectGlob = globalSubRegion(p, itemBounds, filterBounds, primitiveUnits, filterUnits);
444 if (!QImageIOHandler::allocateImage(trueClipRectGlob.toRect().size(), QImage::Format_ARGB32_Premultiplied, &result)) {
445 qCWarning(lcSvgDraw) << warningMessage;
448 result.setOffset(trueClipRectGlob.toRect().topLeft());
449 result.fill(Qt::transparent);
450 QPainter transformPainter(&result);
451 transformPainter.setRenderHint(QPainter::Antialiasing,
true);
453 transformPainter.translate(-result.offset());
454 transformPainter.setTransform(restXr,
true);
455 transformPainter.drawImage(clipRectGlob.topLeft(), tempSource);
456 transformPainter.end();
458 clipToTransformedBounds(&result, p, localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
479QImage QSvgFeOffset::apply(
const QMap<QString, QImage> &sources, QPainter *p,
480 const QRectF &itemBounds,
const QRectF &filterBounds,
481 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
483 if (!sources.contains(m_input))
486 const QImage &source = sources[m_input];
488 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
489 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
491 QPoint offset(m_dx, m_dy);
492 if (primitiveUnits == QtSvg::UnitTypes::objectBoundingBox) {
493 offset = QPoint(m_dx * itemBounds.width(),
494 m_dy * itemBounds.height());
496 offset = p->transform().map(offset) - p->transform().map(QPoint(0, 0));
498 if (clipRectGlob.isEmpty())
502 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
503 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
506 result.setOffset(clipRectGlob.topLeft());
507 result.fill(Qt::transparent);
509 QPainter copyPainter(&result);
510 copyPainter.drawImage(source.offset()
511 - result.offset() + offset, source);
514 clipToTransformedBounds(&result, p, clipRect);
534QImage QSvgFeMerge::apply(
const QMap<QString, QImage> &sources, QPainter *p,
535 const QRectF &itemBounds,
const QRectF &filterBounds,
536 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
538 QList<QImage> mergeNodeResults;
539 for (
const auto &node : renderers()) {
540 if (node->type() == QSvgNode::FeMergenode) {
541 const QSvgFeMergeNode &filter =
static_cast<
const QSvgFeMergeNode&>(*node);
542 mergeNodeResults.append(filter.apply(sources, p, itemBounds, filterBounds, primitiveUnits, filterUnits));
546 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
547 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
548 if (clipRectGlob.isEmpty())
552 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
553 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
556 result.setOffset(clipRectGlob.topLeft());
557 result.fill(Qt::transparent);
559 QPainter proxyPainter(&result);
560 for (
const QImage &i : mergeNodeResults) {
561 proxyPainter.drawImage(QRect(i.offset() - result.offset(), i.size()), i);
565 clipToTransformedBounds(&result, p, clipRect);
621QImage QSvgFeComposite::apply(
const QMap<QString, QImage> &sources, QPainter *p,
622 const QRectF &itemBounds,
const QRectF &filterBounds,
623 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
625 if (!sources.contains(m_input))
627 if (!sources.contains(m_input2))
629 QImage source1 = sources[m_input];
630 QImage source2 = sources[m_input2];
631 Q_ASSERT(source1.depth() == 32);
632 Q_ASSERT(source2.depth() == 32);
634 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
635 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
637 if (clipRectGlob.isEmpty())
641 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
642 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
645 result.setOffset(clipRectGlob.topLeft());
646 result.fill(Qt::transparent);
648 if (m_operator == Operator::Arithmetic) {
649 const qreal k1 = m_k.x();
650 const qreal k2 = m_k.y();
651 const qreal k3 = m_k.z();
652 const qreal k4 = m_k.w();
654 for (
int j = 0; j < result.height(); j++) {
655 int jj1 = j - source1.offset().y() + result.offset().y();
656 int jj2 = j - source2.offset().y() + result.offset().y();
658 QRgb *resultLine =
reinterpret_cast<QRgb *>(result.scanLine(j));
659 QRgb *source1Line =
nullptr;
660 QRgb *source2Line =
nullptr;
662 if (jj1 >= 0 && jj1 < source1.size().height())
663 source1Line =
reinterpret_cast<QRgb *>(source1.scanLine(jj1));
664 if (jj2 >= 0 && jj2 < source2.size().height())
665 source2Line =
reinterpret_cast<QRgb *>(source2.scanLine(jj2));
667 for (
int i = 0; i < result.width(); i++) {
668 int ii1 = i - source1.offset().x() + result.offset().x();
669 int ii2 = i - source2.offset().x() + result.offset().x();
671 QVector4D s1 = QVector4D(0, 0, 0, 0);
672 QVector4D s2 = QVector4D(0, 0, 0, 0);
674 if (ii1 >= 0 && ii1 < source1.size().width() && source1Line) {
675 QRgb pixel1 = source1Line[ii1];
676 s1 = QVector4D(qRed(pixel1),
682 if (ii2 >= 0 && ii2 < source2.size().width() && source2Line) {
683 QRgb pixel2 = source2Line[ii2];
684 s2 = QVector4D(qRed(pixel2),
690 int r = k1 * s1.x() * s2.x() / 255. + k2 * s1.x() + k3 * s2.x() + k4 * 255.;
691 int g = k1 * s1.y() * s2.y() / 255. + k2 * s1.y() + k3 * s2.y() + k4 * 255.;
692 int b = k1 * s1.z() * s2.z() / 255. + k2 * s1.z() + k3 * s2.z() + k4 * 255.;
693 int a = k1 * s1.w() * s2.w() / 255. + k2 * s1.w() + k3 * s2.w() + k4 * 255.;
695 a = qBound(0, a, 255);
696 resultLine[i] = qRgba(qBound(0, r, a),
703 QPainter proxyPainter(&result);
704 proxyPainter.drawImage(QRect(source1.offset() - result.offset(), source1.size()), source1);
706 switch (m_operator) {
708 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
711 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
714 proxyPainter.setCompositionMode(QPainter::CompositionMode_Xor);
716 case Operator::Lighter:
717 proxyPainter.setCompositionMode(QPainter::CompositionMode_Lighten);
720 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationAtop);
723 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
725 case Operator::Arithmetic:
729 proxyPainter.drawImage(QRect(source2.offset()-result.offset(), source2.size()), source2);
733 clipToTransformedBounds(&result, p, clipRect);
798QImage QSvgFeBlend::apply(
const QMap<QString, QImage> &sources, QPainter *p,
799 const QRectF &itemBounds,
const QRectF &filterBounds,
800 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
802 if (!sources.contains(m_input))
804 if (!sources.contains(m_input2))
806 QImage source1 = sources[m_input];
807 QImage source2 = sources[m_input2];
808 Q_ASSERT(source1.depth() == 32);
809 Q_ASSERT(source2.depth() == 32);
811 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
812 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
815 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
816 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
819 result.setOffset(clipRectGlob.topLeft());
820 result.fill(Qt::transparent);
822 for (
int j = 0; j < result.height(); j++) {
823 int jj1 = j - source1.offset().y() + result.offset().y();
824 int jj2 = j - source2.offset().y() + result.offset().y();
826 QRgb *resultLine =
reinterpret_cast<QRgb *>(result.scanLine(j));
827 QRgb *source1Line =
nullptr;
828 QRgb *source2Line =
nullptr;
830 if (jj1 >= 0 && jj1 < source1.size().height())
831 source1Line =
reinterpret_cast<QRgb *>(source1.scanLine(jj1));
832 if (jj2 >= 0 && jj2 < source2.size().height())
833 source2Line =
reinterpret_cast<QRgb *>(source2.scanLine(jj2));
835 for (
int i = 0; i < result.width(); i++) {
836 int ii1 = i - source1.offset().x() + result.offset().x();
837 int ii2 = i - source2.offset().x() + result.offset().x();
839 QRgb pixel1 = (ii1 >= 0 && ii1 < source1.size().width() && source1Line) ?
840 source1Line[ii1] : qRgba(0, 0, 0, 0);
841 QRgb pixel2 = (ii2 >= 0 && ii2 < source2.size().width() && source2Line) ?
842 source2Line[ii2] : qRgba(0, 0, 0, 0);
844 qreal r = 0, g = 0, b = 0;
845 qreal red1 = qRed(pixel1);
846 qreal red2 = qRed(pixel2);
847 qreal green1 = qGreen(pixel1);
848 qreal green2 = qGreen(pixel2);
849 qreal blue1 = qBlue(pixel1);
850 qreal blue2 = qBlue(pixel2);
851 qreal alpha1 = qAlpha(pixel1) / 255.;
852 qreal alpha2 = qAlpha(pixel2) / 255.;
853 qreal a = 255 - (1 - alpha1) * (1 - alpha2) * 255.;
857 r = (1 - alpha1) * red2 + red1;
858 g = (1 - alpha1) * green2 + green1;
859 b = (1 - alpha1) * blue2 + blue1;
862 r = (1 - alpha1) * red2 + (1 - alpha2) * red1 + red1 * red2 / 255.;
863 g = (1 - alpha1) * green2 + (1 - alpha2) * green1 + green1 * green2 / 255.;
864 b = (1 - alpha1) * blue2 + (1 - alpha2) * blue1 + blue1 * blue2 / 255.;
867 r = red2 + red1 - red1 * red2 / 255.;
868 g = green2 + green1 - green1 * green2 / 255.;
869 b = blue2 + blue1 - blue1 * blue2 / 255.;
872 r = qMin((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2);
873 g = qMin((1 - alpha1) * green2 + green1, (1 - alpha2) * green1 + green2);
874 b = qMin((1 - alpha1) * blue2 + blue1, (1 - alpha2) * blue1 + blue2);
877 r = qMax((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2);
878 g = qMax((1 - alpha1) * green2 + green1, (1 - alpha2) * green1 + green2);
879 b = qMax((1 - alpha1) * blue2 + blue1, (1 - alpha2) * blue1 + blue2);
882 a = qBound(0., a, 255.);
883 resultLine[i] = qRgba(qBound(0., r, a),
889 clipToTransformedBounds(&result, p, clipRect);