krita / gimp / inkscape:三个画图工具的 harness 怎么分工

2026-08-10

在 CLI-Anything 仓库里,krita、gimp、inkscape 是三个挨着的目录,包路径也长得一模一样,都是 <app>/agent-harness/cli_anything/<pkg>/<pkg>_cli.py。三个都是「画图软件」,你很容易预期它们的 harness 是一个模子刻出来的三份拷贝。

我们采集时(2026-08-10,对应仓库快照 39634a6)把这三个包逐个读了一遍,结论是:外壳同构,里子不同。而且差异的方向和直觉是反的——命令面最小的 krita,恰恰是三个里最离不开你本机那份宿主软件的一个

这里先声明一件事,后面反复要用:这类 harness 是要在你本机起外部进程、跑外部程序的。三个包里的 backend 模块都有 subprocess.run,krita 在 utils/krita_backend.py:108,gimp 在 utils/gimp_backend.py:47-63batch_script_fu() 里,inkscape 在 utils/inkscape_backend.py:58:92:124 三处。宿主软件也得你自己装。这不是细节,是这三个 harness 分工差异的根因。

三行对照

驱动宿主的方式core 模块组内命令非测试行 / 测试行test 函数e2e 要不要真宿主
kritakrita --export --export-filename …utils/krita_backend.py:2063192822 / 58547要,且明说不降级
gimpScript-Fu 批处理,缺 GIMP 回落 Pillow(utils/gimp_backend.py:47core/export.py:977404010 / 1187115部分(真实图片 I/O)
inkscape自己改 SVG,导出时调 inkscape --export-filename=…utils/inkscape_backend.py:5210604726 / 2010207不要

(组内命令不含各自的顶层 repl;core 模块数不含 __init__.py。这三行是从 12 个代表应用的对照里摘出来的,完整那张表另有一篇专门讲。)

这张表最好按最后两列反过来读:一个 harness 能自己读写宿主的文件格式到什么程度,决定了它的命令面能长多大、以及它的测试离不离得开真软件。

inkscape:因为格式是开的,所以命令能长到 60 条

inkscape 的 SKILL.md 里那句自述说得很直白——它「直接操作 SVG(XML)文档,用一个 JSON 工程格式做状态跟踪」(skills/SKILL.md:11)。SVG 就是一份 XML,Python 自己就能改。于是这个 harness 根本不需要为了「加一个矩形」去启动 Inkscape。

结果就是 core 下摊出了 10 个模块:document.pyexport.pygradients.pylayers.pypaths.pysession.pyshapes.pystyles.pytext.pytransforms.py,另有一份 231 行的 utils/svg_utils.py。命令面 10 个组 60 条:document(8)、shape(11)、text(3)、style(6)、transform(7)、layer(7)、path(6)、gradient(4)、export(4)、session(4)。内置形状类型 10 种(core/shapes.py:15SHAPE_TYPES)。

真正调 Inkscape 可执行文件的只有导出这一步,utils/inkscape_backend.py:52 拼的是 cmd = [inkscape, svg_path, f"--export-filename={output_path}", f"--export-dpi={dpi}"],导出预设 6 个(core/export.py:17:png_web、png_print、png_hires、svg、pdf、eps)。所以它的 e2e 测试文件头才敢写「No actual Inkscape installation is required.」(tests/test_full_e2e.py:1-6)。

但命令存在 ≠ 这条命令做完了它字面上的事

这是本篇最该记住的一处,也是整个注册表体系最容易被读错的地方:注册表里有这个条目、--help 里有这条命令,不等于这个能力是齐的。

inkscape 的 path 组里有一整套布尔路径运算,union / intersection / difference / exclusion / convert / list-operations,从 inkscape_cli.py:769 起。看命令名,这是矢量编辑里最硬的一块功能。但翻到 core/paths.py:171:布尔运算结果对象的 d(路径数据)字段,取的是 obj_a 的原 d 值,那一行的注释里自己标了 # Placeholder

从代码路径看,这条命令会走完并往 session 里落一个新对象,但真正的路径求交求并在这一行是占位的。仓库里把它标出来了,我们照实记;至于调用它会得到什么样的文件,我们没有运行过,不作判断。

你要评估任何一个 harness 的时候,这就是那个可复现的核查动作:别只数命令条数,去 core/grep -rn 'Placeholder\|TODO\|NotImplemented' 一遍。

gimp:三个里唯一一个宿主不在也能出图的

gimp 走的是 Script-Fu 批处理这条路。utils/gimp_backend.py:1-6 的模块说明就写着用 GIMP 的 Script-Fu batch mode,:47-63batch_script_fu() 起子进程;:14-22 有个 _script_fu_escape() 专门对传进去的字符串做转义;可执行名探测三种:gimpgimp-2.10gimp-2.99:27)。

