Как работает система координат QGraphicsView?

Следуя этому уроку, я написал небольшую программу с QGraphicsView и функцию рисования в классе, который наследует QGraphicsItem, которая может рисовать в этом QGraphicsView. Но вот тут я в замешательстве. Если я говорю:

painter->drawLine(-50,-50,50,50);

в какой системе координат я работаю? Что определяет, где находится начало координат, и что считается единицей? Покопавшись, я знаю, что размерыboundingRect() имеют к этому какое-то отношение. Но они явно не определяют территорию. Если я вернусь:

return QRectF(0,0,200,200);

Я все еще могу нарисовать линию выше, и она даже не появляется на краю моего пространства для рисования! Так откуда же возникает система координат и как ею можно управлять?

Обновлено: Чтобы сделать это немного более понятным, у меня есть QGraphicsView и QGraphicsScene, которые я помещаю в него. Затем у меня есть класс, наследующий QGraphicsItem, и я добавляю этот класс в сцену. Итак, теперь у меня в классе есть функция рисования, и все, что я там рисую с помощью QPainter, должно отображаться в QGraphicsView, как если бы это было мини-окно. По крайней мере, я так понимаю.

Однако в функции рисования я писал вещи, опираясь на систему координат. Моя проблема в том, что я не уверен, как определить систему координат внутри моего элемента, который находится внутри сцены, которая находится в представлении. Чтобы рисовать, мне нужно знать, где находится начало координат и сколько точек в каждом направлении. Кажется, я понимаю это последовательно.

Я думаю, вопрос должен быть более конкретным, чем «Как... работает?»

user12002570 26.05.2024 05:08

Как бы то ни было, я думаю, что этот вопрос и единственный ответ на данный момент являются гораздо более ценными ресурсами, чем большинство вопросов типа «ChatGPT написал сломанный код, исправьте это», которые я вижу во входящих. Даже если на эти вопросы есть ответы, они редко бывают полезны другим. Я бы действительно хотел прочитать это, если бы работал в Qt.

rand'Chris 26.05.2024 05:23
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Относительная система координат

Все в структуре графического представления использует координаты относительно своего контекста. Точнее, почти любой класс, связанный с геометрией в Qt, в первую очередь использует локальную систему координат, которую при необходимости можно в конечном итоге перевести в ее родительский класс.

Это не сильно отличается от того, что обычно делается с виджетами и даже с окнами. Предположим, у вас есть собственный виджет, который также представляет собой окно верхнего уровня без границ и строки заголовка, и вы рисуете квадрат 10x10 на расстоянии (10, 10) пикселей от его начала (его верхнего левого угла, он же (0, 0)[1]). Вы размещаете это окно в верхнем левом углу экрана, и квадрат будет нарисован ровно на расстоянии (10, 10) пикселей от верхнего левого угла экрана. Однако если вы переместите это окно, квадрат будет отображаться в другом положении относительно экрана, но все равно на (10, 10) относительно окна.

Если этот виджет затем отображается в родительском окне, концепция будет той же, но на более высоком уровне: если окно выровнено по верхнему левому краю экрана, а виджет помещен в (0, 0), вы снова увидите Квадрат 10x10, показанный в 10 пикселях сверху и слева от экрана. Если затем виджет перемещается внутри своего родителя в (10, 10), квадрат будет отображаться в 20, 20 верхнего левого угла экрана, но если вы переместите окно, координаты экрана также изменятся.

Тем не менее, во всех этих случаях виджет всегда будет рисовать квадрат 10x10 по своим собственным (10, 10) координатам.

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

Все это потому, что по умолчанию позиции всех элементов всегда инициализируются как (0, 0) (как и виджеты!), или, точнее, QPointF(), что неявно равно QPointF(0.0, 0.0) (или целочисленному значению QPoint() для виджетов, также известному как QPoint(0, 0)).

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

Возьмем, к примеру, это:

scene.addRect(10, 10, 10, 10)

Note, I'm no C++ dev, so I won't attempt to write C++ code that will probably be wrong. For simplicity, I'll use Python syntax, which can be considered as a form of pseudo code.

На рисунке выше показан прямоугольник 10, 10 относительно прямоугольника сцены.

