101QSvgFeColorMatrix::QSvgFeColorMatrix(QSvgNode *parent,
const QString &input,
const QString &result,
102 const QSvgRectF &rect, ColorShiftType type,
103 const Matrix &matrix)
104 : QSvgFeFilterPrimitive(parent, input, result, rect)
108 constexpr qreal v1[] = {0.213, 0.213, 0.213,
110 0.072, 0.072, 0.072};
111 static const Matrix3x3 m1(v1);
113 constexpr qreal v2[] = { 0.787, -0.213, -0.213,
114 -0.715, 0.285, -0.715,
115 -0.072, -0.072, 0.928};
116 static const Matrix3x3 m2(v2);
118 constexpr qreal v3[] = {-0.213, 0.143, -0.787,
119 -0.715, 0.140, 0.715,
120 0.928, -0.283, 0.072};
121 static const Matrix3x3 m3(v3);
123 if (type == ColorShiftType::Saturate) {
124 qreal s = qBound(0., m_matrix.data()[0], 1.);
128 const Matrix3x3 m = m1 + m2 * s;
130 for (
int j = 0; j < 3; ++j)
131 for (
int i = 0; i < 3; ++i)
132 m_matrix.data()[i+j*5] = m.data()[i+j*3];
133 m_matrix.data()[3+3*5] = 1;
135 }
else if (type == ColorShiftType::HueRotate){
136 qreal angle = m_matrix.data()[0]/180.*M_PI;
137 qreal s = sin(angle);
138 qreal c = cos(angle);
142 const Matrix3x3 m = m1 + m2 * c + m3 * s;
144 for (
int j = 0; j < 3; ++j)
145 for (
int i = 0; i < 3; ++i)
146 m_matrix.data()[i+j*5] = m.data()[i+j*3];
147 m_matrix.data()[3+3*5] = 1;
148 }
else if (type == ColorShiftType::LuminanceToAlpha){
151 m_matrix.data()[0+3*5] = 0.2125;
152 m_matrix.data()[1+3*5] = 0.7154;
153 m_matrix.data()[2+3*5] = 0.0721;
162QImage QSvgFeColorMatrix::apply(
const QMap<QString, QImage> &sources, QPainter *p,
163 const QRectF &itemBounds,
const QRectF &filterBounds,
164 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
166 if (!sources.contains(m_input))
168 QImage source = sources[m_input];
170 QRect clipRectGlob = globalSubRegion(p, itemBounds, filterBounds, primitiveUnits, filterUnits).toRect();
171 if (clipRectGlob.isEmpty())
175 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
176 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
179 result.setOffset(clipRectGlob.topLeft());
180 result.fill(Qt::transparent);
182 Q_ASSERT(source.depth() == 32);
184 const Matrix transposedMatrix = m_matrix.transposed();
186 for (
int i = 0; i < result.height(); i++) {
187 int sourceI = i - source.offset().y() + result.offset().y();
189 if (sourceI < 0 || sourceI >= source.height())
192 QRgb *sourceLine =
reinterpret_cast<QRgb *>(source.scanLine(sourceI));
193 QRgb *resultLine =
reinterpret_cast<QRgb *>(result.scanLine(i));
195 for (
int j = 0; j < result.width(); j++) {
196 int sourceJ = j - source.offset().x() + result.offset().x();
198 if (sourceJ < 0 || sourceJ >= source.width())
201 QRgb sourceColor = qUnpremultiply(sourceLine[sourceJ]);
202 const qreal values[] = {qreal(qRed(sourceColor)),
203 qreal(qGreen(sourceColor)),
204 qreal(qBlue(sourceColor)),
205 qreal(qAlpha(sourceColor)),
207 const QGenericMatrix<1, 5, qreal> sourceVector(values);
208 const QGenericMatrix<1, 5, qreal> resultVector = transposedMatrix * sourceVector;
210 QRgb rgba = qRgba(qBound(0,
int(resultVector(0, 0)), 255),
211 qBound(0,
int(resultVector(1, 0)), 255),
212 qBound(0,
int(resultVector(2, 0)), 255),
213 qBound(0,
int(resultVector(3, 0)), 255));
214 resultLine[j] = qPremultiply(rgba);
218 clipToTransformedBounds(&result, p, localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
301QImage QSvgFeGaussianBlur::apply(
const QMap<QString, QImage> &sources, QPainter *p,
302 const QRectF &itemBounds,
const QRectF &filterBounds,
303 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
305 if (!sources.contains(m_input))
307 QImage source = sources[m_input];
308 Q_ASSERT(source.depth() == 32);
310 if (m_stdDeviationX == 0 && m_stdDeviationY == 0)
313 const qreal scaleX = qHypot(p->transform().m11(), p->transform().m21());
314 const qreal scaleY = qHypot(p->transform().m12(), p->transform().m22());
316 qreal sigma_x = scaleX * m_stdDeviationX;
317 qreal sigma_y = scaleY * m_stdDeviationY;
318 if (primitiveUnits == QtSvg::UnitTypes::objectBoundingBox) {
319 sigma_x *= itemBounds.width();
320 sigma_y *= itemBounds.height();
323 constexpr double sd = 3. * M_SQRT1_2 / M_2_SQRTPI;
324 const int dx = floor(sigma_x * sd + 0.5);
325 const int dy = floor(sigma_y * sd + 0.5);
327 const QTransform scaleXr = QTransform::fromScale(scaleX, scaleY);
328 const QTransform restXr = scaleXr.inverted() * p->transform();
330 QRect clipRectGlob = scaleXr.mapRect(localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits)).toRect();
331 if (clipRectGlob.isEmpty())
335 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &tempSource)) {
336 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
339 tempSource.setOffset(clipRectGlob.topLeft());
340 tempSource.fill(Qt::transparent);
341 QPainter copyPainter(&tempSource);
342 copyPainter.translate(-tempSource.offset());
343 copyPainter.setTransform(restXr.inverted(),
true);
344 copyPainter.drawImage(source.offset(), source);
347 QVarLengthArray<ColorValues, 32 * 32> buffer(tempSource.width() * tempSource.height());
349 const int sourceHeight = tempSource.height();
350 const int sourceWidth = tempSource.width();
351 QRgb *rawImage =
reinterpret_cast<QRgb *>(tempSource.bits());
356 for (
int m = 0; m < 3; m++) {
363 auto adjustD = [](
int d,
int iteration) {
365 std::pair<
int,
int> result;
367 result = {d / 2 + 1, d / 2};
368 else if (iteration == 0)
369 result = {d / 2 + 1, d / 2 - 1};
370 else if (iteration == 1)
371 result = {d / 2, d / 2};
373 result = {d / 2 + 1, d / 2};
374 Q_ASSERT(result.first + result.second > 0);
378 const auto [dxleft, dxright] = adjustD(dx, m);
379 const auto [dytop, dybottom] = adjustD(dy, m);
383 for (
int j = 0; j < sourceHeight; j++) {
384 for (
int i = 0; i < sourceWidth; i++) {
385 ColorValues colorValues(rawImage[i + j * sourceWidth]);
387 colorValues += buffer[(i - 1) + j * sourceWidth];
389 colorValues += buffer[i + (j - 1) * sourceWidth];
391 colorValues -= buffer[(i - 1) + (j - 1) * sourceWidth];
392 buffer[i + j * sourceWidth] = colorValues;
396 for (
int j = 0; j < sourceHeight; j++) {
397 const int j1 = qMax(0, j - dytop);
398 const int j2 = qMin(sourceHeight - 1, j + dybottom);
399 for (
int i = 0; i < sourceWidth; i++) {
400 const int i1 = qMax(0, i - dxleft);
401 const int i2 = qMin(sourceWidth - 1, i + dxright);
402 ColorValues colorValues = buffer[i2 + j2 * sourceWidth]
403 - buffer[i1 + j2 * sourceWidth]
404 - buffer[i2 + j1 * sourceWidth]
405 + buffer[i1 + j1 * sourceWidth];
406 colorValues /= uint64_t(dxleft + dxright) * uint64_t(dytop + dybottom);
407 rawImage[i + j * sourceWidth] = colorValues.toRgb();
412 QRectF trueClipRectGlob = globalSubRegion(p, itemBounds, filterBounds, primitiveUnits, filterUnits);
415 if (!QImageIOHandler::allocateImage(trueClipRectGlob.toRect().size(), QImage::Format_ARGB32_Premultiplied, &result)) {
416 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
419 result.setOffset(trueClipRectGlob.toRect().topLeft());
420 result.fill(Qt::transparent);
421 QPainter transformPainter(&result);
422 transformPainter.setRenderHint(QPainter::Antialiasing,
true);
424 transformPainter.translate(-result.offset());
425 transformPainter.setTransform(restXr,
true);
426 transformPainter.drawImage(clipRectGlob.topLeft(), tempSource);
427 transformPainter.end();
429 clipToTransformedBounds(&result, p, localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits));
447QImage QSvgFeOffset::apply(
const QMap<QString, QImage> &sources, QPainter *p,
448 const QRectF &itemBounds,
const QRectF &filterBounds,
449 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
451 if (!sources.contains(m_input))
454 const QImage &source = sources[m_input];
456 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
457 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
459 QPoint offset(m_dx, m_dy);
460 if (primitiveUnits == QtSvg::UnitTypes::objectBoundingBox) {
461 offset = QPoint(m_dx * itemBounds.width(),
462 m_dy * itemBounds.height());
464 offset = p->transform().map(offset) - p->transform().map(QPoint(0, 0));
466 if (clipRectGlob.isEmpty())
470 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
471 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
474 result.setOffset(clipRectGlob.topLeft());
475 result.fill(Qt::transparent);
477 QPainter copyPainter(&result);
478 copyPainter.drawImage(source.offset()
479 - result.offset() + offset, source);
482 clipToTransformedBounds(&result, p, clipRect);
499QImage QSvgFeMerge::apply(
const QMap<QString, QImage> &sources, QPainter *p,
500 const QRectF &itemBounds,
const QRectF &filterBounds,
501 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
503 QList<QImage> mergeNodeResults;
504 for (
const auto &node : renderers()) {
505 if (node->type() == QSvgNode::FeMergenode) {
506 const QSvgFeMergeNode &filter =
static_cast<
const QSvgFeMergeNode&>(*node);
507 mergeNodeResults.append(filter.apply(sources, p, itemBounds, filterBounds, primitiveUnits, filterUnits));
511 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
512 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
513 if (clipRectGlob.isEmpty())
517 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
518 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
521 result.setOffset(clipRectGlob.topLeft());
522 result.fill(Qt::transparent);
524 QPainter proxyPainter(&result);
525 for (
const QImage &i : mergeNodeResults) {
526 proxyPainter.drawImage(QRect(i.offset() - result.offset(), i.size()), i);
530 clipToTransformedBounds(&result, p, clipRect);
580QImage QSvgFeComposite::apply(
const QMap<QString, QImage> &sources, QPainter *p,
581 const QRectF &itemBounds,
const QRectF &filterBounds,
582 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
584 if (!sources.contains(m_input))
586 if (!sources.contains(m_input2))
588 QImage source1 = sources[m_input];
589 QImage source2 = sources[m_input2];
590 Q_ASSERT(source1.depth() == 32);
591 Q_ASSERT(source2.depth() == 32);
593 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
594 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
596 if (clipRectGlob.isEmpty())
600 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
601 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
604 result.setOffset(clipRectGlob.topLeft());
605 result.fill(Qt::transparent);
607 if (m_operator == Operator::Arithmetic) {
608 const qreal k1 = m_k.x();
609 const qreal k2 = m_k.y();
610 const qreal k3 = m_k.z();
611 const qreal k4 = m_k.w();
613 for (
int j = 0; j < result.height(); j++) {
614 int jj1 = j - source1.offset().y() + result.offset().y();
615 int jj2 = j - source2.offset().y() + result.offset().y();
617 QRgb *resultLine =
reinterpret_cast<QRgb *>(result.scanLine(j));
618 QRgb *source1Line =
nullptr;
619 QRgb *source2Line =
nullptr;
621 if (jj1 >= 0 && jj1 < source1.size().height())
622 source1Line =
reinterpret_cast<QRgb *>(source1.scanLine(jj1));
623 if (jj2 >= 0 && jj2 < source2.size().height())
624 source2Line =
reinterpret_cast<QRgb *>(source2.scanLine(jj2));
626 for (
int i = 0; i < result.width(); i++) {
627 int ii1 = i - source1.offset().x() + result.offset().x();
628 int ii2 = i - source2.offset().x() + result.offset().x();
630 QVector4D s1 = QVector4D(0, 0, 0, 0);
631 QVector4D s2 = QVector4D(0, 0, 0, 0);
633 if (ii1 >= 0 && ii1 < source1.size().width() && source1Line) {
634 QRgb pixel1 = source1Line[ii1];
635 s1 = QVector4D(qRed(pixel1),
641 if (ii2 >= 0 && ii2 < source2.size().width() && source2Line) {
642 QRgb pixel2 = source2Line[ii2];
643 s2 = QVector4D(qRed(pixel2),
649 int r = k1 * s1.x() * s2.x() / 255. + k2 * s1.x() + k3 * s2.x() + k4 * 255.;
650 int g = k1 * s1.y() * s2.y() / 255. + k2 * s1.y() + k3 * s2.y() + k4 * 255.;
651 int b = k1 * s1.z() * s2.z() / 255. + k2 * s1.z() + k3 * s2.z() + k4 * 255.;
652 int a = k1 * s1.w() * s2.w() / 255. + k2 * s1.w() + k3 * s2.w() + k4 * 255.;
654 a = qBound(0, a, 255);
655 resultLine[i] = qRgba(qBound(0, r, a),
662 QPainter proxyPainter(&result);
663 proxyPainter.drawImage(QRect(source1.offset() - result.offset(), source1.size()), source1);
665 switch (m_operator) {
667 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
670 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
673 proxyPainter.setCompositionMode(QPainter::CompositionMode_Xor);
675 case Operator::Lighter:
676 proxyPainter.setCompositionMode(QPainter::CompositionMode_Lighten);
679 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationAtop);
682 proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
684 case Operator::Arithmetic:
688 proxyPainter.drawImage(QRect(source2.offset()-result.offset(), source2.size()), source2);
692 clipToTransformedBounds(&result, p, clipRect);
751QImage QSvgFeBlend::apply(
const QMap<QString, QImage> &sources, QPainter *p,
752 const QRectF &itemBounds,
const QRectF &filterBounds,
753 QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits)
const
755 if (!sources.contains(m_input))
757 if (!sources.contains(m_input2))
759 QImage source1 = sources[m_input];
760 QImage source2 = sources[m_input2];
761 Q_ASSERT(source1.depth() == 32);
762 Q_ASSERT(source2.depth() == 32);
764 QRectF clipRect = localSubRegion(itemBounds, filterBounds, primitiveUnits, filterUnits);
765 QRect clipRectGlob = p->transform().mapRect(clipRect).toRect();
768 if (!QImageIOHandler::allocateImage(clipRectGlob.size(), QImage::Format_ARGB32_Premultiplied, &result)) {
769 qCWarning(lcSvgDraw) <<
"The requested filter buffer is too big, ignoring";
772 result.setOffset(clipRectGlob.topLeft());
773 result.fill(Qt::transparent);
775 for (
int j = 0; j < result.height(); j++) {
776 int jj1 = j - source1.offset().y() + result.offset().y();
777 int jj2 = j - source2.offset().y() + result.offset().y();
779 QRgb *resultLine =
reinterpret_cast<QRgb *>(result.scanLine(j));
780 QRgb *source1Line =
nullptr;
781 QRgb *source2Line =
nullptr;
783 if (jj1 >= 0 && jj1 < source1.size().height())
784 source1Line =
reinterpret_cast<QRgb *>(source1.scanLine(jj1));
785 if (jj2 >= 0 && jj2 < source2.size().height())
786 source2Line =
reinterpret_cast<QRgb *>(source2.scanLine(jj2));
788 for (
int i = 0; i < result.width(); i++) {
789 int ii1 = i - source1.offset().x() + result.offset().x();
790 int ii2 = i - source2.offset().x() + result.offset().x();
792 QRgb pixel1 = (ii1 >= 0 && ii1 < source1.size().width() && source1Line) ?
793 source1Line[ii1] : qRgba(0, 0, 0, 0);
794 QRgb pixel2 = (ii2 >= 0 && ii2 < source2.size().width() && source2Line) ?
795 source2Line[ii2] : qRgba(0, 0, 0, 0);
797 qreal r = 0, g = 0, b = 0;
798 qreal red1 = qRed(pixel1);
799 qreal red2 = qRed(pixel2);
800 qreal green1 = qGreen(pixel1);
801 qreal green2 = qGreen(pixel2);
802 qreal blue1 = qBlue(pixel1);
803 qreal blue2 = qBlue(pixel2);
804 qreal alpha1 = qAlpha(pixel1) / 255.;
805 qreal alpha2 = qAlpha(pixel2) / 255.;
806 qreal a = 255 - (1 - alpha1) * (1 - alpha2) * 255.;
810 r = (1 - alpha1) * red2 + red1;
811 g = (1 - alpha1) * green2 + green1;
812 b = (1 - alpha1) * blue2 + blue1;
815 r = (1 - alpha1) * red2 + (1 - alpha2) * red1 + red1 * red2;
816 g = (1 - alpha1) * green2 + (1 - alpha2) * green1 + green1 * green2;
817 b = (1 - alpha1) * blue2 + (1 - alpha2) * blue1 + blue1 * blue2;
820 r = red2 + red1 - red1 * red2;
821 g = green2 + green1 - green1 * green2;
822 b = blue2 + blue1 - blue1 * blue2;
825 r = qMin((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2);
826 g = qMin((1 - alpha1) * green2 + green1, (1 - alpha2) * green1 + green2);
827 b = qMin((1 - alpha1) * blue2 + blue1, (1 - alpha2) * blue1 + blue2);
830 r = qMax((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2);
831 g = qMax((1 - alpha1) * green2 + green1, (1 - alpha2) * green1 + green2);
832 b = qMax((1 - alpha1) * blue2 + blue1, (1 - alpha2) * blue1 + blue2);
835 a = qBound(0., a, 255.);
836 resultLine[i] = qRgba(qBound(0., r, a),
842 clipToTransformedBounds(&result, p, clipRect);