|
Основы работы с графикой
Пришло время поговорить
о графических возможностях пакета wxPython и посмотреть
как выполняется рисование графических примитивов в оконном интерфейсе программы.
Сам процесс
рисования осуществляется через контекст устройства (device context),
сокращенно по-английски DC. И wxPython поддерживает
следующие основные их разновидности:
-
wx.MemoryDC
– контекст памяти (рисование в объекте Bitmap, расположенном
в памяти устройства);
-
wx.PrinterDC – контекст
принтера (для ОС Windows и Mac);
-
wx.ScreenDC – контекст
экрана устройства (рисование на экране без привязки к окну);
-
wx.ClientDC, wx.PaintDC – рисование в
клиентской области окна;
-
wx.WindowDC – рисование во
всем окне.
Здесь оба
контекста wx.ClientDC, wx.PaintDC выполняют
рисование в клиентской области окна, но, тем не менее, между ними есть
принципиальная разница, о которой я скажу чуть позже.
Давайте для
начала нарисуем в окне линию. В самом простом случае это можно сделать так:
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title, pos=(0,0), size=(700, 400))
wx.CallLater(100, self.onDraw)
def onDraw(self):
dc = wx.ClientDC(self)
dc.DrawLine(0,0, 200, 100)
app = wx.App()
frame = MyFrame(None, 'wxPython')
frame.Show()
app.MainLoop()
Смотрите, мы в
конструкторе MyFrame вызываем метод onDraw через 100
миллисекунд и в этом методе получаем контекст для клиентской области окна с
помощью метода ClientDC и рисуем линию в этом контексте, то
есть, в клиентской области. После запуска программы увидим следующее:
Здесь сразу
возникает вопрос: зачем нужно было делать эту задержку на 100 миллисекунд? Дело
в том, что пока мы находимся в конструкторе оконного класса, контекст для него
не может быть получен, его еще пока попросту нет. Он будет доступен только при
отображении окна на экране. Поэтому нам и нужна здесь эта задержка, чтобы окно
успело появиться. Но, как вы понимаете, задержка – это плохая практика программирования:
мы ее никогда не сможем точно подобрать, чтобы линия отображалась точно в
момент показа окна. И, кроме того, если сместить это окно за пределы экрана, а
затем, опять вернуть, то линия перерисовываться не будет. ОС не берут на себя
такую функциональности и предоставляют самому приложению перерисовывать
фрагменты интерфейса и другую графическую информацию. Но тогда как узнать: в
какой момент выполнять эту перерисовку? Для этого в приложении на wxPython генерируется
специальное событие
EVT_PAINT
которое и
сигнализирует о необходимости перерисовать содержимое окна. То есть, в нашей
программе вместо задержки нужно повесить обработчик на это событие:
self.Bind(wx.EVT_PAINT, self.OnPaint)
И уже в нем
реализовывать всю прорисовку:
def OnPaint(self, e):
dc = wx.PaintDC(self)
dc.DrawLine(50, 60, 190, 60)
Причем, мы
используем контекст PaintDC, а не ClientDC, так как в
момент возникновения события EVT_PAINT контекст PaintDC создается
автоматически и это заметно ускоряет работу с графикой (здесь не тратится время
на создание и удаление контекста устройства).
Теперь, при
запуске программы мы можем совершенно спокойно перемещать окно и линия не будет
пропадать. Итак, чтобы что-либо нарисовать непосредственно в окне, мы должны
получить контекст DC для рисования и уже через него выполнять
рисование изображений, текста и графических примитивов. Это основная логика
работы с графикой в оконном интерфейсе.
wx.Pen
Для рисования
графических примитивов с разными цветами линий используется класс Pen, который имеет
следующий синтаксис:
wx.Pen(wx.Colour
colour, width=1, style=wx.SOLID)
Все параметры, в
принципе должны быть понятны, кроме style, который может
принимать следующие значения:
-
wx.SOLID – сплошная
линия;
-
wx.DOT – линия из
точек;
-
wx.LONG_DASH – линия из
длинных отрезков;
-
wx.SHORT_DASH – линия из
коротких отрезков;
-
wx.DOT_DASH –
черточка-точка;
-
wx.TRANSPARENT
– прозрачная линия.
В самом простом
случае мы можем его использовать так:
dc.SetPen(wx.Pen('#fdc073'))
dc.DrawLine(0, 0, 200, 100)
dc.DrawRectangle(300, 10, 200, 100)
Мы здесь для контекста
dc указали свой
собственный объект Pen. В результате получили оранжевый цвет линии.
Теперь пару слов
о цвете. Его можно определить или в виде строки формата
#RRGGBB
где RR, GG, BB – красная,
зеленая и синяя компоненты цвета в шестнадцатиричной записи. Их комбинации
определяют все возможные цвета. Также цвет можно задать через предопределенные
константы, например:
-
wx.BLACK
– черный;
-
wx.BLUE – синий;
-
wx.GREEN – зеленый;
-
wx.GREY – серый;
-
wx.RED – красный;
-
wx.YELLOW – желтый;
-
wx.WHITE – белый.
Полный их набор
можно посмотреть на странице:
https://docs.wxpython.org/wx.ColourDatabase.html
Наконец, первым
параметром в конструкторе класса Pen можно указать ссылку на объект wx.Colour,
содержащий цвет. Но, чаще всего записывают строку в шестнадцатиричном формате.
Давайте для
примера создадим класс Pen с синей линией толщиной 5 пикселей и
стилем LONG_DASH:
dc.SetPen(wx.Pen(wx.BLUE, 5, wx.LONG_DASH))
При запуске
программы увидим такую картину:
wx.Brush
Этот класс (Brush – кисть в
переводе с англ.) определяет цвет заливки графических примитивов. Его
конструктор, следующий:
Brush(colour,
style=BRUSHSTYLE_SOLID)
Первый параметр
также является цветом, а второй определяет стиль заливки:
-
wx.SOLID – непрерывная
заливка;
-
wx.BDIAGONAL_HATCH
– диагональная штриховка;
-
wx.CROSSDIAG_HATCH
– диагональная штриховка внахлест;
-
wx.FDIAGONAL_HATCH – обратная
диагональная штриховка;
-
wx.CROSS_HATCH – штриховка
сеточкой;
-
wx.HORIZONTAL_HATCH –
горизонтальная штриховка;
-
wx.VERTICAL_HATCH
– вертикальная штриховка;
-
wx.TRANSPARENT
– прозрачная заливка.
В качестве
примера реализуем такие стили заливки:
def OnPaint(self, e):
dc = wx.PaintDC(self)
dc.SetPen(wx.Pen(wx.BLUE, 5, wx.LONG_DASH))
dc.DrawLine(0, 0, 200, 100)
dc.SetBrush(wx.Brush('#3F4137', wx.BDIAGONAL_HATCH))
dc.DrawRectangle(300, 10, 200, 100)
dc.SetBrush(wx.Brush(wx.YELLOW, wx.CROSSDIAG_HATCH))
dc.DrawRectangle(10, 150, 100, 100)
dc.SetBrush(wx.Brush(wx.GREEN, wx.CROSS_HATCH))
dc.DrawRectangle(200, 150, 100, 100)
dc.SetBrush(wx.Brush(wx.RED, wx.VERTICAL_HATCH))
dc.DrawRectangle(400, 150, 100, 100)
Дополнительные параметры при рисовании линий
Объект Pen дополнительно
может оперировать двумя атрибутами:
-
join – способ
сопряжения линий в точке соединения;
-
cap – способ прорисовки
конца линии;
через методы SetJoin
и SetCap. Первый метод принимает константы:
-
wx.JOIN_MITER – обычное
сопряжение (значение по умолчанию);
-
wx.JOIN_BEVEL – создание
фаски;
-
wx.JOIN_ROUND
– создание скругления.
А второй:
-
wx.CAP_ROUND – скругленный
конец линии;
-
wx.CAP_PROJECTING – продолжает
линию за граничную точку на величину ее половинной толщины;
-
wx.CAP_BUTT
– рисует линию до конечной точки (не переходя ее).
В качестве
примера добавим в нашу программу вызовы этих методов. Вначале создадим отдельно
объект класса Pen и установим его
для контекста:
pen = wx.Pen(wx.BLUE, 10)
dc.SetPen(pen)
Далее, перед
рисованием прямоугольника запишем такие строчки:
pen.SetJoin(wx.JOIN_BEVEL)
dc.SetPen(pen)
То есть, мы
меняем инструмент Pen и снова передаем его контексту, обновляя его.
Теперь, при запуске программы наши прямоугольники будут иметь скошенные углы.
Причем, этот эффект будет наблюдаться, если толщина линии больше 1. А вот
параметр JOIN_ROUND в моем случае не сработал?!
Для демонстрации
эффекта параметра cap, нарисуем следующие линии:
def OnPaint(self, e):
dc = wx.PaintDC(self)
pen = wx.Pen(wx.BLUE, 10)
pen.SetCap(wx.CAP_BUTT)
dc.SetPen(pen)
dc.DrawLine(30, 150, 150, 150)
pen.SetCap(wx.CAP_PROJECTING)
dc.SetPen(pen)
dc.DrawLine(30, 190, 150, 190)
pen.SetCap(wx.CAP_ROUND)
dc.SetPen(pen)
dc.DrawLine(30, 230, 150, 230)
pen2 = wx.Pen('#4c4c4c', 1, wx.SOLID)
dc.SetPen(pen2)
dc.DrawLine(30, 130, 30, 250)
dc.DrawLine(150, 130, 150, 250)
dc.DrawLine(155, 130, 155, 250)
При запуске
программы увидим такое изображение:
Пользовательская заливка
С помощью класса
Brush можно создавать
свои собственные виды заливок. Для этого нужно заранее создать изображение,
например, такое (pattern1.png):
И, затем,
указать его в качестве рисунка у кисти:
def OnPaint(self, e):
dc = wx.PaintDC(self)
rect = self.GetClientRect()
pen = wx.Pen(wx.BLUE, 0, wx.TRANSPARENT)
dc.SetPen(pen)
dc.SetBrush(wx.Brush(wx.Bitmap('pattern1.png')))
dc.DrawRectangle(0, 0, rect.width, rect.height)
При запуске
программы увидим такое окно:
Градиентная заливка
Наконец, можно
создавать вот такие прямоугольные градиентные заливки:
Это делается с
помощью инструмента:
GradientFillLinear(self,
rect, initialColour, destColour, nDirection=RIGHT)
-
rect
– прямоугольная
область заливки;
-
initialColour – начальный
цвет заливки;
-
destColour – конечный цвет
заливки;
-
nDirection – направление
градиента.
Изображение окна
выше создается вот таким обработчиком:
def OnPaint(self, e):
dc = wx.PaintDC(self)
dc.GradientFillLinear((10, 10, 600, 50), '#00cc00', '#444444', wx.NORTH)
dc.GradientFillLinear((10, 80, 600, 50), '#0000cc', '#444444', wx.SOUTH)
dc.GradientFillLinear((10, 140, 600, 50), '#cc0000', '#444444', wx.EAST)
dc.GradientFillLinear((10, 200, 600, 50), '#ffccff', '#444444', wx.WEST)
Как видите, все
довольно просто. Конечно, здесь может возникнуть вопрос: а как сделать
градиентные заливки у произвольных фигур? Об этом мы еще поговорим, когда будем
рассматривать инструмент Clipping – ограничение области рисования.
|