// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include class tst_QTransform : public QObject { Q_OBJECT private slots: void mapRect_data(); void mapToPolygon_data(); void mapRect(); void assignments(); void mapToPolygon(); void qhash(); void translate(); void scale(); void types(); void types2_data(); void types2(); void scalarOps(); void transform(); void mapEmptyPath(); void boundingRect(); void controlPointRect(); void inverted_data(); void inverted(); void projectivePathMapping(); void mapInt(); void mapPathWithPoint(); private: void mapping_data(); }; Q_DECLARE_METATYPE(QTransform) Q_DECLARE_METATYPE(QTransform::TransformationType) void tst_QTransform::mapRect_data() { mapping_data(); // rotations that are not multiples of 90 degrees. mapRect returns the bounding rect here. qreal deg = -45; QTest::newRow( "rot 45 a" ) << QTransform().rotate(deg) << QRect( 0, 0, 10, 10 ) << QPolygon( QRect( 0, -7, 14, 14 ) ); QTest::newRow( "rot 45 b" ) << QTransform().rotate(deg) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( 21, -14, 50, 49 ) ); QTest::newRow( "rot 45 c" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 0, 0, 10, 10 ) << QPolygon( QRect( 0, -71, 141, 142 ) ); QTest::newRow( "rot 45 d" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( 212, -141, 495, 495 ) ); deg = 45; QTest::newRow( "rot -45 a" ) << QTransform().rotate(deg) << QRect( 0, 0, 10, 10 ) << QPolygon( QRect( -7, 0, 14, 14 ) ); QTest::newRow( "rot -45 b" ) << QTransform().rotate(deg) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -35, 21, 49, 50 ) ); QTest::newRow( "rot -45 c" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 0, 0, 10, 10 ) << QPolygon( QRect( -71, 0, 142, 141 ) ); QTest::newRow( "rot -45 d" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -354, 212, 495, 495 ) ); } void tst_QTransform::mapToPolygon_data() { mapping_data(); } void tst_QTransform::mapping_data() { //create the testtable instance and define the elements QTest::addColumn("matrix"); QTest::addColumn("src"); QTest::addColumn("res"); //next we fill it with data // identity QTest::newRow( "identity" ) << QTransform() << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( 10, 20, 30, 40 ) ); // scaling QTest::newRow( "scale 0" ) << QTransform().scale(2, 2) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( 20, 40, 60, 80 ) ); QTest::newRow( "scale 1" ) << QTransform().scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( 100, 200, 300, 400 ) ); // mirroring QTest::newRow( "mirror 0" ) << QTransform().scale(-1, 1) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -40, 20, 30, 40 ) ); QTest::newRow( "mirror 1" ) << QTransform().scale(1, -1) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( 10, -60, 30, 40 ) ); QTest::newRow( "mirror 2" ) << QTransform().scale(-1, -1) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -40, -60, 30, 40 ) ); QTest::newRow( "mirror 3" ) << QTransform().scale(-2, -2) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -80, -120, 60, 80 ) ); QTest::newRow( "mirror 4" ) << QTransform().scale(-10, -10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -400, -600, 300, 400 ) ); QTest::newRow( "mirror 5" ) << QTransform().scale(-1, 1) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( -30, 0, 30, 40 ) ); QTest::newRow( "mirror 6" ) << QTransform().scale(1, -1) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( 0, -40, 30, 40 ) ); QTest::newRow( "mirror 7" ) << QTransform().scale(-1, -1) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( -30, -40, 30, 40 ) ); QTest::newRow( "mirror 8" ) << QTransform().scale(-2, -2) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( -60, -80, 60, 80 ) ); QTest::newRow( "mirror 9" ) << QTransform().scale(-10, -10) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( -300, -400, 300, 400 ) ); // rotations float deg = 0.; QTest::newRow( "rot 0 a" ) << QTransform().rotate(deg) << QRect( 0, 0, 30, 40 ) << QPolygon ( QRect( 0, 0, 30, 40 ) ); deg = 0.00001f; QTest::newRow( "rot 0 b" ) << QTransform().rotate(deg) << QRect( 0, 0, 30, 40 ) << QPolygon ( QRect( 0, 0, 30, 40 ) ); deg = 0.; QTest::newRow( "rot 0 c" ) << QTransform().rotate(deg) << QRect( 10, 20, 30, 40 ) << QPolygon ( QRect( 10, 20, 30, 40 ) ); deg = 0.00001f; QTest::newRow( "rot 0 d" ) << QTransform().rotate(deg) << QRect( 10, 20, 30, 40 ) << QPolygon ( QRect( 10, 20, 30, 40 ) ); // rotations deg = -90.f; QTest::newRow( "rotscale 90 a" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( 0, -300, 400, 300 ) ); deg = -90.00001f; QTest::newRow( "rotscale 90 b" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( 0, -300, 400, 300 ) ); deg = -90.f; QTest::newRow( "rotscale 90 c" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( 200, -400, 400, 300 ) ); deg = -90.00001f; QTest::newRow( "rotscale 90 d" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( 200, -400, 400, 300 ) ); deg = 180.f; QTest::newRow( "rotscale 180 a" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( -300, -400, 300, 400 ) ); deg = 180.000001f; QTest::newRow( "rotscale 180 b" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( -300, -400, 300, 400 ) ); deg = 180.f; QTest::newRow( "rotscale 180 c" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -400, -600, 300, 400 ) ); deg = 180.000001f; QTest::newRow( "rotscale 180 d" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -400, -600, 300, 400 ) ); deg = -270.f; QTest::newRow( "rotscale 270 a" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( -400, 0, 400, 300 ) ); deg = -270.0000001f; QTest::newRow( "rotscale 270 b" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 0, 0, 30, 40 ) << QPolygon( QRect( -400, 0, 400, 300 ) ); deg = -270.f; QTest::newRow( "rotscale 270 c" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -600, 100, 400, 300 ) ); deg = -270.000001f; QTest::newRow( "rotscale 270 d" ) << QTransform().rotate(deg).scale(10, 10) << QRect( 10, 20, 30, 40 ) << QPolygon( QRect( -600, 100, 400, 300 ) ); } void tst_QTransform::mapRect() { QFETCH( QTransform, matrix ); QFETCH( QRect, src ); QFETCH( QPolygon, res ); QRect mapped = matrix.mapRect(src); QCOMPARE( mapped, res.boundingRect().adjusted(0, 0, -1, -1) ); QRectF r = matrix.mapRect(QRectF(src)); QRect ir(r.topLeft().toPoint(), r.bottomRight().toPoint() - QPoint(1, 1)); QCOMPARE( mapped, ir ); } void tst_QTransform::assignments() { QTransform m; m.scale(2, 3); m.rotate(45); m.shear(4, 5); QTransform c1(m); QCOMPARE(m.m11(), c1.m11()); QCOMPARE(m.m12(), c1.m12()); QCOMPARE(m.m21(), c1.m21()); QCOMPARE(m.m22(), c1.m22()); QCOMPARE(m.dx(), c1.dx()); QCOMPARE(m.dy(), c1.dy()); QTransform c2 = m; QCOMPARE(m.m11(), c2.m11()); QCOMPARE(m.m12(), c2.m12()); QCOMPARE(m.m21(), c2.m21()); QCOMPARE(m.m22(), c2.m22()); QCOMPARE(m.dx(), c2.dx()); QCOMPARE(m.dy(), c2.dy()); } void tst_QTransform::mapToPolygon() { QFETCH( QTransform, matrix ); QFETCH( QRect, src ); QFETCH( QPolygon, res ); QPolygon poly = matrix.mapToPolygon(src); // don't care about starting point bool equal = false; for (int i = 0; i < poly.size(); ++i) { QPolygon rot; for (int j = i; j < poly.size(); ++j) rot << poly[j]; for (int j = 0; j < i; ++j) rot << poly[j]; if (rot == res) equal = true; } QVERIFY(equal); } void tst_QTransform::qhash() { QTransform t1; t1.shear(3.0, 2.0); t1.rotate(44); QTransform t2 = t1; // not really much to test here, so just the bare minimum: QCOMPARE(qHash(t1), qHash(t2)); } void tst_QTransform::translate() { QTransform m( 1, 2, 3, 4, 5, 6 ); QTransform res2( m ); QTransform res( 1, 2, 3, 4, 75, 106 ); m.translate( 10, 20 ); QVERIFY( m == res ); m.translate( -10, -20 ); QVERIFY( m == res2 ); QVERIFY( QTransform::fromTranslate( 0, 0 ).type() == QTransform::TxNone ); QVERIFY( QTransform::fromTranslate( 10, 0 ).type() == QTransform::TxTranslate ); QVERIFY( QTransform::fromTranslate( -1, 5 ) == QTransform().translate( -1, 5 )); QVERIFY( QTransform::fromTranslate( 0, 0 ) == QTransform()); } void tst_QTransform::scale() { QTransform m( 1, 2, 3, 4, 5, 6 ); QTransform res2( m ); QTransform res( 10, 20, 60, 80, 5, 6 ); m.scale( 10, 20 ); QVERIFY( m == res ); m.scale( 1./10., 1./20. ); QVERIFY( m == res2 ); QVERIFY( QTransform::fromScale( 1, 1 ).type() == QTransform::TxNone ); QVERIFY( QTransform::fromScale( 2, 4 ).type() == QTransform::TxScale ); QVERIFY( QTransform::fromScale( 2, 4 ) == QTransform().scale( 2, 4 )); QVERIFY( QTransform::fromScale( 1, 1 ) == QTransform()); } void tst_QTransform::types() { QTransform m1; QCOMPARE(m1.type(), QTransform::TxNone); m1.translate(1.0f, 0.0f); QCOMPARE(m1.type(), QTransform::TxTranslate); QCOMPARE(m1.inverted().type(), QTransform::TxTranslate); m1.scale(1.0f, 2.0f); QCOMPARE(m1.type(), QTransform::TxScale); QCOMPARE(m1.inverted().type(), QTransform::TxScale); m1.rotate(45.0f); // Rotation after non-uniform scaling -> shearing. Uniform scale + rotate tested below. QCOMPARE(m1.type(), QTransform::TxShear); QCOMPARE(m1.inverted().type(), QTransform::TxShear); m1.shear(0.5f, 0.25f); QCOMPARE(m1.type(), QTransform::TxShear); QCOMPARE(m1.inverted().type(), QTransform::TxShear); m1.rotate(45.0f, Qt::XAxis); QCOMPARE(m1.type(), QTransform::TxProject); m1.shear(0.5f, 0.25f); QCOMPARE(m1.type(), QTransform::TxProject); m1.rotate(45.0f); QCOMPARE(m1.type(), QTransform::TxProject); m1.scale(1.0f, 2.0f); QCOMPARE(m1.type(), QTransform::TxProject); m1.translate(1.0f, 0.0f); QCOMPARE(m1.type(), QTransform::TxProject); QTransform m2(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, -1.0f, 1.0f); QCOMPARE(m2.type(), QTransform::TxTranslate); QCOMPARE((m1 * m2).type(), QTransform::TxProject); m1 *= QTransform(); QCOMPARE(m1.type(), QTransform::TxProject); m1 *= QTransform(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f); QCOMPARE(m1.type(), QTransform::TxProject); m2.reset(); QCOMPARE(m2.type(), QTransform::TxNone); m2.setMatrix(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); QCOMPARE(m2.type(), QTransform::TxNone); m2 *= QTransform(); QCOMPARE(m2.type(), QTransform::TxNone); m2.setMatrix(2.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); QCOMPARE(m2.type(), QTransform::TxScale); m2 *= QTransform(); QCOMPARE(m2.type(), QTransform::TxScale); m2.setMatrix(0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f); QCOMPARE(m2.type(), QTransform::TxRotate); m2 *= QTransform(); QCOMPARE(m2.type(), QTransform::TxRotate); m2.setMatrix(1.0f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); QCOMPARE(m2.type(), QTransform::TxProject); m2 *= QTransform(); QCOMPARE(m2.type(), QTransform::TxProject); m2.setMatrix(1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f); QCOMPARE(m2.type(), QTransform::TxShear); m2 *= m2.inverted(); QCOMPARE(m2.type(), QTransform::TxNone); m2.translate(5.0f, 5.0f); m2.rotate(45.0f); m2.rotate(-45.0f); QCOMPARE(m2.type(), QTransform::TxTranslate); m2.scale(2.0f, 3.0f); m2.shear(1.0f, 0.0f); m2.shear(-1.0f, 0.0f); QCOMPARE(m2.type(), QTransform::TxScale); m2 *= QTransform(1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); QCOMPARE(m2.type(), QTransform::TxShear); m2 *= QTransform(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f); QCOMPARE(m2.type(), QTransform::TxShear); QTransform m3(1.8f, 0.0f, 0.0f, 0.0f, 1.8f, 0.0f, 0.0f, 0.0f, 1.0f); QCOMPARE(m3.type(), QTransform::TxScale); m3.translate(5.0f, 5.0f); QCOMPARE(m3.type(), QTransform::TxScale); QCOMPARE(m3.inverted().type(), QTransform::TxScale); m3.setMatrix(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 2.0f); QCOMPARE(m3.type(), QTransform::TxProject); m3.setMatrix(0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f); QCOMPARE(m3.type(), QTransform::TxProject); QTransform m4; m4.scale(5, 5); m4.translate(4, 2); m4.rotate(45); QCOMPARE(m4.type(), QTransform::TxRotate); QTransform m5; m5.scale(5, 5); m5 = m5.adjoint() / m5.determinant(); QCOMPARE(m5.type(), QTransform::TxScale); } void tst_QTransform::types2_data() { QTest::addColumn("t1"); QTest::addColumn("type"); QTest::newRow( "identity" ) << QTransform() << QTransform::TxNone; QTest::newRow( "translate" ) << QTransform().translate(10, -0.1) << QTransform::TxTranslate; QTest::newRow( "scale" ) << QTransform().scale(10, -0.1) << QTransform::TxScale; QTest::newRow( "rotate" ) << QTransform().rotate(10) << QTransform::TxRotate; QTest::newRow( "shear" ) << QTransform().shear(10, -0.1) << QTransform::TxShear; QTest::newRow( "project" ) << QTransform().rotate(10, Qt::XAxis) << QTransform::TxProject; QTest::newRow( "combined" ) << QTransform().translate(10, -0.1).scale(10, -0.1).rotate(10, Qt::YAxis) << QTransform::TxProject; } void tst_QTransform::types2() { #define CHECKTXTYPE(func) { QTransform t2(func); \ QTransform t3(t2.m11(), t2.m12(), t2.m13(), t2.m21(), t2.m22(), t2.m23(), t2.m31(), t2.m32(), t2.m33()); \ QVERIFY2(t3.type() == t2.type(), #func); \ } QFETCH( QTransform, t1 ); QFETCH( QTransform::TransformationType, type ); Q_ASSERT(t1.type() == type); CHECKTXTYPE(t1.adjoint()); CHECKTXTYPE(t1.inverted()); CHECKTXTYPE(t1.transposed()); #undef CHECKTXTYPE } void tst_QTransform::scalarOps() { QTransform t; QCOMPARE(t.m11(), 1.); QCOMPARE(t.m33(), 1.); QCOMPARE(t.m21(), 0.); t = QTransform() + 3; QCOMPARE(t.m11(), 4.); QCOMPARE(t.m33(), 4.); QCOMPARE(t.m21(), 3.); t = t - 3; QCOMPARE(t.m11(), 1.); QCOMPARE(t.m33(), 1.); QCOMPARE(t.m21(), 0.); QCOMPARE(t.isIdentity(), true); t += 3; t = t * 2; QCOMPARE(t.m11(), 8.); QCOMPARE(t.m33(), 8.); QCOMPARE(t.m21(), 6.); } void tst_QTransform::transform() { QTransform t; t.rotate(30, Qt::YAxis); t.translate(15, 10); t.scale(2, 2); t.rotate(30); t.shear(0.5, 0.5); QTransform a, b, c, d, e; a.rotate(30, Qt::YAxis); b.translate(15, 10); c.scale(2, 2); d.rotate(30); e.shear(0.5, 0.5); QVERIFY(qFuzzyCompare(t, e * d * c * b * a)); } void tst_QTransform::mapEmptyPath() { QPainterPath path; path.moveTo(10, 10); path.lineTo(10, 10); QCOMPARE(QTransform().map(path), path); } void tst_QTransform::boundingRect() { QPainterPath path; path.moveTo(10, 10); path.lineTo(10, 10); QCOMPARE(path.boundingRect(), QRectF(10, 10, 0, 0)); } void tst_QTransform::controlPointRect() { QPainterPath path; path.moveTo(10, 10); path.lineTo(10, 10); QCOMPARE(path.controlPointRect(), QRectF(10, 10, 0, 0)); } void tst_QTransform::inverted_data() { QTest::addColumn("matrix"); QTest::newRow("identity") << QTransform(); QTest::newRow("TxTranslate") << QTransform().translate(200, 10); QTest::newRow("TxScale") << QTransform().scale(5, 2); QTest::newRow("TxTranslate TxScale") << QTransform().translate(100, -10).scale(40, 2); QTest::newRow("TxScale TxTranslate") << QTransform().scale(40, 2).translate(100, -10); QTest::newRow("TxRotate") << QTransform().rotate(40, Qt::ZAxis); QTest::newRow("TxRotate TxScale") << QTransform().rotate(60, Qt::ZAxis).scale(2, 0.25); QTest::newRow("TxScale TxRotate") << QTransform().scale(2, 0.25).rotate(30, Qt::ZAxis); QTest::newRow("TxRotate TxScale TxTranslate") << QTransform().rotate(60, Qt::ZAxis).scale(2, 0.25).translate(200, -3000); QTest::newRow("TxRotate TxTranslate TxScale") << QTransform().rotate(60, Qt::ZAxis).translate(200, -3000).scale(19, 77); QTest::newRow("TxShear") << QTransform().shear(10, 10); QTest::newRow("TxShear TxRotate") << QTransform().shear(10, 10).rotate(45, Qt::ZAxis); QTest::newRow("TxShear TxRotate TxScale") << QTransform().shear(10, 10).rotate(45, Qt::ZAxis).scale(19, 81); QTest::newRow("TxTranslate TxShear TxRotate TxScale") << QTransform().translate(150, -1).shear(10, 10).rotate(45, Qt::ZAxis).scale(19, 81); const qreal s = 500000; QTransform big; big.scale(s, s); QTest::newRow("big") << big; QTransform smallTransform; smallTransform.scale(1/s, 1/s); QTest::newRow("small") << smallTransform; } void tst_QTransform::inverted() { if (sizeof(qreal) != sizeof(double)) QSKIP("precision error if qreal is not double"); QFETCH(QTransform, matrix); const QTransform inverted = matrix.inverted(); QCOMPARE(matrix.isIdentity(), inverted.isIdentity()); QCOMPARE(matrix.type(), inverted.type()); QVERIFY((matrix * inverted).isIdentity()); QVERIFY((inverted * matrix).isIdentity()); } void tst_QTransform::projectivePathMapping() { QPainterPath path; path.addRect(-50, -50, 100, 100); const QRectF view(0, 0, 1024, 1024); QVERIFY(view.intersects(path.boundingRect())); for (int i = 0; i < 85; i += 5) { QTransform transform; transform.translate(512, 512); transform.rotate(i, Qt::YAxis); const QPainterPath mapped = transform.map(path); QVERIFY(view.intersects(mapped.boundingRect())); QVERIFY(transform.inverted().mapRect(view).intersects(path.boundingRect())); } } void tst_QTransform::mapInt() { int x = 0; int y = 0; QTransform::fromTranslate(10, 10).map(x, y, &x, &y); QCOMPARE(x, 10); QCOMPARE(y, 10); } void tst_QTransform::mapPathWithPoint() { QPainterPath p(QPointF(10, 10)); p = QTransform::fromTranslate(10, 10).map(p); QCOMPARE(p.currentPosition(), QPointF(20, 20)); } QTEST_APPLESS_MAIN(tst_QTransform) #include "tst_qtransform.moc"