Files
InpaintRegionEditor Dev 23c748bcf0 feat: 自定义图像渲染 + 选区框绘制 + 拖动功能
主要更改:
- 重写前端 extension.js,完全自己控制图像渲染
- 去掉 nodes.py 的 image_upload,避免双图像问题
- 实现选区框绘制(橙色)+ 遮罩区域显示(红色)
- 实现选区框拖动功能
- 修复 onMouseDown 参数格式: (e, pos, canvas)
- 更新文档和开发日志
2026-03-02 22:31:54 +08:00

10 KiB
Raw Permalink Blame History

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 同时加载两个文件,并且蒙版作为图层蒙版?

测试脚本:

// 方法 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 用脚本创建图层蒙版

测试目标:能否用脚本把一张黑白图转换为图层蒙版?

测试脚本:

// 假设已经有一个黑白图在剪贴板
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 单独导出蒙版

测试目标:能否只导出蒙版(黑白图)?

❌ 原方案失败:

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 检测用户是否禁用了蒙版

测试场景:

  1. 用户在 Photopea 里编辑
  2. 点击图层蒙版眼睛图标(禁用蒙版)
  3. 点保存

问题:

  • 这时应该保存什么?
  • 原图(忽略蒙版)还是报错?

技术难点:

  • 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 错误提示

场景:

  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 项目