主要更改: - 重写前端 extension.js,完全自己控制图像渲染 - 去掉 nodes.py 的 image_upload,避免双图像问题 - 实现选区框绘制(橙色)+ 遮罩区域显示(红色) - 实现选区框拖动功能 - 修复 onMouseDown 参数格式: (e, pos, canvas) - 更新文档和开发日志
10 KiB
InpaintRegionEditor 技术验证清单
版本:1.0 | 日期:2026-03-02 目的:验证所有不确定的技术点,为最终方案提供依据 状态:✅ 已完成核心验证
零、验证结论摘要
✅ 已验证可行的方案
| 功能 | 方案 | 来源 |
|---|---|---|
| 图像导出 | app.activeDocument.saveToOE("png") |
Photopea 官方 API |
| 从选区创建蒙版 | 创建临时图层 + 填充黑白 | sd-webui-photopea-embed |
| 单独导出选中图层 | 隐藏其他图层 + saveToOE | sd-webui-photopea-embed |
❌ 已确认不可行
| 功能 | 原因 | 备选方案 |
|---|---|---|
| 直接访问 layer.mask | API 返回 null (Issue #7341) | 使用选区 workaround |
| 检测图层是否有蒙版 | layer.mask 始终为 null | 让用户通过选区操作 |
📋 推荐工作流
用户创建蒙版的方式:
- 在 Photopea 中使用选择工具创建选区
- 点击"从选区创建蒙版"按钮
- 系统自动创建黑白蒙版图层并导出
- 或者:用户直接上传带 Alpha 通道的 PNG
一、Photopea 加载验证
1.1 同时加载原图 + 蒙版
测试目标:能否让 Photopea 同时加载两个文件,并且蒙版作为图层蒙版?
测试脚本:
// 方法 1:先打开图,再加载蒙版作为 mask
app.open("image.png");
var doc = app.activeDocument;
var layer = doc.activeLayer;
app.open("mask.png");
var maskDoc = app.activeDocument;
// 复制 mask 到原图的 layer.mask
预期结果:
- ✅ 成功:图层右侧出现蒙版缩略图
- ❌ 失败:两张独立的图,或者蒙版只是普通图层
优先级:🔴 最高(决定整个工作流)
1.2 加载带 Alpha 通道的 PNG
测试目标:如果上传的 PNG 本身带 Alpha 通道,Photopea 能否自动识别为蒙版?
测试方法:
- 准备一个 RGBA PNG(RGB 是图像,Alpha 是蒙版)
- 用
app.open("data:image/png;base64,...")加载 - 检查
layer.mask是否存在
预期结果:
- ✅ 成功:Alpha 通道自动转为图层蒙版
- ❌ 失败:Alpha 通道只是透明,不是蒙版
1.3 用脚本创建图层蒙版
测试目标:能否用脚本把一张黑白图转换为图层蒙版?
测试脚本:
// 假设已经有一个黑白图在剪贴板
doc.selection.load("mask.png");
layer.addMask(true);
参考:DEVLOG 中提到的 loadTransparentPixels() + addMask()
二、Photopea 导出验证
2.1 导出带蒙版的图像
测试目标:如果图层有蒙版,导出 PNG 时能否保留蒙版到 Alpha 通道?
测试方法:
- 创建一个带蒙版的图层
- 执行
app.activeDocument.saveToOE("png") - 检查导出的 PNG 是否有 Alpha 通道
预期结果:
- ✅ 成功:PNG 的 Alpha = 蒙版
- ❌ 失败:蒙版被丢弃,或者蒙版渲染到 RGB 里
2.2 单独导出蒙版
测试目标:能否只导出蒙版(黑白图)?
❌ 原方案失败:
var mask = layer.mask; // ⚠️ 已知返回 null
doc.activeChannel = layer.mask; // 无法执行
✅ Workaround 方案(已验证):
参考 sd-webui-photopea-embed 的 createMaskFromSelection() 实现:
// 从选区创建蒙版图层
function createMaskFromSelection() {
if (app.activeDocument.selection === null) {
app.echo("No selection!");
return;
}
// 创建临时图层
newLayer = app.activeDocument.artLayers.add();
newLayer.name = "TempMaskLayer";
// 选区外部填充黑色
app.activeDocument.selection.invert();
color = new SolidColor();
color.rgb.red = 0;
color.rgb.green = 0;
color.rgb.blue = 0;
app.activeDocument.selection.fill(color);
// 选区内部填充白色
color.rgb.red = 255;
color.rgb.green = 255;
color.rgb.blue = 255;
app.activeDocument.selection.invert();
app.activeDocument.selection.fill(color);
}
// 导出蒙版图层
function exportMaskLayer() {
// 隐藏其他图层,只显示蒙版图层
// ... (参考 exportSelectedLayerOnly)
app.activeDocument.saveToOE("png");
// 删除临时蒙版图层
app.activeDocument.activeLayer.remove();
}
验证来源:sd-webui-photopea-embed 项目 (https://github.com/yankooliveira/sd-webui-photopea-embed)
结论:✅ 可以通过选区 workaround 实现蒙版导出
2.3 检测当前是否有蒙版
测试目标:在用户点击"保存"前,能否检测当前文档是否有蒙版?
测试脚本:
var hasMask = (app.activeDocument.activeLayer.mask != null);
// 或者
var hasMask = (app.activeDocument.activeLayer.layerMask != null);
预期结果:
- ✅ 成功:能准确检测
- ❌ 失败:无法检测(因为 layer.mask 总是返回 null)
影响:如果无法检测,就无法在保存前提示用户
三、前端交互验证
3.1 检测用户是否禁用了蒙版
测试场景:
- 用户在 Photopea 里编辑
- 点击图层蒙版眼睛图标(禁用蒙版)
- 点保存
问题:
- 这时应该保存什么?
- 原图(忽略蒙版)还是报错?
技术难点:
- Photopea 的 UI 状态(哪个图层可见、蒙版是否启用)无法通过 API 获取
- 只能通过导出后的结果反推
3.2 两次导出 + 两次上传
测试目标:连续导出两次(图像 + 蒙版),能否都成功上传?
测试流程:
// 1. 导出图像
var imageBlob = await exportImage();
var imageResult = await uploadToComfyUI(imageBlob, "edited.png");
// 2. 导出蒙版
var maskBlob = await exportMask();
var maskResult = await uploadToComfyUI(maskBlob, "mask.png");
// 3. 更新节点
node.widgets.find(w => w.name === "image").value = imageResult.name;
node.widgets.find(w => w.name === "mask").value = maskResult.name;
潜在问题:
- Photopea 的 postMessage 是异步的,两次导出会不会冲突?
- 上传需要时间,UI 上怎么显示进度?
四、后端处理验证
4.1 从两个文件合成一个 PNG
测试目标:后端能否把 RGB 图 + 黑白蒙版图 合成到一个 PNG?
测试代码:
from PIL import Image
# 加载 RGB 图
rgb_img = Image.open("edited.png").convert("RGB")
# 加载蒙版图(黑白)
mask_img = Image.open("mask.png").convert("L") # 转为灰度
# 合成
rgb_img.putalpha(mask_img)
rgb_img.save("combined.png")
预期结果:
- ✅ 成功:combined.png 的 Alpha = 蒙版
- ❌ 失败:尺寸不匹配、格式问题
4.2 节点同时输出两个文件名
测试方案 A:两个独立的 widget
"image": (sorted(files), {"image_upload": True}),
"mask": (sorted(files), {"image_upload": True}),
测试方案 B:一个 widget 存 JSON
"files": ("STRING", {"default": '{"image": "", "mask": ""}'}),
测试方案 C:一个 widget + node.properties
"image": (sorted(files), {"image_upload": True}),
# mask 存在 node.properties["mask_file"]
五、UI/UX 验证
5.1 保存按钮的设计
选项 A:两个按钮
[💾 保存图像] [🎭 保存蒙版]
- 优点:用户明确知道在保存什么
- 缺点:步骤多,可能忘记保存另一个
选项 B:一个按钮,自动两次导出
[💾 保存全部]
- 优点:一键完成
- 缺点:慢,失败时难以定位
选项 C:下拉菜单
[💾 保存 ▼]
├─ 保存图像
├─ 保存蒙版
└─ 保存全部
- 优点:灵活
- 缺点:UI 复杂
5.2 错误提示
场景:
- 用户没画蒙版就点"保存蒙版"
- Photopea 里没有活动选区
- 上传失败
提示方式:
- A) alert() 弹窗(简单但打断体验)
- B) 状态栏文字(不打断但可能忽略)
- C) Toast 通知(折中方案)
六、优先级排序
| 编号 | 测试项 | 优先级 | 工作量 | 依赖关系 |
|---|---|---|---|---|
| 1.1 | 同时加载原图 + 蒙版 | 🔴 最高 | 2h | 无 |
| 1.2 | 加载带 Alpha 的 PNG | 🔴 最高 | 1h | 无 |
| 2.1 | 导出带蒙版的 PNG | 🔴 最高 | 1h | 1.1 |
| 2.2 | 单独导出蒙版 | 🔴 最高 | 2h | 1.1 |
| 2.3 | 检测是否有蒙版 | 🟡 中 | 1h | 1.1 |
| 4.1 | 后端合成 PNG | 🟡 中 | 1h | 2.1+2.2 |
| 3.2 | 两次导出上传 | 🟡 中 | 2h | 2.1+2.2 |
| 4.2 | 节点输出两个文件名 | 🟢 低 | 1h | 4.1 |
| 5.1 | UI 按钮设计 | 🟢 低 | 1h | 所有 |
七、下一步行动
Phase 1:核心验证(1-2 天)
- 创建测试脚本
test_photopea.js - 手动测试 1.1, 1.2, 2.1, 2.2
- 记录结果到本文档
Phase 2:原型开发(2-3 天)
基于测试结果,选择可行的方案 开发最小可用原型(MVP)
Phase 3:集成测试(1-2 天)
与 ComfyUI 集成 测试完整工作流
八、已知限制与解决方案
8.1 Photopea API 限制
| 限制 | 影响 | 解决方案 |
|---|---|---|
layer.mask 返回 null |
无法直接访问图层蒙版 | 使用选区创建蒙版图层 |
| 无法获取 UI 状态 | 无法检测蒙版是否启用 | 信任用户操作 |
| postMessage 异步 | 需要正确处理回调 | Promise 封装 |
8.2 推荐的蒙版创建工作流
方案 A:用户上传带 Alpha 的 PNG(最简单)
用户 → 外部工具创建蒙版 → 上传 PNG → 节点自动提取 Alpha
方案 B:在 Photopea 中通过选区创建(已实现)
用户 → Photopea 选择工具 → 创建选区 → "从选区创建蒙版"按钮 → 自动导出
方案 C:使用 ComfyUI 系统蒙版工具
用户 → 系统蒙版编辑器 → 保存 → 节点使用
九、实现状态
| 功能 | 状态 | 文件 |
|---|---|---|
| 后端节点(padding + 无蒙版报错) | ✅ 已实现 | nodes.py |
| 选区框 Canvas UI | ✅ 已实现 | region-box-editor.js |
| Photopea 图像编辑 | ✅ 已实现 | extension.js |
| Photopea 蒙版导出 | ⚠️ Workaround 可行 | 待添加选区功能 |
文档创建时间:2026-03-02 验证完成时间:2026-03-02 验证来源:Photopea 官方文档 + sd-webui-photopea-embed 项目