Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use Qt graphics floating point classes?

Tags:

qt

Qt has a set of graphical classes that come in 2 variants: with integer precision and floating point precision.
These are the ones I can remember

|    QLine | QLineF    |
| QMargins | QMarginsF |
|   QPoint | QPointF   |
|    QRect | QRectF    |
|    QSize | QSizeF    |

Apart from the obvious difference, that one uses integers and the other uses floats, as stated in their names and in the official documentation, I have quite a few doubts...

  • What are the use cases for one family of classes and the other?
  • Positions and sizes bigger than their integer counterpart?
  • Fractional values?
  • Does it make a difference when drawing?
  • Are plots smoother if I use QLineF instead of QLine?
like image 490
Jack Lilhammers Avatar asked Nov 23 '25 11:11

Jack Lilhammers


1 Answers

While those classes are often interchangeable and have almost the same implementations, there are peculiar differences in their usage and result.

Integer based classes are mostly used for screen coordinates (widget positions, sizes, etc), which is normally considered based on pixel units (which are obviously integers).

There are important differences when dealing with positioning/collision and drawing, though.

Consider the following:

>>> p = QtCore.QPoint(1, 1)
>>> r = QtCore.QRect(0, 0, 1, 1)
>>> print(r.contains(p))
False
>>> r = QtCore.QRectF(0, 0, 1, 1)
>>> print(r.contains(p))
True

This is because QRect considers only the integer ("pixel") size, and obviously (1, 1) is on "another pixel".

QRect is also peculiar for the right() and bottom() functions, because (as explained in the documentation) they always return left+width-1 and top+height-1 for historical reasons:

>>> r = QtCore.QRect(0, 0, 1, 1)
>>> print(r.right())
0

In light of this, always keep in mind that the same works for the setters of those coordinates (both set* and move*):

>>> r.setRight(1)
>>> print(r)
PyQt5.QtCore.QRect(0, 0, 2, 1)
>>> r.moveRight(0)
>>> print(r)
PyQt5.QtCore.QRect(-1, 0, 2, 1)

Floating point classes also have features not available to integer based ones (mostly because it wouldn't be possible/useful/reasonable to implement them).
For example, the QLineF class:

  • can return an intersection point with another QLineF (or the intersection of their extension);
  • has functions to get and set the angle of the line itself (from its p1 starting point), or the angle created with another line (or, better, their extensions, if they don't intersect) [1];
  • can be created from a polar, given a length and an angle;

Floating point classes allow more precise drawing and positioning, which are important aspects when you need antialiased painting or you're dealing with content that can be scaled or is based on proportional values (consider a magnified QGraphicsScene, or text displaying since fonts are vector based).
For instance, the following will give you very different results:

painter.setRenderHints(painter.Antialiasing)
painter.drawRect(QtCore.QRect(1, 1, 2, 2))
painter.drawRect(QtCore.QRectF(1, 1, 2.5, 2.5))

Then, it's important to remember that all simple drawing functions of QPainter that accept numeric values as main parameters will always use integer values (I believe that's due to Python's dynamic typing, as C++ functions only accept signed integers):

painter.drawRect(0.5, 0.5, 5.5, 5.5)
# same as:
painter.drawRect(0, 0, 5, 5)
# so you should use:
painter.drawRect(QtCore.QRectF(0.5, 0.5, 5.5, 5.5))

Finally, while Qt (and Python) sometimes allows transparent usage of both types, in general one or the other is strictly required:

  • all widget geometry related functions only accept integer classes (setGeometry(QRect), resize(QSize), etc.);
  • the same goes for function overrides that must return geometry values, such as sizeHint(), the SizeHintRole of an item model, rectangles returned by a QStyle subclass or set for a QStyleOption;
  • QRegion can only accept QPolygon and QRect, since it's a pixel mapped object;
  • QGraphicsRectItem, QGraphicsEllipseItem and QGraphicsPolygonItem only allow floating point classes in their constructors or setters;
  • complex constructors use classes of their precision type (QRect won't accept QPointF or QSizeF, etc.);
  • all functions that map QGraphicsScene coordinates must use integer classes when mapping to the scene and floating point when from the scene;

Whenever you need a conversion from one type to the other, just use the constructor of the floating point class or the to[*] function it provides:

intRect = QtCore.QRect(0, 0, 10, 10)
floatRect = QtCore.QRectF(intRect)
newIntRect = floatRect.toRect()
intTopLeft = floatRect.topLeft().toPoint()
intSize = floatRect.size().toSize()
midRadiusLine = QtCore.QLineF.fromPolar(
    intRect.width() * .5, 45).translated(floatRect.center())
intMidRadiusLine = midRadius.toLine()

[1] Be aware that setting angles outside the 0-360 range might give you unexpected results: line.setAngle(361) will result in a line.angle() equal to 0.9999999999999748 due to floating point "[im]precision" and the nature of Pi.

like image 56
musicamante Avatar answered Nov 28 '25 17:11

musicamante



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!