Как сделать paint в qt
А в этом уроке освоим рисование мышью в Qt на основе примитивнейшего аналога Paint с использованием QGraphicsScene . Никаких регулировок, ни размеров кисти, ни палитры, ни спецэффектов, а просто красная линия, которую мы будем рисовать мышью.
Задача поставлена - вперёд исполнять!
Структура проекта
В структуру проекта входят следующие файлы:
- paint.h - заголовочный файл виджета, в котором будет располагаться графическая сцена для рисования;
- paint.cpp - соответственно файл исходных кодов для этого виджета;
- paintscene.h - заголовочный файл кастомизированной графической сцены, с которой мы будем работать;
- paintscene.cpp - файл исходных кодов кастомизированной графической сцены.
paint.ui
Форма главного окна приложения состоит из самого виджета и помещенного в него объекта QGraphicsView.
paint.h
В данном файле объявляется кастомизированная графическая сцена, а также таймер со слотом для этого таймера, который служит для корректной обработки изменения размеров окна приложения.
paint.cpp
В данном классе происходит добавление кастомизированной графической сцены в объект класса QGraphicsView , фактически в целях обучения программный код из данной части примера не имеет особого отношения к самому процессу рисования, но отработка изменения размеров окна включена в данный пример для полноты картины. Само рисование происходит исключительно в кастомизированной графической сцене.
paintscene.h
А вот и заголовочный файл виновника данного примера. Рисование происходит с помощью линий, посредством обработки события mouseMoveEvent. Для этого переопределяется функция mouseMoveEvent в которой по двум координатам, первая от прошлого события и вторая от текущего, строятся красные линии, которые в итоге образуют общую кривую. Но чтобы при отпускании кнопки мыши и повторном нажатии мы могли рисовать новую линию, а не продолжать старую, переопределяем функцию mousePressEvent.
В mousePressEvent отрисовывается эллипс, который служит стартовой точкой рисования кривой мышью. Этот метод обновляет значения первой координаты для первой линии, таким образом разрывая и отделяя старую кривую от новой. В видеоуроке это продемонстрировано. Точка первой координаты для линии сохраняется в объект previousPoint.
paintscene.cpp
В данном файле вся работа с рисованием происходит в методах mouseMoveEvent и mousePressEvent. Тогда как в конструкторе класса вообще не происходит какой-либо инициализации.
В результате Вы сможете рисовать на графической сцене красные линии, а как дальше уже развить эти возможности - это уже зависит от Вас.
Также вы можете ознакомиться с комментариями и демонстрацией проекта в видеоуроке по данной статье.
Архив с исходным кодом проекта: Qt paint
Видеоурок про рисование в Qt
Qt предоставляет программисту очень богатые возможности, однако набор виджетов ограничен. Если ничего из имеющегося в наличии не подходит, приходится рисовать что-то свое. Простейший способ — использовать готовые картинки — имеет серьезные недостатки: необходимость хранения изображений в файле или ресурсах, проблемы с масштабируемостью, с переносимостью форматов изображений. Ниже описывается вариант использования принципов векторной графики без использования собственно векторных изображений.
Преамбула
Началось все с того, что понадобилась однажды индикация одноразрядных признаков. Некоторое приложение получает по некоторому порту некоторые данные, пакет надо разобрать и отобразить на экране. Хорошо бы при этом как-то имитировать привычную приборную лицевую панель. Для отображения цифровых данных Qt предлагает «из коробки» класс QLCDNumber, похожий на знакомые семисегментные индикаторы, а вот одиночных лампочек что-то не видно.
Использование флажков (они же check boxes) и переключателей (они же radio buttons) для этих целей плохо, и вот список причин:
- Это неправильно семантически. Кнопки — они и есть кнопки, и предназначены для ввода пользователем, а не для показа ему чего-либо.
- Отсюда вытекает второе: пользователь так и норовит тыкнуть в такие кнопки. Если при этом обновление информации не особенно быстрое, индикация будет врать, а пользователь — сообщать о неправильной работе программы, мерзко хихикая.
- Если заблокировать кнопку для нажатия (setEnabled(false)), то она становится некрасиво серой. Помнится, в Delphi, в районе версии 6, был такой финт ушами: можно было положить флажок на панель и отключить доступность панели, а не флажка, тогда флажок не был ни серым, ни активным. Тут такой фокус не проходит.
- Кнопки имеют фокус ввода. Соответственно, если в окне есть элементы ввода, и пользователь гуляет по ним с помощью клавиши «Tab», ему придется погулять и по элементам вывода, это неудобно и некрасиво.
- В конце концов, такие кнопки просто неэстетично смотрятся, особенно рядом с семисегментниками.
Вывод: надо рисовать лампочку самому.
Муки выбора
Сначала поискал готовые решения. В ту далекую пору, когда использовал Delphi, можно было найти просто гигантское количество готовых компонентов, как от серьезных фирм, так и любительского изготовления. В Qt с этим напряженка. У QWT есть кое-какие элементы, но не то. Любительщины вообще не видел. Наверное, если грамотно рыть на Github`е, то можно что-то найти, но я, пожалуй, быстрее сам сделаю.
Первое, что напрашивалось из самодельного — использовать два файла-картинки с изображениями включенной и выключенной лампочки. Плохо:
- Надо найти хорошие картинки (или нарисовать, но художник я никакой);
- Принципиальный вопрос: тырить нехорошо, даже картинки, даже валяющиеся под ногами;
- Надо их хранить где-то. В файлах совсем плохо: случайно сотрется — и нету кнопок. В ресурсах получше, но тоже не хочется, если можно обойтись;
- Масштабируемость никакая;
- Настраиваемость (цвета, например) достигается только добавлением файлов. То есть, ресурсоемко и негибко.
Второе, что вытекает из первого — вместо картинок использовать векторные изображения. Тем более, что Qt умеет рендерить SVG. Тут уже чуть проще с поиском собственно изображения: в сети много уроков по векторной графике, можно найти что-то более-менее подходящее и адаптировать под свои нужды. Но остается вопрос по хранению и настраиваемости, да и рендеринг не бесплатен по ресурсам. Копейки, конечно, но все же.
И третье вытекает из второго: можно же воспользоваться принципами векторной графики при самостоятельной прорисовке изображения! Файл векторной картинки в текстовом виде указывает, что и как рисовать. Я могу кодом указать то же самое, используя векторные туториалы. Благо, у объекта QPainter имеются в наличии необходимые инструменты: перо, кисть, градиент и рисование примитивов, даже заливка текстурой. Да, инструменты далеко не все: нет масок, режимов наложения, но совсем уж фотореалистичности не требуется.
Поискал немного примеры в сети. Взял первый попавшийся урок: «Рисуем кнопку в графическом редакторе Inkscape» с сайта «Рисовать легко». Кнопка из этого урока гораздо больше похожа на лампочку, чем на кнопку, что меня вполне устраивает. Делаю заготовку: вместо Inkscape — проект в Qt.
Проба пера
Создаю новый проект. Выбираю название проекта rgbled (потому что хочу сделать что-то вроде RGB-светодиода) и путь к нему. Выбираю базовый класс QWidget и название RgbLed, отказываюсь создавать файл формы. Проект по умолчанию после запуска делает пустое окно, оно пока неинтересное.
Подготовка к рисованию
Заготовка есть. Теперь надо завести закрытые члены класса, которые будут определять геометрию рисунка. Существенным плюсом векторной графики является ее масштабируемость, поэтому константных чисел должно быть по минимуму, и те лишь задавать пропорции. Размеры будут пересчитываться в событии resizeEvent(), которое надо будет переопределить.
В используемом уроке по рисованию размеры задаются в пикселах по ходу действия. Мне же нужно заранее определить, что я буду использовать и как пересчитывать.
Рисуемая картинка состоит из таких элементов:
- внешнее кольцо (с наклоном наружу, часть выпуклого ободка)
- внутреннее кольцо (с наклоном внутрь)
- корпус лампочки-светодиода, «стекло»
- тень по краю стекла
- верхний блик
- нижний блик
Концентрические круги, то есть, всё, кроме бликов, определяется позицией центра и радиусом. Блики определяются центром, шириной и высотой, причем позиция X центров бликов совпадает с позицией X центра всего рисунка.
Для расчетов элементов геометрии понадобится определить, что больше — ширина или высота, потому что лампочка круглая и должна вписываться в квадрат со стороной, равной меньшему из двух измерений. Итак, добавляю соответствующие закрытые члены в заголовочный файл.
Затем переопределяю защищенную функцию, вызываемую при изменении размеров виджета.
Здесь вычисляется сторона квадрата, в который вписана лампочка, центр этого квадрата, радиус ободка, занимающего максимально возможную площадь, ширина ободка, внешняя часть которого пусть будет 1/10 от диаметра, а внутренняя — 1/14. Затем вычисляется положение бликов, которые находятся в серединах верхнего и нижнего радиусов, ширина и высота подбираются на глазок.
Кроме того, в защищенные поля сразу добавлю набор цветов, которые будут использоваться.
По названиям примерно понятно, что это цвета лампочки, светлой части тени, темной части тени, три цвета кольцевой тени вокруг лампочки и цвета градиентов бликов.
Цвета надо бы инициализировать, поэтому дополню заготовку конструктора.
Еще надо не забыть вставить в заголовочный файл инклуды классов, которые понадобятся при рисовании.
Этот код компилируется успешно, но в окне виджета ничего не изменилось. Пора начинать рисовать.
Рисование
Ввожу закрытую функцию
и переопределяю защищенную функцию
Событие перерисовки будет вызывать собственно рисование, которому в качестве параметра передается цвет «стекла».
Пока так. А функцию рисования начинаем понемногу заполнять.
Сперва создается объект-художник, который и будет заниматься рисованием. Затем создается карандаш, который нужен для того, чтобы карандаша не было: в данном изображении обводка по контуру не просто не нужна, а вообще не нужна.
Затем рисуется первый круг в примерном соответствии с уроком по векторной графике: большой круг, залитый радиальным градиентом. У градиента светлая опорная точка вверху, но не на самом краю, а темная — внизу, но тоже не на самом краю. На основе градиента создается кисть, этой кистью художник painter закрашивает круг (то есть, эллипс, вписанный в квадрат). Получается такой код
Среда подчеркивает параметр color функции drawLed, потому что он не используется. Пусть потерпит, он пока не нужен, но скоро понадобится. Запущенный проект выдает такой результат:
Добавляем еще порцию кода.
Почти тот же самый круг, только меньше размером и вверх ногами. Получаем такую картинку:
Дальше наконец-то понадобится цвет стекла:
Здесь при помощи функции darker из переданного цвета получается такой же цвет, но потемнее, для организации градиента. Коэффициент 120 подобран на глазок. Вот результат:
Добавляю кольцевую тень вокруг стекла. Так сделано в уроке по векторной графике, и это должно добавить объему и реалистичности:
Тут градиент трехступенчатый, чтобы тень была гуще к краю и бледнела к центру. Получается так:
Добавляю блики, сразу оба. Верхний блик в отличие от нижнего (и всех остальных элементов) сделан линейным градиентом. Художник из меня так себе, поверю на слово автору урока. Возможно, в этом есть какая-то правда, экспериментировать с разными видами градиентов не буду.
Вот, собственно, и все, готовая лампочка, как на КДПВ.
На заметность бликов и выпуклости стекла влияет цвет, точнее, то, насколько он темный. Возможно, имеет смысл добавить регулировку яркости бликов и коэффициента затемнения в функции darker в зависимости от темности, но это уже перфекционизм, я считаю.
Ниже — пример использования в окне программы.
Баловство
Для интереса можно поиграться с цветами. Например, переопределив защищенное событие клацанья мыши
не забыв добавить мышиные события в заголовок:
Теперь щелчок мыши по компоненту будет переключать цвет лампочки: красный, зеленый, синий, серый и какой-то от фонаря наугад подобранный.
Эпилог
Что касается рисования, то на этом все. А виджету следует добавить функциональности. В моем случае было добавлено булево поле «использовать ли состояние»", еще одно булево поле, определяющее состояние «Вкл» или «Выкл» и цвета по умолчанию для этих состояний, а также открытые геттеры и сеттеры для всего этого. Эти поля используются в функции paintEvent() для выбора цвета, передаваемого drawLed() в виде параметра. В результате можно отключить использование состояний и задавать «лампочке» любой цвет, а можно включить состояния и зажигать или гасить лампочку по событиям. Особенно удобно сделать сеттер состояния открытым слотом и соединить его с сигналом, который надо отслеживать.
Использование mousePressEvent демонстрирует, что виджет можно сделать не только индикатором, но и кнопкой, делая ее нажатой, отпущенной, гнутой, скрученной, раскрашенной и какой хотите еще по событиям наведения, нажатия и отпускания.
Но это уже не принципиально. Целью было показать, где можно взять образцы для подражания при прорисовке собственных виджетов и как эту прорисовку несложно реализовать без использования картинок растровых или векторных, в ресурсах или файлах.
Сегодня мы немного поупражняемся в рисовании. Для этого в Qt5 есть очень мощный класс QPainter. Он может рисовать всё: от простых линий до сложных геометрических фигур, таких как секторы, дуги окружностей, многоугольники и т.д.
Методика отрисовки объектов средствами Qt5 сводится к следующему:
сначала нужно при помощи публичного наследования создать дочерний класс от класса QWidget;
в созданном классе переопределить метод перерисовки paintEvent(QPaintEvent *) .
Метод paintEvent(QPaintEvent *) всегда вызывается при создании виджета, а также всякий раз, когда нужно перерисовать его внешний вид (например, мы растянули/сжали окно и вместе с этим изменили размеры нашего виджета).
Линии
А начнем мы с простого примера, в котором нарисуем в клиентской области окна шесть линий, которые будут отличаться друг от друга стилем.
Для этого мы создадим дочерний класс Lines, наследуя класс QWidget, и поместим код отрисовки линий внутри переопределенного метода paintEvent().
Заголовочный файл — lines.h:
Файл реализации — lines.cpp:
Т.к. метод paintEvent() вызывается каждый раз при обновлении виджета, то мы будем создавать объект класса QPainter и выполнять операцию рисования внутри данного метода. Поскольку сам объект QPaintEvent *e не используется, то желательно заранее сообщить об этом с помощью макроса Q_UNUSED , иначе компилятор выдаст предупреждение. Фактическая отрисовка линии делегируется методу drawLines():
При помощи класса QPen мы создаем объект pen со следующими параметрами:
толщина 2 пикселя;
Объект pen используется для рисования линий и контуров фигур. Устанавливаем его с помощью метода setPen():
Метод drawLine() рисует линию, его четыре параметра — это координаты двух точек (начала и конца линии):
Метод setStyle() устанавливает стиль Qt::DashLine (пунктирная линия) для объекта pen :
Основной файл программы — main.cpp:
Результат выполнения программы:
Палитра цветов
Цвет — это объект, представляющий собой комбинацию значений интенсивности красного, зеленого и синего цветов (сокр. «RGB» от англ. «Red, Green, Blue»). Допустимые значения RGB находятся в диапазоне от 0 до 255. В следующем примере мы попробуем нарисовать 9 прямоугольников с серым контуром, заполненных разными цветными заливками.
Заголовочный файл — colours.h:
Файл реализации — colours.cpp:
Класс кисти QBrush определяет шаблон заливки фигур, нарисованных с помощью объекта класса QPainter. Метод drawRect() рисует прямоугольник:
первые два параметра метода — это координаты ( x;y ) верхнего левого угла прямоугольника;
другие два параметра — это значения ширины прямоугольника и его высоты.
Для указания цвета мы будем использовать шестнадцатеричное представление:
Основной файл программы — main.cpp:
Результат выполнения программы:
Шаблоны узоров
Следующий пример аналогичен предыдущему, но в этот раз мы будем заполнять прямоугольники не сплошным цветом, а с помощью уже готовых шаблонов узоров кисти.
Заголовочный файл — patterns.h:
Файл реализации — patterns.cpp:
Создаем прямоугольник с определенным рисунком. Qt::HorPattern — это константа, используемая для создания шаблона горизонтальных линий:
Внешний вид всех шаблонов узоров кисти приведен в результате выполнения программы.
Основной файл программы — main.cpp:
Результат выполнения программы:
Прозрачные прямоугольники
Прозрачность — это величина, характеризующая возможность объекта пропускать свет. Самый простой способ понять прозрачность — это представить себе кусок стекла или воды. Технически лучи света могут проходить сквозь стекло, и таким образом наблюдатель имеет возможность видеть объекты, находящиеся за ним.
В компьютерной графике мы можем добиться эффекта прозрачности, используя метод альфа-композитинга. Данный метод комбинирует изображение с фоном для создания эффекта частичной прозрачности. При этом в процессе композиции используется так называемый альфа-канал — это дополнительный канал, который может быть добавлен в рисунок, и содержащий информацию о его прозрачности.
В следующем примере мы нарисуем 11 прямоугольников с разными уровнями прозрачности.
Во фреймворке Qt имеется возможность рисовать как на графической сцене , так и прямо на виджетах. Для этого используется класс QPainter. Рисование объектов на виджетах может производится в функции paintEvent(*event) , которая вызывается при отрисовке виджета.
В данном уроке будет производится работа именно с этой функцией. В ней будет создаваться объект класса QPainter, и с его помощью будет рисоваться круг. При этом рисование круга будет зависеть от трех объектов класса QRadioButton. В зависимости от того, какой радиобаттон выбран, будет выбираться и цвет круга, если же ни один радиобаттон не будет выбран, то круг будет рисоваться белого цвета.
- 1. Структура проекта для QPainter
- 2. widget.ui
- 3. widget.h
- 4. widget.cpp
- 5. Итог
Структура проекта для QPainter
Проект содержит следующие файлы:
- painter.pro - профайл проекта;
- widget.h - заголовочный файл класса, в котором и производится работа с QPainter;
- widget.cpp - файл исходных кодов по работе с QPainter;
- main.cpp - запускающий файл проекта;
- widget.ui - интерфейс приложения.
widget.ui
В дизайнере форм добавляем в виджет GroupBox с радиобаттонами и вертикальный spacer.
Внешний вид приложения
widget.h
В данном файле только определяем метод paintEvent() .
widget.cpp
А вот логика работы приложения полностью помещается в данный файл. В методе paintEvent() реализуем опрос радиобаттонов и по их состоянию рисуем круг на основном виджете.
В результате у Вас получится приложение, показанное на ниже следующем рисунке. Демонстрация работы приложения присутствует в видеоуроке по данной статье.
Архив с исходниками: painter
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.Рекомендуемые статьи по этой тематике
По статье задано2 вопрос(ов)
Подписчики
Платёжная система
Доброго времени суток. Подскажите, пожалуйста, следующую вещь: что такое private slots: void on_radioButton_red_clicked(); void on_radioButton_green_clicked(); void on_radioButton_blue_clicked(); Откуда они взялись. Это какие - то предопределенные слоты класса radiobutton. Почему они исполняются при нажатии переключателей. Хоть убей, не пойму. Никакой инфы не нашел. Где они задаются привязанными к переключателям При попытке изменить имя слотов, они перестают работать. Сформулирую вопрос иначе: Где связываются переключатели и эти три слота.
Зы, т.е. если, я переименовываю слот, скажем,с void on_radioButton_blue_clicked() на void blue_clicked() в файлах widget.h и widget.cpp, все компилируется, но, переключатель radioButton_blue перестает работать ЗЫ: компилирую пример в Visual Studio. Creatorom и Designerom не пользуюсь. Эти слоты привязываются где - то в них?
Да. Совершенно верно. Данные слоты создаются через дизайнер. Если в дизайнере кликнуть правой кнопкой мыши на какой-то объект и выбрать создание слота clicked() , то будет выглядеть примерно так, как показано в примере.
Qt имеет этап предкомпиляции, когда создаются всякие мок файлы, и в данном случае привязка идёт по текстовому наименованию слота. Если слот не находится, то он не подключается во время компиляции.
Вы должны будете найти подключение этих слотов в ui_widget.h , который автоматически создаётя во время компиляции.
Понятно,спасибо,посмотрю. У нас,в виду исторических обстоятельств,весь код писан чисто в vs,без использования креатора(когда собирали qt под студии,креатор,даже,не компилировали),все интерфейсы писались через кодирование,и,все эти ui для меня темный лес. интересно расширить познания в этой области))
Проект, в котором я работаю на данный момент развивается уже более 5-ти лет. Я в нём работаю последние 8 месяцев. Интерфейс полностью написан на Ui, причём с использованием плагинов для Qt Designer, также над продуктом работает команда переводчиков. В продукте сотни диалоговых окон. И из своих наблюдений могу сказать, что если бы не использовались Ui, то в какой-то момент работать стало бы очень грустно, причём не только программистам, но и переводчикам, поскольку тот же самый Qt Linguist, поддерживает отображение Ui файлов, что гораздо удобнее, чем смотреть в код и не понимать, к какому всё-таки окну это относится.
Увидел, еще, один интересный момент. Вы написали про редактирование слота clicked(), а, в дизайнере я этой возможности не нашел, но, тут такой момент. у меня qt собрана самостоятельно, без creator-a, есть, только, дизайнер. А, в дизайнере нет поддержки слотов(по правой клавише мыши). Потом, поставил Creator, а, там, дизайнер открывается в контексте проекта, вот, там создание слота clicked,уже есть. Про этот момент я не знал, поэтому, и, не нашел в дизайнере, как создать слот. В новых версиях qt создание слотов из дизайнера вынесли.
Но согласитесь, что отсутствие ui - это проблема того, что проекты давно развиваются, имеется много легаси кода и того, что на момента развития проектов и сам Qt не был так сильно развит. Например, Qt4 и Qt5 довольно сильно различаются, да даже Qt 5.6 очень отличается от Qt 5.8 по некоторым модулям, особенно если полезть в сторону QML. А после пары калымов я вовсе зарёкся браться за проекты с Qt4, особенно те, которые пишутся на устаревших версиях VS. Наверняка, многое можно стандартизировать и шаблонизировать. В нашем проекте сотни диалоговых окон с ui, но все они создаются и наполняются динамически. А также имеют всего несколько базовых классов. Просто различные вкладки интерфейса скрываются или добавляются в зависимости от этих самых условий, про которые вы сказали. Ну а так да. - Переписывать всё действительно жёстко, если проекты давно развиваются, но это уже немного другая история.
Читайте также: