Когда использовать классы с плавающей запятой Qt graphics?

#qt

#qt

Вопрос:

Qt имеет набор графических классов, которые представлены в 2 вариантах: с целочисленной точностью и точностью с плавающей запятой.
Это те, которые я помню

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

Помимо очевидного различия в том, что один использует целые числа, а другой использует числа с плавающей запятой, как указано в их названиях и в официальной документации, у меня есть немало сомнений…

  • Каковы варианты использования для одного семейства классов и другого?
  • Позиции и размеры больше, чем их целочисленный аналог?
  • Дробные значения?
  • Имеет ли это значение при рисовании?
  • Будут ли графики более плавными, если я использую QLineF вместо QLine?

Комментарии:

1. Да, вы можете размещать объекты на субпиксельных границах, используя значения fp.

2. Хорошо, это имеет немного больше смысла, но в документах Qt этого немного не хватает.

Ответ №1:

Хотя эти классы часто взаимозаменяемы и имеют почти одинаковые реализации, существуют особые различия в их использовании и результате.

Классы на основе целых чисел в основном используются для определения координат экрана (положения виджетов, размеров и т. Д.), Которые обычно рассматриваются на основе единиц измерения в пикселях (которые, очевидно, являются целыми числами).

Однако существуют важные различия при работе с позиционированием / столкновением и рисованием.

Рассмотрим следующее:

 >>> 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
 

Это связано с тем, что QRect учитывает только целочисленный («пиксельный») размер и, очевидно (1, 1) , находится на «другом пикселе».

QRect также характерен для функций right() and bottom() , потому что (как объясняется в документации) они всегда возвращают left width-1 and top height-1 по историческим причинам:

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

В свете этого всегда имейте в виду, что то же самое работает для установщиков этих координат (как set* и 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)
 

Классы с плавающей запятой также имеют функции, недоступные для классов, основанных на целых числах (в основном потому, что их реализация была бы невозможной / полезной / разумной).
Например, класс QLineF:

  • может возвращать точку пересечения с другим QLineF (или пересечение их расширения);
  • имеет функции для получения и установки угла самой линии (от ее p1 начальной точки) или угла, созданного с помощью другой линии (или, лучше, их расширений, если они не пересекаются) [1];
  • может быть создан из полярного, с заданной длиной и углом;

Классы с плавающей запятой позволяют более точно рисовать и позиционировать, что является важным аспектом, когда вам нужно сглаживание рисования или вы имеете дело с контентом, который можно масштабировать или основанным на пропорциональных значениях (рассмотрим увеличенный QGraphicsScene или отображение текста, поскольку шрифты основаны на векторах).
Например, следующее даст вам оченьразные результаты:

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

Затем важно помнить, что все простые функции рисования QPainter, которые принимают числовые значения в качестве основных параметров, всегда будут использовать целочисленные значения (я полагаю, это связано с динамической типизацией Python, поскольку функции C принимают только целые числа со знаком):

 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))
 

Наконец, хотя Qt (и Python) иногда допускают прозрачное использование обоих типов, в общем случае строго требуется один или другой:

  • все функции, связанные с геометрией виджета, принимают только целочисленные классы ( setGeometry(QRect) , resize(QSize) , и т.д.);
  • то же самое касается переопределений функций, которые должны возвращать значения геометрии, такие как sizeHint() модель SizeHintRole элемента, прямоугольники, возвращаемые подклассом QStyle или set для QStyleOption;
  • QRegion может принимать только QPolygon и QRect, поскольку это объект с отображением в пикселях;
  • QGraphicsRectItem, QGraphicsEllipseItem и QGraphicsPolygonItem разрешают только классы с плавающей запятой в своих конструкторах или установщиках;
  • сложные конструкторы используют классы своего типа точности (QRect не принимает QPointF или QSizeF и т. Д.);
  • все функции, которые отображают координаты QGraphicsScene, должны использовать целочисленные классы при отображении на сцену и с плавающей запятой при выводе из сцены;

Всякий раз, когда вам нужно преобразование из одного типа в другой, просто используйте конструктор класса с плавающей запятой или to[*] функцию, которую он предоставляет:

 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] Имейте в виду, что установка углов за пределами диапазона 0-360 может дать вам неожиданные результаты: line.setAngle(361) приведет к line.angle() значению, равному 0,9999999999999748 из-за точности с плавающей запятой «[im]» и характера числа Pi.