强基初中数学&学Python——第273课 数字和数学第三方模块Matplotlib之八:高级教程——Blitting加速渲染

  高级教程是针对那些有经验的用户和开发者而编写的。

使用blitting加快渲染速度

  Blitting是光栅图形(raster graphics)中的一种标准技术(standard technique),在Matplotlib的上下文中,可以用来(大幅)提高交互式图形的性能。例如,动画(animation)和小部件(widgets)模块内置使用blitting。在这里,我们演示如何在这些类之外实现自己的blitting。

  Blitting通过将所有不变的图形元素一次性渲染到背景图像中来避免重复绘制,从而加快绘制速度。然后,对于每一次绘制,只需要将不断变化的元素绘制到该背景上。例如,如果一个图表域(Axes)的范围没有改变,我们可以渲染一次性绘制一个包括所有记号和标签的空图表域(Axes),然后只绘制改变的数据。

实现策略:

  · 准备恒定背景:

     绘制图形,但一些艺术家(artists)被标记为动画而被排除(请参见Artist.set_animated)。

     保存RBGA缓冲区的副本。

  · 渲染各个图像:

     恢复RGBA缓冲区的副本。

     使用Axes.draw_artist/Figure.draw_artist重新绘制动画艺术家。

     在屏幕上显示生成的图像。

  此过程的一个结果是,动画艺术家总是绘制在静态艺术家之上。

  并非所有后端都支持blitting。可以通过FigureCanvasBase.supports_blit属性检查给定画布是否支持。

提醒:

  此代码不适用于OSX后端(但适用于Mac上的其他GUI后端)。

最小示例

  我们可以使用FigureCanvasAggcopy_from_bboxrestore_region方法,并将艺术家设置为animated=True,以实现一个使用blitting来加速渲染的最小示例。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

import matplotlib.pyplot as pltimport numpy as np
x = np.linspace(0, 2 * np.pi, 100)
fig, ax = plt.subplots()
# animated=True tells matplotlib to only draw the artist when we# explicitly request it(ln,) = ax.plot(x, np.sin(x), animated=True)
# make sure the window is raised, but the script keeps goingplt.show(block=False)
# stop to admire our empty window axes and ensure it is rendered at# least once.## We need to fully draw the figure at its final size on the screen# before we continue on so that :#  a) we have the correctly sized and drawn background to grab#  b) we have a cached renderer so that ``ax.draw_artist`` works# so we spin the event loop to let the backend process any pending operationsplt.pause(0.1)
# get copy of entire figure (everything inside fig.bbox) sans animated artistbg = fig.canvas.copy_from_bbox(fig.bbox)# draw the animated artist, this uses a cached rendererax.draw_artist(ln)# show the result to the screen, this pushes the updated RGBA buffer from the# renderer to the GUI framework so you can see itfig.canvas.blit(fig.bbox)
for j in range(1000):    # reset the background back in the canvas state, screen unchanged    fig.canvas.restore_region(bg)    # update the artist, neither the canvas state nor the screen have changed    ln.set_ydata(np.sin(x + (j / 100) * np.pi))    # re-render the artist, updating the canvas state, but not the screen    ax.draw_artist(ln)    # copy the image to the GUI state, but screen might not be changed yet    fig.canvas.blit(fig.bbox)    # flush any pending GUI events, re-painting the screen if needed    fig.canvas.flush_events()    # you can put a pause in if you want to slow things down    # plt.pause(.1)

  结果视频:

,时长00:11

  这个例子很有效,显示了一个简单的动画,但因为我们只抓取背景一次,如果图形的像素大小发生变化(由于图形的大小或分别率发生变化),背景将无效,并导致不正确的图像(但有时看起来很酷!)。还有一个全局变量和相当多的样板,这表明我们应该将其封装在一个类中。

类化示例

  我们可以使用一个类来封装样板逻辑和恢复背景的状态、绘制艺术家,然后将结果闪电式(blitting )传输到屏幕。此外,我们可以使用“draw_event”回调来捕捉一个新的背景,只要一个完整的重新绘制能够正确处理大小调整。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

