Сталкивались ли вы с ситуацией, когда «вроде всё нарисовали», а на экране — пусто? Или когда окно мерцает, линии выглядят «лесенкой», а прозрачность ведёт себя так, будто у неё собственные правила? В графике Qt одна мелкая ошибка превращается в часы отладки — и это знакомо каждому, кто хотя бы раз трогал paintEvent().
Эта глава раскроет, почему у «меча» (QPainter) нет смысла без «ножен» (QPaintDevice) — и что именно профессиональные разработчики держат в голове, чтобы рисование всегда было предсказуемым. Вы обнаружите неочевидные причины мерцания, «пропажи» фигур и странного поведения альфа-канала — и узнаете секрет, как получать чистую картинку без лишних затрат производительности.
Здесь задействованы 3 краеугольных класса (QPainter / QPaintDevice / QPaintEngine), 3 типа градиентов, режимы Anti-aliasing, QPicture для записи команд, трансформации (translate/scale/rotate/shear), отсечения (ClipRegion/ClipPath) и «магия» composition mode, которая часто заменяет ручную обработку пикселей.
Пропустить эту главу — значит продолжать рисовать «на удачу». А можно сделать так, чтобы графика в Qt наконец начала работать как система.
И да — примеры не придётся «восстанавливать по тексту». В конце доступны архив со всеми исходниками, готовыми для компиляции, и 16 бесплатных глав — чтобы сразу закрепить материал на практике.
Самопроверка по главе
Почему рисование в Qt должно выполняться исключительно в методе paintEvent(), а не в конструкторе виджета или обычных слотах?Ответ
Правильный ответ: Это фундаментальный принцип работы с графикой в Qt, связанный с циклом обработки событий и механизмом перерисовки. Попытки рисовать вне paintEvent() не дадут результата, так как контекст рисования не инициализирован должным образом.
Зачем Qt использует двойную буферизацию, если это требует дополнительной памяти?Ответ
Правильный ответ: Двойная буферизация подавляет эффект мерцания при перерисовке, формируя изображение в невидимом буфере и перенося его в видимую область за один раз. Это происходит автоматически без необходимости реализации кода.
Что означает «космическая ширина» пера и в каких случаях её следует использовать?Ответ
Правильный ответ: Это перо с шириной равной нулю, что означает отрисовку линии как можно тоньше в зависимости от устройства вывода. Используется для максимально тонких линий при масштабировании или печати.
Почему при включенном сглаживании чёрный пиксел с координатами (50; 50) отображается как четыре серых пиксела?Ответ
Правильный ответ: Центр пиксела находится в его середине (50,5; 50,5), а сглаживание добавляет промежуточные цвета для сглаживания краёв, распределяя цвет на четыре соседних пиксела с координатами (49,5; 49,5), (49,5; 50,5), (50,5; 49,5) и (50,5; 50,5).
В чём заключается главное преимущество использования QPainterPath по сравнению с прямым рисованием фигур?Ответ
Правильный ответ: Единожды созданную траекторию можно отображать многократно одним вызовом drawPath(), что эффективнее при необходимости повторного рисования сложных геометрических композиций.
Почему порядок следования трансформаций (translate, scale, rotate) влияет на конечный результат?Ответ
Правильный ответ: Трансформации применяются последовательно к системе координат, и каждая последующая трансформация работает уже в преобразованной системе координат. Например, поворот после смещения даст иной результат, чем поворот до смещения.
Зачем нужны методы save() и restore() объекта QPainter?Ответ
Правильный ответ: Они сохраняют и восстанавливают состояние QPainter (перо, кисть, трансформации, шрифты), что позволяет временно изменять настройки для рисования отдельных элементов, а затем вернуться к исходным настройкам.
В каком случае перо со стилем NoPen может быть полезным?Ответ
Правильный ответ: Когда нужно вывести замкнутую фигуру (прямоугольник, эллипс) определённого цвета без контурной линии, используется заливка кистью без рисования контура.
Чем отличается линейный градиент от радиального с точки зрения распределения цветов?Ответ
Правильный ответ: Линейный градиент интерполирует цвета вдоль прямой линии между двумя контрольными точками, а радиальный — от центральной точки (или точки фокуса) к окружности заданного радиуса.
Что произойдёт, если попытаться нарисовать эллипс с установленной областью отсечения, не покрывающей его полностью?Ответ
Правильный ответ: Будет видна только та часть эллипса, которая попадает в область отсечения; остальная часть рисования будет невидимой, так как выход за пределы области отсечения блокируется.
Зачем в Qt6 введена архитектура RHI (Rendering Hardware Interface)?Ответ
Правильный ответ: RHI является абстрактным слоем над различными графическими API (Vulkan, Metal, Direct3D, OpenGL), позволяя Qt автоматически выбирать оптимальный бэкенд для каждой платформы, что улучшает производительность и совместимость.
Как использование QPicture может помочь при необходимости вывода одинаковой графики на экран и принтер?Ответ
Правильный ответ: QPicture записывает команды рисования в независимом от устройства формате, которые затем можно воспроизвести на любом контексте рисования (экран, принтер, файл) без изменения кода.
Почему для работы с прозрачностью рекомендуется использовать формат Format_ARGB32_Premultiplied?Ответ
Правильный ответ: Этот формат использует предумноженные цветовые значения (premultiplied alpha), что ускоряет операции смешивания и совмещения, особенно при работе с градиентами и режимами композиции.
В чём разница между методами united() и xored() при комбинировании областей QRegion?Ответ
Правильный ответ: united() возвращает объединение двух областей (всё, что входит хотя бы в одну), а xored() возвращает симметрическую разность (точки из каждой области, но не из обеих одновременно).
Зачем проверять режим совмещения перед манипуляцией с альфа-каналом пикселов растрового изображения?Ответ
Правильный ответ: Часто нужный эффект можно достичь просто установкой подходящего режима совмещения (CompositionMode), что гораздо эффективнее, чем ручное прохождение по всем пикселам изображения.
Практические задания
Простой уровень
Цветовой градиентный калейдоскоп
Создайте виджет, который отображает четыре квадрата, каждый с разным типом градиента: линейным (красный→синий), коническим (зелёный→жёлтый→зелёный), радиальным (белый→чёрный) и обычной сплошной заливкой. Расположите квадраты в сетке 2×2. Включите сглаживание для плавных переходов.
Подсказки: Используйте QLinearGradient, QConicalGradient и QRadialGradient. Метод setColorAt() устанавливает цвета в точках от 0 до 1. Для рисования квадратов используйте drawRect(). Не забудьте вызвать setRenderHint(QPainter::Antialiasing, true).
Средний уровень
Анимированные трансформации
Создайте приложение, которое отображает прямоугольник с текстом внутри. Добавьте три кнопки: «Повернуть», «Масштабировать» и «Сбросить». При нажатии кнопок применяйте соответствующие трансформации: поворот на 15°, масштабирование ×1.2 или возврат к исходному состоянию. Используйте save()/restore() для корректного управления состоянием QPainter.
Подсказки: Храните текущую трансформационную матрицу QTransform как член класса виджета. В paintEvent() применяйте её через setTransform(). Методы rotate(), scale() и reset() изменяют матрицу. Вызывайте update() после изменения для перерисовки.
Сложный уровень
Графический редактор с режимами совмещения
Создайте мини-редактор, позволяющий рисовать мышью на холсте. Добавьте выбор инструментов (кисть, ластик), настройку толщины пера и выпадающий список режимов совмещения (SourceOver, Clear, Xor, Multiply). Реализуйте отмену последнего действия. Используйте QPicture или QPixmap для хранения нарисованного, чтобы избежать потери данных при перерисовке. Добавьте возможность сохранения результата в файл.
Подсказки: Используйте события mousePressEvent, mouseMoveEvent, mouseReleaseEvent для отслеживания рисования. Храните QPainterPath для текущего штриха. Сохраняйте историю операций в QList<QPicture> для функции отмены. Метод setCompositionMode() переключает режимы совмещения. Для сохранения используйте QPixmap::save().
🎨 Присоединяйтесь к обсуждению!
Разобрались с архитектурой рисования «Артур»? Применили градиенты или освоили трансформации координат?
Может быть, у вас возникли вопросы о режимах совмещения или оптимизации графических операций? А может, вы создали интересный визуальный эффект и хотите поделиться опытом?
Обсудите свои находки, задайте вопросы по QPainter или помогите другим читателям понять тонкости работы с графикой в Qt. Ваш опыт бесценен для сообщества!