|
Буферизация графических данных
На этом занятии поговорим
об ускорении процесса перерисовки окна за счет буферизации ранее созданного
рисунка. Но, вначале я приведу пример, когда это может быть необходимо.
Предположим, что
мы создаем вот такой графический элемент:
то, есть,
отображаем данные в виде разноцветных столбцов (баров). Используя наши текущие
знания, это можно реализовать следующим образом:
import wx
import random
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)
self.Bind(wx.EVT_SIZE, self.OnResize)
def OnResize(self, e):
self.Refresh()
def OnPaint(self, e):
dc = wx.PaintDC(self)
self.Draw(dc)
def Draw(self, dc):
width, height = self.GetSize()
bars = [10, 20, 5, 100, 1]
colors = [wx.BLUE, wx.RED, wx.GREEN, wx.YELLOW, wx.CYAN]
total = len(bars)
offset = 20 #отсутпы от границ окна
bottom = height-3*offset #нижняя точка окна при рисовании
right = width-2*offset #правая точка
widthBar = round((width-3*offset)/total) #ширина бара
scale = (height-4*offset)/max(bars) #масштаб по высоте
dc.SetPen(wx.Pen(None, style=wx.TRANSPARENT))
dc.SetBrush(wx.Brush(wx.WHITE))
dc.DrawRectangle(0,0, width, height)
dc.SetPen(wx.Pen("#aaaaaa", 1, wx.SOLID))
dc.DrawLine(offset, offset, offset, bottom)
dc.DrawLine(offset, bottom, right, bottom)
x = offset
for b, c in zip(bars, colors):
b = round(b*scale)
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(x, bottom-b, widthBar, b)
x += widthBar
dc.SetPen(wx.Pen('GREEN'))
for i in range(10000):
x = random.randint(1, width - 1)
y = random.randint(1, height - 1)
dc.DrawPoint(x, y)
app = wx.App()
frame = MyFrame(None, 'wxPython')
frame.Show()
app.MainLoop()
Запустим
программу, и смотрите, уводя окно за пределы экрана и возвращая его обратно,
оно постоянно перерисовывается и это сильно затормаживает процесс: оно
дергается. И это неудивительно, при таком обилии графических примитивов
затрачивается немало времени на прорисовку окна.
Чтобы ускорить
этот процесс, можно заранее прорисовать содержимое окна и хранить в памяти
(буфере), а при необходимости, брать из буфера готовое изображение и рисовать
его как обычный растр:
Первый вопрос:
как организовать такой буфер? Для этого создается объект wx.Bitmap в памяти
устройства, покрывающий весь экран:
self.btm = wx.Bitmap(wx.GetDisplaySize())
На тот случай,
если пользователь распахнет окно и оно будет занимать всю область экрана. Затем,
для рисования в wx.Bitmap нужно связать с ней контекст DC. Для этого мы
создаем контекст
self.btmDC = wx.MemoryDC()
и связываем его
с объектом wx.Bitmap:
self.btmDC.SelectObject(self.btm)
Все, теперь
через self.btmDC можно рисовать
в wx.Bitmap, расположенной
в памяти устройства, любые графические примитивы. Нарисуем наш график в этом
буфере. Объявим еще один метод
DrawIntoBitmap()
в котором и
будет происходить рисование в буфере:
def DrawIntoBitmap(self):
self.sizeBtm = self.GetSize()
width, height = self.sizeBtm
bars = [10, 20, 5, 100, 1]
colors = [wx.BLUE, wx.RED, wx.GREEN, wx.YELLOW, wx.CYAN]
total = len(bars)
offset = 20 #отсутпы от границ окна
bottom = height-3*offset #нижняя точка окна при рисовании
right = width-2*offset #правая точка
widthBar = round((width-3*offset)/total) #ширина бара
scale = (height-4*offset)/max(bars) #масштаб по высоте
self.btmDC.SetPen(wx.Pen(None, style=wx.TRANSPARENT))
self.btmDC.SetBrush(wx.Brush(wx.WHITE))
self.btmDC.DrawRectangle(0,0, width, height)
self.btmDC.SetPen(wx.Pen("#aaaaaa", 1, wx.SOLID))
self.btmDC.DrawLine(offset, offset, offset, bottom)
self.btmDC.DrawLine(offset, bottom, right, bottom)
x = offset
for b, c in zip(bars, colors):
b = round(b*scale)
self.btmDC.SetBrush(wx.Brush(c))
self.btmDC.DrawRectangle(x, bottom-b, widthBar, b)
x += widthBar
self.btmDC.SetPen(wx.Pen('GREEN'))
for i in range(10000):
x = random.randint(1, width - 1)
y = random.randint(1, height - 1)
self.btmDC.DrawPoint(x, y)
То есть, мы
просто перенесли фрагмент текста программы из метода Draw в метод DrawIntoBitmap
и немного модифицировали его, чтобы рисование происходило в памяти, а не на
экране. Соответственно, из метода Draw удалим все
строчки и добавим вот такую:
def Draw(self, dc):
dc.Blit(0, 0, self.sizeBtm.width, self.sizeBtm.height, self.btmDC, 0, 0)
Данный метод
выполняет копирование содержимого из контекста self.btmDC в контекст dc, связанный с
экраном. В результате, подготовленное растровое изображение копируется в
экранную область и отображается.
Также в методе
def OnResize(self, e):
self.DrawIntoBitmap()
self.Draw(wx.ClientDC(self))
добавим вызов
метода DrawIntoBitmap и запустим
программу. Смотрите, теперь убирая окно за край экрана, и возвращая его
обратно, оно движется плавно, т.к. графические данные не формируются заново, а
берутся из буфера и отображаются в виде готовой картинки. То есть, это заметно
ускорило работу нашей программы. Хотя, если менять размеры изображения, то оно
все равно продолжает дергаться, т.к. приходится формировать новый график с
новым масштабом. Но, здесь уже ничего не поделаешь, да и изменение размеров
окна – это не такой уж и частый процесс. С этим можно смириться.
Вот так
буферизация графической информации позволяет ускорять процесс перерисовки окна.
|