diff options
Diffstat (limited to 'src/gui/painting/qtransform.cpp')
-rw-r--r-- | src/gui/painting/qtransform.cpp | 353 |
1 files changed, 119 insertions, 234 deletions
diff --git a/src/gui/painting/qtransform.cpp b/src/gui/painting/qtransform.cpp index 8f2dc21a21..df57d2c190 100644 --- a/src/gui/painting/qtransform.cpp +++ b/src/gui/painting/qtransform.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 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 #include "qtransform.h" #include "qdatastream.h" @@ -45,7 +9,7 @@ #include "qpainterpath.h" #include "qpainterpath_p.h" #include "qvariant.h" -#include <qmath.h> +#include "qmath_p.h" #include <qnumeric.h> #include <private/qbezier_p.h> @@ -90,7 +54,7 @@ static void nanWarning(const char *func) if (t == TxProject) { \ qreal w = (m_matrix[0][2] * FX_ + m_matrix[1][2] * FY_ + m_matrix[2][2]); \ if (w < qreal(Q_NEAR_CLIP)) w = qreal(Q_NEAR_CLIP); \ - w = 1./w; \ + w = qreal(1.)/w; \ nx *= w; \ ny *= w; \ } \ @@ -213,6 +177,7 @@ static void nanWarning(const char *func) transformation is achieved by setting both the projection factors and the scaling factors. + \section2 Combining Transforms Here's the combined transformations example using basic matrix operations: @@ -223,6 +188,26 @@ static void nanWarning(const char *func) \snippet transform/main.cpp 2 \endtable + The combined transform first scales each operand, then rotates it, and + finally translates it, just as in the order in which the product of its + factors is written. This means the point to which the transforms are + applied is implicitly multiplied on the left with the transform + to its right. + + \section2 Relation to Matrix Notation + The matrix notation in QTransform is the transpose of a commonly-taught + convention which represents transforms and points as matrices and vectors. + That convention multiplies its matrix on the left and column vector to the + right. In other words, when several transforms are applied to a point, the + right-most matrix acts directly on the vector first. Then the next matrix + to the left acts on the result of the first operation - and so on. As a + result, that convention multiplies the matrices that make up a composite + transform in the reverse of the order in QTransform, as you can see in + \l {Combining Transforms}. Transposing the matrices, and combining them to + the right of a row vector that represents the point, lets the matrices of + transforms appear, in their product, in the order in which we think of the + transforms being applied to the point. + \sa QPainter, {Coordinate System}, {painting/affine}{Affine Transformations Example}, {Transformations Example} */ @@ -373,7 +358,7 @@ QTransform &QTransform::translate(qreal dx, qreal dy) if (dx == 0 && dy == 0) return *this; #ifndef QT_NO_DEBUG - if (qIsNaN(dx) | qIsNaN(dy)) { + if (qIsNaN(dx) || qIsNaN(dy)) { nanWarning("translate"); return *this; } @@ -416,7 +401,7 @@ QTransform &QTransform::translate(qreal dx, qreal dy) QTransform QTransform::fromTranslate(qreal dx, qreal dy) { #ifndef QT_NO_DEBUG - if (qIsNaN(dx) | qIsNaN(dy)) { + if (qIsNaN(dx) || qIsNaN(dy)) { nanWarning("fromTranslate"); return QTransform(); } @@ -441,7 +426,7 @@ QTransform & QTransform::scale(qreal sx, qreal sy) if (sx == 1 && sy == 1) return *this; #ifndef QT_NO_DEBUG - if (qIsNaN(sx) | qIsNaN(sy)) { + if (qIsNaN(sx) || qIsNaN(sy)) { nanWarning("scale"); return *this; } @@ -482,7 +467,7 @@ QTransform & QTransform::scale(qreal sx, qreal sy) QTransform QTransform::fromScale(qreal sx, qreal sy) { #ifndef QT_NO_DEBUG - if (qIsNaN(sx) | qIsNaN(sy)) { + if (qIsNaN(sx) || qIsNaN(sy)) { nanWarning("fromScale"); return QTransform(); } @@ -507,7 +492,7 @@ QTransform & QTransform::shear(qreal sh, qreal sv) if (sh == 0 && sv == 0) return *this; #ifndef QT_NO_DEBUG - if (qIsNaN(sh) | qIsNaN(sv)) { + if (qIsNaN(sh) || qIsNaN(sv)) { nanWarning("shear"); return *this; } @@ -548,29 +533,33 @@ QTransform & QTransform::shear(qreal sh, qreal sv) return *this; } -const qreal deg2rad = qreal(0.017453292519943295769); // pi/180 -const qreal inv_dist_to_plane = 1. / 1024.; - /*! - \fn QTransform &QTransform::rotate(qreal angle, Qt::Axis axis) + \since 6.5 - Rotates the coordinate system counterclockwise by the given \a angle - about the specified \a axis and returns a reference to the matrix. + Rotates the coordinate system counterclockwise by the given angle \a a + about the specified \a axis at distance \a distanceToPlane from the + screen and returns a reference to the matrix. +//! [transform-rotate-note] Note that if you apply a QTransform to a point defined in widget coordinates, the direction of the rotation will be clockwise because the y-axis points downwards. The angle is specified in degrees. +//! [transform-rotate-note] + + If \a distanceToPlane is zero, it will be ignored. This is suitable + for implementing orthographic projections where the z coordinate should + be dropped rather than projected. \sa setMatrix() */ -QTransform & QTransform::rotate(qreal a, Qt::Axis axis) +QTransform & QTransform::rotate(qreal a, Qt::Axis axis, qreal distanceToPlane) { if (a == 0) return *this; #ifndef QT_NO_DEBUG - if (qIsNaN(a)) { + if (qIsNaN(a) || qIsNaN(distanceToPlane)) { nanWarning("rotate"); return *this; } @@ -585,7 +574,7 @@ QTransform & QTransform::rotate(qreal a, Qt::Axis axis) else if (a == 180.) cosa = -1.; else{ - qreal b = deg2rad*a; // convert to radians + qreal b = qDegreesToRadians(a); sina = qSin(b); // fast and convenient cosa = qCos(b); } @@ -633,13 +622,16 @@ QTransform & QTransform::rotate(qreal a, Qt::Axis axis) if (m_dirty < TxRotate) m_dirty = TxRotate; } else { + if (!qIsNull(distanceToPlane)) + sina /= distanceToPlane; + QTransform result; if (axis == Qt::YAxis) { result.m_matrix[0][0] = cosa; - result.m_matrix[0][2] = -sina * inv_dist_to_plane; + result.m_matrix[0][2] = -sina; } else { result.m_matrix[1][1] = cosa; - result.m_matrix[1][2] = -sina * inv_dist_to_plane; + result.m_matrix[1][2] = -sina; } result.m_type = TxProject; *this = result * *this; @@ -648,24 +640,49 @@ QTransform & QTransform::rotate(qreal a, Qt::Axis axis) return *this; } +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) /*! - \fn QTransform & QTransform::rotateRadians(qreal angle, Qt::Axis axis) + \overload + + Rotates the coordinate system counterclockwise by the given angle \a a + about the specified \a axis at distance 1024.0 from the screen and + returns a reference to the matrix. + + \include qtransform.cpp transform-rotate-note - Rotates the coordinate system counterclockwise by the given \a angle - about the specified \a axis and returns a reference to the matrix. + \sa setMatrix +*/ +QTransform &QTransform::rotate(qreal a, Qt::Axis axis) +{ + return rotate(a, axis, 1024.0); +} +#endif + +/*! + \since 6.5 + Rotates the coordinate system counterclockwise by the given angle \a a + about the specified \a axis at distance \a distanceToPlane from the + screen and returns a reference to the matrix. + +//! [transform-rotate-radians-note] Note that if you apply a QTransform to a point defined in widget coordinates, the direction of the rotation will be clockwise because the y-axis points downwards. The angle is specified in radians. +//! [transform-rotate-radians-note] + + If \a distanceToPlane is zero, it will be ignored. This is suitable + for implementing orthographic projections where the z coordinate should + be dropped rather than projected. \sa setMatrix() */ -QTransform & QTransform::rotateRadians(qreal a, Qt::Axis axis) +QTransform & QTransform::rotateRadians(qreal a, Qt::Axis axis, qreal distanceToPlane) { #ifndef QT_NO_DEBUG - if (qIsNaN(a)) { + if (qIsNaN(a) || qIsNaN(distanceToPlane)) { nanWarning("rotateRadians"); return *this; } @@ -716,13 +733,16 @@ QTransform & QTransform::rotateRadians(qreal a, Qt::Axis axis) if (m_dirty < TxRotate) m_dirty = TxRotate; } else { + if (!qIsNull(distanceToPlane)) + sina /= distanceToPlane; + QTransform result; if (axis == Qt::YAxis) { result.m_matrix[0][0] = cosa; - result.m_matrix[0][2] = -sina * inv_dist_to_plane; + result.m_matrix[0][2] = -sina; } else { result.m_matrix[1][1] = cosa; - result.m_matrix[1][2] = -sina * inv_dist_to_plane; + result.m_matrix[1][2] = -sina; } result.m_type = TxProject; *this = result * *this; @@ -730,6 +750,24 @@ QTransform & QTransform::rotateRadians(qreal a, Qt::Axis axis) return *this; } +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +/*! + \overload + + Rotates the coordinate system counterclockwise by the given angle \a a + about the specified \a axis at distance 1024.0 from the screen and + returns a reference to the matrix. + + \include qtransform.cpp transform-rotate-radians-note + + \sa setMatrix() +*/ +QTransform &QTransform::rotateRadians(qreal a, Qt::Axis axis) +{ + return rotateRadians(a, axis, 1024.0); +} +#endif + /*! \fn bool QTransform::operator==(const QTransform &matrix) const Returns \c true if this matrix is equal to the given \a matrix, @@ -1107,30 +1145,8 @@ QPoint QTransform::map(const QPoint &p) const qreal x = 0, y = 0; TransformationType t = inline_type(); - switch(t) { - case TxNone: - x = fx; - y = fy; - break; - case TxTranslate: - x = fx + m_matrix[2][0]; - y = fy + m_matrix[2][1]; - break; - case TxScale: - x = m_matrix[0][0] * fx + m_matrix[2][0]; - y = m_matrix[1][1] * fy + m_matrix[2][1]; - break; - case TxRotate: - case TxShear: - case TxProject: - x = m_matrix[0][0] * fx + m_matrix[1][0] * fy + m_matrix[2][0]; - y = m_matrix[0][1] * fx + m_matrix[1][1] * fy + m_matrix[2][1]; - if (t == TxProject) { - qreal w = 1./(m_matrix[0][2] * fx + m_matrix[1][2] * fy + m_matrix[2][2]); - x *= w; - y *= w; - } - } + MAP(fx, fy, x, y); + return QPoint(qRound(x), qRound(y)); } @@ -1158,30 +1174,8 @@ QPointF QTransform::map(const QPointF &p) const qreal x = 0, y = 0; TransformationType t = inline_type(); - switch(t) { - case TxNone: - x = fx; - y = fy; - break; - case TxTranslate: - x = fx + m_matrix[2][0]; - y = fy + m_matrix[2][1]; - break; - case TxScale: - x = m_matrix[0][0] * fx + m_matrix[2][0]; - y = m_matrix[1][1] * fy + m_matrix[2][1]; - break; - case TxRotate: - case TxShear: - case TxProject: - x = m_matrix[0][0] * fx + m_matrix[1][0] * fy + m_matrix[2][0]; - y = m_matrix[0][1] * fx + m_matrix[1][1] * fy + m_matrix[2][1]; - if (t == TxProject) { - qreal w = 1./(m_matrix[0][2] * fx + m_matrix[1][2] * fy + m_matrix[2][2]); - x *= w; - y *= w; - } - } + MAP(fx, fy, x, y); + return QPointF(x, y); } @@ -1229,41 +1223,9 @@ QLine QTransform::map(const QLine &l) const qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0; TransformationType t = inline_type(); - switch(t) { - case TxNone: - x1 = fx1; - y1 = fy1; - x2 = fx2; - y2 = fy2; - break; - case TxTranslate: - x1 = fx1 + m_matrix[2][0]; - y1 = fy1 + m_matrix[2][1]; - x2 = fx2 + m_matrix[2][0]; - y2 = fy2 + m_matrix[2][1]; - break; - case TxScale: - x1 = m_matrix[0][0] * fx1 + m_matrix[2][0]; - y1 = m_matrix[1][1] * fy1 + m_matrix[2][1]; - x2 = m_matrix[0][0] * fx2 + m_matrix[2][0]; - y2 = m_matrix[1][1] * fy2 + m_matrix[2][1]; - break; - case TxRotate: - case TxShear: - case TxProject: - x1 = m_matrix[0][0] * fx1 + m_matrix[1][0] * fy1 + m_matrix[2][0]; - y1 = m_matrix[0][1] * fx1 + m_matrix[1][1] * fy1 + m_matrix[2][1]; - x2 = m_matrix[0][0] * fx2 + m_matrix[1][0] * fy2 + m_matrix[2][0]; - y2 = m_matrix[0][1] * fx2 + m_matrix[1][1] * fy2 + m_matrix[2][1]; - if (t == TxProject) { - qreal w = 1./(m_matrix[0][2] * fx1 + m_matrix[1][2] * fy1 + m_matrix[2][2]); - x1 *= w; - y1 *= w; - w = 1./(m_matrix[0][2] * fx2 + m_matrix[1][2] * fy2 + m_matrix[2][2]); - x2 *= w; - y2 *= w; - } - } + MAP(fx1, fy1, x1, y1); + MAP(fx2, fy2, x2, y2); + return QLine(qRound(x1), qRound(y1), qRound(x2), qRound(y2)); } @@ -1288,66 +1250,12 @@ QLineF QTransform::map(const QLineF &l) const qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0; TransformationType t = inline_type(); - switch(t) { - case TxNone: - x1 = fx1; - y1 = fy1; - x2 = fx2; - y2 = fy2; - break; - case TxTranslate: - x1 = fx1 + m_matrix[2][0]; - y1 = fy1 + m_matrix[2][1]; - x2 = fx2 + m_matrix[2][0]; - y2 = fy2 + m_matrix[2][1]; - break; - case TxScale: - x1 = m_matrix[0][0] * fx1 + m_matrix[2][0]; - y1 = m_matrix[1][1] * fy1 + m_matrix[2][1]; - x2 = m_matrix[0][0] * fx2 + m_matrix[2][0]; - y2 = m_matrix[1][1] * fy2 + m_matrix[2][1]; - break; - case TxRotate: - case TxShear: - case TxProject: - x1 = m_matrix[0][0] * fx1 + m_matrix[1][0] * fy1 + m_matrix[2][0]; - y1 = m_matrix[0][1] * fx1 + m_matrix[1][1] * fy1 + m_matrix[2][1]; - x2 = m_matrix[0][0] * fx2 + m_matrix[1][0] * fy2 + m_matrix[2][0]; - y2 = m_matrix[0][1] * fx2 + m_matrix[1][1] * fy2 + m_matrix[2][1]; - if (t == TxProject) { - qreal w = 1./(m_matrix[0][2] * fx1 + m_matrix[1][2] * fy1 + m_matrix[2][2]); - x1 *= w; - y1 *= w; - w = 1./(m_matrix[0][2] * fx2 + m_matrix[1][2] * fy2 + m_matrix[2][2]); - x2 *= w; - y2 *= w; - } - } - return QLineF(x1, y1, x2, y2); -} + MAP(fx1, fy1, x1, y1); + MAP(fx2, fy2, x2, y2); -static QPolygonF mapProjective(const QTransform &transform, const QPolygonF &poly) -{ - if (poly.size() == 0) - return poly; - - if (poly.size() == 1) - return QPolygonF() << transform.map(poly.at(0)); - - QPainterPath path; - path.addPolygon(poly); - - path = transform.map(path); - - QPolygonF result; - const int elementCount = path.elementCount(); - result.reserve(elementCount); - for (int i = 0; i < elementCount; ++i) - result << path.elementAt(i); - return result; + return QLineF(x1, y1, x2, y2); } - /*! \fn QPolygonF operator *(const QPolygonF &polygon, const QTransform &matrix) \since 4.3 @@ -1381,9 +1289,6 @@ QPolygonF QTransform::map(const QPolygonF &a) const if (t <= TxTranslate) return a.translated(m_matrix[2][0], m_matrix[2][1]); - if (t >= QTransform::TxProject) - return mapProjective(*this, a); - int size = a.size(); int i; QPolygonF p(size); @@ -1411,9 +1316,6 @@ QPolygon QTransform::map(const QPolygon &a) const if (t <= TxTranslate) return a.translated(qRound(m_matrix[2][0]), qRound(m_matrix[2][1])); - if (t >= QTransform::TxProject) - return mapProjective(*this, QPolygonF(a)).toPolygon(); - int size = a.size(); int i; QPolygon p(size); @@ -1466,16 +1368,16 @@ QRegion QTransform::map(const QRegion &r) const QRegion res; if (m11() < 0 || m22() < 0) { for (const QRect &rect : r) - res += mapRect(QRectF(rect)).toRect(); + res += qt_mapFillRect(QRectF(rect), *this); } else { QVarLengthArray<QRect, 32> rects; rects.reserve(r.rectCount()); for (const QRect &rect : r) { - QRect nr = mapRect(QRectF(rect)).toRect(); + QRect nr = qt_mapFillRect(QRectF(rect), *this); if (!nr.isEmpty()) rects.append(nr); } - res.setRects(rects.constData(), rects.count()); + res.setRects(rects.constData(), rects.size()); } return res; } @@ -1704,7 +1606,7 @@ QPolygon QTransform::mapToPolygon(const QRect &rect) const MAP(rect.x(), bottom, x[3], y[3]); } - // all coordinates are correctly, tranform to a pointarray + // all coordinates are correctly, transform to a pointarray // (rounding to the next integer) a.setPoints(4, qRound(x[0]), qRound(y[0]), qRound(x[1]), qRound(y[1]), @@ -1722,7 +1624,7 @@ QPolygon QTransform::mapToPolygon(const QRect &rect) const */ bool QTransform::squareToQuad(const QPolygonF &quad, QTransform &trans) { - if (quad.count() != 4) + if (quad.size() != 4) return false; qreal dx0 = quad[0].x(); @@ -1844,14 +1746,6 @@ void QTransform::setMatrix(qreal m11, qreal m12, qreal m13, m_dirty = TxProject; } -static inline bool needsPerspectiveClipping(const QRectF &rect, const QTransform &transform) -{ - const qreal wx = qMin(transform.m13() * rect.left(), transform.m13() * rect.right()); - const qreal wy = qMin(transform.m23() * rect.top(), transform.m23() * rect.bottom()); - - return wx + wy + transform.m33() < Q_NEAR_CLIP; -} - QRect QTransform::mapRect(const QRect &rect) const { TransformationType t = inline_type(); @@ -1872,8 +1766,7 @@ QRect QTransform::mapRect(const QRect &rect) const y -= h; } return QRect(x, y, w, h); - } else if (t < TxProject || !needsPerspectiveClipping(rect, *this)) { - // see mapToPolygon for explanations of the algorithm. + } else { qreal x = 0, y = 0; MAP(rect.left(), rect.top(), x, y); qreal xmin = x; @@ -1895,11 +1788,7 @@ QRect QTransform::mapRect(const QRect &rect) const ymin = qMin(ymin, y); xmax = qMax(xmax, x); ymax = qMax(ymax, y); - return QRect(qRound(xmin), qRound(ymin), qRound(xmax)-qRound(xmin), qRound(ymax)-qRound(ymin)); - } else { - QPainterPath path; - path.addRect(rect); - return map(path).boundingRect().toRect(); + return QRectF(xmin, ymin, xmax-xmin, ymax-ymin).toRect(); } } @@ -1942,7 +1831,7 @@ QRectF QTransform::mapRect(const QRectF &rect) const y -= h; } return QRectF(x, y, w, h); - } else if (t < TxProject || !needsPerspectiveClipping(rect, *this)) { + } else { qreal x = 0, y = 0; MAP(rect.x(), rect.y(), x, y); qreal xmin = x; @@ -1965,10 +1854,6 @@ QRectF QTransform::mapRect(const QRectF &rect) const xmax = qMax(xmax, x); ymax = qMax(ymax, y); return QRectF(xmin, ymin, xmax-xmin, ymax - ymin); - } else { - QPainterPath path; - path.addRect(rect); - return map(path).boundingRect(); } } |