P7-04 · smudge 引擎:拖动已有颜色
- Phase / ID: P7 / P7-04
- Depends on: P7-02(沉积内核)、P7-03(
sampleRegion) - Files:
packages/core/src/brush/SmudgeEngine.ts(新)、packages/core/src/brush/BrushPreset.ts、packages/core/src/brush/index.ts、packages/saier/src/brush/index.ts、packages/saier/test/ - Effort: L
Context
P7 的核心。smudge / color-mixing 笔刷维护一个颜色桶(smudge bucket),沿途取色、留色、再沉积,从而「拖动已有颜色」「两色交界自然过渡」。模型对齐 Krita color smudge / MyPaint:
每个 dab(位置 x,y,半径 r):
- 取色:
S = sampleRegion(layerId, dabRect)(P7-03)。 - 留色(persistence
p):bucket = lerp(S, bucket, p)——p越大颜色拖得越远。 - 自色(colorAmount
c):deposit = lerp(bucket, brushColor, c)——c=0纯抹(手指 / blender),c=1纯上色。 - 沉积:以
deposit+ density / dilution(P7-02)落 dab。
实现要点:取色必须看到「刚落的上一笔」(D10 已定)
BrushEngine.addPoint 一次返回一批 dab;但 smudge 第 N+1 个 dab 的取色依赖第 N 个 dab 已经画到画布。若沿用「先收集整批 dab 再统一 paint」,同批内取色读到的是落笔前的旧像素 → 快速描边拖不动色。业界(MyPaint / Krita / SAI)一律逐 dab 串行,这不是可选项。
做法(D10):SmudgeEngine 注入一个 SurfaceSampler,集成层把绘制循环改为逐 dab 交错——sample → mix → paintDab → 下一颗(saier/src/brush 现在是 paintDabs(addPoint(point)) 批量;smudge 路径改为单颗即画即采)。引擎仍「只产 dab」、不持后端句柄,采样经注入接口;确定性成立(采样是 surface 状态的纯函数,surface 状态确定演化)。
已落地(2026-06-30):SmudgeEngine 复用 SimpleBrushEngine 产几何 dab,并通过 prepareDab(dab, sample) 维护 smudge bucket、计算沉积色;saier/src/brush 检测 smudge 引擎后逐 dab 执行 sampleRegion(layerId, fromCircle(localDab), { dab }) → prepareDab → paintDab,因此同批第 N+1 颗 dab 能看到第 N 颗刚写入的 CPU tile 像素。默认预设新增 smudge / blender,RenderTexture 后端因无 sampleRegion 走明确门控。
Steps
- 引擎:实现
SmudgeEngine implements BrushEngine(集成层逐 dab 传入 sample),按上式维护bucket、读persistence/smudge/colorAmount;beginStroke初始化桶(建议首点采样初始化,或透明起手,二选一并测)。 - 集成:
saier/src/brush为 smudge 引擎走逐 dab 交错路径(即画即采),其余笔刷保持批量;smudge 笔迹照常beginStroke/endStroke/applyPatch(bbox/tile undo 复用)。 - 预设 / factory:
createBrushEngineFromPreset支持engine: 'smudge';DEFAULT_BRUSH_PRESETS加smudge(纯抹,colorAmount≈0)与blender预设。 - 后端门控:smudge 需
sampleRegion,tile 后端提供(P7-00 已让 tile 成为默认,D11);显式选RenderTexture后端时 smudge 禁用(与 P7-07 协同)。 - 单测(浏览器真实 WebGL,参照
clipping-layers.browser.spec.ts):① 在红块上用 smudge 划向空白 → 颜色被拖出带状渐隐;② 红 / 蓝交界处划过 → 中间出现自然过渡的紫;③persistence越大拖痕越长;④colorAmount越大越接近纯上色;⑤ undo / redo 像素一致;⑥ 同输入确定性。
Acceptance
- [x] smudge 能拖动已有颜色(拖痕随 persistence 变长)。
- [x] 两色交界自然过渡(边界采样到中间色并沉积)。
- [x] smudge 描边可独立 undo / redo,像素一致。
- [x] 取色看到同批内刚落的上一 dab(交错路径生效);同输入确定性。
Out of scope
- 湿边 / 纸纹(P7-05 / P7-06);GPU 后端 smudge(D11);UI(P7-07)。