Qt виджет для рисования

Qt виджет для рисования

Статья «нулевого уровня», но пару примеров кода содержит 🙂

1. Общие принципы

Общий порядок работы с графикой в QT похож на другие современные среды программирования. В основе работы лежит взаимодействие 3 классов:

  • QPainter – «рисовальщик» QT, класс-исполнитель команд рисования;
  • QPaintEngine – «движок» рисования, обычно не используемый из кода непосредственно. Он бывает нужен программисту лишь при создании собственных контекстов рисования, отсутствующих в системе;
  • QPaintDevice – контекст рисования (канва), который можно понимать как графическое «полотно», состоящее из пикселов.

В обычных случаях программе достаточно «захватить» адрес объекта контекста, отрисовать нужные графические примитивы и освободить контекст:

Здесь рисуется заштрихованный прямоугольник на канве текущего виджета. Существенно, что этот код должен выполняться из метода paintEvent, который может быть переопределён в любом пользовательском виджете, являющемся наследником имеющего канву класса (см.п.2).

Как и в других подобных графических системах, для рисования используются 2 основных инструмента:

  • Перо QPen – для рисования контуров;
  • Кисть QBrush – для заполнения контуров цветом.

Непосредственно отрисовкой занимаются методы класса QPainter с названиями на draw (drawLine, drawRect, drawPolygon, drawEllipse) или fill (fillRect, fillPath). Кроме настроек пера и кисти, этим методам часто требуется информация о координатах точек, размерах прямоугольников, текущих цветах и т.п. Для хранения всей этой информации в QT предусмотрен ряд геометрических классов, которые ничего не рисуют, но хранят описания размеров или расположений графических объектов. Большинство геометрических классов реализованы «дважды», например, класс QPoint работает с целочисленными координатами точек на плоскости, а QPointF – с вещественными:

Читайте также:  Комус клеенка для рисования

Целочисленные и вещественные координаты точки на плоскости

. x (), . y () – получение координат;

. setX (), . setY () – установка координат.

В классах определены сравнения, арифметические действия над координатами

Размер, то есть, совокупность ширины и высоты

. width (), . height () – получить размеры;

. setWidth (), . setHeight () – установить размеры;

. scale () – масштабировать размер.

Прямоугольник, фактически, это » точка+размер «

См. QPoint , QSize

Отрезки на плоскости

. x 1(), . y 1(), . x 2(), . y 2() – координаты начала и конца отрезка;

. dx (), . dy () – проекции на оси 0 X и 0 Y

Многоугольник на плоскости, фактически, массив точек (координат вершин)

. point ( i ) – вернуть i — ую точку

Цвет в модели RGB (или HSV )

. setRgb ( r,g,b ) установить интенсивности цветов;

. red (), . green , . blue (), . alpha () – получить интенсивности цветов и прозрачность

В классе QPainter также определены удобные методы для манипулирования с системой координат:

  • translate – сдвинуть начало координат в указанную точку;
  • scale – масштабировать систему координат;
  • rotate – повернуть систему координат;
  • shear – исказить систему координат (выполнить скос);
  • save, restore – сохранить/восстановить состояние рисовальщика.

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

2. Работа с графической канвой

Создадим проект на основе класса QWidget (добавились файлы main.cpp, widget.h, widget.cpp). Так как отрисовка выполняется по событию paintEvent, в заголовочном файле widget.h нам понадобится добавить прототип виртуального метода paintEvent, предусмотренного в каждом виджете. Чтобы виджет мог рисовать на канве, он должен переопределить этот метод:

В файле widget.cpp нам остается написать реализацию метода. Покажем простейшие действия с кистью, пером и строкой текста:

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

На самом деле, конечно, painter надо прописать в заголовочном файле класса, инициализировать в конструкторе класса, а не создавать каждый раз заново при перерисовке формы. Здесь и выше так сделано только для краткости примера.

3. Работа с изображениями

QT может как записывать, так и загружать файлы основных растровых форматов, включая PNG, BMP, ICO, TIFF, JPEG, GIF и некоторые другие. При этом поддерживается контекстно-независимое представление графики. Фактически, это означает, что данные изображений помещаются в массивы, содержащие данные об отдельных пикселах рисунка. Основным классом представления изображений является QImage. Этот класс унаследован от контекста рисования QPaintDevice, что позволяет использовать все методы рисования, определённые в QPainter. Метод класса format() позволяет узнать формат текущего изображения, а метод convertToFormat() – изменить его, вернув новый объект класса QImage. Некоторые значения перечисления Format указаны ниже:

  • Format_Invalid — формат неверен;
  • Format_Mono – монохромное изображение с 1 битом на пиксел;
  • Format_Index8 – данные представляют собой 8-битные индексы цветовой палитры;
  • Format_RGB32 – каждый пиксел представлен 32 битами (интенсивности красного, зелёного и синего, плюс значение альфа-канала, всегда равное 0xFF, то есть, прозрачность не поддерживается);
  • Format_ARGB32 – 32 бита на пиксел с поддержкой альфа-канала прозрачности.

