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 [57319 просмотров]