Revision control

++ // Copyright (C) 2016 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 "qsgsoftwareinternalimagenode_p.h"
++
++ #include "qsgsoftwarepixmaptexture_p.h"
++ #include "qsgsoftwarelayer_p.h"
++ #include <QPainter>
++ #include <qmath.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QSGSoftwareHelpers {
++ // Helper from widgets/styles/qdrawutil.cpp
++
++ static inline QMargins normalizedMargins(const QMargins &m)
++ {
++ return QMargins(qMax(m.left(), 0), qMax(m.top(), 0), qMax(m.right(), 0), qMax(m.bottom(), 0));
++ }
++
++ void qDrawBorderPixmap(QPainter *painter, const QRect &targetRect, const QMargins &targetMarginsIn,
++ const QPixmap &pixmap, const QRect &sourceRect, const QMargins &sourceMarginsIn,
++ const QTileRules &rules, QDrawBorderPixmap::DrawingHints hints)
++ {
++ QPainter::PixmapFragment d;
++ d.opacity = 1.0;
++ d.rotation = 0.0;
++
++ QPixmapFragmentsArray opaqueData;
++ QPixmapFragmentsArray translucentData;
++
++ QMargins sourceMargins = normalizedMargins(sourceMarginsIn);
++ QMargins targetMargins = normalizedMargins(targetMarginsIn);
++
++ const qreal sourceDpr = pixmap.devicePixelRatio();
++ sourceMargins *= sourceDpr;
++
++ // source center
++ const int sourceCenterTop = sourceRect.top() + sourceMargins.top();
++ const int sourceCenterLeft = sourceRect.left() + sourceMargins.left();
++ const int sourceCenterBottom = sourceRect.bottom() - sourceMargins.bottom() + 1;
++ const int sourceCenterRight = sourceRect.right() - sourceMargins.right() + 1;
++ const int sourceCenterWidth = sourceCenterRight - sourceCenterLeft;
++ const int sourceCenterHeight = sourceCenterBottom - sourceCenterTop;
++ // target center
++ const int targetCenterTop = targetRect.top() + targetMargins.top();
++ const int targetCenterLeft = targetRect.left() + targetMargins.left();
++ const int targetCenterBottom = targetRect.bottom() - targetMargins.bottom() + 1;
++ const int targetCenterRight = targetRect.right() - targetMargins.right() + 1;
++ const int targetCenterWidth = targetCenterRight - targetCenterLeft;
++ const int targetCenterHeight = targetCenterBottom - targetCenterTop;
++
++ QVarLengthArray<qreal, 16> xTarget; // x-coordinates of target rectangles
++ QVarLengthArray<qreal, 16> yTarget; // y-coordinates of target rectangles
++
++ int columns = 3;
++ int rows = 3;
++ if (rules.horizontal != Qt::StretchTile && sourceCenterWidth != 0)
++ columns = qMax(3, 2 + qCeil((targetCenterWidth * sourceDpr) / qreal(sourceCenterWidth)));
++ if (rules.vertical != Qt::StretchTile && sourceCenterHeight != 0)
++ rows = qMax(3, 2 + qCeil((targetCenterHeight * sourceDpr) / qreal(sourceCenterHeight)));
++
++ xTarget.resize(columns + 1);
++ yTarget.resize(rows + 1);
++
++ xTarget[0] = targetRect.left();
++ xTarget[1] = targetCenterLeft;
++ xTarget[columns - 1] = targetCenterRight;
++ xTarget[columns] = targetRect.left() + targetRect.width();
++
++ yTarget[0] = targetRect.top();
++ yTarget[1] = targetCenterTop;
++ yTarget[rows - 1] = targetCenterBottom;
++ yTarget[rows] = targetRect.top() + targetRect.height();
++
++ qreal dx = targetCenterWidth;
++ qreal dy = targetCenterHeight;
++
++ switch (rules.horizontal) {
++ case Qt::StretchTile:
++ dx = targetCenterWidth;
++ break;
++ case Qt::RepeatTile:
++ dx = sourceCenterWidth / sourceDpr;
++ break;
++ case Qt::RoundTile:
++ dx = targetCenterWidth / qreal(columns - 2);
++ break;
++ }
++
++ for (int i = 2; i < columns - 1; ++i)
++ xTarget[i] = xTarget[i - 1] + dx;
++
++ switch (rules.vertical) {
++ case Qt::StretchTile:
++ dy = targetCenterHeight;
++ break;
++ case Qt::RepeatTile:
++ dy = sourceCenterHeight / sourceDpr;
++ break;
++ case Qt::RoundTile:
++ dy = targetCenterHeight / qreal(rows - 2);
++ break;
++ }
++
++ for (int i = 2; i < rows - 1; ++i)
++ yTarget[i] = yTarget[i - 1] + dy;
++
++ // corners
++ if (targetMargins.top() > 0 && targetMargins.left() > 0 && sourceMargins.top() > 0 && sourceMargins.left() > 0) { // top left
++ d.x = (0.5 * (xTarget[1] + xTarget[0]));
++ d.y = (0.5 * (yTarget[1] + yTarget[0]));
++ d.sourceLeft = sourceRect.left();
++ d.sourceTop = sourceRect.top();
++ d.width = sourceMargins.left();
++ d.height = sourceMargins.top();
++ d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
++ d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
++ if (hints & QDrawBorderPixmap::OpaqueTopLeft)
++ opaqueData.append(d);
++ else
++ translucentData.append(d);
++ }
++ if (targetMargins.top() > 0 && targetMargins.right() > 0 && sourceMargins.top() > 0 && sourceMargins.right() > 0) { // top right
++ d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
++ d.y = (0.5 * (yTarget[1] + yTarget[0]));
++ d.sourceLeft = sourceCenterRight;
++ d.sourceTop = sourceRect.top();
++ d.width = sourceMargins.right();
++ d.height = sourceMargins.top();
++ d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
++ d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
++ if (hints & QDrawBorderPixmap::OpaqueTopRight)
++ opaqueData.append(d);
++ else
++ translucentData.append(d);
++ }
++ if (targetMargins.bottom() > 0 && targetMargins.left() > 0 && sourceMargins.bottom() > 0 && sourceMargins.left() > 0) { // bottom left
++ d.x = (0.5 * (xTarget[1] + xTarget[0]));
++ d.y =(0.5 * (yTarget[rows] + yTarget[rows - 1]));
++ d.sourceLeft = sourceRect.left();
++ d.sourceTop = sourceCenterBottom;
++ d.width = sourceMargins.left();
++ d.height = sourceMargins.bottom();
++ d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
++ d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
++ if (hints & QDrawBorderPixmap::OpaqueBottomLeft)
++ opaqueData.append(d);
++ else
++ translucentData.append(d);
++ }
++ if (targetMargins.bottom() > 0 && targetMargins.right() > 0 && sourceMargins.bottom() > 0 && sourceMargins.right() > 0) { // bottom right
++ d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
++ d.y = (0.5 * (yTarget[rows] + yTarget[rows - 1]));
++ d.sourceLeft = sourceCenterRight;
++ d.sourceTop = sourceCenterBottom;
++ d.width = sourceMargins.right();
++ d.height = sourceMargins.bottom();
++ d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
++ d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
++ if (hints & QDrawBorderPixmap::OpaqueBottomRight)
++ opaqueData.append(d);
++ else
++ translucentData.append(d);
++ }
++
++ // horizontal edges
++ if (targetCenterWidth > 0 && sourceCenterWidth > 0) {
++ if (targetMargins.top() > 0 && sourceMargins.top() > 0) { // top
++ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueTop ? opaqueData : translucentData;
++ d.sourceLeft = sourceCenterLeft;
++ d.sourceTop = sourceRect.top();
++ d.width = sourceCenterWidth;
++ d.height = sourceMargins.top();
++ d.y = (0.5 * (yTarget[1] + yTarget[0]));
++ d.scaleX = dx / d.width;
++ d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
++ for (int i = 1; i < columns - 1; ++i) {
++ d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
++ data.append(d);
++ }
++ if (rules.horizontal == Qt::RepeatTile)
++ data[data.size() - 1].width = ((xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX);
++ }
++ if (targetMargins.bottom() > 0 && sourceMargins.bottom() > 0) { // bottom
++ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueBottom ? opaqueData : translucentData;
++ d.sourceLeft = sourceCenterLeft;
++ d.sourceTop = sourceCenterBottom;
++ d.width = sourceCenterWidth;
++ d.height = sourceMargins.bottom();
++ d.y = (0.5 * (yTarget[rows] + yTarget[rows - 1]));
++ d.scaleX = dx / d.width;
++ d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
++ for (int i = 1; i < columns - 1; ++i) {
++ d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
++ data.append(d);
++ }
++ if (rules.horizontal == Qt::RepeatTile)
++ data[data.size() - 1].width = ((xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX);
++ }
++ }
++
++ // vertical edges
++ if (targetCenterHeight > 0 && sourceCenterHeight > 0) {
++ if (targetMargins.left() > 0 && sourceMargins.left() > 0) { // left
++ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueLeft ? opaqueData : translucentData;
++ d.sourceLeft = sourceRect.left();
++ d.sourceTop = sourceCenterTop;
++ d.width = sourceMargins.left();
++ d.height = sourceCenterHeight;
++ d.x = (0.5 * (xTarget[1] + xTarget[0]));
++ d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
++ d.scaleY = dy / d.height;
++ for (int i = 1; i < rows - 1; ++i) {
++ d.y = (0.5 * (yTarget[i + 1] + yTarget[i]));
++ data.append(d);
++ }
++ if (rules.vertical == Qt::RepeatTile)
++ data[data.size() - 1].height = ((yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY);
++ }
++ if (targetMargins.right() > 0 && sourceMargins.right() > 0) { // right
++ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueRight ? opaqueData : translucentData;
++ d.sourceLeft = sourceCenterRight;
++ d.sourceTop = sourceCenterTop;
++ d.width = sourceMargins.right();
++ d.height = sourceCenterHeight;
++ d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
++ d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
++ d.scaleY = dy / d.height;
++ for (int i = 1; i < rows - 1; ++i) {
++ d.y = (0.5 * (yTarget[i + 1] + yTarget[i]));
++ data.append(d);
++ }
++ if (rules.vertical == Qt::RepeatTile)
++ data[data.size() - 1].height = ((yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY);
++ }
++ }
++
++ // center
++ if (targetCenterWidth > 0 && targetCenterHeight > 0 && sourceCenterWidth > 0 && sourceCenterHeight > 0) {
++ QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueCenter ? opaqueData : translucentData;
++ d.sourceLeft = sourceCenterLeft;
++ d.sourceTop = sourceCenterTop;
++ d.width = sourceCenterWidth;
++ d.height = sourceCenterHeight;
++ d.scaleX = dx / d.width;
++ d.scaleY = dy / d.height;
++
++ qreal repeatWidth = (xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX;
++ qreal repeatHeight = (yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY;
++
++ for (int j = 1; j < rows - 1; ++j) {
++ d.y = (0.5 * (yTarget[j + 1] + yTarget[j]));
++ for (int i = 1; i < columns - 1; ++i) {
++ d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
++ data.append(d);
++ }
++ if (rules.horizontal == Qt::RepeatTile)
++ data[data.size() - 1].width = repeatWidth;
++ }
++ if (rules.vertical == Qt::RepeatTile) {
++ for (int i = 1; i < columns - 1; ++i)
++ data[data.size() - i].height = repeatHeight;
++ }
++ }
++
++ if (opaqueData.size())
++ painter->drawPixmapFragments(opaqueData.data(), opaqueData.size(), pixmap, QPainter::OpaqueHint);
++ if (translucentData.size())
++ painter->drawPixmapFragments(translucentData.data(), translucentData.size(), pixmap);
++ }
++
++ } // QSGSoftwareHelpers namespace
++
++ QSGSoftwareInternalImageNode::QSGSoftwareInternalImageNode()
++ : m_innerSourceRect(0, 0, 1, 1)
++ , m_subSourceRect(0, 0, 1, 1)
++ , m_texture(nullptr)
++ , m_mirrorHorizontally(false)
++ , m_mirrorVertically(false)
++ , m_textureIsLayer(false)
++ , m_smooth(true)
++ , m_tileHorizontal(false)
++ , m_tileVertical(false)
++ , m_cachedMirroredPixmapIsDirty(false)
++ {
++ setMaterial((QSGMaterial*)1);
++ setGeometry((QSGGeometry*)1);
++ }
++
++
++ void QSGSoftwareInternalImageNode::setTargetRect(const QRectF &rect)
++ {
++ if (rect == m_targetRect)
++ return;
++ m_targetRect = rect;
++ markDirty(DirtyGeometry);
++ }
++
++ void QSGSoftwareInternalImageNode::setInnerTargetRect(const QRectF &rect)
++ {
++ if (rect == m_innerTargetRect)
++ return;
++ m_innerTargetRect = rect;
++ markDirty(DirtyGeometry);
++ }
++
++ void QSGSoftwareInternalImageNode::setInnerSourceRect(const QRectF &rect)
++ {
++ if (rect == m_innerSourceRect)
++ return;
++ m_innerSourceRect = rect;
++ markDirty(DirtyGeometry);
++ }
++
++ void QSGSoftwareInternalImageNode::setSubSourceRect(const QRectF &rect)
++ {
++ if (rect == m_subSourceRect)
++ return;
++ m_subSourceRect = rect;
++ markDirty(DirtyGeometry);
++ }
++
++ void QSGSoftwareInternalImageNode::setTexture(QSGTexture *texture)
++ {
++ m_texture = texture;
++ m_cachedMirroredPixmapIsDirty = true;
++ m_textureIsLayer = static_cast<bool>(qobject_cast<QSGSoftwareLayer*>(texture));
++ markDirty(DirtyMaterial);
++ }
++
++ void QSGSoftwareInternalImageNode::setMirror(bool mirrorHorizontally, bool mirrorVertically)
++ {
++ if (mirrorHorizontally == m_mirrorHorizontally && mirrorVertically == m_mirrorVertically)
++ return;
++ m_mirrorHorizontally = mirrorHorizontally;
++ m_mirrorVertically = mirrorVertically;
++ m_cachedMirroredPixmapIsDirty = true;
++ markDirty(DirtyMaterial);
++ }
++
++ void QSGSoftwareInternalImageNode::setMipmapFiltering(QSGTexture::Filtering /*filtering*/)
++ {
++ }
++
++ void QSGSoftwareInternalImageNode::setFiltering(QSGTexture::Filtering filtering)
++ {
++ bool smooth = (filtering == QSGTexture::Linear);
++ if (smooth == m_smooth)
++ return;
++
++ m_smooth = smooth;
++ markDirty(DirtyMaterial);
++ }
++
++ void QSGSoftwareInternalImageNode::setHorizontalWrapMode(QSGTexture::WrapMode wrapMode)
++ {
++ bool tileHorizontal = (wrapMode == QSGTexture::Repeat);
++ if (tileHorizontal == m_tileHorizontal)
++ return;
++
++ m_tileHorizontal = tileHorizontal;
++ markDirty(DirtyMaterial);
++ }
++
++ void QSGSoftwareInternalImageNode::setVerticalWrapMode(QSGTexture::WrapMode wrapMode)
++ {
++ bool tileVertical = (wrapMode == QSGTexture::Repeat);
++ if (tileVertical == m_tileVertical)
++ return;
++
++ m_tileVertical = (wrapMode == QSGTexture::Repeat);
++ markDirty(DirtyMaterial);
++ }
++
++ void QSGSoftwareInternalImageNode::update()
++ {
++ updateCachedMirroredPixmap();
++ }
++
++ void QSGSoftwareInternalImageNode::preprocess()
++ {
++ bool doDirty = false;
++ QSGLayer *t = qobject_cast<QSGLayer *>(m_texture);
++ if (t) {
++ doDirty = t->updateTexture();
++ markDirty(DirtyGeometry);
++ }
++ if (doDirty)
++ markDirty(DirtyMaterial);
++ m_cachedMirroredPixmapIsDirty = doDirty;
++ }
++
++ static Qt::TileRule getTileRule(qreal factor)
++ {
++ int ifactor = qRound(factor);
++ if (qFuzzyCompare(factor, ifactor )) {
++ if (ifactor == 1 || ifactor == 0)
++ return Qt::StretchTile;
++ return Qt::RoundTile;
++ }
++ return Qt::RepeatTile;
++ }
++
++
++ void QSGSoftwareInternalImageNode::paint(QPainter *painter)
++ {
++ painter->setRenderHint(QPainter::SmoothPixmapTransform, m_smooth);
++ // Disable antialiased clipping. It causes transformed tiles to have gaps.
++ painter->setRenderHint(QPainter::Antialiasing, false);
++
++ updateCachedMirroredPixmap();
++ const QPixmap &pm = m_mirrorHorizontally || m_mirrorVertically || m_textureIsLayer ? m_cachedMirroredPixmap : pixmap();
++
++ if (m_innerTargetRect != m_targetRect) {
++ // border image
++ QMargins margins(m_innerTargetRect.left() - m_targetRect.left(), m_innerTargetRect.top() - m_targetRect.top(),
++ m_targetRect.right() - m_innerTargetRect.right(), m_targetRect.bottom() - m_innerTargetRect.bottom());
++ QSGSoftwareHelpers::QTileRules tilerules(getTileRule(m_subSourceRect.width()), getTileRule(m_subSourceRect.height()));
++ QSGSoftwareHelpers::qDrawBorderPixmap(painter, m_targetRect.toRect(), margins, pm, QRect(0, 0, pm.width(), pm.height()),
++ margins, tilerules, QSGSoftwareHelpers::QDrawBorderPixmap::DrawingHints{});
++ return;
++ }
++
++ if (m_tileHorizontal || m_tileVertical) {
++ painter->save();
++ qreal sx = m_targetRect.width()/(m_subSourceRect.width()*pm.width());
++ qreal sy = m_targetRect.height()/(m_subSourceRect.height()*pm.height());
++ painter->setTransform(QTransform::fromScale(sx, sy), true);
++ painter->drawTiledPixmap(QRectF(m_targetRect.x()/sx, m_targetRect.y()/sy, m_targetRect.width()/sx, m_targetRect.height()/sy),
++ pm,
++ QPointF(m_subSourceRect.left()*pm.width(), m_subSourceRect.top()*pm.height()));
++ painter->restore();
++ } else {
++ QRectF sr(m_subSourceRect.left()*pm.width(), m_subSourceRect.top()*pm.height(),
++ m_subSourceRect.width()*pm.width(), m_subSourceRect.height()*pm.height());
++ painter->drawPixmap(m_targetRect, pm, sr);
++ }
++ }
++
++
++ QRectF QSGSoftwareInternalImageNode::rect() const
++ {
++ return m_targetRect;
++ }
++
++ const QPixmap &QSGSoftwareInternalImageNode::pixmap() const
++ {
++ if (QSGSoftwarePixmapTexture *pt = qobject_cast<QSGSoftwarePixmapTexture*>(m_texture))
++ return pt->pixmap();
++ if (QSGSoftwareLayer *layer = qobject_cast<QSGSoftwareLayer*>(m_texture))
++ return layer->pixmap();
++ Q_ASSERT(m_texture == nullptr);
++ static const QPixmap nullPixmap;
++ return nullPixmap;
++ }
++
++ void QSGSoftwareInternalImageNode::updateCachedMirroredPixmap()
++ {
++ if (m_cachedMirroredPixmapIsDirty) {
++ if (m_mirrorHorizontally || m_mirrorVertically || m_textureIsLayer) {
++ QTransform transform(
++ (m_mirrorHorizontally ? -1 : 1), 0,
++ 0 , (m_textureIsLayer ? -1 : 1) * (m_mirrorVertically ? -1 : 1),
++ 0 , 0
++ );
++ m_cachedMirroredPixmap = pixmap().transformed(transform);
++ } else {
++ //Cleanup cached pixmap if necessary
++ if (!m_cachedMirroredPixmap.isNull())
++ m_cachedMirroredPixmap = QPixmap();
++ }
++ m_cachedMirroredPixmapIsDirty = false;
++ }
++ }
++
++ QT_END_NAMESPACE