Для создания изображения в конструктор класса достаточно передать его размеры в пикселах и формат:

Загрузить изображение можно, передав конструктору путь к нужному файлу:

или воспользовавшись методом load:

Как видно из кода, отобразить изображение можно методом drawImage класса QPainter. Показать часть изображения можно с помощью дополнительных параметров метода.

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

Сохранить изображение может метод save:

Сохранение произойдёт в ту папку, которая является текущей в вашей конфигурации проекта.

Для чтения отдельных пикселов удобен метод pixel:

Записать пикселы можно с помощью метода setPixel:

Показанный код рисует линейный градиент в окне формы, для работы нужны директивы

В классе QImage реализован ряд методов для редактирования изображений:

  • invertPixels – позволяет инвертировать цвета пикселов и/или альфа-канал;
  • scaled – позволяет масштабировать изображение;
  • mirrored – выполняет зеркальное отражение картинки по горизонтали и/или вертикали.

Класс контекстно-зависимого представления изображений QPixmap также унаследован от QPaintDevice. Его использование целесообразно там, где нужен промежуточный буфер для рисования или критична скорость отрисовки графических объектов. Существует также отдельный класс-потомок QBitmap для работы с монохромными изображениями.

4. Построение графиков и диаграмм

Хорошее введение в тему даёт эта статья.

5. «Продвинутый» пример рисования мышью в QT

По всем правилам создаём графическую сцену, таймер и пример простейшей «рисовалки» нажатой левой кнопкой мыши.

Скачать проект paint QT5 в архиве .zip (3 Кб)

В качестве задания по теме подойдёт примерно следующее:

Напишите виджет, позволяющий:

  • Загружать изображения и отслеживать цвета отдельных пикселов на них;
  • Добавлять на изображения графические примитивы по выбору пользователя и сохранять изменённую картинку;
  • Строить график выбранной функции.

27.04.2016, 16:12; рейтинг: 42583

Источник

Рисование в Qt5. Введение. Трансформации. Трансляция. Часть 1.

Для рисования и вывода графики в Qt5 существует класс QPainter. С его помощью вы можете отрисовывать примитивы (линии, квадраты, арки и т.д.) и производить над ними трансформации (вращение, перемещение, масштабирование и т.д.). Так же он позволяет выводить текст с выравниванием и растровые изображения.

В данной статье мы рассмотрим основы рисования на Qt5 с помощью класса QPainter.

Введение в 2D рисование.

QPainter предоставляет богатый выбор методов для отрисовки разнообразной графики. Прежде всего давайте разберемся, как осуществляется вывод графических примитивов.

При рисовании графических примитивов всегда очень важно знать, где располагается начало координат, так как рисование всегда начинается с точки с координатами (0,0). По умолчанию QPainter использует в качестве этой точки верхний левый угол виджета. Давайте проверим это на практике.

В QtCreator Создадим новый проект Qt Widgets Application , назовем его QtDrawing .

Все настройки оставим по-умолчанию.

Откроем форму mainwindow.ui и изменим свойство Geometry выставив ширину и высоту равными 300 сохраним и закроем редактор форм.

Откроем mainwindow.cpp нажмем F4 и добавим после Ui::MainWindow *ui; строку

Нажмем еще раз F4 и добавим в конец файла метод:

Запустим сборку Ctrl+R и увидим на экране такое окно:

И так, что же мы сделали? Мы добавили новый обработчик события paintEvent . Он вызывается каждый раз когда возникает необходимост в отрисовке содержимого нашего виджета. В данном случае нашей формы. Согласно документации QPainter painter(this); должен вызываться только один раз в paintEvent , но об этом мы поговорим позже.

Далее мы задаем цвет, которым будут нарисованы линии и текст.

QColor coordLineColor(255, 0, 0, 255);

Мы используем класс QPen, он позволяет нам контролировать цвет или толщину линий. Мы задаем цвет линии и её толщину, чтобы оси координат были заметнее.

В самом конце мы просто вызываем painter.setPen(apen); и, тем самым, задаем цвет и толщину линий для рисования.

Далее все очень просто, строки:

отрисовывают оси X и Y, от начала координат.

Для задании координат линий используется класс QLine, в качестве параметров в который мы передаем координаты начала и конца отрезка.

