位图与 SVG¶
本节介绍两种“外部资源”图形:像素图片(ImageMobject 家族)与矢量图(SVGMobject 家族)。
AbstractImageMobject¶
AbstractImageMobject 是所有图片 mobject 的抽象基类,位于 manim.mobject.types.image_mobject 模块(没有导出到 manim 顶级命名空间,需要显式导入)。它负责像素数据的读取、缩放与分辨率换算,日常使用直接用其子类 ImageMobject。
from manim.mobject.types.image_mobject import AbstractImageMobject
ImageMobject¶
ImageMobject(filename_or_array, scale_to_resolution=1080, invert=False, image_mode='RGBA', **kwargs) 官方文档 ↗
ImageMobject 在场景中显示一张位图,图片来源可以是文件路径,也可以直接是内存中的 numpy 数组。
name |
type |
default |
desc |
|---|---|---|---|
|
StrPath | NDArray |
— |
图片文件路径,或 HxW(xC) 的 numpy 数组 |
|
int |
1080 |
构造时把图片高度缩放到该像素数对应的 Manim 高度 |
|
bool |
False |
是否做黑白反转 |
|
str |
RGBA |
像素格式,如 RGBA、RGB、L |
ImageMobject("photo.png") # 从文件加载
ImageMobject(np.zeros((64, 64, 4), dtype=np.uint8)) # 从内存数组构造
ImageMobject 直接用 numpy 数组构造 64×64 棋盘格,无需任何外部图片文件;scale_to_resolution 保证像素图在不同分辨率下尺寸一致,之后用 scale / .animate 控制大小。
查看源码 image_array_demo.py
"""ImageArrayDemo: ImageMobject built from an in-memory numpy array."""
import numpy as np
from manim import *
class ImageArrayDemo(Scene):
def construct(self):
# Build a 64x64 RGBA checkerboard in memory — no external file needed.
n = 64
grid = np.indices((n, n)).sum(axis=0) % 2
rgb = np.zeros((n, n, 4), dtype=np.uint8)
rgb[grid == 0] = (88, 196, 221, 255) # TEAL-ish
rgb[grid == 1] = (10, 20, 68, 255) # dark navy
image = ImageMobject(rgb)
image.scale(3)
self.play(FadeIn(image))
self.play(image.animate.rotate(PI / 8).shift(LEFT * 2))
self.wait(1)
常见坑:
数组构造时 dtype 必须是
np.uint8,通道顺序为 RGBA;数组必须是三维(高 × 宽 × 通道)。ImageMobject是像素图,set_stroke/set_fill对它无效;想“框住”图片请叠一个SurroundingRectangle。
SVGMobject¶
SVGMobject(file_name=None, should_center=True, height=2, width=None, color=None, opacity=None, fill_color=None, fill_opacity=None, stroke_color=None, stroke_opacity=None, stroke_width=None, svg_default=None, path_string_config=None, use_svg_cache=True, **kwargs) 官方文档 ↗
SVGMobject 把 SVG 文件解析为矢量 mobject:路径、填充、描边都会尽量保留,之后可以像普通 VMobject 一样着色、变换。
name |
type |
default |
desc |
|---|---|---|---|
|
str | PathLike | None |
None |
SVG 文件路径(相对渲染时的当前工作目录) |
|
bool |
True |
是否把图形居中到原点 |
|
float | None |
2 |
构造后缩放到该高度;None 保持原尺寸 |
|
float | None |
None |
缩放到该宽度;与 height 同时给时按最后逻辑生效 |
|
ParsableManimColor | None |
None |
整体描边色覆盖 |
|
float | None |
None |
整体不透明度覆盖 |
|
— | None |
None |
填充覆盖 |
|
— | None |
None |
描边覆盖 |
|
dict | None |
None |
解析 SVG 时的默认样式(如 |
|
dict | None |
None |
路径字符串解析配置 |
|
bool |
True |
是否缓存解析结果(按文件内容哈希) |
star = SVGMobject("examples/_shared/assets/sample.svg", height=3)
star.set_fill(YELLOW, opacity=1)
从 examples/_shared/assets/sample.svg 加载 SVG,先按文件原样 DrawBorderThenFill 入场,再整体替换为黄色填充、红色描边的版本并缩小。CI 从仓库根目录运行 manim,因此这里使用仓库相对路径。
查看源码 svg_mobject_demo.py
"""SvgMobjectDemo: load examples/_shared/assets/sample.svg and restyle it."""
from manim import *
class SvgMobjectDemo(Scene):
def construct(self):
star = SVGMobject("examples/_shared/assets/sample.svg", height=3)
self.play(DrawBorderThenFill(star))
self.wait(0.5)
star_colored = SVGMobject(
"examples/_shared/assets/sample.svg",
height=3,
fill_color=YELLOW,
stroke_color=RED,
stroke_width=6,
).shift(RIGHT * 0.2)
self.play(Transform(star, star_colored))
self.play(star.animate.shift(LEFT * 2).scale(0.6))
self.wait(0.5)
常见错误
SVGMobject 的 file_name 是相对当前工作目录的路径,不是相对场景文件的路径。本地运行和 CI 的工作目录可能不同,跨环境复现请使用仓库根目录相对的完整路径(如 examples/_shared/assets/sample.svg)。
提示
SVG 文件内容有改动而画面没变?use_svg_cache=True 时解析结果按文件内容哈希缓存,正常会自动失效;如果仍看到旧图形,传 use_svg_cache=False 排查,或清理媒体缓存目录。
VMobjectFromSVGPath¶
VMobjectFromSVGPath(path_obj, long_lines=False, should_subdivide_sharp_curves=False, should_remove_null_curves=False, **kwargs) 官方文档 ↗
VMobjectFromSVGPath 从单个 SVG 路径对象(svgelements.Path)构造 mobject,是 SVGMobject 解析每一段路径时使用的内部类,也被 Brace 用来把大括号 SVG 路径变成矢量图形。
name |
type |
default |
desc |
|---|---|---|---|
|
se.Path |
— |
svgelements 路径对象 |
|
bool |
False |
是否把长直线段细分为多段 |
|
bool |
False |
是否细分急转弯曲线 |
|
bool |
False |
是否移除零长度曲线 |
日常几乎不需要手动构造;理解它有助于排查 SVG 解析问题(例如急角显示异常时开启 should_subdivide_sharp_curves)。
常见错误与建议¶
提示
位图与矢量的选择:需要照片、截图、渐变时选 ImageMobject;需要无损缩放、按路径着色时选 SVGMobject。两者都支持 FadeIn、.animate 等所有通用动画。
自测¶
✏️ 练习
场景要展示一张临时生成的热力图(代码里用 numpy 算出来的 2D 数组),又不想写临时图片文件,怎么做?
✅ 参考答案
把 2D 数据归一化后映射成 RGBA 的 np.uint8 数组,直接传给 ImageMobject:
data = np.random.rand(64, 64)
rgba = np.zeros((64, 64, 4), dtype=np.uint8)
rgba[..., 0] = (data * 255).astype(np.uint8) # 红通道表示数值
rgba[..., 3] = 255 # 不透明
self.add(ImageMobject(rgba))
下一步¶
最后一节介绍颜色系统:ManimColor、调色板常量与颜色工具函数。