# AI 图片蒙版编辑经验 更新日期:2026-05-17 ## 背景 GPT Image 的局部编辑不是把蒙版笔迹合成到参考图里,而是通过 `/images/edits` 的独立 `mask` multipart 字段表达编辑区域。画布 UI 可以把蒙版显示成半透明笔迹,但提交给模型时必须按 OpenAI 语义输出:透明区域表示需要编辑,非透明区域表示保留。 这次问题的核心不是 UI 是否能看到蒙版,而是从画布选择、AI 输入框、任务创建、重新生成、同步/异步适配器到最终 FormData 的每一层都要保留 `maskImage`。 ## 问题表现 - 蒙版笔迹被当作普通画笔合成进参考图,最终请求没有独立 `mask` 参数。 - AI 输入框预览能显示参考图,但看不出该图携带蒙版,容易误判是否会局部编辑。 - 重新生成只回填 prompt 和参考图,漏掉原任务的 `maskImage`。 - 异步图片链路只透传 `referenceImages`,未把 `maskImage` 带到 provider 表单。 - edit 请求与 generation 请求的默认参数不一致,`response_format` 没有统一默认到 `url`。 ## 修复思路 - 把“蒙版画笔”作为 Freehand 的一种 shape,不新增独立实体;大小、形状、快捷键和画笔体验沿用现有工具。 - 选择单张普通图片时扫描与图片相交的蒙版笔迹,按图片显示矩形裁剪,并输出与原图自然尺寸一致的 mask 图片。 - `processSelectedContentForAI` 返回 `maskImage`,并确保蒙版笔迹不再进入普通 `graphicsImage` 合成。 - AI 输入框创建图片编辑任务时使用 `referenceImages: [原图]`、`generationMode: "image_edit"`、`maskImage`,预览中同时展示原图和蒙版。 - 任务预填、重新生成、storage、executor、同步图片适配器和异步图片适配器都透传 `maskImage`。 - GPT Image generation/edit 都默认写入 `response_format: "url"`;edit FormData 必须同时包含 `image[]` 与 `mask`。 ## 代码落点 - `ai-mask-brush.ts`:蒙版笔迹发现、裁剪和导出。 - `selection-utils.ts`:选择内容时识别单图蒙版,避免把蒙版合成进普通图形。 - `AIInputBar.tsx` / `SelectedContentPreview.tsx`:AI 输入框提交与蒙版预览。 - `image-task-prefill.ts` / `task-utils.ts`:重新生成、任务回填和 mask 元数据保留。 - `gpt-image-adapter.ts`:GPT Image 请求体和 edit FormData 的默认 `response_format` 与 `mask` 字段。 - `async-image-api-service.ts` / `media-api` / `media-executor`:异步图片任务透传并提交 `maskImage`。 - `popup-toolbar.tsx` / `freehand-panel.tsx` / `with-hotkey.ts`:蒙版反选入口、工具栏入口和快捷键。 ## 经验规则 - 蒙版是任务参数,不是参考图的一部分;任何合成预览都不能替代最终请求里的独立 `mask`。 - `maskImage` 必须和 `referenceImages` 一样进入任务协议、缓存解析、重新生成和异步 provider 表单。 - UI 层的 60% 透明度只是可视化效果;提交给 OpenAI 时要重新生成 alpha 语义正确的 PNG。 - 预览要展示“原图 + 蒙版”这对媒体,避免用户看见参考图却不知道是否带 mask。 - 本地缓存 URL、素材库 URL 等虚拟路径在提交 FormData 前必须解析成真实图片数据或可上传值。 - 新增图片参数时至少覆盖:选择解析、AI 输入框工作流、任务预填、同步 adapter、异步 adapter、重新生成。 ## 验证 - `pnpm --dir packages/drawnix exec tsc --noEmit --pretty false` - `pnpm --dir packages/drawnix exec vitest run src/services/__tests__/async-image-api-service.test.ts src/services/__tests__/default-image-adapter.test.ts src/services/__tests__/media-api-routing.test.ts src/services/__tests__/media-executor.test.ts` - 建议补充抽查:`ai-mask-brush.test.ts`、`selection-utils.test.ts`、`image-task-prefill.test.ts`、`gpt-image-adapter.test.ts`、`workflow-converter.test.ts` ## 九、蒙版预览缓存不进入素材库 这次还补了一条更底层的经验:**预览可以缓存,不能默认入库**。蒙版画笔导出的 PNG 只是编辑过程中的中间态,不应被素材库当成用户素材展示。 ### 问题表现 - 蒙版预览图会出现在素材库网格里,和真正的图片素材混在一起。 - 这些预览图并不是用户主动保存的资产,却会参与素材库排序、去重和浏览。 - 从产品语义上看,这会把“编辑辅助资源”误当成“可管理素材”。 ### 根因 - `exportImageMaskFromBrushes()` 会把预览 PNG 写入统一缓存。 - `AssetContext` 加载素材时会扫统一缓存,并把可见缓存合并进素材库。 - 之前只排除了 `video-frame` 这类内部缓存,没覆盖蒙版预览这一类新中间态。 ### 修复思路 - 不改蒙版生成逻辑,保留预览缓存能力。 - 在素材库侧新增内部缓存排除规则。 - 把 `ai-mask-brush`、`ai-mask-reference-resize` 归为内部缓存,不参与素材库展示。 ### 架构变更 - 统一缓存继续承担“预览/中间态/可复用资源”的底层存储。 - 素材库不再把统一缓存全量当成业务素材全集,而是先做“可见性过滤”。 - 资产边界被拆成两层: - 用户素材:上传、AI 成果、可管理资源 - 内部缓存:蒙版预览、参考图缩放、视频帧等编辑辅助资源 ### 经验规则 - 任何编辑辅助图都应先问一句:它是“展示给用户看的结果”,还是“给系统内部流程用的中间态”。 - 如果是中间态,允许缓存,但不要直接进入素材库。 - 以后新增内部缓存 source 时,优先补到排除名单,而不是改素材库主流程。 ### 关联代码 - [asset-utils.ts]() - [AssetContext.tsx]()