БлогNot. QT: начинаем работать с графикой

QT: начинаем работать с графикой

Статья "нулевого уровня", но пару примеров кода содержит :)

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

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

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

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

QPainter painter1; //создание рисовальщика
painter1.begin(this); //захват контекста
painter1.fillRect(0,0,width(),height(),Qt::CrossPattern); //отрисовка
painter1.end();//освобождение контекста

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

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

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

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

Классы

Описание

Основные методы

QPoint,

QPointF

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

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

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

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

QSize,

QSizeF

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

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

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

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

QRect,

QRectF

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

См. QPoint, QSize

QLine,

QLineF

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

.x1(), .y1(), .x2(), .y2() – координаты начала и конца отрезка;

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

QPolygon,

QPolygonF

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

<< добавить точку QPoint

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

QColor

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

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

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

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

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

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

void drawTriangle (QPainter &ptr, QPoint point) {
 QPolygon q;
 q << QPoint(0,0) << QPoint(100,0) << QPoint(50,100);
 ptr.save();
 ptr.translate(point); //сдвинули начало координат в точку, указанную параметром point
 ptr.drawPolygon(q);
 ptr.restore();
}

void QPaintWidget::paintEvent(QPaintEvent *event) {
 QPainter painter1;
 painter1.begin(this);
 drawTriangle (painter1,QPoint(100,100));
 drawTriangle (painter1,QPoint(200,200));
 //... 
 painter1.end();
}
2. Работа с графической канвой

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

#include <QWidget>
#include <QPainter>

class Widget : public QWidget
{
    Q_OBJECT
    //...
protected:
    void paintEvent(QPaintEvent *);

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

void Widget::paintEvent(QPaintEvent *event) {
 QPainter painter(this); //новый объект "рисовальщика"
 painter.setPen (QPen(Qt::red,Qt::SolidLine));
  //создать и установить перо - красная сплошная линия
 painter.drawLine(0,0,width(),height());
  //нарисовать линию через рабочую область формы
 painter.setBrush(QBrush(Qt::green,Qt::SolidPattern));
  //создать и установить кисть - зелёная слошная заливка
 QPoint center(width()/2,height()/2);
 int rad = qMin(width()/4,height()/4);
 painter.drawEllipse(center,rad,rad);
  //нарисовать окружность по центру
 painter.setFont(QFont("sans-serif",-1,10));
  //установить шрифт заданного начертания и размера 10 пт
 QRect rect(center.x()-rad,center.y()-rad,rad*2,rad*2);
 painter.drawText(rect, Qt::AlignCenter, tr("Hello,\nworld!"));
  //вывели строку текста, выравненную по центру
}

Теперь класс 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 бита на пиксел с поддержкой альфа-канала прозрачности.

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

QImage img(width(),height(),QImage::Format_RGB32);

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

QImage img("my.jpg");

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

QImage img; img.load("my.jpg");
QPainter painter(this);
painter.drawImage(0,0,img);

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

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

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

QImage img(width(),height(),QImage::Format_RGB32);
img.save("my2.jpg","JPG");

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

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

QRgb pixel = img.pixel(100,100);
QString str; str.setNum(pixel);
setWindowTitle(str);

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

QPainter painter;
 painter.begin(this);
 QImage img(width(),height(),QImage::Format_RGB32);
 QRgb rgb;
 for (int x=0; x<width(); x++) {
  for (int y=0; y<height(); y++) {
   int c = qRound(y/255.*100);
   rgb = qRgb(c,c,c);
   img.setPixel (x,y,rgb);
  }
 }
 painter.drawImage(0,0,img);
 painter.end();

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

#include <QPainter>
#include <qmath.h>

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

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

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

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

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

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

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

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

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

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

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

27.04.2016, 16:12 [56075 просмотров]


теги: программирование c++ графика qt

К этой статье пока нет комментариев, Ваш будет первым