Теперь подумайте об этом:

rect = scene.addRect(0, 0, 10, 10)
rect.setPos(10, 10)

Вышеупомянутое покажет то же самое. Но с двумя важными отличиями:

  • первый пример размещается по умолчанию (0, 0), а второй — (10, 10);
  • у первого элемента есть прямоугольник (и, по совпадению, аналогичный ограничивающий прямоугольник[2]) в (10, 10), а у второго в (0, 0);

Итак, раз они идентичны, зачем использовать второй? Смотря как. В некоторых случаях использование явно абсолютных координат может оказаться эффективным. Но во многих случаях необходимо всегда использовать относительные координаты. Рассмотрим случай рисования контрольной точки (например, для изменения размера прямоугольника), которая обычно находится в центре своего исходного положения.

Если вы используете псевдоабсолютный подход, вам всегда следует учитывать опорную позицию и вычитать половину размера:

# show a 10x10 control point and then "center" it at 100, 50
size = 10
cp = scene.addRect(0, 0, size, size)

... later, somewhere else

x = 100
y = 50
cp.setRect(x - cp.width() / 2, y - cp.height() / 2, cp.width(), cp.height())

Используя правильные относительные координаты, это намного проще и логичнее:

size = 10
cp = scene.addRect(-size / 2, -size / 2, size, size)

...

cp.setPos(x, y)

Все это усложнит ситуацию при использовании псевдоабсолютных координат, если элемент является дочерним элементом другого[3].

Ограничивающий прямоугольник

Основная цель boundingRect() — определить границы, в которых должно происходить рисование. Чтобы упростить ситуацию, поведение QGraphicsItem по умолчанию (которое немного отличается от поведения примитивных элементов, предоставляемых Qt) также использует ограничивающий прямоугольник для обнаружения столкновений, например, чтобы разрешить выбор элемента или перемещение элемента с помощью мыши.

На самом деле его основное назначение — оптимизация рисования: когда в графической сцене происходит небольшое изменение, представление обновит только ту часть сцены, которая заинтересована в этом изменении. Вот причина такого примечания в документации:

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

Теперь ваш вопрос верен: если ограничивающий прямоугольник равен QRectF(0,0,200,200), как вы можете сделать painter->drawLine(-50,-50,50,50);?

Реальность такова, что ограничивающий прямоугольник на самом деле не ограничивает рисование за его пределами.

По умолчанию графическое представление будет рисовать все, что запрашивает функция paint() его элемента; в частности, это то, что обычно происходит при первом показе.

Но вот в чем загвоздка: обратите внимание на перерисовку в цитате выше. Основная оптимизация графического представления заключается в том, чтобы рисовать только то, что действительно необходимо. Если вы измените небольшую часть огромной сцены, нет смысла перерисовывать все остальное: рендерер максимально все закеширует и очистит/перерисует только небольшие изменения.
А вот и ограничивающий прямоугольник: если изменение не интересует ограничивающий прямоугольник, сцена не будет его обновлять. Если ваш элемент выходит за пределы ограничивающего прямоугольника, все, что было нарисовано за его пределами, не будет очищено, что приведет к появлению «призрачных артефактов».

Попробуйте setFlag(QGraphicsItem::ItemIsMovable) свой предмет и переместить его по сцене, тогда вы, вероятно, увидите что-то вроде следующего:

Прямоугольная сцена

По умолчанию графическая сцена имеет SceneRect(), которая включает в себя весь ограничивающий прямоугольник элементов, которые она содержит. Аналогично, по умолчанию графическое представление использует sceneRect() сцены. Тем не менее, вы должны учитывать следующее:

  • если прямоугольник сцены задан явно, он никогда не изменится, даже если для ограничивающего прямоугольника элементов потребуется другой прямоугольник; поведение по умолчанию можно восстановить, используя нулевой QRectF;
  • аналогично QGraphicsScene, QGraphicsView может иметь свой собственный прямоугольник сцены, который может отличаться от прямоугольника сцены;
  • если текущий отображаемый прямоугольник сцены меньше, чем то, что может показать вид, прямоугольник сцены всегда выравнивается по центру (если не установлено свойство выравнивание);

