主要更改: - 重写前端 extension.js,完全自己控制图像渲染 - 去掉 nodes.py 的 image_upload,避免双图像问题 - 实现选区框绘制(橙色)+ 遮罩区域显示(红色) - 实现选区框拖动功能 - 修复 onMouseDown 参数格式: (e, pos, canvas) - 更新文档和开发日志
396 lines
10 KiB
Markdown
396 lines
10 KiB
Markdown
# 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 | 让用户通过选区操作 |
|
||
|
||
### 📋 推荐工作流
|
||
|
||
**用户创建蒙版的方式**:
|
||
1. 在 Photopea 中使用选择工具创建选区
|
||
2. 点击"从选区创建蒙版"按钮
|
||
3. 系统自动创建黑白蒙版图层并导出
|
||
4. 或者:用户直接上传带 Alpha 通道的 PNG
|
||
|
||
---
|
||
|
||
## 一、Photopea 加载验证
|
||
|
||
### 1.1 同时加载原图 + 蒙版
|
||
|
||
**测试目标**:能否让 Photopea 同时加载两个文件,并且蒙版作为图层蒙版?
|
||
|
||
**测试脚本**:
|
||
```javascript
|
||
// 方法 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 能否自动识别为蒙版?
|
||
|
||
**测试方法**:
|
||
1. 准备一个 RGBA PNG(RGB 是图像,Alpha 是蒙版)
|
||
2. 用 `app.open("data:image/png;base64,...")` 加载
|
||
3. 检查 `layer.mask` 是否存在
|
||
|
||
**预期结果**:
|
||
- ✅ 成功:Alpha 通道自动转为图层蒙版
|
||
- ❌ 失败:Alpha 通道只是透明,不是蒙版
|
||
|
||
---
|
||
|
||
### 1.3 用脚本创建图层蒙版
|
||
|
||
**测试目标**:能否用脚本把一张黑白图转换为图层蒙版?
|
||
|
||
**测试脚本**:
|
||
```javascript
|
||
// 假设已经有一个黑白图在剪贴板
|
||
doc.selection.load("mask.png");
|
||
layer.addMask(true);
|
||
```
|
||
|
||
**参考**:DEVLOG 中提到的 `loadTransparentPixels() + addMask()`
|
||
|
||
---
|
||
|
||
## 二、Photopea 导出验证
|
||
|
||
### 2.1 导出带蒙版的图像
|
||
|
||
**测试目标**:如果图层有蒙版,导出 PNG 时能否保留蒙版到 Alpha 通道?
|
||
|
||
**测试方法**:
|
||
1. 创建一个带蒙版的图层
|
||
2. 执行 `app.activeDocument.saveToOE("png")`
|
||
3. 检查导出的 PNG 是否有 Alpha 通道
|
||
|
||
**预期结果**:
|
||
- ✅ 成功:PNG 的 Alpha = 蒙版
|
||
- ❌ 失败:蒙版被丢弃,或者蒙版渲染到 RGB 里
|
||
|
||
---
|
||
|
||
### 2.2 单独导出蒙版
|
||
|
||
**测试目标**:能否只导出蒙版(黑白图)?
|
||
|
||
**❌ 原方案失败**:
|
||
```javascript
|
||
var mask = layer.mask; // ⚠️ 已知返回 null
|
||
doc.activeChannel = layer.mask; // 无法执行
|
||
```
|
||
|
||
**✅ Workaround 方案(已验证)**:
|
||
参考 `sd-webui-photopea-embed` 的 `createMaskFromSelection()` 实现:
|
||
|
||
```javascript
|
||
// 从选区创建蒙版图层
|
||
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 检测当前是否有蒙版
|
||
|
||
**测试目标**:在用户点击"保存"前,能否检测当前文档是否有蒙版?
|
||
|
||
**测试脚本**:
|
||
```javascript
|
||
var hasMask = (app.activeDocument.activeLayer.mask != null);
|
||
// 或者
|
||
var hasMask = (app.activeDocument.activeLayer.layerMask != null);
|
||
```
|
||
|
||
**预期结果**:
|
||
- ✅ 成功:能准确检测
|
||
- ❌ 失败:无法检测(因为 layer.mask 总是返回 null)
|
||
|
||
**影响**:如果无法检测,就无法在保存前提示用户
|
||
|
||
---
|
||
|
||
## 三、前端交互验证
|
||
|
||
### 3.1 检测用户是否禁用了蒙版
|
||
|
||
**测试场景**:
|
||
1. 用户在 Photopea 里编辑
|
||
2. 点击图层蒙版眼睛图标(禁用蒙版)
|
||
3. 点保存
|
||
|
||
**问题**:
|
||
- 这时应该保存什么?
|
||
- 原图(忽略蒙版)还是报错?
|
||
|
||
**技术难点**:
|
||
- Photopea 的 UI 状态(哪个图层可见、蒙版是否启用)无法通过 API 获取
|
||
- 只能通过导出后的结果反推
|
||
|
||
---
|
||
|
||
### 3.2 两次导出 + 两次上传
|
||
|
||
**测试目标**:连续导出两次(图像 + 蒙版),能否都成功上传?
|
||
|
||
**测试流程**:
|
||
```javascript
|
||
// 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?
|
||
|
||
**测试代码**:
|
||
```python
|
||
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
|
||
```python
|
||
"image": (sorted(files), {"image_upload": True}),
|
||
"mask": (sorted(files), {"image_upload": True}),
|
||
```
|
||
|
||
**测试方案 B**:一个 widget 存 JSON
|
||
```python
|
||
"files": ("STRING", {"default": '{"image": "", "mask": ""}'}),
|
||
```
|
||
|
||
**测试方案 C**:一个 widget + node.properties
|
||
```python
|
||
"image": (sorted(files), {"image_upload": True}),
|
||
# mask 存在 node.properties["mask_file"]
|
||
```
|
||
|
||
---
|
||
|
||
## 五、UI/UX 验证
|
||
|
||
### 5.1 保存按钮的设计
|
||
|
||
**选项 A**:两个按钮
|
||
```
|
||
[💾 保存图像] [🎭 保存蒙版]
|
||
```
|
||
- 优点:用户明确知道在保存什么
|
||
- 缺点:步骤多,可能忘记保存另一个
|
||
|
||
**选项 B**:一个按钮,自动两次导出
|
||
```
|
||
[💾 保存全部]
|
||
```
|
||
- 优点:一键完成
|
||
- 缺点:慢,失败时难以定位
|
||
|
||
**选项 C**:下拉菜单
|
||
```
|
||
[💾 保存 ▼]
|
||
├─ 保存图像
|
||
├─ 保存蒙版
|
||
└─ 保存全部
|
||
```
|
||
- 优点:灵活
|
||
- 缺点:UI 复杂
|
||
|
||
---
|
||
|
||
### 5.2 错误提示
|
||
|
||
**场景**:
|
||
1. 用户没画蒙版就点"保存蒙版"
|
||
2. Photopea 里没有活动选区
|
||
3. 上传失败
|
||
|
||
**提示方式**:
|
||
- 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 天)
|
||
1. 创建测试脚本 `test_photopea.js`
|
||
2. 手动测试 1.1, 1.2, 2.1, 2.2
|
||
3. 记录结果到本文档
|
||
|
||
### 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 项目*
|