Регионы и операции с ними

На этом занятии продолжим говорить о графике и рассмотрим такую вещь как clipping или, другими словами, ограничение области рисования для контекста устройства (device context – DC). Этот инструмент, во-первых, позволяет ускорять работу с графикой, т.к. мы можем попросту сказать: что нужно рисовать, а что нет. И, во-вторых, с его помощью можно получать некоторые дополнительные визуальные эффекты. Но, обо всем поподробнее.

В самом простом случае мы можем создать прямоугольную ограничивающую область с помощью метода:

DC.SetClippingRegion(self, x, y, width, height)

И после рисования обязательно нужно вызвать метод для уничтожения этого региона:

DC.DestroyClippingRegion(self)

Давайте для нашего примера с рисованием линий из центра клиентской области:

import wx
import math
 
class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, pos=(0, 0), size=(700, 400))
        self.Bind(wx.EVT_PAINT, self.OnPaint)
 
    def OnPaint(self, e):
        dc = wx.PaintDC(self)
 
        size_x, size_y = self.GetClientSize()
        dc.SetDeviceOrigin(size_x // 2, size_y // 2)
        radius = math.hypot(size_x / 2, size_y / 2)
        angle = 0
 
        while (angle < 2 * math.pi):
            x = round(radius * math.cos(angle))
            y = round(radius * math.sin(angle))
            dc.DrawLine((0, 0), (x, y))
            angle = angle + 2 * math.pi / 360
 
app = wx.App()
frame = MyFrame(None, 'wxPython')
frame.Show()
app.MainLoop()

Создадим вот такой прямоугольный регион рисования:

dc.SetClippingRegion(20, 20, 200, 100)

а в конце метода OnPaint уничтожим его:

dc.DestroyClippingRegion()

При запуске программы увидим такую картину:

Видите, рисование было ограничено нашим регионом, за его пределами все осталось без изменений – стандартный фон окна. Причем, скорость работы программы также увеличилась, так как стала меньше площадь прорисовки графических объектов.

Но в wxPython можно создавать регионы и на основе полигонов с помощью метода:

DC.SetDeviceClippingRegion(self, region)

Здесь region – это объект класса wx.Region, который определяет форму региона. Давайте его зададим набором вот таких точек:

points = (((0, 85), (75, 75), (100, 10), (125, 75), (200, 85),
            (150, 125), (160, 190), (100, 150), (40, 190), (50, 125)))

И на их основе создадим регион:

region = wx.Region(points)

Затем, вызывая метод:

dc.SetDeviceClippingRegion(region)

Получим вот такое изображение:

Или, сделать так. Вместо линий воспользуемся градиентной заливкой:

dc.GradientFillLinear((0, 10, 210, 200), '#FF0000', '#444444', wx.EAST)

и получим красную звезду:

Операции над регионами

Класс wx.Region дает нам дополнительные возможности создания еще более сложных форм посредством операций:

объединения, пересечения, вычитания и XOR (симметричного вычитания)

Чтобы было проще это понять, возьмем прямоугольные регионы:

region1 = wx.Region(100, 20, 70, 70)
region2 = wx.Region(120, 40, 70, 70)

и, затем, первый объединим со вторым:

region1.Union(region2)

Соответственно, в качестве ограничивающей области рисования, выберем результат объединения, то есть, region1:

dc.SetDeviceClippingRegion(region1)

При запуске программы увидим следующее:

То есть, площади двух наших регионов были объединены и результатом стала вот такая область более сложной формы.

Теперь, давайте сделаем пересечение этих регионов:

region1.Intersect(region2)

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

dc.SetBrush(wx.Brush('#ffffff'))
dc.DrawRectangle(100, 20, 70, 70)
dc.DrawRectangle(120, 40, 70, 70)

Запускаем программу и видим следующее:

Результирующая область была получена путем пересечения соответствующих прямоугольных областей. Причем, результат был сохранен в первом регионе, т.к. мы из него вызывали метод Intersect.

Далее, сделаем вычитание из первого региона второго:

region1.Subtract(region2)

Получилась вполне ожидаемая картина. Если сделать наоборот, из второго вычесть первый:

region2.Subtract(region1)
dc.SetDeviceClippingRegion(region2)

То все будет выглядеть уже так:

Ну и последняя операция XOR дает следующий эффект:

region2.Xor(region1)

Здесь точки, относящиеся одновременно к обоим регионам – исключаются. Вот такими операциями можно получать регионы самых разных форм.

Видео по теме