1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtGui module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | #include "qtransform.h" |
40 | |
41 | #include "qdatastream.h" |
42 | #include "qdebug.h" |
43 | #include "qhashfunctions.h" |
44 | #include "qmatrix.h" |
45 | #include "qregion.h" |
46 | #include "qpainterpath.h" |
47 | #include "qpainterpath_p.h" |
48 | #include "qvariant.h" |
49 | #include <qmath.h> |
50 | #include <qnumeric.h> |
51 | |
52 | #include <private/qbezier_p.h> |
53 | |
54 | QT_BEGIN_NAMESPACE |
55 | |
56 | #ifndef QT_NO_DEBUG |
57 | Q_NEVER_INLINE |
58 | static void nanWarning(const char *func) |
59 | { |
60 | qWarning(msg: "QTransform::%s with NaN called" , func); |
61 | } |
62 | #endif // QT_NO_DEBUG |
63 | |
64 | #define Q_NEAR_CLIP (sizeof(qreal) == sizeof(double) ? 0.000001 : 0.0001) |
65 | |
66 | #ifdef MAP |
67 | # undef MAP |
68 | #endif |
69 | #define MAP(x, y, nx, ny) \ |
70 | do { \ |
71 | qreal FX_ = x; \ |
72 | qreal FY_ = y; \ |
73 | switch(t) { \ |
74 | case TxNone: \ |
75 | nx = FX_; \ |
76 | ny = FY_; \ |
77 | break; \ |
78 | case TxTranslate: \ |
79 | nx = FX_ + affine._dx; \ |
80 | ny = FY_ + affine._dy; \ |
81 | break; \ |
82 | case TxScale: \ |
83 | nx = affine._m11 * FX_ + affine._dx; \ |
84 | ny = affine._m22 * FY_ + affine._dy; \ |
85 | break; \ |
86 | case TxRotate: \ |
87 | case TxShear: \ |
88 | case TxProject: \ |
89 | nx = affine._m11 * FX_ + affine._m21 * FY_ + affine._dx; \ |
90 | ny = affine._m12 * FX_ + affine._m22 * FY_ + affine._dy; \ |
91 | if (t == TxProject) { \ |
92 | qreal w = (m_13 * FX_ + m_23 * FY_ + m_33); \ |
93 | if (w < qreal(Q_NEAR_CLIP)) w = qreal(Q_NEAR_CLIP); \ |
94 | w = 1./w; \ |
95 | nx *= w; \ |
96 | ny *= w; \ |
97 | } \ |
98 | } \ |
99 | } while (0) |
100 | |
101 | /*! |
102 | \class QTransform |
103 | \brief The QTransform class specifies 2D transformations of a coordinate system. |
104 | \since 4.3 |
105 | \ingroup painting |
106 | \inmodule QtGui |
107 | |
108 | A transformation specifies how to translate, scale, shear, rotate |
109 | or project the coordinate system, and is typically used when |
110 | rendering graphics. |
111 | |
112 | QTransform differs from QMatrix in that it is a true 3x3 matrix, |
113 | allowing perspective transformations. QTransform's toAffine() |
114 | method allows casting QTransform to QMatrix. If a perspective |
115 | transformation has been specified on the matrix, then the |
116 | conversion will cause loss of data. |
117 | |
118 | QTransform is the recommended transformation class in Qt. |
119 | |
120 | A QTransform object can be built using the setMatrix(), scale(), |
121 | rotate(), translate() and shear() functions. Alternatively, it |
122 | can be built by applying \l {QTransform#Basic Matrix |
123 | Operations}{basic matrix operations}. The matrix can also be |
124 | defined when constructed, and it can be reset to the identity |
125 | matrix (the default) using the reset() function. |
126 | |
127 | The QTransform class supports mapping of graphic primitives: A given |
128 | point, line, polygon, region, or painter path can be mapped to the |
129 | coordinate system defined by \e this matrix using the map() |
130 | function. In case of a rectangle, its coordinates can be |
131 | transformed using the mapRect() function. A rectangle can also be |
132 | transformed into a \e polygon (mapped to the coordinate system |
133 | defined by \e this matrix), using the mapToPolygon() function. |
134 | |
135 | QTransform provides the isIdentity() function which returns \c true if |
136 | the matrix is the identity matrix, and the isInvertible() function |
137 | which returns \c true if the matrix is non-singular (i.e. AB = BA = |
138 | I). The inverted() function returns an inverted copy of \e this |
139 | matrix if it is invertible (otherwise it returns the identity |
140 | matrix), and adjoint() returns the matrix's classical adjoint. |
141 | In addition, QTransform provides the determinant() function which |
142 | returns the matrix's determinant. |
143 | |
144 | Finally, the QTransform class supports matrix multiplication, addition |
145 | and subtraction, and objects of the class can be streamed as well |
146 | as compared. |
147 | |
148 | \tableofcontents |
149 | |
150 | \section1 Rendering Graphics |
151 | |
152 | When rendering graphics, the matrix defines the transformations |
153 | but the actual transformation is performed by the drawing routines |
154 | in QPainter. |
155 | |
156 | By default, QPainter operates on the associated device's own |
157 | coordinate system. The standard coordinate system of a |
158 | QPaintDevice has its origin located at the top-left position. The |
159 | \e x values increase to the right; \e y values increase |
160 | downward. For a complete description, see the \l {Coordinate |
161 | System} {coordinate system} documentation. |
162 | |
163 | QPainter has functions to translate, scale, shear and rotate the |
164 | coordinate system without using a QTransform. For example: |
165 | |
166 | \table 100% |
167 | \row |
168 | \li \inlineimage qtransform-simpletransformation.png |
169 | \li |
170 | \snippet transform/main.cpp 0 |
171 | \endtable |
172 | |
173 | Although these functions are very convenient, it can be more |
174 | efficient to build a QTransform and call QPainter::setTransform() if you |
175 | want to perform more than a single transform operation. For |
176 | example: |
177 | |
178 | \table 100% |
179 | \row |
180 | \li \inlineimage qtransform-combinedtransformation.png |
181 | \li |
182 | \snippet transform/main.cpp 1 |
183 | \endtable |
184 | |
185 | \section1 Basic Matrix Operations |
186 | |
187 | \image qtransform-representation.png |
188 | |
189 | A QTransform object contains a 3 x 3 matrix. The \c m31 (\c dx) and |
190 | \c m32 (\c dy) elements specify horizontal and vertical translation. |
191 | The \c m11 and \c m22 elements specify horizontal and vertical scaling. |
192 | The \c m21 and \c m12 elements specify horizontal and vertical \e shearing. |
193 | And finally, the \c m13 and \c m23 elements specify horizontal and vertical |
194 | projection, with \c m33 as an additional projection factor. |
195 | |
196 | QTransform transforms a point in the plane to another point using the |
197 | following formulas: |
198 | |
199 | \snippet code/src_gui_painting_qtransform.cpp 0 |
200 | |
201 | The point \e (x, y) is the original point, and \e (x', y') is the |
202 | transformed point. \e (x', y') can be transformed back to \e (x, |
203 | y) by performing the same operation on the inverted() matrix. |
204 | |
205 | The various matrix elements can be set when constructing the |
206 | matrix, or by using the setMatrix() function later on. They can also |
207 | be manipulated using the translate(), rotate(), scale() and |
208 | shear() convenience functions. The currently set values can be |
209 | retrieved using the m11(), m12(), m13(), m21(), m22(), m23(), |
210 | m31(), m32(), m33(), dx() and dy() functions. |
211 | |
212 | Translation is the simplest transformation. Setting \c dx and \c |
213 | dy will move the coordinate system \c dx units along the X axis |
214 | and \c dy units along the Y axis. Scaling can be done by setting |
215 | \c m11 and \c m22. For example, setting \c m11 to 2 and \c m22 to |
216 | 1.5 will double the height and increase the width by 50%. The |
217 | identity matrix has \c m11, \c m22, and \c m33 set to 1 (all others are set |
218 | to 0) mapping a point to itself. Shearing is controlled by \c m12 |
219 | and \c m21. Setting these elements to values different from zero |
220 | will twist the coordinate system. Rotation is achieved by |
221 | setting both the shearing factors and the scaling factors. Perspective |
222 | transformation is achieved by setting both the projection factors and |
223 | the scaling factors. |
224 | |
225 | \section2 Combining Transforms |
226 | Here's the combined transformations example using basic matrix |
227 | operations: |
228 | |
229 | \table 100% |
230 | \row |
231 | \li \inlineimage qtransform-combinedtransformation2.png |
232 | \li |
233 | \snippet transform/main.cpp 2 |
234 | \endtable |
235 | |
236 | The combined transform first scales each operand, then rotates it, and |
237 | finally translates it, just as in the order in which the product of its |
238 | factors is written. This means the point to which the transforms are |
239 | applied is implicitly multiplied on the left with the transform |
240 | to its right. |
241 | |
242 | \section2 Relation to Matrix Notation |
243 | The matrix notation in QTransform is the transpose of a commonly-taught |
244 | convention which represents transforms and points as matrices and vectors. |
245 | That convention multiplies its matrix on the left and column vector to the |
246 | right. In other words, when several transforms are applied to a point, the |
247 | right-most matrix acts directly on the vector first. Then the next matrix |
248 | to the left acts on the result of the first operation - and so on. As a |
249 | result, that convention multiplies the matrices that make up a composite |
250 | transform in the reverse of the order in QTransform, as you can see in |
251 | \l {Combining Transforms}. Transposing the matrices, and combining them to |
252 | the right of a row vector that represents the point, lets the matrices of |
253 | transforms appear, in their product, in the order in which we think of the |
254 | transforms being applied to the point. |
255 | |
256 | \sa QPainter, {Coordinate System}, {painting/affine}{Affine |
257 | Transformations Example}, {Transformations Example} |
258 | */ |
259 | |
260 | /*! |
261 | \enum QTransform::TransformationType |
262 | |
263 | \value TxNone |
264 | \value TxTranslate |
265 | \value TxScale |
266 | \value TxRotate |
267 | \value TxShear |
268 | \value TxProject |
269 | */ |
270 | |
271 | /*! |
272 | \fn QTransform::QTransform(Qt::Initialization) |
273 | \internal |
274 | */ |
275 | |
276 | /*! |
277 | Constructs an identity matrix. |
278 | |
279 | All elements are set to zero except \c m11 and \c m22 (specifying |
280 | the scale) and \c m33 which are set to 1. |
281 | |
282 | \sa reset() |
283 | */ |
284 | QTransform::QTransform() |
285 | : affine(true) |
286 | , m_13(0), m_23(0), m_33(1) |
287 | , m_type(TxNone) |
288 | , m_dirty(TxNone) |
289 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
290 | , d(nullptr) |
291 | #endif |
292 | { |
293 | } |
294 | |
295 | /*! |
296 | \fn QTransform::QTransform(qreal m11, qreal m12, qreal m13, qreal m21, qreal m22, qreal m23, qreal m31, qreal m32, qreal m33) |
297 | |
298 | Constructs a matrix with the elements, \a m11, \a m12, \a m13, |
299 | \a m21, \a m22, \a m23, \a m31, \a m32, \a m33. |
300 | |
301 | \sa setMatrix() |
302 | */ |
303 | QTransform::QTransform(qreal h11, qreal h12, qreal h13, |
304 | qreal h21, qreal h22, qreal h23, |
305 | qreal h31, qreal h32, qreal h33) |
306 | : affine(h11, h12, h21, h22, h31, h32, true) |
307 | , m_13(h13), m_23(h23), m_33(h33) |
308 | , m_type(TxNone) |
309 | , m_dirty(TxProject) |
310 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
311 | , d(nullptr) |
312 | #endif |
313 | { |
314 | } |
315 | |
316 | /*! |
317 | \fn QTransform::QTransform(qreal m11, qreal m12, qreal m21, qreal m22, qreal dx, qreal dy) |
318 | |
319 | Constructs a matrix with the elements, \a m11, \a m12, \a m21, \a m22, \a dx and \a dy. |
320 | |
321 | \sa setMatrix() |
322 | */ |
323 | QTransform::QTransform(qreal h11, qreal h12, qreal h21, |
324 | qreal h22, qreal dx, qreal dy) |
325 | : affine(h11, h12, h21, h22, dx, dy, true) |
326 | , m_13(0), m_23(0), m_33(1) |
327 | , m_type(TxNone) |
328 | , m_dirty(TxShear) |
329 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
330 | , d(nullptr) |
331 | #endif |
332 | { |
333 | } |
334 | |
335 | #if QT_DEPRECATED_SINCE(5, 15) |
336 | /*! |
337 | \fn QTransform::QTransform(const QMatrix &matrix) |
338 | \obsolete |
339 | |
340 | Constructs a matrix that is a copy of the given \a matrix. |
341 | Note that the \c m13, \c m23, and \c m33 elements are set to 0, 0, |
342 | and 1 respectively. |
343 | */ |
344 | QTransform::QTransform(const QMatrix &mtx) |
345 | : affine(mtx._m11, mtx._m12, mtx._m21, mtx._m22, mtx._dx, mtx._dy, true), |
346 | m_13(0), m_23(0), m_33(1) |
347 | , m_type(TxNone) |
348 | , m_dirty(TxShear) |
349 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
350 | , d(nullptr) |
351 | #endif |
352 | { |
353 | } |
354 | #endif // QT_DEPRECATED_SINCE(5, 15) |
355 | |
356 | /*! |
357 | Returns the adjoint of this matrix. |
358 | */ |
359 | QTransform QTransform::adjoint() const |
360 | { |
361 | qreal h11, h12, h13, |
362 | h21, h22, h23, |
363 | h31, h32, h33; |
364 | h11 = affine._m22*m_33 - m_23*affine._dy; |
365 | h21 = m_23*affine._dx - affine._m21*m_33; |
366 | h31 = affine._m21*affine._dy - affine._m22*affine._dx; |
367 | h12 = m_13*affine._dy - affine._m12*m_33; |
368 | h22 = affine._m11*m_33 - m_13*affine._dx; |
369 | h32 = affine._m12*affine._dx - affine._m11*affine._dy; |
370 | h13 = affine._m12*m_23 - m_13*affine._m22; |
371 | h23 = m_13*affine._m21 - affine._m11*m_23; |
372 | h33 = affine._m11*affine._m22 - affine._m12*affine._m21; |
373 | |
374 | return QTransform(h11, h12, h13, |
375 | h21, h22, h23, |
376 | h31, h32, h33, true); |
377 | } |
378 | |
379 | /*! |
380 | Returns the transpose of this matrix. |
381 | */ |
382 | QTransform QTransform::transposed() const |
383 | { |
384 | QTransform t(affine._m11, affine._m21, affine._dx, |
385 | affine._m12, affine._m22, affine._dy, |
386 | m_13, m_23, m_33, true); |
387 | return t; |
388 | } |
389 | |
390 | /*! |
391 | Returns an inverted copy of this matrix. |
392 | |
393 | If the matrix is singular (not invertible), the returned matrix is |
394 | the identity matrix. If \a invertible is valid (i.e. not 0), its |
395 | value is set to true if the matrix is invertible, otherwise it is |
396 | set to false. |
397 | |
398 | \sa isInvertible() |
399 | */ |
400 | QTransform QTransform::inverted(bool *invertible) const |
401 | { |
402 | QTransform invert(true); |
403 | bool inv = true; |
404 | |
405 | switch(inline_type()) { |
406 | case TxNone: |
407 | break; |
408 | case TxTranslate: |
409 | invert.affine._dx = -affine._dx; |
410 | invert.affine._dy = -affine._dy; |
411 | break; |
412 | case TxScale: |
413 | inv = !qFuzzyIsNull(d: affine._m11); |
414 | inv &= !qFuzzyIsNull(d: affine._m22); |
415 | if (inv) { |
416 | invert.affine._m11 = 1. / affine._m11; |
417 | invert.affine._m22 = 1. / affine._m22; |
418 | invert.affine._dx = -affine._dx * invert.affine._m11; |
419 | invert.affine._dy = -affine._dy * invert.affine._m22; |
420 | } |
421 | break; |
422 | case TxRotate: |
423 | case TxShear: |
424 | invert.affine = affine.inverted(invertible: &inv); |
425 | break; |
426 | default: |
427 | // general case |
428 | qreal det = determinant(); |
429 | inv = !qFuzzyIsNull(d: det); |
430 | if (inv) |
431 | invert = adjoint() / det; |
432 | break; |
433 | } |
434 | |
435 | if (invertible) |
436 | *invertible = inv; |
437 | |
438 | if (inv) { |
439 | // inverting doesn't change the type |
440 | invert.m_type = m_type; |
441 | invert.m_dirty = m_dirty; |
442 | } |
443 | |
444 | return invert; |
445 | } |
446 | |
447 | /*! |
448 | Moves the coordinate system \a dx along the x axis and \a dy along |
449 | the y axis, and returns a reference to the matrix. |
450 | |
451 | \sa setMatrix() |
452 | */ |
453 | QTransform &QTransform::translate(qreal dx, qreal dy) |
454 | { |
455 | if (dx == 0 && dy == 0) |
456 | return *this; |
457 | #ifndef QT_NO_DEBUG |
458 | if (qIsNaN(d: dx) | qIsNaN(d: dy)) { |
459 | nanWarning(func: "translate" ); |
460 | return *this; |
461 | } |
462 | #endif |
463 | |
464 | switch(inline_type()) { |
465 | case TxNone: |
466 | affine._dx = dx; |
467 | affine._dy = dy; |
468 | break; |
469 | case TxTranslate: |
470 | affine._dx += dx; |
471 | affine._dy += dy; |
472 | break; |
473 | case TxScale: |
474 | affine._dx += dx*affine._m11; |
475 | affine._dy += dy*affine._m22; |
476 | break; |
477 | case TxProject: |
478 | m_33 += dx*m_13 + dy*m_23; |
479 | Q_FALLTHROUGH(); |
480 | case TxShear: |
481 | case TxRotate: |
482 | affine._dx += dx*affine._m11 + dy*affine._m21; |
483 | affine._dy += dy*affine._m22 + dx*affine._m12; |
484 | break; |
485 | } |
486 | if (m_dirty < TxTranslate) |
487 | m_dirty = TxTranslate; |
488 | return *this; |
489 | } |
490 | |
491 | /*! |
492 | Creates a matrix which corresponds to a translation of \a dx along |
493 | the x axis and \a dy along the y axis. This is the same as |
494 | QTransform().translate(dx, dy) but slightly faster. |
495 | |
496 | \since 4.5 |
497 | */ |
498 | QTransform QTransform::fromTranslate(qreal dx, qreal dy) |
499 | { |
500 | #ifndef QT_NO_DEBUG |
501 | if (qIsNaN(d: dx) | qIsNaN(d: dy)) { |
502 | nanWarning(func: "fromTranslate" ); |
503 | return QTransform(); |
504 | } |
505 | #endif |
506 | QTransform transform(1, 0, 0, 0, 1, 0, dx, dy, 1, true); |
507 | if (dx == 0 && dy == 0) |
508 | transform.m_type = TxNone; |
509 | else |
510 | transform.m_type = TxTranslate; |
511 | transform.m_dirty = TxNone; |
512 | return transform; |
513 | } |
514 | |
515 | /*! |
516 | Scales the coordinate system by \a sx horizontally and \a sy |
517 | vertically, and returns a reference to the matrix. |
518 | |
519 | \sa setMatrix() |
520 | */ |
521 | QTransform & QTransform::scale(qreal sx, qreal sy) |
522 | { |
523 | if (sx == 1 && sy == 1) |
524 | return *this; |
525 | #ifndef QT_NO_DEBUG |
526 | if (qIsNaN(d: sx) | qIsNaN(d: sy)) { |
527 | nanWarning(func: "scale" ); |
528 | return *this; |
529 | } |
530 | #endif |
531 | |
532 | switch(inline_type()) { |
533 | case TxNone: |
534 | case TxTranslate: |
535 | affine._m11 = sx; |
536 | affine._m22 = sy; |
537 | break; |
538 | case TxProject: |
539 | m_13 *= sx; |
540 | m_23 *= sy; |
541 | Q_FALLTHROUGH(); |
542 | case TxRotate: |
543 | case TxShear: |
544 | affine._m12 *= sx; |
545 | affine._m21 *= sy; |
546 | Q_FALLTHROUGH(); |
547 | case TxScale: |
548 | affine._m11 *= sx; |
549 | affine._m22 *= sy; |
550 | break; |
551 | } |
552 | if (m_dirty < TxScale) |
553 | m_dirty = TxScale; |
554 | return *this; |
555 | } |
556 | |
557 | /*! |
558 | Creates a matrix which corresponds to a scaling of |
559 | \a sx horizontally and \a sy vertically. |
560 | This is the same as QTransform().scale(sx, sy) but slightly faster. |
561 | |
562 | \since 4.5 |
563 | */ |
564 | QTransform QTransform::fromScale(qreal sx, qreal sy) |
565 | { |
566 | #ifndef QT_NO_DEBUG |
567 | if (qIsNaN(d: sx) | qIsNaN(d: sy)) { |
568 | nanWarning(func: "fromScale" ); |
569 | return QTransform(); |
570 | } |
571 | #endif |
572 | QTransform transform(sx, 0, 0, 0, sy, 0, 0, 0, 1, true); |
573 | if (sx == 1. && sy == 1.) |
574 | transform.m_type = TxNone; |
575 | else |
576 | transform.m_type = TxScale; |
577 | transform.m_dirty = TxNone; |
578 | return transform; |
579 | } |
580 | |
581 | /*! |
582 | Shears the coordinate system by \a sh horizontally and \a sv |
583 | vertically, and returns a reference to the matrix. |
584 | |
585 | \sa setMatrix() |
586 | */ |
587 | QTransform & QTransform::shear(qreal sh, qreal sv) |
588 | { |
589 | if (sh == 0 && sv == 0) |
590 | return *this; |
591 | #ifndef QT_NO_DEBUG |
592 | if (qIsNaN(d: sh) | qIsNaN(d: sv)) { |
593 | nanWarning(func: "shear" ); |
594 | return *this; |
595 | } |
596 | #endif |
597 | |
598 | switch(inline_type()) { |
599 | case TxNone: |
600 | case TxTranslate: |
601 | affine._m12 = sv; |
602 | affine._m21 = sh; |
603 | break; |
604 | case TxScale: |
605 | affine._m12 = sv*affine._m22; |
606 | affine._m21 = sh*affine._m11; |
607 | break; |
608 | case TxProject: { |
609 | qreal tm13 = sv*m_23; |
610 | qreal tm23 = sh*m_13; |
611 | m_13 += tm13; |
612 | m_23 += tm23; |
613 | } |
614 | Q_FALLTHROUGH(); |
615 | case TxRotate: |
616 | case TxShear: { |
617 | qreal tm11 = sv*affine._m21; |
618 | qreal tm22 = sh*affine._m12; |
619 | qreal tm12 = sv*affine._m22; |
620 | qreal tm21 = sh*affine._m11; |
621 | affine._m11 += tm11; affine._m12 += tm12; |
622 | affine._m21 += tm21; affine._m22 += tm22; |
623 | break; |
624 | } |
625 | } |
626 | if (m_dirty < TxShear) |
627 | m_dirty = TxShear; |
628 | return *this; |
629 | } |
630 | |
631 | const qreal deg2rad = qreal(0.017453292519943295769); // pi/180 |
632 | const qreal inv_dist_to_plane = 1. / 1024.; |
633 | |
634 | /*! |
635 | \fn QTransform &QTransform::rotate(qreal angle, Qt::Axis axis) |
636 | |
637 | Rotates the coordinate system counterclockwise by the given \a angle |
638 | about the specified \a axis and returns a reference to the matrix. |
639 | |
640 | Note that if you apply a QTransform to a point defined in widget |
641 | coordinates, the direction of the rotation will be clockwise |
642 | because the y-axis points downwards. |
643 | |
644 | The angle is specified in degrees. |
645 | |
646 | \sa setMatrix() |
647 | */ |
648 | QTransform & QTransform::rotate(qreal a, Qt::Axis axis) |
649 | { |
650 | if (a == 0) |
651 | return *this; |
652 | #ifndef QT_NO_DEBUG |
653 | if (qIsNaN(d: a)) { |
654 | nanWarning(func: "rotate" ); |
655 | return *this; |
656 | } |
657 | #endif |
658 | |
659 | qreal sina = 0; |
660 | qreal cosa = 0; |
661 | if (a == 90. || a == -270.) |
662 | sina = 1.; |
663 | else if (a == 270. || a == -90.) |
664 | sina = -1.; |
665 | else if (a == 180.) |
666 | cosa = -1.; |
667 | else{ |
668 | qreal b = deg2rad*a; // convert to radians |
669 | sina = qSin(v: b); // fast and convenient |
670 | cosa = qCos(v: b); |
671 | } |
672 | |
673 | if (axis == Qt::ZAxis) { |
674 | switch(inline_type()) { |
675 | case TxNone: |
676 | case TxTranslate: |
677 | affine._m11 = cosa; |
678 | affine._m12 = sina; |
679 | affine._m21 = -sina; |
680 | affine._m22 = cosa; |
681 | break; |
682 | case TxScale: { |
683 | qreal tm11 = cosa*affine._m11; |
684 | qreal tm12 = sina*affine._m22; |
685 | qreal tm21 = -sina*affine._m11; |
686 | qreal tm22 = cosa*affine._m22; |
687 | affine._m11 = tm11; affine._m12 = tm12; |
688 | affine._m21 = tm21; affine._m22 = tm22; |
689 | break; |
690 | } |
691 | case TxProject: { |
692 | qreal tm13 = cosa*m_13 + sina*m_23; |
693 | qreal tm23 = -sina*m_13 + cosa*m_23; |
694 | m_13 = tm13; |
695 | m_23 = tm23; |
696 | Q_FALLTHROUGH(); |
697 | } |
698 | case TxRotate: |
699 | case TxShear: { |
700 | qreal tm11 = cosa*affine._m11 + sina*affine._m21; |
701 | qreal tm12 = cosa*affine._m12 + sina*affine._m22; |
702 | qreal tm21 = -sina*affine._m11 + cosa*affine._m21; |
703 | qreal tm22 = -sina*affine._m12 + cosa*affine._m22; |
704 | affine._m11 = tm11; affine._m12 = tm12; |
705 | affine._m21 = tm21; affine._m22 = tm22; |
706 | break; |
707 | } |
708 | } |
709 | if (m_dirty < TxRotate) |
710 | m_dirty = TxRotate; |
711 | } else { |
712 | QTransform result; |
713 | if (axis == Qt::YAxis) { |
714 | result.affine._m11 = cosa; |
715 | result.m_13 = -sina * inv_dist_to_plane; |
716 | } else { |
717 | result.affine._m22 = cosa; |
718 | result.m_23 = -sina * inv_dist_to_plane; |
719 | } |
720 | result.m_type = TxProject; |
721 | *this = result * *this; |
722 | } |
723 | |
724 | return *this; |
725 | } |
726 | |
727 | /*! |
728 | \fn QTransform & QTransform::rotateRadians(qreal angle, Qt::Axis axis) |
729 | |
730 | Rotates the coordinate system counterclockwise by the given \a angle |
731 | about the specified \a axis and returns a reference to the matrix. |
732 | |
733 | Note that if you apply a QTransform to a point defined in widget |
734 | coordinates, the direction of the rotation will be clockwise |
735 | because the y-axis points downwards. |
736 | |
737 | The angle is specified in radians. |
738 | |
739 | \sa setMatrix() |
740 | */ |
741 | QTransform & QTransform::rotateRadians(qreal a, Qt::Axis axis) |
742 | { |
743 | #ifndef QT_NO_DEBUG |
744 | if (qIsNaN(d: a)) { |
745 | nanWarning(func: "rotateRadians" ); |
746 | return *this; |
747 | } |
748 | #endif |
749 | qreal sina = qSin(v: a); |
750 | qreal cosa = qCos(v: a); |
751 | |
752 | if (axis == Qt::ZAxis) { |
753 | switch(inline_type()) { |
754 | case TxNone: |
755 | case TxTranslate: |
756 | affine._m11 = cosa; |
757 | affine._m12 = sina; |
758 | affine._m21 = -sina; |
759 | affine._m22 = cosa; |
760 | break; |
761 | case TxScale: { |
762 | qreal tm11 = cosa*affine._m11; |
763 | qreal tm12 = sina*affine._m22; |
764 | qreal tm21 = -sina*affine._m11; |
765 | qreal tm22 = cosa*affine._m22; |
766 | affine._m11 = tm11; affine._m12 = tm12; |
767 | affine._m21 = tm21; affine._m22 = tm22; |
768 | break; |
769 | } |
770 | case TxProject: { |
771 | qreal tm13 = cosa*m_13 + sina*m_23; |
772 | qreal tm23 = -sina*m_13 + cosa*m_23; |
773 | m_13 = tm13; |
774 | m_23 = tm23; |
775 | Q_FALLTHROUGH(); |
776 | } |
777 | case TxRotate: |
778 | case TxShear: { |
779 | qreal tm11 = cosa*affine._m11 + sina*affine._m21; |
780 | qreal tm12 = cosa*affine._m12 + sina*affine._m22; |
781 | qreal tm21 = -sina*affine._m11 + cosa*affine._m21; |
782 | qreal tm22 = -sina*affine._m12 + cosa*affine._m22; |
783 | affine._m11 = tm11; affine._m12 = tm12; |
784 | affine._m21 = tm21; affine._m22 = tm22; |
785 | break; |
786 | } |
787 | } |
788 | if (m_dirty < TxRotate) |
789 | m_dirty = TxRotate; |
790 | } else { |
791 | QTransform result; |
792 | if (axis == Qt::YAxis) { |
793 | result.affine._m11 = cosa; |
794 | result.m_13 = -sina * inv_dist_to_plane; |
795 | } else { |
796 | result.affine._m22 = cosa; |
797 | result.m_23 = -sina * inv_dist_to_plane; |
798 | } |
799 | result.m_type = TxProject; |
800 | *this = result * *this; |
801 | } |
802 | return *this; |
803 | } |
804 | |
805 | /*! |
806 | \fn bool QTransform::operator==(const QTransform &matrix) const |
807 | Returns \c true if this matrix is equal to the given \a matrix, |
808 | otherwise returns \c false. |
809 | */ |
810 | bool QTransform::operator==(const QTransform &o) const |
811 | { |
812 | return affine._m11 == o.affine._m11 && |
813 | affine._m12 == o.affine._m12 && |
814 | affine._m21 == o.affine._m21 && |
815 | affine._m22 == o.affine._m22 && |
816 | affine._dx == o.affine._dx && |
817 | affine._dy == o.affine._dy && |
818 | m_13 == o.m_13 && |
819 | m_23 == o.m_23 && |
820 | m_33 == o.m_33; |
821 | } |
822 | |
823 | /*! |
824 | \since 5.6 |
825 | \relates QTransform |
826 | |
827 | Returns the hash value for \a key, using |
828 | \a seed to seed the calculation. |
829 | */ |
830 | uint qHash(const QTransform &key, uint seed) noexcept |
831 | { |
832 | QtPrivate::QHashCombine hash; |
833 | seed = hash(seed, key.m11()); |
834 | seed = hash(seed, key.m12()); |
835 | seed = hash(seed, key.m21()); |
836 | seed = hash(seed, key.m22()); |
837 | seed = hash(seed, key.dx()); |
838 | seed = hash(seed, key.dy()); |
839 | seed = hash(seed, key.m13()); |
840 | seed = hash(seed, key.m23()); |
841 | seed = hash(seed, key.m33()); |
842 | return seed; |
843 | } |
844 | |
845 | |
846 | /*! |
847 | \fn bool QTransform::operator!=(const QTransform &matrix) const |
848 | Returns \c true if this matrix is not equal to the given \a matrix, |
849 | otherwise returns \c false. |
850 | */ |
851 | bool QTransform::operator!=(const QTransform &o) const |
852 | { |
853 | return !operator==(o); |
854 | } |
855 | |
856 | /*! |
857 | \fn QTransform & QTransform::operator*=(const QTransform &matrix) |
858 | \overload |
859 | |
860 | Returns the result of multiplying this matrix by the given \a |
861 | matrix. |
862 | */ |
863 | QTransform & QTransform::operator*=(const QTransform &o) |
864 | { |
865 | const TransformationType otherType = o.inline_type(); |
866 | if (otherType == TxNone) |
867 | return *this; |
868 | |
869 | const TransformationType thisType = inline_type(); |
870 | if (thisType == TxNone) |
871 | return operator=(o); |
872 | |
873 | TransformationType t = qMax(a: thisType, b: otherType); |
874 | switch(t) { |
875 | case TxNone: |
876 | break; |
877 | case TxTranslate: |
878 | affine._dx += o.affine._dx; |
879 | affine._dy += o.affine._dy; |
880 | break; |
881 | case TxScale: |
882 | { |
883 | qreal m11 = affine._m11*o.affine._m11; |
884 | qreal m22 = affine._m22*o.affine._m22; |
885 | |
886 | qreal m31 = affine._dx*o.affine._m11 + o.affine._dx; |
887 | qreal m32 = affine._dy*o.affine._m22 + o.affine._dy; |
888 | |
889 | affine._m11 = m11; |
890 | affine._m22 = m22; |
891 | affine._dx = m31; affine._dy = m32; |
892 | break; |
893 | } |
894 | case TxRotate: |
895 | case TxShear: |
896 | { |
897 | qreal m11 = affine._m11*o.affine._m11 + affine._m12*o.affine._m21; |
898 | qreal m12 = affine._m11*o.affine._m12 + affine._m12*o.affine._m22; |
899 | |
900 | qreal m21 = affine._m21*o.affine._m11 + affine._m22*o.affine._m21; |
901 | qreal m22 = affine._m21*o.affine._m12 + affine._m22*o.affine._m22; |
902 | |
903 | qreal m31 = affine._dx*o.affine._m11 + affine._dy*o.affine._m21 + o.affine._dx; |
904 | qreal m32 = affine._dx*o.affine._m12 + affine._dy*o.affine._m22 + o.affine._dy; |
905 | |
906 | affine._m11 = m11; affine._m12 = m12; |
907 | affine._m21 = m21; affine._m22 = m22; |
908 | affine._dx = m31; affine._dy = m32; |
909 | break; |
910 | } |
911 | case TxProject: |
912 | { |
913 | qreal m11 = affine._m11*o.affine._m11 + affine._m12*o.affine._m21 + m_13*o.affine._dx; |
914 | qreal m12 = affine._m11*o.affine._m12 + affine._m12*o.affine._m22 + m_13*o.affine._dy; |
915 | qreal m13 = affine._m11*o.m_13 + affine._m12*o.m_23 + m_13*o.m_33; |
916 | |
917 | qreal m21 = affine._m21*o.affine._m11 + affine._m22*o.affine._m21 + m_23*o.affine._dx; |
918 | qreal m22 = affine._m21*o.affine._m12 + affine._m22*o.affine._m22 + m_23*o.affine._dy; |
919 | qreal m23 = affine._m21*o.m_13 + affine._m22*o.m_23 + m_23*o.m_33; |
920 | |
921 | qreal m31 = affine._dx*o.affine._m11 + affine._dy*o.affine._m21 + m_33*o.affine._dx; |
922 | qreal m32 = affine._dx*o.affine._m12 + affine._dy*o.affine._m22 + m_33*o.affine._dy; |
923 | qreal m33 = affine._dx*o.m_13 + affine._dy*o.m_23 + m_33*o.m_33; |
924 | |
925 | affine._m11 = m11; affine._m12 = m12; m_13 = m13; |
926 | affine._m21 = m21; affine._m22 = m22; m_23 = m23; |
927 | affine._dx = m31; affine._dy = m32; m_33 = m33; |
928 | } |
929 | } |
930 | |
931 | m_dirty = t; |
932 | m_type = t; |
933 | |
934 | return *this; |
935 | } |
936 | |
937 | /*! |
938 | \fn QTransform QTransform::operator*(const QTransform &matrix) const |
939 | Returns the result of multiplying this matrix by the given \a |
940 | matrix. |
941 | |
942 | Note that matrix multiplication is not commutative, i.e. a*b != |
943 | b*a. |
944 | */ |
945 | QTransform QTransform::operator*(const QTransform &m) const |
946 | { |
947 | const TransformationType otherType = m.inline_type(); |
948 | if (otherType == TxNone) |
949 | return *this; |
950 | |
951 | const TransformationType thisType = inline_type(); |
952 | if (thisType == TxNone) |
953 | return m; |
954 | |
955 | QTransform t(true); |
956 | TransformationType type = qMax(a: thisType, b: otherType); |
957 | switch(type) { |
958 | case TxNone: |
959 | break; |
960 | case TxTranslate: |
961 | t.affine._dx = affine._dx + m.affine._dx; |
962 | t.affine._dy += affine._dy + m.affine._dy; |
963 | break; |
964 | case TxScale: |
965 | { |
966 | qreal m11 = affine._m11*m.affine._m11; |
967 | qreal m22 = affine._m22*m.affine._m22; |
968 | |
969 | qreal m31 = affine._dx*m.affine._m11 + m.affine._dx; |
970 | qreal m32 = affine._dy*m.affine._m22 + m.affine._dy; |
971 | |
972 | t.affine._m11 = m11; |
973 | t.affine._m22 = m22; |
974 | t.affine._dx = m31; t.affine._dy = m32; |
975 | break; |
976 | } |
977 | case TxRotate: |
978 | case TxShear: |
979 | { |
980 | qreal m11 = affine._m11*m.affine._m11 + affine._m12*m.affine._m21; |
981 | qreal m12 = affine._m11*m.affine._m12 + affine._m12*m.affine._m22; |
982 | |
983 | qreal m21 = affine._m21*m.affine._m11 + affine._m22*m.affine._m21; |
984 | qreal m22 = affine._m21*m.affine._m12 + affine._m22*m.affine._m22; |
985 | |
986 | qreal m31 = affine._dx*m.affine._m11 + affine._dy*m.affine._m21 + m.affine._dx; |
987 | qreal m32 = affine._dx*m.affine._m12 + affine._dy*m.affine._m22 + m.affine._dy; |
988 | |
989 | t.affine._m11 = m11; t.affine._m12 = m12; |
990 | t.affine._m21 = m21; t.affine._m22 = m22; |
991 | t.affine._dx = m31; t.affine._dy = m32; |
992 | break; |
993 | } |
994 | case TxProject: |
995 | { |
996 | qreal m11 = affine._m11*m.affine._m11 + affine._m12*m.affine._m21 + m_13*m.affine._dx; |
997 | qreal m12 = affine._m11*m.affine._m12 + affine._m12*m.affine._m22 + m_13*m.affine._dy; |
998 | qreal m13 = affine._m11*m.m_13 + affine._m12*m.m_23 + m_13*m.m_33; |
999 | |
1000 | qreal m21 = affine._m21*m.affine._m11 + affine._m22*m.affine._m21 + m_23*m.affine._dx; |
1001 | qreal m22 = affine._m21*m.affine._m12 + affine._m22*m.affine._m22 + m_23*m.affine._dy; |
1002 | qreal m23 = affine._m21*m.m_13 + affine._m22*m.m_23 + m_23*m.m_33; |
1003 | |
1004 | qreal m31 = affine._dx*m.affine._m11 + affine._dy*m.affine._m21 + m_33*m.affine._dx; |
1005 | qreal m32 = affine._dx*m.affine._m12 + affine._dy*m.affine._m22 + m_33*m.affine._dy; |
1006 | qreal m33 = affine._dx*m.m_13 + affine._dy*m.m_23 + m_33*m.m_33; |
1007 | |
1008 | t.affine._m11 = m11; t.affine._m12 = m12; t.m_13 = m13; |
1009 | t.affine._m21 = m21; t.affine._m22 = m22; t.m_23 = m23; |
1010 | t.affine._dx = m31; t.affine._dy = m32; t.m_33 = m33; |
1011 | } |
1012 | } |
1013 | |
1014 | t.m_dirty = type; |
1015 | t.m_type = type; |
1016 | |
1017 | return t; |
1018 | } |
1019 | |
1020 | /*! |
1021 | \fn QTransform & QTransform::operator*=(qreal scalar) |
1022 | \overload |
1023 | |
1024 | Returns the result of performing an element-wise multiplication of this |
1025 | matrix with the given \a scalar. |
1026 | */ |
1027 | |
1028 | /*! |
1029 | \fn QTransform & QTransform::operator/=(qreal scalar) |
1030 | \overload |
1031 | |
1032 | Returns the result of performing an element-wise division of this |
1033 | matrix by the given \a scalar. |
1034 | */ |
1035 | |
1036 | /*! |
1037 | \fn QTransform & QTransform::operator+=(qreal scalar) |
1038 | \overload |
1039 | |
1040 | Returns the matrix obtained by adding the given \a scalar to each |
1041 | element of this matrix. |
1042 | */ |
1043 | |
1044 | /*! |
1045 | \fn QTransform & QTransform::operator-=(qreal scalar) |
1046 | \overload |
1047 | |
1048 | Returns the matrix obtained by subtracting the given \a scalar from each |
1049 | element of this matrix. |
1050 | */ |
1051 | |
1052 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
1053 | /*! |
1054 | Assigns the given \a matrix's values to this matrix. |
1055 | */ |
1056 | QTransform & QTransform::operator=(const QTransform &matrix) noexcept |
1057 | { |
1058 | affine._m11 = matrix.affine._m11; |
1059 | affine._m12 = matrix.affine._m12; |
1060 | affine._m21 = matrix.affine._m21; |
1061 | affine._m22 = matrix.affine._m22; |
1062 | affine._dx = matrix.affine._dx; |
1063 | affine._dy = matrix.affine._dy; |
1064 | m_13 = matrix.m_13; |
1065 | m_23 = matrix.m_23; |
1066 | m_33 = matrix.m_33; |
1067 | m_type = matrix.m_type; |
1068 | m_dirty = matrix.m_dirty; |
1069 | |
1070 | return *this; |
1071 | } |
1072 | #endif |
1073 | |
1074 | /*! |
1075 | Resets the matrix to an identity matrix, i.e. all elements are set |
1076 | to zero, except \c m11 and \c m22 (specifying the scale) and \c m33 |
1077 | which are set to 1. |
1078 | |
1079 | \sa QTransform(), isIdentity(), {QTransform#Basic Matrix |
1080 | Operations}{Basic Matrix Operations} |
1081 | */ |
1082 | void QTransform::reset() |
1083 | { |
1084 | affine._m11 = affine._m22 = m_33 = 1.0; |
1085 | affine._m12 = m_13 = affine._m21 = m_23 = affine._dx = affine._dy = 0; |
1086 | m_type = TxNone; |
1087 | m_dirty = TxNone; |
1088 | } |
1089 | |
1090 | #ifndef QT_NO_DATASTREAM |
1091 | /*! |
1092 | \fn QDataStream &operator<<(QDataStream &stream, const QTransform &matrix) |
1093 | \since 4.3 |
1094 | \relates QTransform |
1095 | |
1096 | Writes the given \a matrix to the given \a stream and returns a |
1097 | reference to the stream. |
1098 | |
1099 | \sa {Serializing Qt Data Types} |
1100 | */ |
1101 | QDataStream & operator<<(QDataStream &s, const QTransform &m) |
1102 | { |
1103 | s << double(m.m11()) |
1104 | << double(m.m12()) |
1105 | << double(m.m13()) |
1106 | << double(m.m21()) |
1107 | << double(m.m22()) |
1108 | << double(m.m23()) |
1109 | << double(m.m31()) |
1110 | << double(m.m32()) |
1111 | << double(m.m33()); |
1112 | return s; |
1113 | } |
1114 | |
1115 | /*! |
1116 | \fn QDataStream &operator>>(QDataStream &stream, QTransform &matrix) |
1117 | \since 4.3 |
1118 | \relates QTransform |
1119 | |
1120 | Reads the given \a matrix from the given \a stream and returns a |
1121 | reference to the stream. |
1122 | |
1123 | \sa {Serializing Qt Data Types} |
1124 | */ |
1125 | QDataStream & operator>>(QDataStream &s, QTransform &t) |
1126 | { |
1127 | double m11, m12, m13, |
1128 | m21, m22, m23, |
1129 | m31, m32, m33; |
1130 | |
1131 | s >> m11; |
1132 | s >> m12; |
1133 | s >> m13; |
1134 | s >> m21; |
1135 | s >> m22; |
1136 | s >> m23; |
1137 | s >> m31; |
1138 | s >> m32; |
1139 | s >> m33; |
1140 | t.setMatrix(m11, m12, m13, |
1141 | m21, m22, m23, |
1142 | m31, m32, m33); |
1143 | return s; |
1144 | } |
1145 | |
1146 | #endif // QT_NO_DATASTREAM |
1147 | |
1148 | #ifndef QT_NO_DEBUG_STREAM |
1149 | QDebug operator<<(QDebug dbg, const QTransform &m) |
1150 | { |
1151 | static const char typeStr[][12] = |
1152 | { |
1153 | "TxNone" , |
1154 | "TxTranslate" , |
1155 | "TxScale" , |
1156 | "" , |
1157 | "TxRotate" , |
1158 | "" , "" , "" , |
1159 | "TxShear" , |
1160 | "" , "" , "" , "" , "" , "" , "" , |
1161 | "TxProject" |
1162 | }; |
1163 | |
1164 | QDebugStateSaver saver(dbg); |
1165 | dbg.nospace() << "QTransform(type=" << typeStr[m.type()] << ',' |
1166 | << " 11=" << m.m11() |
1167 | << " 12=" << m.m12() |
1168 | << " 13=" << m.m13() |
1169 | << " 21=" << m.m21() |
1170 | << " 22=" << m.m22() |
1171 | << " 23=" << m.m23() |
1172 | << " 31=" << m.m31() |
1173 | << " 32=" << m.m32() |
1174 | << " 33=" << m.m33() |
1175 | << ')'; |
1176 | |
1177 | return dbg; |
1178 | } |
1179 | #endif |
1180 | |
1181 | /*! |
1182 | \fn QPoint operator*(const QPoint &point, const QTransform &matrix) |
1183 | \relates QTransform |
1184 | |
1185 | This is the same as \a{matrix}.map(\a{point}). |
1186 | |
1187 | \sa QTransform::map() |
1188 | */ |
1189 | QPoint QTransform::map(const QPoint &p) const |
1190 | { |
1191 | qreal fx = p.x(); |
1192 | qreal fy = p.y(); |
1193 | |
1194 | qreal x = 0, y = 0; |
1195 | |
1196 | TransformationType t = inline_type(); |
1197 | switch(t) { |
1198 | case TxNone: |
1199 | x = fx; |
1200 | y = fy; |
1201 | break; |
1202 | case TxTranslate: |
1203 | x = fx + affine._dx; |
1204 | y = fy + affine._dy; |
1205 | break; |
1206 | case TxScale: |
1207 | x = affine._m11 * fx + affine._dx; |
1208 | y = affine._m22 * fy + affine._dy; |
1209 | break; |
1210 | case TxRotate: |
1211 | case TxShear: |
1212 | case TxProject: |
1213 | x = affine._m11 * fx + affine._m21 * fy + affine._dx; |
1214 | y = affine._m12 * fx + affine._m22 * fy + affine._dy; |
1215 | if (t == TxProject) { |
1216 | qreal w = 1./(m_13 * fx + m_23 * fy + m_33); |
1217 | x *= w; |
1218 | y *= w; |
1219 | } |
1220 | } |
1221 | return QPoint(qRound(d: x), qRound(d: y)); |
1222 | } |
1223 | |
1224 | |
1225 | /*! |
1226 | \fn QPointF operator*(const QPointF &point, const QTransform &matrix) |
1227 | \relates QTransform |
1228 | |
1229 | Same as \a{matrix}.map(\a{point}). |
1230 | |
1231 | \sa QTransform::map() |
1232 | */ |
1233 | |
1234 | /*! |
1235 | \overload |
1236 | |
1237 | Creates and returns a QPointF object that is a copy of the given point, |
1238 | \a p, mapped into the coordinate system defined by this matrix. |
1239 | */ |
1240 | QPointF QTransform::map(const QPointF &p) const |
1241 | { |
1242 | qreal fx = p.x(); |
1243 | qreal fy = p.y(); |
1244 | |
1245 | qreal x = 0, y = 0; |
1246 | |
1247 | TransformationType t = inline_type(); |
1248 | switch(t) { |
1249 | case TxNone: |
1250 | x = fx; |
1251 | y = fy; |
1252 | break; |
1253 | case TxTranslate: |
1254 | x = fx + affine._dx; |
1255 | y = fy + affine._dy; |
1256 | break; |
1257 | case TxScale: |
1258 | x = affine._m11 * fx + affine._dx; |
1259 | y = affine._m22 * fy + affine._dy; |
1260 | break; |
1261 | case TxRotate: |
1262 | case TxShear: |
1263 | case TxProject: |
1264 | x = affine._m11 * fx + affine._m21 * fy + affine._dx; |
1265 | y = affine._m12 * fx + affine._m22 * fy + affine._dy; |
1266 | if (t == TxProject) { |
1267 | qreal w = 1./(m_13 * fx + m_23 * fy + m_33); |
1268 | x *= w; |
1269 | y *= w; |
1270 | } |
1271 | } |
1272 | return QPointF(x, y); |
1273 | } |
1274 | |
1275 | /*! |
1276 | \fn QPoint QTransform::map(const QPoint &point) const |
1277 | \overload |
1278 | |
1279 | Creates and returns a QPoint object that is a copy of the given \a |
1280 | point, mapped into the coordinate system defined by this |
1281 | matrix. Note that the transformed coordinates are rounded to the |
1282 | nearest integer. |
1283 | */ |
1284 | |
1285 | /*! |
1286 | \fn QLineF operator*(const QLineF &line, const QTransform &matrix) |
1287 | \relates QTransform |
1288 | |
1289 | This is the same as \a{matrix}.map(\a{line}). |
1290 | |
1291 | \sa QTransform::map() |
1292 | */ |
1293 | |
1294 | /*! |
1295 | \fn QLine operator*(const QLine &line, const QTransform &matrix) |
1296 | \relates QTransform |
1297 | |
1298 | This is the same as \a{matrix}.map(\a{line}). |
1299 | |
1300 | \sa QTransform::map() |
1301 | */ |
1302 | |
1303 | /*! |
1304 | \overload |
1305 | |
1306 | Creates and returns a QLineF object that is a copy of the given line, |
1307 | \a l, mapped into the coordinate system defined by this matrix. |
1308 | */ |
1309 | QLine QTransform::map(const QLine &l) const |
1310 | { |
1311 | qreal fx1 = l.x1(); |
1312 | qreal fy1 = l.y1(); |
1313 | qreal fx2 = l.x2(); |
1314 | qreal fy2 = l.y2(); |
1315 | |
1316 | qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0; |
1317 | |
1318 | TransformationType t = inline_type(); |
1319 | switch(t) { |
1320 | case TxNone: |
1321 | x1 = fx1; |
1322 | y1 = fy1; |
1323 | x2 = fx2; |
1324 | y2 = fy2; |
1325 | break; |
1326 | case TxTranslate: |
1327 | x1 = fx1 + affine._dx; |
1328 | y1 = fy1 + affine._dy; |
1329 | x2 = fx2 + affine._dx; |
1330 | y2 = fy2 + affine._dy; |
1331 | break; |
1332 | case TxScale: |
1333 | x1 = affine._m11 * fx1 + affine._dx; |
1334 | y1 = affine._m22 * fy1 + affine._dy; |
1335 | x2 = affine._m11 * fx2 + affine._dx; |
1336 | y2 = affine._m22 * fy2 + affine._dy; |
1337 | break; |
1338 | case TxRotate: |
1339 | case TxShear: |
1340 | case TxProject: |
1341 | x1 = affine._m11 * fx1 + affine._m21 * fy1 + affine._dx; |
1342 | y1 = affine._m12 * fx1 + affine._m22 * fy1 + affine._dy; |
1343 | x2 = affine._m11 * fx2 + affine._m21 * fy2 + affine._dx; |
1344 | y2 = affine._m12 * fx2 + affine._m22 * fy2 + affine._dy; |
1345 | if (t == TxProject) { |
1346 | qreal w = 1./(m_13 * fx1 + m_23 * fy1 + m_33); |
1347 | x1 *= w; |
1348 | y1 *= w; |
1349 | w = 1./(m_13 * fx2 + m_23 * fy2 + m_33); |
1350 | x2 *= w; |
1351 | y2 *= w; |
1352 | } |
1353 | } |
1354 | return QLine(qRound(d: x1), qRound(d: y1), qRound(d: x2), qRound(d: y2)); |
1355 | } |
1356 | |
1357 | /*! |
1358 | \overload |
1359 | |
1360 | \fn QLineF QTransform::map(const QLineF &line) const |
1361 | |
1362 | Creates and returns a QLine object that is a copy of the given \a |
1363 | line, mapped into the coordinate system defined by this matrix. |
1364 | Note that the transformed coordinates are rounded to the nearest |
1365 | integer. |
1366 | */ |
1367 | |
1368 | QLineF QTransform::map(const QLineF &l) const |
1369 | { |
1370 | qreal fx1 = l.x1(); |
1371 | qreal fy1 = l.y1(); |
1372 | qreal fx2 = l.x2(); |
1373 | qreal fy2 = l.y2(); |
1374 | |
1375 | qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0; |
1376 | |
1377 | TransformationType t = inline_type(); |
1378 | switch(t) { |
1379 | case TxNone: |
1380 | x1 = fx1; |
1381 | y1 = fy1; |
1382 | x2 = fx2; |
1383 | y2 = fy2; |
1384 | break; |
1385 | case TxTranslate: |
1386 | x1 = fx1 + affine._dx; |
1387 | y1 = fy1 + affine._dy; |
1388 | x2 = fx2 + affine._dx; |
1389 | y2 = fy2 + affine._dy; |
1390 | break; |
1391 | case TxScale: |
1392 | x1 = affine._m11 * fx1 + affine._dx; |
1393 | y1 = affine._m22 * fy1 + affine._dy; |
1394 | x2 = affine._m11 * fx2 + affine._dx; |
1395 | y2 = affine._m22 * fy2 + affine._dy; |
1396 | break; |
1397 | case TxRotate: |
1398 | case TxShear: |
1399 | case TxProject: |
1400 | x1 = affine._m11 * fx1 + affine._m21 * fy1 + affine._dx; |
1401 | y1 = affine._m12 * fx1 + affine._m22 * fy1 + affine._dy; |
1402 | x2 = affine._m11 * fx2 + affine._m21 * fy2 + affine._dx; |
1403 | y2 = affine._m12 * fx2 + affine._m22 * fy2 + affine._dy; |
1404 | if (t == TxProject) { |
1405 | qreal w = 1./(m_13 * fx1 + m_23 * fy1 + m_33); |
1406 | x1 *= w; |
1407 | y1 *= w; |
1408 | w = 1./(m_13 * fx2 + m_23 * fy2 + m_33); |
1409 | x2 *= w; |
1410 | y2 *= w; |
1411 | } |
1412 | } |
1413 | return QLineF(x1, y1, x2, y2); |
1414 | } |
1415 | |
1416 | static QPolygonF mapProjective(const QTransform &transform, const QPolygonF &poly) |
1417 | { |
1418 | if (poly.size() == 0) |
1419 | return poly; |
1420 | |
1421 | if (poly.size() == 1) |
1422 | return QPolygonF() << transform.map(p: poly.at(i: 0)); |
1423 | |
1424 | QPainterPath path; |
1425 | path.addPolygon(polygon: poly); |
1426 | |
1427 | path = transform.map(p: path); |
1428 | |
1429 | QPolygonF result; |
1430 | const int elementCount = path.elementCount(); |
1431 | result.reserve(asize: elementCount); |
1432 | for (int i = 0; i < elementCount; ++i) |
1433 | result << path.elementAt(i); |
1434 | return result; |
1435 | } |
1436 | |
1437 | |
1438 | /*! |
1439 | \fn QPolygonF operator *(const QPolygonF &polygon, const QTransform &matrix) |
1440 | \since 4.3 |
1441 | \relates QTransform |
1442 | |
1443 | This is the same as \a{matrix}.map(\a{polygon}). |
1444 | |
1445 | \sa QTransform::map() |
1446 | */ |
1447 | |
1448 | /*! |
1449 | \fn QPolygon operator*(const QPolygon &polygon, const QTransform &matrix) |
1450 | \relates QTransform |
1451 | |
1452 | This is the same as \a{matrix}.map(\a{polygon}). |
1453 | |
1454 | \sa QTransform::map() |
1455 | */ |
1456 | |
1457 | /*! |
1458 | \fn QPolygonF QTransform::map(const QPolygonF &polygon) const |
1459 | \overload |
1460 | |
1461 | Creates and returns a QPolygonF object that is a copy of the given |
1462 | \a polygon, mapped into the coordinate system defined by this |
1463 | matrix. |
1464 | */ |
1465 | QPolygonF QTransform::map(const QPolygonF &a) const |
1466 | { |
1467 | TransformationType t = inline_type(); |
1468 | if (t <= TxTranslate) |
1469 | return a.translated(dx: affine._dx, dy: affine._dy); |
1470 | |
1471 | if (t >= QTransform::TxProject) |
1472 | return mapProjective(transform: *this, poly: a); |
1473 | |
1474 | int size = a.size(); |
1475 | int i; |
1476 | QPolygonF p(size); |
1477 | const QPointF *da = a.constData(); |
1478 | QPointF *dp = p.data(); |
1479 | |
1480 | for(i = 0; i < size; ++i) { |
1481 | MAP(da[i].xp, da[i].yp, dp[i].xp, dp[i].yp); |
1482 | } |
1483 | return p; |
1484 | } |
1485 | |
1486 | /*! |
1487 | \fn QPolygon QTransform::map(const QPolygon &polygon) const |
1488 | \overload |
1489 | |
1490 | Creates and returns a QPolygon object that is a copy of the given |
1491 | \a polygon, mapped into the coordinate system defined by this |
1492 | matrix. Note that the transformed coordinates are rounded to the |
1493 | nearest integer. |
1494 | */ |
1495 | QPolygon QTransform::map(const QPolygon &a) const |
1496 | { |
1497 | TransformationType t = inline_type(); |
1498 | if (t <= TxTranslate) |
1499 | return a.translated(dx: qRound(d: affine._dx), dy: qRound(d: affine._dy)); |
1500 | |
1501 | if (t >= QTransform::TxProject) |
1502 | return mapProjective(transform: *this, poly: QPolygonF(a)).toPolygon(); |
1503 | |
1504 | int size = a.size(); |
1505 | int i; |
1506 | QPolygon p(size); |
1507 | const QPoint *da = a.constData(); |
1508 | QPoint *dp = p.data(); |
1509 | |
1510 | for(i = 0; i < size; ++i) { |
1511 | qreal nx = 0, ny = 0; |
1512 | MAP(da[i].xp, da[i].yp, nx, ny); |
1513 | dp[i].xp = qRound(d: nx); |
1514 | dp[i].yp = qRound(d: ny); |
1515 | } |
1516 | return p; |
1517 | } |
1518 | |
1519 | /*! |
1520 | \fn QRegion operator*(const QRegion ®ion, const QTransform &matrix) |
1521 | \relates QTransform |
1522 | |
1523 | This is the same as \a{matrix}.map(\a{region}). |
1524 | |
1525 | \sa QTransform::map() |
1526 | */ |
1527 | |
1528 | extern QPainterPath qt_regionToPath(const QRegion ®ion); |
1529 | |
1530 | /*! |
1531 | \fn QRegion QTransform::map(const QRegion ®ion) const |
1532 | \overload |
1533 | |
1534 | Creates and returns a QRegion object that is a copy of the given |
1535 | \a region, mapped into the coordinate system defined by this matrix. |
1536 | |
1537 | Calling this method can be rather expensive if rotations or |
1538 | shearing are used. |
1539 | */ |
1540 | QRegion QTransform::map(const QRegion &r) const |
1541 | { |
1542 | TransformationType t = inline_type(); |
1543 | if (t == TxNone) |
1544 | return r; |
1545 | |
1546 | if (t == TxTranslate) { |
1547 | QRegion copy(r); |
1548 | copy.translate(dx: qRound(d: affine._dx), dy: qRound(d: affine._dy)); |
1549 | return copy; |
1550 | } |
1551 | |
1552 | if (t == TxScale) { |
1553 | QRegion res; |
1554 | if (m11() < 0 || m22() < 0) { |
1555 | for (const QRect &rect : r) |
1556 | res += mapRect(QRectF(rect)).toRect(); |
1557 | } else { |
1558 | QVarLengthArray<QRect, 32> rects; |
1559 | rects.reserve(asize: r.rectCount()); |
1560 | for (const QRect &rect : r) { |
1561 | QRect nr = mapRect(QRectF(rect)).toRect(); |
1562 | if (!nr.isEmpty()) |
1563 | rects.append(t: nr); |
1564 | } |
1565 | res.setRects(rect: rects.constData(), num: rects.count()); |
1566 | } |
1567 | return res; |
1568 | } |
1569 | |
1570 | QPainterPath p = map(p: qt_regionToPath(region: r)); |
1571 | return p.toFillPolygon(matrix: QTransform()).toPolygon(); |
1572 | } |
1573 | |
1574 | struct QHomogeneousCoordinate |
1575 | { |
1576 | qreal x; |
1577 | qreal y; |
1578 | qreal w; |
1579 | |
1580 | QHomogeneousCoordinate() {} |
1581 | QHomogeneousCoordinate(qreal x_, qreal y_, qreal w_) : x(x_), y(y_), w(w_) {} |
1582 | |
1583 | const QPointF toPoint() const { |
1584 | qreal iw = 1. / w; |
1585 | return QPointF(x * iw, y * iw); |
1586 | } |
1587 | }; |
1588 | |
1589 | static inline QHomogeneousCoordinate mapHomogeneous(const QTransform &transform, const QPointF &p) |
1590 | { |
1591 | QHomogeneousCoordinate c; |
1592 | c.x = transform.m11() * p.x() + transform.m21() * p.y() + transform.m31(); |
1593 | c.y = transform.m12() * p.x() + transform.m22() * p.y() + transform.m32(); |
1594 | c.w = transform.m13() * p.x() + transform.m23() * p.y() + transform.m33(); |
1595 | return c; |
1596 | } |
1597 | |
1598 | static inline bool lineTo_clipped(QPainterPath &path, const QTransform &transform, const QPointF &a, const QPointF &b, |
1599 | bool needsMoveTo, bool needsLineTo = true) |
1600 | { |
1601 | QHomogeneousCoordinate ha = mapHomogeneous(transform, p: a); |
1602 | QHomogeneousCoordinate hb = mapHomogeneous(transform, p: b); |
1603 | |
1604 | if (ha.w < Q_NEAR_CLIP && hb.w < Q_NEAR_CLIP) |
1605 | return false; |
1606 | |
1607 | if (hb.w < Q_NEAR_CLIP) { |
1608 | const qreal t = (Q_NEAR_CLIP - hb.w) / (ha.w - hb.w); |
1609 | |
1610 | hb.x += (ha.x - hb.x) * t; |
1611 | hb.y += (ha.y - hb.y) * t; |
1612 | hb.w = qreal(Q_NEAR_CLIP); |
1613 | } else if (ha.w < Q_NEAR_CLIP) { |
1614 | const qreal t = (Q_NEAR_CLIP - ha.w) / (hb.w - ha.w); |
1615 | |
1616 | ha.x += (hb.x - ha.x) * t; |
1617 | ha.y += (hb.y - ha.y) * t; |
1618 | ha.w = qreal(Q_NEAR_CLIP); |
1619 | |
1620 | const QPointF p = ha.toPoint(); |
1621 | if (needsMoveTo) { |
1622 | path.moveTo(p); |
1623 | needsMoveTo = false; |
1624 | } else { |
1625 | path.lineTo(p); |
1626 | } |
1627 | } |
1628 | |
1629 | if (needsMoveTo) |
1630 | path.moveTo(p: ha.toPoint()); |
1631 | |
1632 | if (needsLineTo) |
1633 | path.lineTo(p: hb.toPoint()); |
1634 | |
1635 | return true; |
1636 | } |
1637 | Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale); |
1638 | |
1639 | static inline bool cubicTo_clipped(QPainterPath &path, const QTransform &transform, const QPointF &a, const QPointF &b, const QPointF &c, const QPointF &d, bool needsMoveTo) |
1640 | { |
1641 | // Convert projective xformed curves to line |
1642 | // segments so they can be transformed more accurately |
1643 | |
1644 | qreal scale; |
1645 | qt_scaleForTransform(transform, scale: &scale); |
1646 | |
1647 | qreal curveThreshold = scale == 0 ? qreal(0.25) : (qreal(0.25) / scale); |
1648 | |
1649 | QPolygonF segment = QBezier::fromPoints(p1: a, p2: b, p3: c, p4: d).toPolygon(bezier_flattening_threshold: curveThreshold); |
1650 | |
1651 | for (int i = 0; i < segment.size() - 1; ++i) |
1652 | if (lineTo_clipped(path, transform, a: segment.at(i), b: segment.at(i: i+1), needsMoveTo)) |
1653 | needsMoveTo = false; |
1654 | |
1655 | return !needsMoveTo; |
1656 | } |
1657 | |
1658 | static QPainterPath mapProjective(const QTransform &transform, const QPainterPath &path) |
1659 | { |
1660 | QPainterPath result; |
1661 | |
1662 | QPointF last; |
1663 | QPointF lastMoveTo; |
1664 | bool needsMoveTo = true; |
1665 | for (int i = 0; i < path.elementCount(); ++i) { |
1666 | switch (path.elementAt(i).type) { |
1667 | case QPainterPath::MoveToElement: |
1668 | if (i > 0 && lastMoveTo != last) |
1669 | lineTo_clipped(path&: result, transform, a: last, b: lastMoveTo, needsMoveTo); |
1670 | |
1671 | lastMoveTo = path.elementAt(i); |
1672 | last = path.elementAt(i); |
1673 | needsMoveTo = true; |
1674 | break; |
1675 | case QPainterPath::LineToElement: |
1676 | if (lineTo_clipped(path&: result, transform, a: last, b: path.elementAt(i), needsMoveTo)) |
1677 | needsMoveTo = false; |
1678 | last = path.elementAt(i); |
1679 | break; |
1680 | case QPainterPath::CurveToElement: |
1681 | if (cubicTo_clipped(path&: result, transform, a: last, b: path.elementAt(i), c: path.elementAt(i: i+1), d: path.elementAt(i: i+2), needsMoveTo)) |
1682 | needsMoveTo = false; |
1683 | i += 2; |
1684 | last = path.elementAt(i); |
1685 | break; |
1686 | default: |
1687 | Q_ASSERT(false); |
1688 | } |
1689 | } |
1690 | |
1691 | if (path.elementCount() > 0 && lastMoveTo != last) |
1692 | lineTo_clipped(path&: result, transform, a: last, b: lastMoveTo, needsMoveTo, needsLineTo: false); |
1693 | |
1694 | result.setFillRule(path.fillRule()); |
1695 | return result; |
1696 | } |
1697 | |
1698 | /*! |
1699 | \fn QPainterPath operator *(const QPainterPath &path, const QTransform &matrix) |
1700 | \since 4.3 |
1701 | \relates QTransform |
1702 | |
1703 | This is the same as \a{matrix}.map(\a{path}). |
1704 | |
1705 | \sa QTransform::map() |
1706 | */ |
1707 | |
1708 | /*! |
1709 | \overload |
1710 | |
1711 | Creates and returns a QPainterPath object that is a copy of the |
1712 | given \a path, mapped into the coordinate system defined by this |
1713 | matrix. |
1714 | */ |
1715 | QPainterPath QTransform::map(const QPainterPath &path) const |
1716 | { |
1717 | TransformationType t = inline_type(); |
1718 | if (t == TxNone || path.elementCount() == 0) |
1719 | return path; |
1720 | |
1721 | if (t >= TxProject) |
1722 | return mapProjective(transform: *this, path); |
1723 | |
1724 | QPainterPath copy = path; |
1725 | |
1726 | if (t == TxTranslate) { |
1727 | copy.translate(dx: affine._dx, dy: affine._dy); |
1728 | } else { |
1729 | copy.detach(); |
1730 | // Full xform |
1731 | for (int i=0; i<path.elementCount(); ++i) { |
1732 | QPainterPath::Element &e = copy.d_ptr->elements[i]; |
1733 | MAP(e.x, e.y, e.x, e.y); |
1734 | } |
1735 | } |
1736 | |
1737 | return copy; |
1738 | } |
1739 | |
1740 | /*! |
1741 | \fn QPolygon QTransform::mapToPolygon(const QRect &rectangle) const |
1742 | |
1743 | Creates and returns a QPolygon representation of the given \a |
1744 | rectangle, mapped into the coordinate system defined by this |
1745 | matrix. |
1746 | |
1747 | The rectangle's coordinates are transformed using the following |
1748 | formulas: |
1749 | |
1750 | \snippet code/src_gui_painting_qtransform.cpp 1 |
1751 | |
1752 | Polygons and rectangles behave slightly differently when |
1753 | transformed (due to integer rounding), so |
1754 | \c{matrix.map(QPolygon(rectangle))} is not always the same as |
1755 | \c{matrix.mapToPolygon(rectangle)}. |
1756 | |
1757 | \sa mapRect(), {QTransform#Basic Matrix Operations}{Basic Matrix |
1758 | Operations} |
1759 | */ |
1760 | QPolygon QTransform::mapToPolygon(const QRect &rect) const |
1761 | { |
1762 | TransformationType t = inline_type(); |
1763 | |
1764 | QPolygon a(4); |
1765 | qreal x[4] = { 0, 0, 0, 0 }, y[4] = { 0, 0, 0, 0 }; |
1766 | if (t <= TxScale) { |
1767 | x[0] = affine._m11*rect.x() + affine._dx; |
1768 | y[0] = affine._m22*rect.y() + affine._dy; |
1769 | qreal w = affine._m11*rect.width(); |
1770 | qreal h = affine._m22*rect.height(); |
1771 | if (w < 0) { |
1772 | w = -w; |
1773 | x[0] -= w; |
1774 | } |
1775 | if (h < 0) { |
1776 | h = -h; |
1777 | y[0] -= h; |
1778 | } |
1779 | x[1] = x[0]+w; |
1780 | x[2] = x[1]; |
1781 | x[3] = x[0]; |
1782 | y[1] = y[0]; |
1783 | y[2] = y[0]+h; |
1784 | y[3] = y[2]; |
1785 | } else { |
1786 | qreal right = rect.x() + rect.width(); |
1787 | qreal bottom = rect.y() + rect.height(); |
1788 | MAP(rect.x(), rect.y(), x[0], y[0]); |
1789 | MAP(right, rect.y(), x[1], y[1]); |
1790 | MAP(right, bottom, x[2], y[2]); |
1791 | MAP(rect.x(), bottom, x[3], y[3]); |
1792 | } |
1793 | |
1794 | // all coordinates are correctly, tranform to a pointarray |
1795 | // (rounding to the next integer) |
1796 | a.setPoints(nPoints: 4, firstx: qRound(d: x[0]), firsty: qRound(d: y[0]), |
1797 | qRound(d: x[1]), qRound(d: y[1]), |
1798 | qRound(d: x[2]), qRound(d: y[2]), |
1799 | qRound(d: x[3]), qRound(d: y[3])); |
1800 | return a; |
1801 | } |
1802 | |
1803 | /*! |
1804 | Creates a transformation matrix, \a trans, that maps a unit square |
1805 | to a four-sided polygon, \a quad. Returns \c true if the transformation |
1806 | is constructed or false if such a transformation does not exist. |
1807 | |
1808 | \sa quadToSquare(), quadToQuad() |
1809 | */ |
1810 | bool QTransform::squareToQuad(const QPolygonF &quad, QTransform &trans) |
1811 | { |
1812 | if (quad.count() != 4) |
1813 | return false; |
1814 | |
1815 | qreal dx0 = quad[0].x(); |
1816 | qreal dx1 = quad[1].x(); |
1817 | qreal dx2 = quad[2].x(); |
1818 | qreal dx3 = quad[3].x(); |
1819 | |
1820 | qreal dy0 = quad[0].y(); |
1821 | qreal dy1 = quad[1].y(); |
1822 | qreal dy2 = quad[2].y(); |
1823 | qreal dy3 = quad[3].y(); |
1824 | |
1825 | double ax = dx0 - dx1 + dx2 - dx3; |
1826 | double ay = dy0 - dy1 + dy2 - dy3; |
1827 | |
1828 | if (!ax && !ay) { //afine transform |
1829 | trans.setMatrix(m11: dx1 - dx0, m12: dy1 - dy0, m13: 0, |
1830 | m21: dx2 - dx1, m22: dy2 - dy1, m23: 0, |
1831 | m31: dx0, m32: dy0, m33: 1); |
1832 | } else { |
1833 | double ax1 = dx1 - dx2; |
1834 | double ax2 = dx3 - dx2; |
1835 | double ay1 = dy1 - dy2; |
1836 | double ay2 = dy3 - dy2; |
1837 | |
1838 | /*determinants */ |
1839 | double gtop = ax * ay2 - ax2 * ay; |
1840 | double htop = ax1 * ay - ax * ay1; |
1841 | double bottom = ax1 * ay2 - ax2 * ay1; |
1842 | |
1843 | double a, b, c, d, e, f, g, h; /*i is always 1*/ |
1844 | |
1845 | if (!bottom) |
1846 | return false; |
1847 | |
1848 | g = gtop/bottom; |
1849 | h = htop/bottom; |
1850 | |
1851 | a = dx1 - dx0 + g * dx1; |
1852 | b = dx3 - dx0 + h * dx3; |
1853 | c = dx0; |
1854 | d = dy1 - dy0 + g * dy1; |
1855 | e = dy3 - dy0 + h * dy3; |
1856 | f = dy0; |
1857 | |
1858 | trans.setMatrix(m11: a, m12: d, m13: g, |
1859 | m21: b, m22: e, m23: h, |
1860 | m31: c, m32: f, m33: 1.0); |
1861 | } |
1862 | |
1863 | return true; |
1864 | } |
1865 | |
1866 | /*! |
1867 | \fn bool QTransform::quadToSquare(const QPolygonF &quad, QTransform &trans) |
1868 | |
1869 | Creates a transformation matrix, \a trans, that maps a four-sided polygon, |
1870 | \a quad, to a unit square. Returns \c true if the transformation is constructed |
1871 | or false if such a transformation does not exist. |
1872 | |
1873 | \sa squareToQuad(), quadToQuad() |
1874 | */ |
1875 | bool QTransform::quadToSquare(const QPolygonF &quad, QTransform &trans) |
1876 | { |
1877 | if (!squareToQuad(quad, trans)) |
1878 | return false; |
1879 | |
1880 | bool invertible = false; |
1881 | trans = trans.inverted(invertible: &invertible); |
1882 | |
1883 | return invertible; |
1884 | } |
1885 | |
1886 | /*! |
1887 | Creates a transformation matrix, \a trans, that maps a four-sided |
1888 | polygon, \a one, to another four-sided polygon, \a two. |
1889 | Returns \c true if the transformation is possible; otherwise returns |
1890 | false. |
1891 | |
1892 | This is a convenience method combining quadToSquare() and |
1893 | squareToQuad() methods. It allows the input quad to be |
1894 | transformed into any other quad. |
1895 | |
1896 | \sa squareToQuad(), quadToSquare() |
1897 | */ |
1898 | bool QTransform::quadToQuad(const QPolygonF &one, |
1899 | const QPolygonF &two, |
1900 | QTransform &trans) |
1901 | { |
1902 | QTransform stq; |
1903 | if (!quadToSquare(quad: one, trans)) |
1904 | return false; |
1905 | if (!squareToQuad(quad: two, trans&: stq)) |
1906 | return false; |
1907 | trans *= stq; |
1908 | //qDebug()<<"Final = "<<trans; |
1909 | return true; |
1910 | } |
1911 | |
1912 | /*! |
1913 | Sets the matrix elements to the specified values, \a m11, |
1914 | \a m12, \a m13 \a m21, \a m22, \a m23 \a m31, \a m32 and |
1915 | \a m33. Note that this function replaces the previous values. |
1916 | QTransform provides the translate(), rotate(), scale() and shear() |
1917 | convenience functions to manipulate the various matrix elements |
1918 | based on the currently defined coordinate system. |
1919 | |
1920 | \sa QTransform() |
1921 | */ |
1922 | |
1923 | void QTransform::setMatrix(qreal m11, qreal m12, qreal m13, |
1924 | qreal m21, qreal m22, qreal m23, |
1925 | qreal m31, qreal m32, qreal m33) |
1926 | { |
1927 | affine._m11 = m11; affine._m12 = m12; m_13 = m13; |
1928 | affine._m21 = m21; affine._m22 = m22; m_23 = m23; |
1929 | affine._dx = m31; affine._dy = m32; m_33 = m33; |
1930 | m_type = TxNone; |
1931 | m_dirty = TxProject; |
1932 | } |
1933 | |
1934 | static inline bool needsPerspectiveClipping(const QRectF &rect, const QTransform &transform) |
1935 | { |
1936 | const qreal wx = qMin(a: transform.m13() * rect.left(), b: transform.m13() * rect.right()); |
1937 | const qreal wy = qMin(a: transform.m23() * rect.top(), b: transform.m23() * rect.bottom()); |
1938 | |
1939 | return wx + wy + transform.m33() < Q_NEAR_CLIP; |
1940 | } |
1941 | |
1942 | QRect QTransform::mapRect(const QRect &rect) const |
1943 | { |
1944 | TransformationType t = inline_type(); |
1945 | if (t <= TxTranslate) |
1946 | return rect.translated(dx: qRound(d: affine._dx), dy: qRound(d: affine._dy)); |
1947 | |
1948 | if (t <= TxScale) { |
1949 | int x = qRound(d: affine._m11*rect.x() + affine._dx); |
1950 | int y = qRound(d: affine._m22*rect.y() + affine._dy); |
1951 | int w = qRound(d: affine._m11*rect.width()); |
1952 | int h = qRound(d: affine._m22*rect.height()); |
1953 | if (w < 0) { |
1954 | w = -w; |
1955 | x -= w; |
1956 | } |
1957 | if (h < 0) { |
1958 | h = -h; |
1959 | y -= h; |
1960 | } |
1961 | return QRect(x, y, w, h); |
1962 | } else if (t < TxProject || !needsPerspectiveClipping(rect, transform: *this)) { |
1963 | // see mapToPolygon for explanations of the algorithm. |
1964 | qreal x = 0, y = 0; |
1965 | MAP(rect.left(), rect.top(), x, y); |
1966 | qreal xmin = x; |
1967 | qreal ymin = y; |
1968 | qreal xmax = x; |
1969 | qreal ymax = y; |
1970 | MAP(rect.right() + 1, rect.top(), x, y); |
1971 | xmin = qMin(a: xmin, b: x); |
1972 | ymin = qMin(a: ymin, b: y); |
1973 | xmax = qMax(a: xmax, b: x); |
1974 | ymax = qMax(a: ymax, b: y); |
1975 | MAP(rect.right() + 1, rect.bottom() + 1, x, y); |
1976 | xmin = qMin(a: xmin, b: x); |
1977 | ymin = qMin(a: ymin, b: y); |
1978 | xmax = qMax(a: xmax, b: x); |
1979 | ymax = qMax(a: ymax, b: y); |
1980 | MAP(rect.left(), rect.bottom() + 1, x, y); |
1981 | xmin = qMin(a: xmin, b: x); |
1982 | ymin = qMin(a: ymin, b: y); |
1983 | xmax = qMax(a: xmax, b: x); |
1984 | ymax = qMax(a: ymax, b: y); |
1985 | return QRect(qRound(d: xmin), qRound(d: ymin), qRound(d: xmax)-qRound(d: xmin), qRound(d: ymax)-qRound(d: ymin)); |
1986 | } else { |
1987 | QPainterPath path; |
1988 | path.addRect(rect); |
1989 | return map(path).boundingRect().toRect(); |
1990 | } |
1991 | } |
1992 | |
1993 | /*! |
1994 | \fn QRectF QTransform::mapRect(const QRectF &rectangle) const |
1995 | |
1996 | Creates and returns a QRectF object that is a copy of the given \a |
1997 | rectangle, mapped into the coordinate system defined by this |
1998 | matrix. |
1999 | |
2000 | The rectangle's coordinates are transformed using the following |
2001 | formulas: |
2002 | |
2003 | \snippet code/src_gui_painting_qtransform.cpp 2 |
2004 | |
2005 | If rotation or shearing has been specified, this function returns |
2006 | the \e bounding rectangle. To retrieve the exact region the given |
2007 | \a rectangle maps to, use the mapToPolygon() function instead. |
2008 | |
2009 | \sa mapToPolygon(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2010 | Operations} |
2011 | */ |
2012 | QRectF QTransform::mapRect(const QRectF &rect) const |
2013 | { |
2014 | TransformationType t = inline_type(); |
2015 | if (t <= TxTranslate) |
2016 | return rect.translated(dx: affine._dx, dy: affine._dy); |
2017 | |
2018 | if (t <= TxScale) { |
2019 | qreal x = affine._m11*rect.x() + affine._dx; |
2020 | qreal y = affine._m22*rect.y() + affine._dy; |
2021 | qreal w = affine._m11*rect.width(); |
2022 | qreal h = affine._m22*rect.height(); |
2023 | if (w < 0) { |
2024 | w = -w; |
2025 | x -= w; |
2026 | } |
2027 | if (h < 0) { |
2028 | h = -h; |
2029 | y -= h; |
2030 | } |
2031 | return QRectF(x, y, w, h); |
2032 | } else if (t < TxProject || !needsPerspectiveClipping(rect, transform: *this)) { |
2033 | qreal x = 0, y = 0; |
2034 | MAP(rect.x(), rect.y(), x, y); |
2035 | qreal xmin = x; |
2036 | qreal ymin = y; |
2037 | qreal xmax = x; |
2038 | qreal ymax = y; |
2039 | MAP(rect.x() + rect.width(), rect.y(), x, y); |
2040 | xmin = qMin(a: xmin, b: x); |
2041 | ymin = qMin(a: ymin, b: y); |
2042 | xmax = qMax(a: xmax, b: x); |
2043 | ymax = qMax(a: ymax, b: y); |
2044 | MAP(rect.x() + rect.width(), rect.y() + rect.height(), x, y); |
2045 | xmin = qMin(a: xmin, b: x); |
2046 | ymin = qMin(a: ymin, b: y); |
2047 | xmax = qMax(a: xmax, b: x); |
2048 | ymax = qMax(a: ymax, b: y); |
2049 | MAP(rect.x(), rect.y() + rect.height(), x, y); |
2050 | xmin = qMin(a: xmin, b: x); |
2051 | ymin = qMin(a: ymin, b: y); |
2052 | xmax = qMax(a: xmax, b: x); |
2053 | ymax = qMax(a: ymax, b: y); |
2054 | return QRectF(xmin, ymin, xmax-xmin, ymax - ymin); |
2055 | } else { |
2056 | QPainterPath path; |
2057 | path.addRect(rect); |
2058 | return map(path).boundingRect(); |
2059 | } |
2060 | } |
2061 | |
2062 | /*! |
2063 | \fn QRect QTransform::mapRect(const QRect &rectangle) const |
2064 | \overload |
2065 | |
2066 | Creates and returns a QRect object that is a copy of the given \a |
2067 | rectangle, mapped into the coordinate system defined by this |
2068 | matrix. Note that the transformed coordinates are rounded to the |
2069 | nearest integer. |
2070 | */ |
2071 | |
2072 | /*! |
2073 | Maps the given coordinates \a x and \a y into the coordinate |
2074 | system defined by this matrix. The resulting values are put in *\a |
2075 | tx and *\a ty, respectively. |
2076 | |
2077 | The coordinates are transformed using the following formulas: |
2078 | |
2079 | \snippet code/src_gui_painting_qtransform.cpp 3 |
2080 | |
2081 | The point (x, y) is the original point, and (x', y') is the |
2082 | transformed point. |
2083 | |
2084 | \sa {QTransform#Basic Matrix Operations}{Basic Matrix Operations} |
2085 | */ |
2086 | void QTransform::map(qreal x, qreal y, qreal *tx, qreal *ty) const |
2087 | { |
2088 | TransformationType t = inline_type(); |
2089 | MAP(x, y, *tx, *ty); |
2090 | } |
2091 | |
2092 | /*! |
2093 | \overload |
2094 | |
2095 | Maps the given coordinates \a x and \a y into the coordinate |
2096 | system defined by this matrix. The resulting values are put in *\a |
2097 | tx and *\a ty, respectively. Note that the transformed coordinates |
2098 | are rounded to the nearest integer. |
2099 | */ |
2100 | void QTransform::map(int x, int y, int *tx, int *ty) const |
2101 | { |
2102 | TransformationType t = inline_type(); |
2103 | qreal fx = 0, fy = 0; |
2104 | MAP(x, y, fx, fy); |
2105 | *tx = qRound(d: fx); |
2106 | *ty = qRound(d: fy); |
2107 | } |
2108 | |
2109 | #if QT_DEPRECATED_SINCE(5, 15) |
2110 | /*! |
2111 | \obsolete |
2112 | Returns the QTransform as an affine matrix. |
2113 | |
2114 | \warning If a perspective transformation has been specified, |
2115 | then the conversion will cause loss of data. |
2116 | */ |
2117 | const QMatrix &QTransform::toAffine() const |
2118 | { |
2119 | return affine; |
2120 | } |
2121 | #endif // QT_DEPRECATED_SINCE(5, 15) |
2122 | |
2123 | /*! |
2124 | Returns the transformation type of this matrix. |
2125 | |
2126 | The transformation type is the highest enumeration value |
2127 | capturing all of the matrix's transformations. For example, |
2128 | if the matrix both scales and shears, the type would be \c TxShear, |
2129 | because \c TxShear has a higher enumeration value than \c TxScale. |
2130 | |
2131 | Knowing the transformation type of a matrix is useful for optimization: |
2132 | you can often handle specific types more optimally than handling |
2133 | the generic case. |
2134 | */ |
2135 | QTransform::TransformationType QTransform::type() const |
2136 | { |
2137 | if(m_dirty == TxNone || m_dirty < m_type) |
2138 | return static_cast<TransformationType>(m_type); |
2139 | |
2140 | switch (static_cast<TransformationType>(m_dirty)) { |
2141 | case TxProject: |
2142 | if (!qFuzzyIsNull(d: m_13) || !qFuzzyIsNull(d: m_23) || !qFuzzyIsNull(d: m_33 - 1)) { |
2143 | m_type = TxProject; |
2144 | break; |
2145 | } |
2146 | Q_FALLTHROUGH(); |
2147 | case TxShear: |
2148 | case TxRotate: |
2149 | if (!qFuzzyIsNull(d: affine._m12) || !qFuzzyIsNull(d: affine._m21)) { |
2150 | const qreal dot = affine._m11 * affine._m21 + affine._m12 * affine._m22; |
2151 | if (qFuzzyIsNull(d: dot)) |
2152 | m_type = TxRotate; |
2153 | else |
2154 | m_type = TxShear; |
2155 | break; |
2156 | } |
2157 | Q_FALLTHROUGH(); |
2158 | case TxScale: |
2159 | if (!qFuzzyIsNull(d: affine._m11 - 1) || !qFuzzyIsNull(d: affine._m22 - 1)) { |
2160 | m_type = TxScale; |
2161 | break; |
2162 | } |
2163 | Q_FALLTHROUGH(); |
2164 | case TxTranslate: |
2165 | if (!qFuzzyIsNull(d: affine._dx) || !qFuzzyIsNull(d: affine._dy)) { |
2166 | m_type = TxTranslate; |
2167 | break; |
2168 | } |
2169 | Q_FALLTHROUGH(); |
2170 | case TxNone: |
2171 | m_type = TxNone; |
2172 | break; |
2173 | } |
2174 | |
2175 | m_dirty = TxNone; |
2176 | return static_cast<TransformationType>(m_type); |
2177 | } |
2178 | |
2179 | /*! |
2180 | |
2181 | Returns the transform as a QVariant. |
2182 | */ |
2183 | QTransform::operator QVariant() const |
2184 | { |
2185 | return QVariant(QMetaType::QTransform, this); |
2186 | } |
2187 | |
2188 | |
2189 | /*! |
2190 | \fn bool QTransform::isInvertible() const |
2191 | |
2192 | Returns \c true if the matrix is invertible, otherwise returns \c false. |
2193 | |
2194 | \sa inverted() |
2195 | */ |
2196 | |
2197 | #if QT_DEPRECATED_SINCE(5, 13) |
2198 | /*! |
2199 | \fn qreal QTransform::det() const |
2200 | \obsolete |
2201 | |
2202 | Returns the matrix's determinant. Use determinant() instead. |
2203 | */ |
2204 | #endif |
2205 | |
2206 | /*! |
2207 | \fn qreal QTransform::m11() const |
2208 | |
2209 | Returns the horizontal scaling factor. |
2210 | |
2211 | \sa scale(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2212 | Operations} |
2213 | */ |
2214 | |
2215 | /*! |
2216 | \fn qreal QTransform::m12() const |
2217 | |
2218 | Returns the vertical shearing factor. |
2219 | |
2220 | \sa shear(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2221 | Operations} |
2222 | */ |
2223 | |
2224 | /*! |
2225 | \fn qreal QTransform::m21() const |
2226 | |
2227 | Returns the horizontal shearing factor. |
2228 | |
2229 | \sa shear(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2230 | Operations} |
2231 | */ |
2232 | |
2233 | /*! |
2234 | \fn qreal QTransform::m22() const |
2235 | |
2236 | Returns the vertical scaling factor. |
2237 | |
2238 | \sa scale(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2239 | Operations} |
2240 | */ |
2241 | |
2242 | /*! |
2243 | \fn qreal QTransform::dx() const |
2244 | |
2245 | Returns the horizontal translation factor. |
2246 | |
2247 | \sa m31(), translate(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2248 | Operations} |
2249 | */ |
2250 | |
2251 | /*! |
2252 | \fn qreal QTransform::dy() const |
2253 | |
2254 | Returns the vertical translation factor. |
2255 | |
2256 | \sa translate(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2257 | Operations} |
2258 | */ |
2259 | |
2260 | |
2261 | /*! |
2262 | \fn qreal QTransform::m13() const |
2263 | |
2264 | Returns the horizontal projection factor. |
2265 | |
2266 | \sa translate(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2267 | Operations} |
2268 | */ |
2269 | |
2270 | |
2271 | /*! |
2272 | \fn qreal QTransform::m23() const |
2273 | |
2274 | Returns the vertical projection factor. |
2275 | |
2276 | \sa translate(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2277 | Operations} |
2278 | */ |
2279 | |
2280 | /*! |
2281 | \fn qreal QTransform::m31() const |
2282 | |
2283 | Returns the horizontal translation factor. |
2284 | |
2285 | \sa dx(), translate(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2286 | Operations} |
2287 | */ |
2288 | |
2289 | /*! |
2290 | \fn qreal QTransform::m32() const |
2291 | |
2292 | Returns the vertical translation factor. |
2293 | |
2294 | \sa dy(), translate(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2295 | Operations} |
2296 | */ |
2297 | |
2298 | /*! |
2299 | \fn qreal QTransform::m33() const |
2300 | |
2301 | Returns the division factor. |
2302 | |
2303 | \sa translate(), {QTransform#Basic Matrix Operations}{Basic Matrix |
2304 | Operations} |
2305 | */ |
2306 | |
2307 | /*! |
2308 | \fn qreal QTransform::determinant() const |
2309 | |
2310 | Returns the matrix's determinant. |
2311 | */ |
2312 | |
2313 | /*! |
2314 | \fn bool QTransform::isIdentity() const |
2315 | |
2316 | Returns \c true if the matrix is the identity matrix, otherwise |
2317 | returns \c false. |
2318 | |
2319 | \sa reset() |
2320 | */ |
2321 | |
2322 | /*! |
2323 | \fn bool QTransform::isAffine() const |
2324 | |
2325 | Returns \c true if the matrix represent an affine transformation, |
2326 | otherwise returns \c false. |
2327 | */ |
2328 | |
2329 | /*! |
2330 | \fn bool QTransform::isScaling() const |
2331 | |
2332 | Returns \c true if the matrix represents a scaling |
2333 | transformation, otherwise returns \c false. |
2334 | |
2335 | \sa reset() |
2336 | */ |
2337 | |
2338 | /*! |
2339 | \fn bool QTransform::isRotating() const |
2340 | |
2341 | Returns \c true if the matrix represents some kind of a |
2342 | rotating transformation, otherwise returns \c false. |
2343 | |
2344 | \note A rotation transformation of 180 degrees and/or 360 degrees is treated as a scaling transformation. |
2345 | |
2346 | \sa reset() |
2347 | */ |
2348 | |
2349 | /*! |
2350 | \fn bool QTransform::isTranslating() const |
2351 | |
2352 | Returns \c true if the matrix represents a translating |
2353 | transformation, otherwise returns \c false. |
2354 | |
2355 | \sa reset() |
2356 | */ |
2357 | |
2358 | /*! |
2359 | \fn bool qFuzzyCompare(const QTransform& t1, const QTransform& t2) |
2360 | |
2361 | \relates QTransform |
2362 | \since 4.6 |
2363 | |
2364 | Returns \c true if \a t1 and \a t2 are equal, allowing for a small |
2365 | fuzziness factor for floating-point comparisons; false otherwise. |
2366 | */ |
2367 | |
2368 | |
2369 | // returns true if the transform is uniformly scaling |
2370 | // (same scale in x and y direction) |
2371 | // scale is set to the max of x and y scaling factors |
2372 | Q_GUI_EXPORT |
2373 | bool qt_scaleForTransform(const QTransform &transform, qreal *scale) |
2374 | { |
2375 | const QTransform::TransformationType type = transform.type(); |
2376 | if (type <= QTransform::TxTranslate) { |
2377 | if (scale) |
2378 | *scale = 1; |
2379 | return true; |
2380 | } else if (type == QTransform::TxScale) { |
2381 | const qreal xScale = qAbs(t: transform.m11()); |
2382 | const qreal yScale = qAbs(t: transform.m22()); |
2383 | if (scale) |
2384 | *scale = qMax(a: xScale, b: yScale); |
2385 | return qFuzzyCompare(p1: xScale, p2: yScale); |
2386 | } |
2387 | |
2388 | // rotate then scale: compare columns |
2389 | const qreal xScale1 = transform.m11() * transform.m11() |
2390 | + transform.m21() * transform.m21(); |
2391 | const qreal yScale1 = transform.m12() * transform.m12() |
2392 | + transform.m22() * transform.m22(); |
2393 | |
2394 | // scale then rotate: compare rows |
2395 | const qreal xScale2 = transform.m11() * transform.m11() |
2396 | + transform.m12() * transform.m12(); |
2397 | const qreal yScale2 = transform.m21() * transform.m21() |
2398 | + transform.m22() * transform.m22(); |
2399 | |
2400 | // decide the order of rotate and scale operations |
2401 | if (qAbs(t: xScale1 - yScale1) > qAbs(t: xScale2 - yScale2)) { |
2402 | if (scale) |
2403 | *scale = qSqrt(v: qMax(a: xScale1, b: yScale1)); |
2404 | |
2405 | return type == QTransform::TxRotate && qFuzzyCompare(p1: xScale1, p2: yScale1); |
2406 | } else { |
2407 | if (scale) |
2408 | *scale = qSqrt(v: qMax(a: xScale2, b: yScale2)); |
2409 | |
2410 | return type == QTransform::TxRotate && qFuzzyCompare(p1: xScale2, p2: yScale2); |
2411 | } |
2412 | } |
2413 | |
2414 | QT_END_NAMESPACE |
2415 | |