它跟另外两个最不一样的地方是双后端core/export.py:6-8 把渲染后端按顺序列了两个:1. GIMP(真引擎);2. Pillow(PIL),纯 Python 的回落路径,用于 GIMP 缺席时。:77 有一句注释 fall through to Pillow:97from PIL import Image, ImageDraw, ImageFont:100-102 的报错文案会提示你 pip install Pillow

这条回落路径带来一个直接后果:三个里只有 gimp 在宿主没装的情况下仍有一条能出图的通道。它的滤镜注册表 24 项(core/filters.py:7FILTER_REGISTRY),导出预设 13 个(core/export.py:16),命令面 8 个组 40 条,其中 layer 组一家就占 9 条。

顺带记一处口径差异:gimp 的 skills/SKILL.md:5:11)把自己描述成「built on Pillow」的有状态命令行图像编辑器,而代码里 Pillow 是 GIMP 缺席时的回落路径,主路径是 Script-Fu 批处理(core/export.py:6-8utils/gimp_backend.py:1-3)。两处写的不是一回事,以我们读到的代码为准;为什么会这样,我们不推断。

krita:3 个模块、19 条命令,然后把活全交给 Krita 自己

krita 的 core 只有 3 个模块——export.py(504 行)、project.py(470 行)、session.py(117 行),是 12 个代表应用里 core 最少的之一。命令面 6 个组 19 条:project(4)、layer(4)、filter(2)、canvas(2)、export(4)、session(3),另有 2 条顶层命令 status(L509)与 repl(L538)——顶层带 status 这一点,另外两个包没有。

它没有像 inkscape 那样自己去解 .kra,而是把导出这件事整个转成 Krita 命令行参数。utils/krita_backend.py:206 拼的是 args = [krita, "--export", "--export-filename", output_path]:209 按 key/value 追加 --export-option:253-259--export-sequence / --export-sequence-start / --export-sequence-end 导动画序列。导出预设 13 个(core/export.py:29:png、png-web、jpeg、jpeg-web、jpeg-low、tiff、tiff-lzw、psd、pdf、svg、webp、gif、bmp)——预设数跟 gimp 一样多,但每一个都要靠真 Krita 才能落地。

于是就有了那个反直觉的组合:三个里命令最少(19 条)、代码最少(2822 行)、测试也最少(47 个 test 函数),而对宿主的硬依赖是三个里最绕不开的。它的 e2e 文件头写得毫不含糊:这些测试会调用真实的 Krita 应用做导出,「No graceful degradation — Krita must be installed.」(tests/test_full_e2e.py:1-6)。

同一句话在 inkscape 那边是「不需要装」,在 krita 这边是「必须装、且不降级」。你判断某个 harness 能不能进你的流水线,先读这六行 docstring,比读 README 快。

状态模型:三家的撤销不是同一套

三个包 core 下都有 session.py,但实现不一样。

gimp 用的是与 blender、obs-studio 同款的 Sessioncore/session.py:36-39MAX_UNDO = 50,落盘用 fcntl.flock 加排他锁。这里有一处 Windows 读者要留意:blender 的 core/session.py:10-33_locked_save_jsonfcntl.flock 加排他锁,Windows 无 fcntl 时降级为不加锁继续写,这是代码层面的行为;把这句话直接写进 docstring 的是 freecad 的 core/session.py:20-23。gimp 这份是同款实现。

krita 的不一样。core/session.py:17Session 保存的是 (时间戳, label, dict) 三元组快照列表,撤销时会丢弃 redo 分支(:29-40),落盘走 utils/io.py 里的 locked_save_json。inkscape 的 session 组是 4 条命令,session.py 在 core 的 10 个模块之列。

差别落到用法上很实际:如果你的编排里有「撤到某一步再往另一个方向改」这种回溯,krita 这边撤销之后 redo 分支是丢掉的。具体该怎么组织你的调用顺序,取决于你自己的流程,项目没有给通用建议。

