文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。
使用 blitting 实现更快速的渲染
Blitting 是栅格图形中的一种标准技术,在 Matplotlib 中可用于(显著)提升交互式图形的性能。例如,animation
和 widgets
模块内部就使用了 blitting。下面我们将演示如何在这些类之外自行实现 blitting。
Blitting 通过一次性把所有不变化的图形元素渲染成一张背景图,从而加速重复绘制。之后每次绘图时,只需把变化了的元素绘制到这张背景上即可。例如,如果 Axes 的坐标范围没有改变,我们可以先把不含数据的空 Axes(包括所有刻度与标签)渲染一次,后续只需绘制变化的数据。
具体策略如下:
- 准备不变的背景:
- 绘制 figure,但把需要动画的 artist 通过
Artist.set_animated
标记为 animated,从而排除在背景之外。 - 保存一份 RGBA 缓冲区的拷贝。
- 绘制 figure,但把需要动画的 artist 通过
- 渲染单张图像:
- 还原 RGBA 缓冲区的拷贝。
- 使用
Axes.draw_artist
/Figure.draw_artist
重绘那些 animated 的 artist。 - 把最终图像显示在屏幕上。
该过程的一个结果是:你的 animated artist 永远绘制在静态 artist 之上。
并非所有后端都支持 blitting。你可以通过 FigureCanvasBase.supports_blit
属性检查给定 canvas 是否支持。
警告:以下代码在 macosx 后端上无法工作(但在 Mac 上的其他 GUI 后端可以运行)。
最小示例
我们可以结合 FigureCanvasAgg
的 copy_from_bbox
与 restore_region
方法,并把 artist 设为 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 告诉 matplotlib 只有当我们显式请求时才绘制该 artist
(ln,) = ax.plot(x, np.sin(x), animated=True)
# 让窗口弹出,但脚本继续执行
plt.show(block=False)
# 先停一下,欣赏空坐标轴,并确保至少渲染了一次。
#
# 我们需要先让 figure 以最终尺寸完整绘制到屏幕上,
# 再继续后续步骤,这样:
# a) 我们可以抓取正确尺寸且已绘制好的背景
# b) 我们拥有缓存 renderer,使得 ``ax.draw_artist`` 可以工作
# 因此我们先启动事件循环,让后端处理所有挂起操作
plt.pause(0.1)
# 抓取整个 figure(位于 fig.bbox 内)的拷贝,不包含 animated artist
bg = fig.canvas.copy_from_bbox(fig.bbox)
# 绘制 animated artist,这里会使用缓存的 renderer
ax.draw_artist(ln)
# 把结果输出到屏幕,把更新后的 RGBA 缓冲区推送到 GUI 框架
fig.canvas.blit(fig.bbox)
for j in range(100):
# 重置背景到 canvas 状态,但屏幕不变
fig.canvas.restore_region(bg)
# 更新 artist,此时 canvas 状态和屏幕都未改变
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
# 重新渲染 artist,更新 canvas 状态,但屏幕不变
ax.draw_artist(ln)
# 把图像拷贝到 GUI 状态,但屏幕可能尚未刷新
fig.canvas.blit(fig.bbox)
# 刷新所有挂起的 GUI 事件,按需要重绘屏幕
fig.canvas.flush_events()
# 如想放慢速度,可插入暂停
# plt.pause(.1)
这个示例可以工作并展示了一个简单动画,但由于我们只抓取了一次背景,如果 figure 的像素尺寸发生变化(由于 figure 尺寸或 dpi 改变),背景就会失效,导致图像错误(但有时看起来还挺酷!)。示例里还出现了全局变量和大量样板代码,提示我们应该把它封装成类。
基于类的示例
我们可以用一个类来封装这些样板逻辑和状态:
恢复背景、绘制 artist、把结果 blit 到屏幕。
此外,可以利用 'draw_event'
回调,在发生完整重绘时重新抓取背景,
从而正确处理窗口大小变化。
class BlitManager:
def __init__(self, canvas, animated_artists=()):
"""
参数
----------
canvas : FigureCanvasAgg
要操作的 canvas,仅适用于拥有 `~FigureCanvasAgg.copy_from_bbox`
与 `~FigureCanvasAgg.restore_region` 方法的 Agg 子类。
animated_artists : Iterable[Artist]
要管理的 artist 列表
"""
self.canvas = canvas
self._bg = None
self._artists = []
for a in animated_artists:
self.add_artist(a)
# 每次绘制时抓取背景
self.cid = canvas.mpl_connect("draw_event", self.on_draw)
def on_draw(self, event):
"""与 '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):
"""
添加要管理的 artist。
参数
----------
art : Artist
要添加的 artist。会被设为 'animated'(保险起见)。
*art* 必须位于此类所管理 canvas 对应的 figure 中。
"""
if art.figure != self.canvas.figure:
raise RuntimeError
art.set_animated(True)
self._artists.append(art)
def _draw_animated(self):
"""绘制所有 animated artist。"""
fig = self.canvas.figure
for a in self._artists:
fig.draw_artist(a)
def update(self):
"""用 animated artist 更新屏幕。"""
cv = self.canvas
fig = cv.figure
# 以防万一我们错过了 draw 事件
if self._bg is None:
self.on_draw(None)
else:
# 还原背景
cv.restore_region(self._bg)
# 绘制所有 animated artist
self._draw_animated()
# 更新 GUI 状态
cv.blit(fig.bbox)
# 让 GUI 事件循环处理所有事务
cv.flush_events()
以下展示如何使用这个类。这个例子比前面的稍复杂,因为我们还添加了一个文本帧计数器。
# 创建新 figure
fig, ax = plt.subplots()
# 添加一条线
(ln,) = ax.plot(x, np.sin(x), animated=True)
# 添加帧号
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])
# 确保窗口已显示并绘制
plt.show(block=False)
plt.pause(.1)
for j in range(100):
# 更新 artist
ln.set_ydata(np.sin(x + (j / 100) * np.pi))
fr_number.set_text(f"frame: {j}")
# 通知 blitting manager 进行更新
bm.update()
这个类不依赖 pyplot
,可嵌入更大的 GUI 应用程序。
Download Jupyter notebook: blitting.ipynb
Download Python source code: blitting.py
风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。