Буферизация графических данных

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

Предположим, что мы создаем вот такой графический элемент:

то, есть, отображаем данные в виде разноцветных столбцов (баров). Используя наши текущие знания, это можно реализовать следующим образом:

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 и запустим программу. Смотрите, теперь убирая окно за край экрана, и возвращая его обратно, оно движется плавно, т.к. графические данные не формируются заново, а берутся из буфера и отображаются в виде готовой картинки. То есть, это заметно ускорило работу нашей программы. Хотя, если менять размеры изображения, то оно все равно продолжает дергаться, т.к. приходится формировать новый график с новым масштабом. Но, здесь уже ничего не поделаешь, да и изменение размеров окна – это не такой уж и частый процесс. С этим можно смириться.

Вот так буферизация графической информации позволяет ускорять процесс перерисовки окна.

Видео по теме