Выводят текст по заданным координатам. Для их задания используется класс QPoint, в качестве параметров которому передается координаты точки, в которой должен быть выведен текст.

При выводе текста в QPainter есть целый ряд особенностей, но о них мы поговорим в следующих статьях.

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

Глобальный объект класса для QPainter

Прежде чем мы продолжим, внесем изменения в механизм инициализации самого QPainter. В документации отдельно указывается, что строка QPainter painter(this); должна вызываться только один раз. Таким образом производится инициализация всех необходимых для работы данных. Но в таком подходе, есть небольшая проблема — объект класса painter существует только до конца метода

Если мы хотим использовать отдельные функции для рисования целых частей или убрать в них весь повторяющийся код, нам придется передавать painter как параметр, что не очень хорошая идея. Мы поступим по другому:

Откроем mainwindow.cpp нажмем F4 и добавим после

Таким образом мы объявили приватный указатель на объект класса QPainter Теперь осталось его инициализировать, изменим предыдущий код:

Обратите внимание, так как мы используем ссылку на объект класса, то вместо «.» для доступа к методу класса, мы теперь используем «->». В принципе ничего сильно не изменилось, а теперь давайте уберем код для вывода осей координат в отдельный метод.

В конец файла добавим код:

Нажмем F4 и добавим в блок private стоку

И изменим метод:

Обратите внимание мы добавили

Это нужно сделать для того, чтобы painter знал, когда мы закончили рисование. Иначе в консоли вы можете увидеть подобные ошибки:

QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?

Запустим сборку Ctrl+R. Как видите ничего не поменялась но код основного метода стал более компактным.

Рисование примитивов

Давайте рассмотрим какие основные примитивы можно рисовать с помощью QPainter , для этого нарисуем такой вот детский рисунок:

Для этого добавим новый метод:

Для того чтобы нарисовать эту простую картинку мне пришлось подгонять координаты каждой точки, таким образом, чтобы все было на своих местах. Что не очень удобно, а что если нужно будет передвинуть дом вниз на 40 пикселей? Менять все координаты? Вводить две переменные и добавлять их значение ко всем координатам? А что если я хочу увеличить размер окна или добавить дверь. В общем придется вносить огромное число правок.

Чтобы избежать всего этого и существуют методы трансформации.

Трансформация примитивов

Трансляция

Трансляция — процесс при котором к каждой точке добавляется указанное смещение. Зачем нужна трансляция? Ответ очень прост, она позволяет не вычислять каждый раз смещение для каждой точки примитива, когда требуется переместить его в другую позицию. При этом координаты точек этого примитива не меняются! Давайте напишем простой пример, чтобы лучше разобраться:

Здесь всё просто, мы рисуем оси координат на своем месте, а потом производим трансляцию в середину экрана и снова отрисовываем оси, в центре экрана. Если мы немного расширим размер окна, то убедимся, что на самом деле оси отрисовываются полностью, просто рисунок выходит за пределы экрана.

При это трансляция каждый раз идет от последней точки начала координат, например, мы нарисовали оси в центре экрана и теперь хотим нарисовать их из точки 50,50:

Мы, конечно ожидаем, что новые оси будут нарисованы в точке с координатами 50,50, но на практике у нас получается следующее:

Это происходит потому, что мы уже один раз применили трансляцию в точку 150,150. Так что теперь, когда мы вновь делаем трансляцию в точку 50,50 на самом деле мы переносим начало координат в точку 200,200. Что же делать? Есть 2 способа, можно просто сделать так:

или же сделать так:

resetTransform(); — сбрасывает все трансформации произведенные с помощью QPainter при этом не затрагиваются уже отрисованные примитивы.

Результат будет одинаков:

Теперь изменим наш детский рисунок с использованием трансляции:

Запустим и получим вот такой рисунок:

Сначала мы делаем трансляцию в точку 150,220 , затем рисуем домик, крышу и трубу, обратите внимание, для того чтобы наш домик был симметричен относительно начала координат мы используем отрицательные координаты. Мы слегка модифицировали код отрисовки радуги, таким образом, чтобы проще было изменять её размер и она так же выводится от начала координат, но перед этим мы делаем:

Т.е начинаем рисовать из точки 50,50

Затем отрисовывается солнце. Мы используем вызов

чтобы залить эллипс желтым цветом.

Заключение

Мы, на примерах, разобрали основы рисования 2D графики и рассмотрели первую трансформацию примитивов — трансляцию.

Вот и всё на сегодня, в следующей части мы рассмотрим вращение и масштабирование в Qt5.

Источник

Оцените статью