class BlitManager:    def __init__(self, canvas, animated_artists=()):        """        Parameters        ----------        canvas : FigureCanvasAgg            The canvas to work with, this only works for subclasses of the Agg            canvas which have the `~FigureCanvasAgg.copy_from_bbox` and            `~FigureCanvasAgg.restore_region` methods.
        animated_artists : Iterable[Artist]            List of the artists to manage        """        self.canvas = canvas        self._bg = None        self._artists = []
        for a in animated_artists:            self.add_artist(a)        # grab the background on every draw        self.cid = canvas.mpl_connect("draw_event", self.on_draw)
    def on_draw(self, event):        """Callback to register with 'draw_event'."""        cv = self.canvas        if event is not None:            if event.canvas != cv:                raise RuntimeError        self._bg = cv.copy_from_bbox(cv.figure.bbox)        self._draw_animated()
    def add_artist(self, art):        """        Add an artist to be managed.
        Parameters        ----------        art : Artist
            The artist to be added.  Will be set to 'animated' (just            to be safe).  *art* must be in the figure associated with            the canvas this class is managing.
        """        if art.figure != self.canvas.figure:            raise RuntimeError        art.set_animated(True)        self._artists.append(art)
    def _draw_animated(self):        """Draw all of the animated artists."""        fig = self.canvas.figure        for a in self._artists:            fig.draw_artist(a)
    def update(self):        """Update the screen with animated artists."""        cv = self.canvas        fig = cv.figure        # paranoia in case we missed the draw event,        if self._bg is None:            self.on_draw(None)        else:            # restore the background            cv.restore_region(self._bg)            # draw all of the animated artists            self._draw_animated()            # update the GUI state            cv.blit(fig.bbox)        # let the GUI event loop process anything it has to do        cv.flush_events()

  以下是我们如何使用上面的类。这是一个比第一个示例稍微复杂一些的例子,因为我们还添加了一个文本框计数器。

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

· 

import matplotlib.pyplot as pltimport numpy as np
x = np.linspace(0, 2 * np.pi, 100)
# make a new figurefig, ax = plt.subplots()# add a line(ln,) = ax.plot(x, np.sin(x), animated=True)# add a frame numberfr_number = ax.annotate(    "0",    (0, 1),    xycoords="axes fraction",    xytext=(10, -10),    textcoords="offset points",    ha="left",    va="top",    animated=True,)bm = BlitManager(fig.canvas, [ln, fr_number])# make sure our window is on the screen and drawnplt.show(block=False)plt.pause(.1)
for j in range(1000):    # update the artists    ln.set_ydata(np.sin(x + (j / 100) * np.pi))    fr_number.set_text("frame: {j}".format(j=j))    # tell the blitting manager to do its thing    bm.update()

  结果视频:

,时长00:06

  这个类不依赖于pyplot,适合嵌入到更大的GUI应用程序中。


standard technique:https://en.wikipedia.org/wiki/Bit_blitanimation:https://matplotlib.org/stable/api/animation_api.html#module-matplotlib.animationwidgets:https://matplotlib.org/stable/api/widgets_api.html#module-matplotlib.widgetsArtist.set_animated:https://matplotlib.org/stable/api/_as_gen/matplotlib.artist.Artist.set_animated.html#matplotlib.artist.Artist.set_animatedAxes.draw_artist:https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.draw_artist.html#matplotlib.axes.Axes.draw_artistFigure.draw_artisthttps://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.draw_artistFigureCanvasBase.supports_blit:
https://matplotlib.org/stable/api/backend_bases_api.html#matplotlib.backend_bases.FigureCanvasBase.supports_blitFigureCanvasAgg:https://matplotlib.org/stable/api/backend_agg_api.html#matplotlib.backends.backend_agg.FigureCanvasAggcopy_from_bbox:https://matplotlib.org/stable/api/backend_agg_api.html#matplotlib.backends.backend_agg.FigureCanvasAgg.copy_from_bboxrestore_region:https://matplotlib.org/stable/api/backend_agg_api.html#matplotlib.backends.backend_agg.FigureCanvasAgg.restore_regionpyplot:https://matplotlib.org/stable/api/pyplot_summary.html#module-matplotlib.pyplot