Вышеупомянутое также объясняет, почему ваша линия не отображается поверх представления: прямоугольник сцены маленький, вид большой, поэтому он отображает ваш элемент «по центру»: однако обратите внимание, что центрирование основано на ограничивающем прямоугольнике. элементов на сцене, поэтому отображаемая линия фактически не центрирована.
Добавьте painter->drawRect(boundingRect()) внутри paint(), и вы увидите, что этот прямоугольник фактически центрирован в представлении.

Система координат и единицы измерения

Обратите внимание, что все это не является особенным поведением инфраструктуры графического представления. На самом деле он просто использует общие понятия геометрии: декартову систему координат и связанную с ней систему отсчета.

Как и в базовой алгебраической математике, понятие единицы полностью абстрактно и следует принципу стандартного базиса.
Это логические единицы, где «одна единица» — это всего лишь один, это не пиксель, не дюйм, не миллиметр, не ноготь, не атом, не картофелина и не что-то еще. Но это может стать так, как только будет дана ссылка на эту единицу.

Однако когда сцена рисуется, она использует пиксель устройства, на котором она нарисована, в качестве ссылки для системы координат, а это означает, что 1 обычно представляет собой один «пиксель», если не применяются преобразования или не используется любая другая система отсчета (рассмотрим физический принтер). ).

Например, при рисовании QRectF(0, 0, 10, 10) будет отображаться квадрат 10x10, который фактически переключает квадрат 10x10 пикселей на стандартном экране. Если экран использует HighDPI, он будет выглядеть так же, как и на экране того же физического размера, но для его отображения на самом деле будет использоваться гораздо больше пикселей.

Однако если применить какое-либо преобразование (например, с помощью fitInView()), тот же прямоугольник может быть намного больше или меньше по размеру в пикселях; тем не менее, это все равно будет «квадрат 10x10» в логических координатах.

Заключительные замечания и дальнейшее чтение

  • в большинстве случаев вы не можете полагаться на представление, чтобы нарисовать что-либо в определенных координатах (если sceneRect и возможные преобразования не установлены правильно);
  • как сообщается в упомянутых выше документах, рисование всегда должно происходить в пределах boundingRect() и, в конечном итоге, включать ширину пера[2];
  • даже если вы используете псевдоабсолютные координаты (сцены), вы всегда должны учитывать, что любая геометрия, используемая в контексте элемента, по-прежнему находится в локальных координатах;
  • если вы не очень хорошо разбираетесь в этих темах геометрии, потратьте время на тщательное изучение того, что указано выше, [пере]начиная с декартовой системы координат, относительных координат, концепций логических координат/единиц и расширяя все, что с ними связано; это не должно быть так уж сложно, поскольку большая часть этого, вероятно, покрывается вашим базовым школьным образованием (в то время вы, вероятно, также спрашивали себя, какова цель этого, но теперь вы будете это делать ;-) );

Сопутствующая документация и сообщения SO (для которых вам следует прочитать все ответы):

Просто уделите время вышеизложенному, а также выполните базовый запрос в поисковой системе со связанными ключевыми словами (QGraphicsScene и/или QGraphicsView, система координат, ограничивающий прямоугольник и т. д.).

[1] Cartesian coordinates use positive values for "above" and negatives for "below", as opposite to common screen coordinates: a standard single screen will show y = 0 on its top, meaning that while 10 in Cartesian would show something "up from the bottom" (0 on the horizontal axis), the same value will be displayed as "down from the top" on a common screen layout; QGraphicsScene follows the same screen concept too;
[2] In reality, as the documentation suggests, the bounding rect of an item that uses a QPen to draw its contents should always consider the width of that pen; specifically, at least half of the pen width for orthogonal shapes (rectangles), but that might increase for different shapes and if the pen joinStyle might extend over due to the angles touching the edges;
[3] The local coordinate system used by items is always relative to their parent; if the item is a "top level" (it has no QGraphicsItem parent), the parent is implicitly "the scene", but remember that, in theory, a graphics item could exist and "work" without being added to a scene at all; this follows one of the principles of OOP modularity, for which an object should be able to work on its own, no matter its external context;

Другие вопросы по теме