22 strokes.jsonl 规范
✅ 已验证 · 代码 code/canvas.py 的 _journal_rec() / _JOURNAL_OPS
Session(journal=True) 把落笔与写像素的方法包一层,每调一次写一行 JSON 到 <作品>/strokes.jsonl。它是「这张画是不是真画出来的」的唯一证据,也是回放的输入。
通用字段
每行都有:
| 字段 | 类型 | 含义 |
|---|---|---|
i | int | 序号,从 1 起,连续 |
t | float | 距会话开始的秒数 |
op | str | 操作名,下表之一 |
坐标一律是画布像素(8x 作业就是 8192×12288 的坐标),y 向下。layer 是节点路径(由 build() 建的树)或节点名(否则)。
落笔类(可精确回放)
op | 字段 | 说明 |
|---|---|---|
stroke | layer, pts: [[x,y],…], pressure: [p,…] | 逐段 paintLine,pressure 与 pts 等长,0..1;null = 全程 1.0 |
path | layer, pts | 一次 paintPath,quadTo 平滑,全程压力固定 |
line | layer, pts, width, segments | 分段变宽的浮点路径(线稿专用) |
dab | layer, xy: [x,y], pressure | 一个点 |
ellipse | layer, rect: [x,y,w,h], fill | |
polygon | layer, pts | 自动闭合 |
{"i": 6, "t": 221.045, "op": "stroke", "layer": "BG_Glow",
"pts": [[0.0, 0.0], [292.32, 0.0], [584.56, 0.0]], "pressure": [0.55, 1.0, 0.55]}
{"i": 26612, "t": 239.116, "op": "path", "layer": "Marks",
"pts": [[6448.0, 3976.0], [6406.88, 4000.8]]}
换装类
op | 字段 | 说明 |
|---|---|---|
set_preset | preset, size | 换 brush preset(带 pump) |
set_color | rgb: [r,g,b] | 前景色(带 pump) |
brush | rgb / size / opacity / flow / preset | 快路径,只记真正传了的字段,一条约 40 字节 |
{"i": 4, "t": 221.043, "op": "brush", "preset": "b) Airbrush Soft"}
{"i": 5, "t": 221.044, "op": "brush", "rgb": [0, 0, 0], "size": 584.95}
落笔的颜色和笔径就是它前面最近一条 brush / set_color / set_preset 设的。size 是传给 setBrushSize 的值,不是画出来的宽度(第 21 章的系数)。
写像素类(只记指纹,不记像素)
op | 字段 | 说明 |
|---|---|---|
set_pixels | layer, box: [x,y,w,h], shape, mean: [r,g,b,a], sha1 | 写了什么形状的数组、均值、前 12 位 sha1 |
ground | layer, box, rgb | 铺底色 |
erase | layer, box | 清 alpha |
filter | layer, name, params | 应用 filter |
{"i": 1, "t": 2.426, "op": "set_pixels", "layer": "BG_Base", "box": [],
"shape": [12288, 8192, 4], "mean": [0.0, 0.0, 0.0, 255.0], "sha1": "6629f2f7f361"}
box: [] 表示整幅。这几类不可精确回放——回放时跳过并报告,调用方要自己把数据部分重跑。
缓冲
逐条 flush 在十万笔量级是纯浪费;但进程挂掉时日志是唯一剩下的东西。折中:每 500 条落一次盘,s.flush() / s.stage() / s.final() 时也落盘。进程被 OOM 杀掉时最多丢 500 条。
规模
8 万笔的作业写 209,004 行(每笔约 2.6 行:换装 + 落笔)、18 MB。
一个已知的局限 🧪
layer 字段在没走 build() 的作业里是 node.name()。第五版的树里 73 个色阶组各有一个 Soft 和一个 Marks,名字不唯一,"layer": "Marks" 无法定位回具体哪一层。回放路由会失败(第 24 章)。修法是让 _node_path() 在 index 为空时递归拼全路径,没做。