dry-run 与测试口径

--dry-run 这个开关三家都有,但密度不同:inkscape 7 处、gimp 4 处、krita 3 处(grep -c 'dry.run\|dry_run' <cli>.py)。同一批 12 个应用里,QGIS、zotero、obsidian、comfyui 是 0 处。要在真动文件之前先看一眼命令会做什么,这个计数值得先扫。

测试这边,三家的 TEST.md 与我们数出来的 def test_ 条数都对不上,方向各不相同:

TEST.md 写的我们数到的
gimptest_core 5 类 66 条、e2e 9 类 37 条,合计 103(tests/TEST.md:5-971 与 44,合计 115;类数 7 与 12
inkscapetest_core 11 类 150 条、e2e 5 类 47 条,合计 197(tests/TEST.md:5-9153 与 54,合计 207;类数 12 与 7
krita「~40 unit tests planned」「~20 E2E tests planned」(tests/TEST.md:7-836 与 11

krita 这份和另外两份还不是一类东西:它写的是「planned」,是计划不是结果。我们的计数用的是 grep -h 'def test_' <app>/agent-harness/cli_anything/*/tests/*.py | wc -l,没有排除嵌套在测试内部的辅助函数,也没有展开 pytest.mark.parametrize。所以这些差异只能说明两边口径或时点不同,不能断言哪一边写错了。我们说到这里为止。

按你的处境选

三条路径,倒着推:

  • 你手上就是 SVG,或者产出物要进版本库做 diff:inkscape 那条最合脚,它自己改 XML,本机没装 Inkscape 也能把编辑跑完,只有导出栅格图那一步需要可执行文件。前提是先确认你要用的命令不在占位实现的名单里。
  • 你要批量做位图处理,而机器上不方便装 GIMP:gimp 是三个里唯一留了 Pillow 回落的,代价是回落路径与 GIMP 引擎不是同一套渲染实现,两者是否等价我们没有依据,不比。
  • 你的产出必须是 Krita 的真实导出(比如 .kra 转 psd、导动画序列):krita 那条是唯一给了通道的,但它的前提写死了——Krita 必须装在本机、必须能被命令行调起来,harness 不做降级。
  • 你只是想先看看这三个能不能用:别从 README 开始,先跑下面这几条。
# 1. 各自的驱动方式:backend 模块头六行就写清楚了
sed -n '1,10p' krita/agent-harness/cli_anything/krita/utils/krita_backend.py
sed -n '1,10p' gimp/agent-harness/cli_anything/gimp/utils/gimp_backend.py
sed -n '1,10p' inkscape/agent-harness/cli_anything/inkscape/utils/inkscape_backend.py

# 2. e2e 到底要不要装宿主:读这六行
sed -n '1,6p' krita/agent-harness/cli_anything/krita/tests/test_full_e2e.py
sed -n '1,6p' inkscape/agent-harness/cli_anything/inkscape/tests/test_full_e2e.py

# 3. 命令是不是占位实现
grep -rn 'Placeholder' inkscape/agent-harness/cli_anything/inkscape/core/

# 4. 有多少条命令支持先空跑
grep -c 'dry.run\|dry_run' */agent-harness/cli_anything/*/*_cli.py

以上为按仓库中的文件路径与统计口径组合的示例,未经实测,以官方文档与 --help 的实际输出为准。

最后回到开头那句:三个画图 harness 的差别不在「谁的命令多」,而在「谁自己会读写宿主的格式」。inkscape 会,所以它长成 60 条命令、测试不用装软件;krita 不碰 .kra,所以它只有 19 条命令、却必须有真 Krita;gimp 在中间,多了一条 Pillow 回落。你要给自己的软件套一层 harness 时,这个取舍是第一个要先想清楚的,它会一路决定你的模块划分、命令面大小和测试能不能在 CI 里跑。


本文依据 CLI-Anything 官方仓库(github.com/HKUDS/CLI-Anything)的 README、registry.jsondocs/ 与各 agent-harness/ 下的源码整理,核对日 2026-08-10,对应仓库快照 39634a6。 本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何一个 harness, 也没有在本机驱动过任何一款宿主软件,因此不涉及实际操控效果的任何描述。 这类 harness 会在本机执行外部程序与脚本,是否使用请结合自身环境评估。 注册表与 harness 内容随上游更新而变动,请以仓库最新内容为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。