备注
前往结尾 下载完整示例代码。
通过使用 blitting 实现更快的渲染#
Blitting 是一种 标准技术 在光栅图形中,在 Matplotlib 的上下文中,可以用来(显著)提高交互式图形的性能。例如,animation
和 widgets
模块在内部使用了 blitting。在这里,我们演示如何在不受这些类限制的情况下实现自己的 blitting。
Blitting 通过将所有不变的图形元素一次性渲染到背景图像中来加速重复绘图。然后,对于每次绘图,只需要将变化的元素绘制到这个背景上。例如,如果 Axes 的限制没有改变,我们可以一次性渲染包括所有刻度和标签的空 Axes,之后只绘制变化的数据。
策略是
准备恒定的背景:
绘制图形,但排除所有你想要通过标记为 animated 来动画化的艺术家(参见
Artist.set_animated
)。保存 RBGA 缓冲区的副本。
渲染单个图像:
恢复 RGBA 缓冲区的副本。
使用
Axes.draw_artist
/Figure.draw_artist
重新绘制动画艺术家。在屏幕上显示生成的图像。
此过程的一个结果是,您的动画艺术家总是绘制在静态艺术家之上。
并非所有后端都支持blitting。你可以通过`.FigureCanvasBase.supports_blit`属性检查给定的画布是否支持。
警告
此代码不适用于 macosx 后端(但在 Mac 上的其他 GUI 后端上可以正常工作)。
最小示例#
我们可以结合使用 FigureCanvasAgg
方法 copy_from_bbox
和 restore_region
,并在我们的艺术家上设置 animated=True
,以实现一个使用 blitting 来加速渲染的最小示例。
import matplotlib.pyplot as plt
import 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 going
plt.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 operations
plt.pause(0.1)
# get copy of entire figure (everything inside fig.bbox) sans animated artist
bg = fig.canvas.copy_from_bbox(fig.bbox)
# draw the animated artist, this uses a cached renderer
ax.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 it
fig.canvas.blit(fig.bbox)
for j in range(100):
# 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)

这个例子可以工作并展示了一个简单的动画,但由于我们只抓取了一次背景,如果图形的大小(由于图形的大小或dpi的变化)发生变化,背景将失效,导致图像不正确(但有时看起来很酷!)。这里还有一个全局变量和相当多的样板代码,这表明我们应该将这些内容封装在一个类中。
基于类的示例#
我们可以使用一个类来封装恢复背景、绘制艺术家,然后将结果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()
以下是我们如何使用我们的类。这个例子比第一个案例稍微复杂一些,因为我们还添加了一个文本帧计数器。
# make a new figure
fig, ax = plt.subplots()
# add a line
(ln,) = ax.plot(x, np.sin(x), animated=True)
# add a frame number
fr_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 drawn
plt.show(block=False)
plt.pause(.1)
for j in range(100):
# update the artists
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
fr_number.set_text(f"frame: {j}")
# tell the blitting manager to do its thing
bm.update()

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