dsh-comfyui-mcp 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/index.mjs +3 -1
- package/package.json +4 -2
- package/packaged-skills.mjs +28 -0
- package/skills/krea2-image-prompt/SKILL.md +104 -0
- package/skills/krea2-image-prompt/reference/experiments.md +54 -0
- package/skills/krea2-image-prompt/reference/recipes.md +128 -0
- package/skills/krea2-image-prompt/reference/workflow.json +100 -0
- package/skills/krea2-image-prompt/scripts/batch.py +56 -0
- package/skills/krea2-image-prompt/scripts/key.py +79 -0
- package/skills/krea2-image-prompt/scripts/krea.py +155 -0
- package/skills/visual-asset-production/SKILL.md +32 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ ComfyUI 工具来自 [artokun/comfyui-mcp](https://github.com/artokun/comfyui-mc
|
|
|
8
8
|
|
|
9
9
|
## 安装与配置
|
|
10
10
|
|
|
11
|
-
需要 Node.js 22 及兼容的 DSH(当前声明为 `>=0.2.1-rc.3 <0.3.0`)。在 DSH 插件管理器中安装 `dsh-comfyui-mcp`(指定版本可填 `dsh-comfyui-mcp@0.1.
|
|
11
|
+
需要 Node.js 22 及兼容的 DSH(当前声明为 `>=0.2.1-rc.3 <0.3.0`)。在 DSH 插件管理器中安装 `dsh-comfyui-mcp`(指定版本可填 `dsh-comfyui-mcp@0.1.9`),然后启用组件。也支持安装本地目录或 `.tgz`。不要另外添加同名 MCP 桥。
|
|
12
12
|
|
|
13
13
|
打开「插件 → ComfyUI MCP」,点击「测试连接」探测现有服务。在同页下方的「运行配置」表单设置:
|
|
14
14
|
|
|
@@ -59,11 +59,7 @@ skills/krea2-image-prompt/
|
|
|
59
59
|
└── scripts/ # krea.py 出图、batch.py 批量、key.py 色键抠透明底
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
**路径与发现机制。**
|
|
63
|
-
|
|
64
|
-
```powershell
|
|
65
|
-
New-Item -ItemType Junction -Path "$HOME\.dsh\skills\krea2-image-prompt" -Target "<仓库路径>\skills\krea2-image-prompt"
|
|
66
|
-
```
|
|
62
|
+
**路径与发现机制。** 这些技能随 npm 包分发,由插件启用时注册,禁用或卸载时撤销;正文仍按需加载,资源路径指向包内目录。无需另建用户级副本或目录联接。旧版已有的同名本地入口应核对来源后移到技能扫描范围之外备份,不删除技能正文。独立用户技能不会被插件改写或删除;同名条目遵循宿主优先级,禁用插件后用户条目仍可使用。跨插件技能引用需要启用对应插件。
|
|
67
63
|
|
|
68
64
|
**触发方式。** 任务涉及「画图标 / 画背景 / 生成角色素材 / 做纹理」时自动加载;也可用 `skill` 工具或 `/krea2-image-prompt` 显式调用。
|
|
69
65
|
|
|
@@ -79,7 +75,7 @@ python scripts/key.py in.png out.png # 纯色底 → 透明 PNG
|
|
|
79
75
|
|
|
80
76
|
**与启动方式的关系。** 服务若已由绘世等启动器在 `url` 上运行,插件只探测和连接,启动器里的端口、自定义节点、显存参数全部生效;只有插件**自己**拉起进程时才会追加自己的启动参数。技能里的实测数据来自插件自有启动的精简实例。
|
|
81
77
|
|
|
82
|
-
|
|
78
|
+
技能目录包含在 `package.json` 的 `files` 白名单中,随 npm 包分发;技能中的本机模型参数与实测记录不是新机器的环境保证。
|
|
83
79
|
|
|
84
80
|
## 禁用、卸载与迁移
|
|
85
81
|
|
|
@@ -97,7 +93,7 @@ npm run pack:check
|
|
|
97
93
|
npm pack
|
|
98
94
|
```
|
|
99
95
|
|
|
100
|
-
DSH 提供的 peer 包需在插件运行环境中可解析。安装发布包时由 DSH
|
|
96
|
+
DSH 提供的 peer 包需在插件运行环境中可解析。安装发布包时由 DSH 管理依赖;任何待运行的依赖构建脚本都应先取得用户许可。包采用文件白名单,包含技能参考用的 API 工作流与辅助脚本,不包含模型、Python 运行时、生成图片、凭据或个人配置。图标由 Krea 2 Turbo 生成,经 Photoshop 调色、缩放和锐化,再清除四角连通白底并输出 256 × 256 RGBA PNG;SVG 内嵌同一位图;设计提示词保存在 `assets/icon-prompt.txt`,使用 DSH 的 `./icon` 导出。
|
|
101
97
|
|
|
102
98
|
## 限制与安全
|
|
103
99
|
|
|
@@ -108,3 +104,10 @@ DSH 提供的 peer 包需在插件运行环境中可解析。安装发布包时
|
|
|
108
104
|
## 许可证
|
|
109
105
|
|
|
110
106
|
本项目采用 MIT。上游 `comfyui-mcp` 为 MIT,版权归 Arthur R. Longbottom (artokun)。参见 `THIRD_PARTY_NOTICES.md`。依赖通过包管理器安装,不将上游依赖代码或模型整合进发布包;各依赖许可证继续适用。
|
|
107
|
+
|
|
108
|
+
## 按需技能目录
|
|
109
|
+
|
|
110
|
+
- [visual-asset-production](skills/visual-asset-production/SKILL.md):工具选择及目标场景验收。
|
|
111
|
+
- [krea2-image-prompt](skills/krea2-image-prompt/SKILL.md):Krea 工作流及历史实测。
|
|
112
|
+
|
|
113
|
+
这些技能随 npm 包分发,由插件启用时注册,禁用或卸载时撤销;正文仍按需加载,资源路径指向包内目录。无需另建用户级副本或目录联接。旧版已有的同名本地入口应核对来源后移到技能扫描范围之外备份,不删除技能正文。独立用户技能不会被插件改写或删除;同名条目遵循宿主优先级,禁用插件后用户条目仍可使用。跨插件技能引用需要启用对应插件。
|
package/index.mjs
CHANGED
|
@@ -5,8 +5,9 @@ import { randomBytes } from 'node:crypto';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { createConfiguredRuntime } from './configured-runtime.mjs';
|
|
7
7
|
import { installLazyTools } from './lazy-tools.mjs';
|
|
8
|
+
import { installPackagedSkills } from './packaged-skills.mjs';
|
|
8
9
|
export const name='dsh-comfyui-mcp';
|
|
9
|
-
export const inject=['tools','webServer','agents'];
|
|
10
|
+
export const inject=['tools','webServer','agents','skills'];
|
|
10
11
|
export const Config=z.object({
|
|
11
12
|
lazyTools:z.boolean().default(true).description('Load the three ComfyUI tools only when requested by each agent. Does not start ComfyUI.'),
|
|
12
13
|
url:z.string().default('http://127.0.0.1:8188').description('ComfyUI HTTP(S) address; default is loopback.'),
|
|
@@ -17,6 +18,7 @@ export const Config=z.object({
|
|
|
17
18
|
startupTimeoutMs:z.number().min(1000).max(600000).default(120000)
|
|
18
19
|
}).volatile();
|
|
19
20
|
export async function apply(ctx,config){
|
|
21
|
+
installPackagedSkills(ctx,new URL('./skills/',import.meta.url));
|
|
20
22
|
const runtime=createConfiguredRuntime(()=>config.get());const token=randomBytes(32).toString('hex');let disposed=false;
|
|
21
23
|
const control=createServer(async(req,res)=>{res.setHeader('Content-Type','application/json');try{if(req.method!=='POST'||req.headers.authorization!=='Bearer '+token){res.writeHead(403);return res.end('{}');}const action=req.url.slice(1);if(!['status','start','stop','free','config'].includes(action))throw Error('Unknown action');const value=await runtime.run(action);res.end(JSON.stringify(value));}catch(e){res.writeHead(500);res.end(JSON.stringify({error:e.message}));}});
|
|
22
24
|
await new Promise((resolve,reject)=>{control.once('error',reject);control.listen(0,'127.0.0.1',resolve);});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-comfyui-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "ComfyUI connection and safe on-demand lifecycle for DSH; powered by artokun/comfyui-mcp.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"./locale/*.json": "./locale/*.json"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
|
+
"packaged-skills.mjs",
|
|
32
|
+
"skills",
|
|
31
33
|
"index.mjs",
|
|
32
34
|
"lazy-tools.mjs",
|
|
33
35
|
"runtime.mjs",
|
|
@@ -58,7 +60,7 @@
|
|
|
58
60
|
}
|
|
59
61
|
},
|
|
60
62
|
"scripts": {
|
|
61
|
-
"check": "node --check index.mjs && node --check runtime.mjs && node --check configured-runtime.mjs && node --check server.mjs && node --check client.js && node --check lazy-tools.mjs && node --check tool-router.mjs",
|
|
63
|
+
"check": "node --check index.mjs && node --check runtime.mjs && node --check configured-runtime.mjs && node --check server.mjs && node --check client.js && node --check lazy-tools.mjs && node --check tool-router.mjs && node --check packaged-skills.mjs",
|
|
62
64
|
"test": "node --test test/*.test.mjs",
|
|
63
65
|
"build": "npm run check",
|
|
64
66
|
"pack:check": "npm pack --dry-run --json"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
// Only this package's plain name/description frontmatter is supported.
|
|
5
|
+
export function readPackagedSkills(directory) {
|
|
6
|
+
return readdirSync(directory, { withFileTypes: true })
|
|
7
|
+
.filter(entry => entry.isDirectory()).map(entry => entry.name).sort().map(name => {
|
|
8
|
+
const base = new URL(`${name}/`, directory);
|
|
9
|
+
const path = new URL('SKILL.md', base);
|
|
10
|
+
const source = readFileSync(path, 'utf8');
|
|
11
|
+
const match = /^---\r?\nname: ([^\r\n]+)\r?\ndescription: ([^\r\n]+)\r?\n---\r?\n([\s\S]*)$/u.exec(source);
|
|
12
|
+
if (!match || match[1] !== name || !match[2].trim()) {
|
|
13
|
+
throw new Error(`Invalid packaged skill frontmatter: ${name}`);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
name, description: match[2], content: match[3], source: 'runtime',
|
|
17
|
+
path: fileURLToPath(path),
|
|
18
|
+
resourceBase: { kind: 'directory', path: fileURLToPath(base) },
|
|
19
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function installPackagedSkills(ctx, directory) {
|
|
25
|
+
// Validate every entry before registering any of them.
|
|
26
|
+
const skills = readPackagedSkills(directory);
|
|
27
|
+
for (const skill of skills) ctx.effect(() => ctx.skills.register(skill));
|
|
28
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: krea2-image-prompt
|
|
3
|
+
description: 用本机 Krea 2 Turbo 出位图素材的提示词指南与流水线——UI 图标/背景/空状态/角色/纹理/无缝图案,含透明底抠色、角色一致性、故障速查
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 本机 Krea 2 出图
|
|
7
|
+
|
|
8
|
+
仅在已决定使用 Krea 2 制作位图时加载。选工具与验收见 [素材制作](../visual-asset-production/SKILL.md)。机器事实和耗时为历史实测,使用前核对当前模型、节点和接口,不自动安装缺少的模型。
|
|
9
|
+
|
|
10
|
+
## 机器事实(2026-10 在本机实测)
|
|
11
|
+
|
|
12
|
+
- 模型 `krea2_turbo_fp8_scaled.safetensors` + 编码器 `qwen3vl_4b_fp8_scaled.safetensors`(`CLIPLoader type=krea2`)+ `qwen_image_vae.safetensors`。**本机没有任何 checkpoint。**
|
|
13
|
+
- ComfyUI 以 `--disable-all-custom-nodes` 启动:KJNodes / RMBG / upscale / controlnet / ip-adapter 全部不可用,`generate_image` 的 image 动作会因缺 checkpoint 失败。必须自建节点图:`UNETLoader → CLIPLoader → VAELoader → CLIPTextEncode ×2 → EmptySD3LatentImage → KSampler → VAEDecode → SaveImage`,完整 JSON 见 `reference/workflow.json`。
|
|
14
|
+
- 8GB VRAM 够用;2K 不报错但慢 4.7 倍。
|
|
15
|
+
- 风格 LoRA:`krea2_darkbrush.safetensors`,strength 1.0,**不需要触发词**,效果是把画面推向水彩/油画质感并整体压暗,同时会改人物配色。`krea2_znsfw_V2` 是 NSFW 模型,与素材工作无关,不用。
|
|
16
|
+
|
|
17
|
+
## 采样参数(别乱调)
|
|
18
|
+
|
|
19
|
+
| 项 | 值 | 依据 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| steps | 8 | 蒸馏模型;加到 12 步没有可见收益 |
|
|
22
|
+
| cfg | 1.0 | 1.5+12 步实测耗时 2.8 倍、画面无提升;cfg=1 时引导公式退化为纯条件预测,**负向提示词在数学上不参与计算,别写** |
|
|
23
|
+
| sampler / scheduler | `er_sde` / `simple` | 上游 verified 组合 |
|
|
24
|
+
| seed | 写死具体数字 | 复现与一致性都靠它 |
|
|
25
|
+
|
|
26
|
+
| 尺寸 | 实测耗时 |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| 512×512 | 12s |
|
|
29
|
+
| 768×768 | 26s |
|
|
30
|
+
| 1024×1024 | 30s |
|
|
31
|
+
| 1024×1536 | 45s |
|
|
32
|
+
| 1280×800 | 30s |
|
|
33
|
+
| 1536×640 / 1536×768 | 30–33s |
|
|
34
|
+
| 1920×1080 | 60s |
|
|
35
|
+
| 2048×2048 | 140s |
|
|
36
|
+
|
|
37
|
+
先用 512² 或 1024² 试构图,定了再按目标尺寸重跑。同 prompt 同 seed 换尺寸,结果不保证一致,构图会漂。
|
|
38
|
+
|
|
39
|
+
## 硬规则
|
|
40
|
+
|
|
41
|
+
1. **要出现在图里的文字一律放进英文双引号**,越短越好(1–4 词)。实测 1024²、1280×800、1920×1080 下拉丁字母都清晰正确:`text reads "Preferences"`。
|
|
42
|
+
2. **不打算写字就写 `no text`**。任何"有文字形状但没指定内容"的区域都会被填成伪文字:实测侧栏被编成 `Revanto / Reparls`,图表轴变成 `lor / 1ar / lmo`,还会在背景留下半透明字母纹理。
|
|
43
|
+
3. **中文别指望**。四字常见词偶尔对(`欢迎回来` 对了),稍长就变近字:`登录`→`设各`,`鲸鲸的后花园`→`鲽鲽的后莪园`。中文 UI 一律走:生成无字留白版 → 真文字在 HTML/CSS 里叠。
|
|
44
|
+
4. 只写**看得见的东西**,按这个顺序:主体 → 外观细节(颜色/材质/轮廓/线宽)→ 构图与视角 → 风格媒介 → 光与色 → 背景与留白 → `no text`。Krea 2 用 Qwen3-VL 编码器,按自然语言理解,结尾的"背景 + no text"最容易生效。
|
|
45
|
+
5. **一图一件事**。要四个图标就说"2×2 网格、同风格、等距留白",别指望一次给出十二个一致组件。
|
|
46
|
+
6. **别用 grounded DSL**:`t[160,180,700,280]"..."` 那套是 bbox 微调版的训练格式,在 stock Turbo 上会把方括号原样画进图里(实测确实泄漏成可见文字)。
|
|
47
|
+
7. `app icon` 会自动加圆角方底(squircle)。要出血或无缝贴图改用 `flat vector illustration` / `sticker on a solid ... background`。
|
|
48
|
+
8. 提示词控制在 300 词以内。实测 68 词、14 个元素的长 prompt 全部落地(含最后一个元素),但编码窗口有限(社区记录约 512 token),超了会从尾部截断——而尾部通常正是最关键的 `no text`。
|
|
49
|
+
|
|
50
|
+
## 六段骨架
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
<主体一句话>, <外观细节>, <构图与视角>, <风格媒介>, <光与色>, <背景与留白>, no text
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
例:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Flat vector app icon of a friendly blue whale, thick rounded dark navy outlines, two flat colors plus white belly, centered with generous padding, solid off-white background, no text
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 三条调用路径
|
|
63
|
+
|
|
64
|
+
起手检查:ComfyUI 没在配置地址上响应时,先核对插件配置,再按已授权的启动方式执行 `runtime start`(MCP),或使用用户自己的 ComfyUI 启动器。脚本默认连 `http://127.0.0.1:8188`,可用 `COMFY_URL` 覆盖;脚本都在本 skill 目录的 `scripts/` 下,用绝对路径或先 cd 进去。
|
|
65
|
+
|
|
66
|
+
1. **MCP(正式路径)**:`enqueue_workflow action:"enqueue"` 传 API 格式 graph(`reference/workflow.json`),再 `get_history action:"list"` 取图。已验证能拿到 prompt_id 并出图。
|
|
67
|
+
2. **脚本快跑(本机迭代最快)**:
|
|
68
|
+
```
|
|
69
|
+
python scripts/krea.py --prompt "..." --w 1024 --h 1024 --seed 12345
|
|
70
|
+
```
|
|
71
|
+
支持 `--lora krea2_darkbrush.safetensors:1.0`、`--init 图.png --denoise 0.45`(img2img)、`--out DIR`。批量跑:`python scripts/batch.py jobs.json`。
|
|
72
|
+
3. **透明底**:prompt 写 `on a completely flat solid chroma green background, even lighting, no shadow`,生成后
|
|
73
|
+
```
|
|
74
|
+
python scripts/key.py in.png out.png
|
|
75
|
+
```
|
|
76
|
+
得到 RGBA + 浅/深/粉三联预览。主体本身偏绿就换 magenta 底。
|
|
77
|
+
|
|
78
|
+
## 一致性
|
|
79
|
+
|
|
80
|
+
- **角色**:把一段"角色块"逐字复用(发型 / 眼睛 / 服装 / 配色 / 画风),换 seed、换姿势角色仍是同一个(实测同 seed 换动作、不同 seed 同文案都稳)。姿势用动词替换,别重写角色块。
|
|
81
|
+
- **角色 + 持物**:Q 版全身像抱玩偶会把手臂和玩偶糊成一团(两次复现)。道具单独生成再拼。
|
|
82
|
+
- **img2img**:denoise 0.45 只做细节微调,且会带进淡纹理伪影;要换姿势直接 txt2img 更干净。
|
|
83
|
+
- **图标家族**:一次出 2×2 网格再裁切,比四张单图风格更统一。
|
|
84
|
+
|
|
85
|
+
## 素材配方
|
|
86
|
+
|
|
87
|
+
图标 / 图标家族 / hero 背景 / 空状态 / 无缝图案 / 组件表 / 整屏 mockup / 角色立绘 / 水彩场景 / 材质纹理 —— 每条都有可直接抄的 prompt 和实测结论:`reference/recipes.md`。
|
|
88
|
+
|
|
89
|
+
跑过什么、耗时、哪些失败:`reference/experiments.md`。
|
|
90
|
+
|
|
91
|
+
## 故障速查
|
|
92
|
+
|
|
93
|
+
| 现象 | 原因 | 对策 |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| 文字乱码、伪字母 | 没加引号,或用了中文 | 英文引号写死 ≤4 词;中文后期叠 |
|
|
96
|
+
| 背景浮出半透明字母纹理 | 提示词描述了"有很多文字的界面" | 删掉文字描述,加 `no text` |
|
|
97
|
+
| 手 / 玩偶糊成一团 | Q 版全身像 + 持物 | 去掉持物,或改成"手放在身侧" |
|
|
98
|
+
| 主体被圆角方底包住 | 用了 `app icon` | 改 `sticker` / `illustration on solid background` |
|
|
99
|
+
| 元素都出现但都糊 | 词太多,主体不明确 | 先一句话定主体,512² 试构图 |
|
|
100
|
+
| 抠图边缘发绿 | 主体本身偏绿 | 换 magenta 底再 key |
|
|
101
|
+
|
|
102
|
+
## 还能这么用
|
|
103
|
+
|
|
104
|
+
场景插画、讲概念的配图、演示封面、贴纸与表情包、头像、桌宠立绘、角色三视图与表情表、漫画分镜、等距小地图、徽章成就、材质球(玻璃/金属/纸)、渐变与噪点底图、社交分享卡背景、404 与空状态插画、骨架屏占位图、贺卡海报。凡是"位图素材"都能套这套流程;真正的文案与排版回到 HTML/CSS。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 实测记录(2026-10-25,RTX 5060 Laptop 8GB)
|
|
2
|
+
|
|
3
|
+
这一轮是"从零摸清本机 Krea 2 Turbo 脾气"的记录。结论已进 SKILL.md,这里留过程与证据,避免以后重复踩。
|
|
4
|
+
|
|
5
|
+
## 环境
|
|
6
|
+
|
|
7
|
+
- ComfyUI 0.30.2,`--disable-all-custom-nodes --disable-api-nodes`,`127.0.0.1:8188`。
|
|
8
|
+
- 模型:`krea2_turbo_fp8_scaled` + `qwen3vl_4b_fp8_scaled`(type=krea2) + `qwen_image_vae`。无 checkpoint。
|
|
9
|
+
- 自建节点图跑通;`EmptySD3LatentImage` 正常出图。首图含模型加载约 26–29s,之后 512² 约 12s。
|
|
10
|
+
|
|
11
|
+
## 跑过的对比
|
|
12
|
+
|
|
13
|
+
| # | 目的 | 输入 | 结果 |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| 1 | 管线可用性 | 苹果静物 768² | 正常,画质好 |
|
|
16
|
+
| 2 | 矢量图标基线 | 蓝鲸图标 1024² | 干净两色矢量,`app icon` 未加方底(1024² 时) |
|
|
17
|
+
| 3 | 文字渲染 | 卡片 UI,标题/副标题/按钮全部加引号 | **全部正确**,排版也漂亮 |
|
|
18
|
+
| 4 | grounded DSL | `t[160,180,700,280]"Good morning"` 等 | 布局大致对,但**方括号原样画进图里** → stock 模型不支持该语法,弃用 |
|
|
19
|
+
| 5 | 不引号的文字 | "四行消息列表,每行有名字和预览" | 名字变 `Paties/Baties`,预览变 `oneetirles: olinlslic:`,背景浮出半透明字母纹理 |
|
|
20
|
+
| 6 | cfg 提升 | 图标 cfg 1.5 / 12 步 | 81s vs 29s,画面无提升 → 保持 cfg 1 / 8 步 |
|
|
21
|
+
| 7 | 2K | 图标 2048² | 140s,出图正常,但自动加了圆角方底(squircle) |
|
|
22
|
+
| 8 | 512² 够不够 | 图标 512² | 12s,质量够挑图 |
|
|
23
|
+
| 9 | 透明底 | 蓝鲸 on chroma green / magenta | 两版都能 key 干净;绿的实测色约 `#29BE48`,magenta 约 `#E60E9A` |
|
|
24
|
+
| 10 | 一致性:同 seed 换姿势 | 角色块 + seed 12345,加"waving one hand" | 同一个角色,只有动作变 |
|
|
25
|
+
| 11 | 一致性:换 seed 同文案 | 角色块 + seed 99999 | 仍是同一个角色(发型/兜帽/配色一致) |
|
|
26
|
+
| 12 | img2img | 上一张 + denoise 0.45 + 换姿势提示 | 只微调了表情与细节,**没有换姿势**,并带进淡纹理伪影 |
|
|
27
|
+
| 13 | 图标家族 | 2×2 网格四图标 | 风格统一,间距大小不均,裁切需补 padding |
|
|
28
|
+
| 14 | 留白背景 | 1536×1024,左侧留白叠字 | 留白精准,主体在右上 |
|
|
29
|
+
| 15 | 无缝图案 | 鲸鱼+泡泡平铺 | 规整网格,可作图案但接缝不保证 |
|
|
30
|
+
| 16 | 组件表 | 8 个 UI 组件 | 八件套齐、风格统一,但尺寸偏大;未引号的"计数徽章"出现伪字 `7` |
|
|
31
|
+
| 17 | 满分辨率 UI | 1280×800 设置面板,引号文案 | **所有字符串正确**,30s |
|
|
32
|
+
| 18 | 满分辨率 UI | 1920×1080 仪表盘,部分文案未引号 | 标题/侧栏/轴标签全变伪文字 → 引号覆盖率决定可读性 |
|
|
33
|
+
| 19 | 中文 | 1920×1080 登录页 `欢迎回来/邮箱/密码/登录` | `欢迎回来` 正确;`邮箱`→`箝杨`,`密码`→`答留`,`登录`→`设各` |
|
|
34
|
+
| 20 | 中文 | 1536×640 banner `鲸鲸的后花园` | → `鲽鲽的后莪园`(近字替换) |
|
|
35
|
+
| 21 | 长 prompt | 68 词 / 14 元素读书角 | 全部落地,含最后一个元素(红色小伞) |
|
|
36
|
+
| 22 | 风格 LoRA | `krea2_darkbrush:1.0` | 水彩油画质感 + 压暗,配色改变,无需触发词 |
|
|
37
|
+
| 23 | MCP 路径 | `enqueue_workflow action:"enqueue"` 传 graph | 返回 prompt_id,512² 11.6s,`get_history` 取到图 |
|
|
38
|
+
|
|
39
|
+
## 耗时基线
|
|
40
|
+
|
|
41
|
+
512² 12s · 768² 26s(含首载)· 1024² 30s · 1280×800 30s · 1536×640 30s · 1536×768 33s · 1024×1536 45s · 1920×1080 60s · 2048² 140s
|
|
42
|
+
|
|
43
|
+
## 已知不可用
|
|
44
|
+
|
|
45
|
+
- `generate_image`(走 CheckpointLoaderSimple)——本机无 checkpoint。
|
|
46
|
+
- `remove_background` / `upscale` / `controlnet` / `ip_adapter`——依赖被禁用的自定义节点,且无 upscale_models。
|
|
47
|
+
- grounded DSL(bbox 版训练格式)——在 stock Turbo 上会泄漏成文字。
|
|
48
|
+
|
|
49
|
+
## 还没试过的
|
|
50
|
+
|
|
51
|
+
- `redcraftKREA2RedMix_krea2Edition.safetensors`(第二个 diffusion 模型)。
|
|
52
|
+
- 双 pass 细节增强(上游 `krea2-combo` pack 的思路,需 KJNodes,本机被禁)。
|
|
53
|
+
- img2img denoise ≥0.6 的实际行为(只测到 0.45)。
|
|
54
|
+
- 采样器替换(`euler`/`dpmpp_2m`)是否改变质感。
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# 素材配方
|
|
2
|
+
|
|
3
|
+
每条配方都是在本机实测过的。prompt 可整段复制;"实测"记的是这一次的真实结果,不是推测。
|
|
4
|
+
|
|
5
|
+
通用后缀:`no text`(不需要文字时)或 `text reads "..."`(需要文字时)。分辨率按用途取,先 1024² 试构图。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 矢量图标
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Flat vector app icon of a friendly blue whale, thick rounded dark navy outlines, two flat colors plus white belly, simple geometric shapes, centered with generous padding, solid off-white background, no text
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- 1024²,30s。干净的两色矢量鲸鱼,留白充足。
|
|
16
|
+
- 512²,12s,同样质量——图标类都该先跑 512² 挑图,挑中了再放大重跑。
|
|
17
|
+
- **`app icon` 会让模型加圆角方底(squircle)**;2048² 时尤其明显(整张图变成应用图标瓷砖)。要一张干净的图形就用:
|
|
18
|
+
```
|
|
19
|
+
Flat vector sticker of a friendly blue whale, thick dark navy outline, blue body with white belly, on a solid off-white background, even lighting, no shadow, no text
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 2. 图标家族(一次出四个)
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
A set of four flat vector app icons arranged in a 2x2 grid on an off-white background: a whale, a crescent moon, a star and a soap bubble. Identical style across all four, thick rounded dark navy outlines, two flat colors each, generous equal padding, aligned to a grid, no text
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- 1024²,30s。四个图标风格确实统一(同样的线宽与双色逻辑),但**间距与大小不完全均等**,裁切时按视觉重心补 padding。
|
|
29
|
+
- 裁切用 Pillow 均分四宫格再各自裁 bbox 最省事;不要指望模型给严格的等分。
|
|
30
|
+
|
|
31
|
+
## 3. Hero / 页面背景(带留白给文字)
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
A soft dreamy illustration background for an app onboarding screen: pale peach and lavender watercolor sky, gentle clouds, a calm horizon low in the frame, a small whale silhouette floating near the upper right, large empty smooth negative space across the left half for text, very soft edges and low contrast, no text
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- 1536×1024,45s。左侧留白真的留出来了,主体落在右上,文字可读性没问题。
|
|
38
|
+
- 关键句式是 `large empty smooth negative space across the <位置> for text` + `low contrast`。只说"背景"会得到平均分布的纹理,叠字就糊。
|
|
39
|
+
- 纯抽象底(渐变网格、噪点)也可靠:
|
|
40
|
+
```
|
|
41
|
+
Abstract soft gradient mesh background, pale mint green fading into lavender and warm cream, blurred organic blobs, fine film grain, extremely low contrast, no subject, no text, calm and airy, suitable as a website hero background
|
|
42
|
+
```
|
|
43
|
+
→ 1536×768,33s。柔和可用;紫块偶尔偏饱和,需在 CSS 里叠一层白色蒙版压住。
|
|
44
|
+
|
|
45
|
+
## 4. 空状态 / 错误页插画
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
A charming empty-state illustration for a message inbox: a small round blue whale character lying upside down on a tiny cloud, eyes closed, bored but cute, flat vector style with soft pastel palette and thick outlines, isolated on a plain white background, lots of white space above, no text
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- 1024²,32s。主体落在下方中央、上方大片留白,正好放标题和按钮。
|
|
52
|
+
- 抠透明底见 `scripts/key.py`:换成 `on a solid chroma green background` 再 key。
|
|
53
|
+
|
|
54
|
+
## 5. 无缝平铺图案
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
A seamless repeating pattern of tiny cute blue whales and small soap bubbles scattered evenly on a pale mint background, flat vector illustration, thick outlines, evenly spaced with no empty gaps, tileable edges, no text, no border
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- 1024²,30s。得到规整的重复网格,**但"seamless"只是意图,不是保证**。用前把图偏移半格拼 2×2 看一眼接缝。真要做到无缝,裁一块干净的四宫格单元再重复。
|
|
61
|
+
|
|
62
|
+
## 6. UI 组件表
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
A UI component sheet arranged as a neat grid on a plain off-white background: a dark navy pill button, an outlined secondary button, a toggle switch in the on position, a round avatar placeholder, a text input field with a light grey border, a small badge with a count, a checkbox checked, a progress bar at sixty percent. Consistent flat vector style, thin rounded shapes, soft grey shadows, each component evenly spaced in its own cell, aligned to a grid, no text labels
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- 1024²,31s。八件套都在,风格统一,但**尺寸明显偏大**、不像真实组件比例;适合当"风格参考图",不适合直接切图用。
|
|
69
|
+
- `a small badge with a count` 这种"有个数字但没说数字"的地方会被填成伪数字(实测出现一个 `7` 形状的怪字)。要数字就引号写死。
|
|
70
|
+
|
|
71
|
+
## 7. 整屏 UI mockup(工作主力)
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
A settings panel UI mockup, light theme, rounded window with a soft drop shadow at the centre of a pale lavender background. A title at the top reads "Preferences". Below it four rows, each with a label and a control on the right: "Notifications" with a toggle, "Theme" with a segmented control showing "Light" and "Dark", "Language" with a dropdown reading "English", "Storage" with a progress bar and the text "4.2 GB of 8 GB". Flat vector interface design, crisp sans-serif typography, thin dividers, generous spacing, no photo
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- 1280×800,30s。**所有引号里的字符串全部正确**,开关/分段控件/进度条都对,可以直接当设计稿给人看。
|
|
78
|
+
- 1920×1080 同样可靠(60s),文字在满分辨率下依然干净。
|
|
79
|
+
- 反面教材(同一轮实测):只写"四行菜单、三张卡片分别叫 Revenue/Users/Retention"而不全部加引号,会得到 `Anlytich Dashyiens`、`Revanto`、`Reparls`、`lor / 1ar / lmo` 这类伪文字,背景还会浮出半透明字母纹理。**凡是字,全部加引号;不加就整块别要。**
|
|
80
|
+
- 中文:`欢迎回来` 侥幸正确,`登录` 变 `设各`,整句标题变近字。中文文案一律后期叠。
|
|
81
|
+
|
|
82
|
+
## 8. 角色立绘(UI 头像 / 空状态 / 桌宠)
|
|
83
|
+
|
|
84
|
+
角色块(逐字复用,别改):
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
Full body chibi anime girl mascot wearing an oversized blue whale hoodie with a whale-tail hood, fluffy white hair, big round blue eyes, soft pastel palette of mint blue and cream, soft watercolor and cel-shaded illustration, thick clean linework, plain white background, centered with margin around the character, no text
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- 1024×1536,45s。要换姿势就只改中间那一句:
|
|
91
|
+
```
|
|
92
|
+
... big round blue eyes, waving one hand cheerfully, slight three-quarter turn, soft pastel palette of mint blue and cream ...
|
|
93
|
+
```
|
|
94
|
+
同 seed 换姿势 → 同一个角色换动作;换 seed 同文案 → 还是同一个角色(发型、兜帽、配色都对得上)。**身份来自文字,不是来自 seed。**
|
|
95
|
+
- 失败模式:`hugging a small whale plush` 会让手臂和玩偶糊成一团(两次复现)。要拿道具就单独生成道具再拼。
|
|
96
|
+
- 非 Q 版、正比例的少女立绘同样成立:
|
|
97
|
+
```
|
|
98
|
+
Anime illustration of a girl in a whale hoodie sitting on a rock at the seaside, soft pastel palette, gentle light
|
|
99
|
+
```
|
|
100
|
+
1024²,32s。加 `--lora krea2_darkbrush.safetensors:1.0` 会变成水彩油画质感、调子压暗、配色也变(40s)——想换风格质感时用它,别指望它保持原配色。
|
|
101
|
+
|
|
102
|
+
## 9. 宽幅 banner(含贴纸风角色)
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
A wide app banner with a soft pastel watercolor background of pale peach and mint clouds. On the right side a cute chibi whale character floats happily among bubbles. On the left third large bold dark navy Chinese text reads '鲸鲸的后花园', below it smaller text reads '每天收集一只小鲸鱼'. Flat vector illustration with thick outlines, plenty of clean space, no other text
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- 1536×640,30s。布局、角色、留白全对;**中文标题被画成近字**(`鲽鲽的后莪园`)。把中文换拉丁文案,或让 banner 留空后期叠字。
|
|
109
|
+
|
|
110
|
+
## 10. 透明底素材
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
Flat vector mascot sticker of a friendly blue whale, thick dark navy outline, blue body with white belly, simple geometric shapes, on a completely flat solid chroma green background (hex 00FF00), even lighting, no shadow, no text
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- 1024²,30s。模型给的绿不是纯 `#00FF00`(实测约 `#29BE48`),所以 key 时按四边中位色自适应,不要写死色值。
|
|
117
|
+
- `python scripts/key.py in.png out.png` → RGBA,边缘去绿溢,输出浅/深/粉三联预览。默认阈值 `lo=36 hi=96`,边缘发绿就调高 `lo`。
|
|
118
|
+
- 主体本身偏绿/偏青(比如薄荷色吉祥物)就换 magenta 底:同样实测可用,边缘略更干净。
|
|
119
|
+
|
|
120
|
+
## 11. 场景插画 / 讲概念的配图
|
|
121
|
+
|
|
122
|
+
一条长 prompt 可以同时锁定:视角、构图位置、材质、调子、若干具体物件。实测 68 词 14 个元素全部落地(含最后一句"右下角靠墙放一把红色小伞"):
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
A cozy reading nook in a small attic room, warm afternoon light through a round window, a worn armchair with a knitted blanket, a stack of books on a wooden stool, dust motes in the sunbeam, soft painterly illustration, muted warm palette, quiet and inviting atmosphere, gentle shadows, visible brush texture, careful composition with the window at the upper left and the chair facing it, a small whale plush tucked into the corner of the armchair, a striped mug of tea on the floor beside the stool, a folded newspaper, a hanging plant in the upper right corner, an old rug with faded geometric pattern, three framed prints on the slanted wall, a wooden ladder leaning at the far right, in the extreme lower right corner a tiny red umbrella leaning against the wall
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- 1024²,32s。长 prompt 仍然好用,但控制在 300 词以内。
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"1": {
|
|
3
|
+
"class_type": "UNETLoader",
|
|
4
|
+
"inputs": {
|
|
5
|
+
"unet_name": "krea2_turbo_fp8_scaled.safetensors",
|
|
6
|
+
"weight_dtype": "default"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"2": {
|
|
10
|
+
"class_type": "CLIPLoader",
|
|
11
|
+
"inputs": {
|
|
12
|
+
"clip_name": "qwen3vl_4b_fp8_scaled.safetensors",
|
|
13
|
+
"type": "krea2"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"3": {
|
|
17
|
+
"class_type": "VAELoader",
|
|
18
|
+
"inputs": {
|
|
19
|
+
"vae_name": "qwen_image_vae.safetensors"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"4": {
|
|
23
|
+
"class_type": "CLIPTextEncode",
|
|
24
|
+
"inputs": {
|
|
25
|
+
"clip": [
|
|
26
|
+
"2",
|
|
27
|
+
0
|
|
28
|
+
],
|
|
29
|
+
"text": "YOUR PROMPT HERE"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"5": {
|
|
33
|
+
"class_type": "CLIPTextEncode",
|
|
34
|
+
"inputs": {
|
|
35
|
+
"clip": [
|
|
36
|
+
"2",
|
|
37
|
+
0
|
|
38
|
+
],
|
|
39
|
+
"text": ""
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"6": {
|
|
43
|
+
"class_type": "EmptySD3LatentImage",
|
|
44
|
+
"inputs": {
|
|
45
|
+
"width": 1024,
|
|
46
|
+
"height": 1024,
|
|
47
|
+
"batch_size": 1
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"7": {
|
|
51
|
+
"class_type": "KSampler",
|
|
52
|
+
"inputs": {
|
|
53
|
+
"model": [
|
|
54
|
+
"1",
|
|
55
|
+
0
|
|
56
|
+
],
|
|
57
|
+
"positive": [
|
|
58
|
+
"4",
|
|
59
|
+
0
|
|
60
|
+
],
|
|
61
|
+
"negative": [
|
|
62
|
+
"5",
|
|
63
|
+
0
|
|
64
|
+
],
|
|
65
|
+
"latent_image": [
|
|
66
|
+
"6",
|
|
67
|
+
0
|
|
68
|
+
],
|
|
69
|
+
"seed": 12345,
|
|
70
|
+
"steps": 8,
|
|
71
|
+
"cfg": 1.0,
|
|
72
|
+
"sampler_name": "er_sde",
|
|
73
|
+
"scheduler": "simple",
|
|
74
|
+
"denoise": 1.0
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"8": {
|
|
78
|
+
"class_type": "VAEDecode",
|
|
79
|
+
"inputs": {
|
|
80
|
+
"samples": [
|
|
81
|
+
"7",
|
|
82
|
+
0
|
|
83
|
+
],
|
|
84
|
+
"vae": [
|
|
85
|
+
"3",
|
|
86
|
+
0
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"9": {
|
|
91
|
+
"class_type": "SaveImage",
|
|
92
|
+
"inputs": {
|
|
93
|
+
"images": [
|
|
94
|
+
"8",
|
|
95
|
+
0
|
|
96
|
+
],
|
|
97
|
+
"filename_prefix": "krea/prompt"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Run a list of Krea2 jobs sequentially via the shared graph builder."""
|
|
3
|
+
import json, os, sys, time, urllib.parse, urllib.request, uuid
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
from krea import BASE, build, get, post
|
|
6
|
+
|
|
7
|
+
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "out")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run(job):
|
|
11
|
+
a = SimpleNamespace(
|
|
12
|
+
prompt=job["prompt"], negative=job.get("negative", ""),
|
|
13
|
+
w=job.get("w", 1024), h=job.get("h", 1024), steps=job.get("steps", 8),
|
|
14
|
+
cfg=job.get("cfg", 1.0), seed=job.get("seed", int(time.time() * 1000) % (2 ** 31)),
|
|
15
|
+
sampler=job.get("sampler", "er_sde"), scheduler=job.get("scheduler", "simple"),
|
|
16
|
+
unet=job.get("unet", "krea2_turbo_fp8_scaled.safetensors"),
|
|
17
|
+
lora=job.get("lora"), prefix="krea/" + job["name"],
|
|
18
|
+
latent=job.get("latent", "sd3"), init=job.get("init"),
|
|
19
|
+
denoise=job.get("denoise", 1.0),
|
|
20
|
+
)
|
|
21
|
+
graph = build(a)
|
|
22
|
+
try:
|
|
23
|
+
r = post("/prompt", {"prompt": graph, "client_id": str(uuid.uuid4())})
|
|
24
|
+
except urllib.error.HTTPError as e:
|
|
25
|
+
print(f"[{job['name']}] ENQUEUE FAILED {e.code} {e.read().decode()[:1500]}")
|
|
26
|
+
return
|
|
27
|
+
pid = r["prompt_id"]
|
|
28
|
+
t0 = time.time()
|
|
29
|
+
while time.time() - t0 < 900:
|
|
30
|
+
h = get(f"/history/{pid}")
|
|
31
|
+
if pid in h:
|
|
32
|
+
entry = h[pid]
|
|
33
|
+
st = entry.get("status", {})
|
|
34
|
+
if st.get("status_str") == "error":
|
|
35
|
+
msgs = [m for m in st.get("messages", []) if m[0] in ("execution_error",)]
|
|
36
|
+
print(f"[{job['name']}] ERROR {json.dumps(msgs, ensure_ascii=False)[:1200]}")
|
|
37
|
+
return
|
|
38
|
+
os.makedirs(OUT, exist_ok=True)
|
|
39
|
+
for node in entry.get("outputs", {}).values():
|
|
40
|
+
for im in node.get("images", []):
|
|
41
|
+
q = urllib.parse.urlencode({"filename": im["filename"],
|
|
42
|
+
"subfolder": im.get("subfolder", ""),
|
|
43
|
+
"type": im.get("type", "output")})
|
|
44
|
+
data = urllib.request.urlopen(f"{BASE}/view?{q}").read()
|
|
45
|
+
p = os.path.join(OUT, im["filename"])
|
|
46
|
+
open(p, "wb").write(data)
|
|
47
|
+
print(f"[{job['name']}] SAVED {p} ({time.time() - t0:.0f}s) seed={a.seed}")
|
|
48
|
+
return
|
|
49
|
+
time.sleep(1.0)
|
|
50
|
+
print(f"[{job['name']}] TIMEOUT")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
jobs = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
55
|
+
for j in jobs:
|
|
56
|
+
run(j)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Chroma-key a generated image to RGBA: `python key.py in.png out.png [lo] [hi]`."""
|
|
3
|
+
import sys
|
|
4
|
+
import numpy as np
|
|
5
|
+
from PIL import Image, ImageDraw
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def key(path, out, lo=36, hi=96):
|
|
9
|
+
im = Image.open(path).convert("RGB")
|
|
10
|
+
a = np.asarray(im).astype(np.float32)
|
|
11
|
+
h, w = a.shape[:2]
|
|
12
|
+
band = 10
|
|
13
|
+
ring = np.concatenate([
|
|
14
|
+
a[:band].reshape(-1, 3), a[-band:].reshape(-1, 3),
|
|
15
|
+
a[:, :band].reshape(-1, 3), a[:, -band:].reshape(-1, 3)])
|
|
16
|
+
bg = np.median(ring, axis=0)
|
|
17
|
+
dist = np.linalg.norm(a - bg, axis=2)
|
|
18
|
+
alpha = np.clip((dist - lo) / max(hi - lo, 1e-6), 0, 1)
|
|
19
|
+
|
|
20
|
+
# keep only the component(s) touching the border as background
|
|
21
|
+
from collections import deque
|
|
22
|
+
bgmask = dist < hi
|
|
23
|
+
seen = np.zeros((h, w), bool)
|
|
24
|
+
dq = deque()
|
|
25
|
+
for x in range(w):
|
|
26
|
+
for y in (0, h - 1):
|
|
27
|
+
if bgmask[y, x] and not seen[y, x]:
|
|
28
|
+
seen[y, x] = True
|
|
29
|
+
dq.append((y, x))
|
|
30
|
+
for y in range(h):
|
|
31
|
+
for x in (0, w - 1):
|
|
32
|
+
if bgmask[y, x] and not seen[y, x]:
|
|
33
|
+
seen[y, x] = True
|
|
34
|
+
dq.append((y, x))
|
|
35
|
+
while dq:
|
|
36
|
+
y, x = dq.popleft()
|
|
37
|
+
for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1)):
|
|
38
|
+
ny, nx = y + dy, x + dx
|
|
39
|
+
if 0 <= ny < h and 0 <= nx < w and bgmask[ny, nx] and not seen[ny, nx]:
|
|
40
|
+
seen[ny, nx] = True
|
|
41
|
+
dq.append((ny, nx))
|
|
42
|
+
alpha = np.where(seen, 0.0, alpha)
|
|
43
|
+
# soft edge: any pixel bordering the hard background keeps its gradient alpha
|
|
44
|
+
edge = seen & (dist > lo)
|
|
45
|
+
alpha = np.where(edge, alpha, alpha)
|
|
46
|
+
|
|
47
|
+
# despill: pull the excess key channel out of semi-transparent edge pixels
|
|
48
|
+
key_ix = int(np.argmax(bg))
|
|
49
|
+
others = [i for i in range(3) if i != key_ix]
|
|
50
|
+
semi = (alpha > 0.04) & (alpha < 0.98)
|
|
51
|
+
cap = (a[..., others[0]] + a[..., others[1]]) / 2 * 1.05
|
|
52
|
+
a[..., key_ix] = np.where(semi, np.minimum(a[..., key_ix], cap), a[..., key_ix])
|
|
53
|
+
|
|
54
|
+
rgba = np.dstack([a, alpha * 255]).astype(np.uint8)
|
|
55
|
+
img = Image.fromarray(rgba, "RGBA")
|
|
56
|
+
bbox = img.getchannel("A").point(lambda v: 255 if v > 8 else 0).getbbox()
|
|
57
|
+
if bbox:
|
|
58
|
+
pad = 6
|
|
59
|
+
bbox = (max(0, bbox[0] - pad), max(0, bbox[1] - pad),
|
|
60
|
+
min(w, bbox[2] + pad), min(h, bbox[3] + pad))
|
|
61
|
+
img = img.crop(bbox)
|
|
62
|
+
img.save(out)
|
|
63
|
+
print(f"{out} {img.size} bg={bg.astype(int).tolist()} "
|
|
64
|
+
f"coverage={float((np.asarray(img)[..., 3] > 8).mean()):.2%}")
|
|
65
|
+
|
|
66
|
+
# preview sheet: light, dark, gradient
|
|
67
|
+
W, H = img.size
|
|
68
|
+
sheet = Image.new("RGB", (W * 3, H), "white")
|
|
69
|
+
for i, color in enumerate([(245, 245, 247), (24, 28, 38), (255, 214, 214)]):
|
|
70
|
+
tile = Image.new("RGB", (W, H), color)
|
|
71
|
+
tile.paste(img, (0, 0), img)
|
|
72
|
+
sheet.paste(tile, (i * W, 0))
|
|
73
|
+
sheet.thumbnail((1500, 1500))
|
|
74
|
+
sheet.save(out.replace(".png", "_preview.png"))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
key(sys.argv[1], sys.argv[2],
|
|
79
|
+
*([float(v) for v in sys.argv[3:5]] if len(sys.argv) > 3 else []))
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Minimal Krea2 txt2img/img2img runner against the local ComfyUI.
|
|
3
|
+
|
|
4
|
+
ComfyUI defaults to http://127.0.0.1:8188; override with COMFY_URL.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python krea.py --prompt "..." [--negative "..."] [--w 1024] [--h 1024]
|
|
8
|
+
[--steps 8] [--cfg 1] [--seed 1] [--sampler er_sde]
|
|
9
|
+
[--scheduler simple] [--unet <file>] [--lora name:strength]
|
|
10
|
+
[--prefix krea/prompt] [--latent sd3|plain] [--out DIR]
|
|
11
|
+
[--init image.png --denoise 0.45] [--no-wait]
|
|
12
|
+
"""
|
|
13
|
+
import argparse, json, os, time, urllib.parse, urllib.error, urllib.request, uuid
|
|
14
|
+
|
|
15
|
+
BASE = os.environ.get("COMFY_URL", "http://127.0.0.1:8188")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def post(path, payload):
|
|
19
|
+
req = urllib.request.Request(
|
|
20
|
+
BASE + path,
|
|
21
|
+
data=json.dumps(payload).encode(),
|
|
22
|
+
headers={"Content-Type": "application/json"},
|
|
23
|
+
)
|
|
24
|
+
return json.load(urllib.request.urlopen(req))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get(path):
|
|
28
|
+
return json.load(urllib.request.urlopen(BASE + path))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def upload(path):
|
|
32
|
+
"""POST a local file to ComfyUI's input dir; returns the stored filename."""
|
|
33
|
+
boundary = "----krea" + uuid.uuid4().hex
|
|
34
|
+
name = os.path.basename(path)
|
|
35
|
+
body = b"".join([
|
|
36
|
+
f"--{boundary}\r\nContent-Disposition: form-data; name=\"image\"; filename=\"{name}\"\r\n"
|
|
37
|
+
f"Content-Type: application/octet-stream\r\n\r\n".encode(),
|
|
38
|
+
open(path, "rb").read(),
|
|
39
|
+
f"\r\n--{boundary}\r\nContent-Disposition: form-data; name=\"overwrite\"\r\n\r\ntrue"
|
|
40
|
+
f"\r\n--{boundary}--\r\n".encode(),
|
|
41
|
+
])
|
|
42
|
+
req = urllib.request.Request(
|
|
43
|
+
BASE + "/upload/image", data=body,
|
|
44
|
+
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"})
|
|
45
|
+
return json.load(urllib.request.urlopen(req))["name"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build(a):
|
|
49
|
+
g = {
|
|
50
|
+
"1": {"class_type": "UNETLoader",
|
|
51
|
+
"inputs": {"unet_name": a.unet, "weight_dtype": "default"}},
|
|
52
|
+
"2": {"class_type": "CLIPLoader",
|
|
53
|
+
"inputs": {"clip_name": "qwen3vl_4b_fp8_scaled.safetensors", "type": "krea2"}},
|
|
54
|
+
"3": {"class_type": "VAELoader",
|
|
55
|
+
"inputs": {"vae_name": "qwen_image_vae.safetensors"}},
|
|
56
|
+
"4": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["2", 0], "text": a.prompt}},
|
|
57
|
+
"5": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["2", 0], "text": a.negative}},
|
|
58
|
+
"6": {"class_type": "EmptySD3LatentImage" if a.latent == "sd3" else "EmptyLatentImage",
|
|
59
|
+
"inputs": {"width": a.w, "height": a.h, "batch_size": 1}},
|
|
60
|
+
"7": {"class_type": "KSampler",
|
|
61
|
+
"inputs": {"model": ["1", 0], "positive": ["4", 0], "negative": ["5", 0],
|
|
62
|
+
"latent_image": ["6", 0], "seed": a.seed, "steps": a.steps,
|
|
63
|
+
"cfg": a.cfg, "sampler_name": a.sampler,
|
|
64
|
+
"scheduler": a.scheduler, "denoise": getattr(a, "denoise", 1.0)}},
|
|
65
|
+
"8": {"class_type": "VAEDecode", "inputs": {"samples": ["7", 0], "vae": ["3", 0]}},
|
|
66
|
+
"9": {"class_type": "SaveImage",
|
|
67
|
+
"inputs": {"images": ["8", 0], "filename_prefix": a.prefix}},
|
|
68
|
+
}
|
|
69
|
+
model = ["1", 0]
|
|
70
|
+
for i, spec in enumerate(a.lora or []):
|
|
71
|
+
name, _, strength = spec.partition(":")
|
|
72
|
+
nid = f"l{i}"
|
|
73
|
+
g[nid] = {"class_type": "LoraLoader",
|
|
74
|
+
"inputs": {"model": model, "clip": ["2", 0], "lora_name": name,
|
|
75
|
+
"strength_model": float(strength or 1.0),
|
|
76
|
+
"strength_clip": float(strength or 1.0)}}
|
|
77
|
+
model = [nid, 0]
|
|
78
|
+
g["4"]["inputs"]["clip"] = [nid, 1]
|
|
79
|
+
g["5"]["inputs"]["clip"] = [nid, 1]
|
|
80
|
+
if a.lora:
|
|
81
|
+
g["7"]["inputs"]["model"] = model
|
|
82
|
+
init = getattr(a, "init", None)
|
|
83
|
+
if init:
|
|
84
|
+
stored = upload(init)
|
|
85
|
+
g["i1"] = {"class_type": "LoadImage", "inputs": {"image": stored}}
|
|
86
|
+
g["i2"] = {"class_type": "VAEEncode", "inputs": {"pixels": ["i1", 0], "vae": ["3", 0]}}
|
|
87
|
+
g["7"]["inputs"]["latent_image"] = ["i2", 0]
|
|
88
|
+
del g["6"]
|
|
89
|
+
return g
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main():
|
|
93
|
+
p = argparse.ArgumentParser()
|
|
94
|
+
p.add_argument("--prompt", required=True)
|
|
95
|
+
p.add_argument("--negative", default="")
|
|
96
|
+
p.add_argument("--w", type=int, default=1024)
|
|
97
|
+
p.add_argument("--h", type=int, default=1024)
|
|
98
|
+
p.add_argument("--steps", type=int, default=8)
|
|
99
|
+
p.add_argument("--cfg", type=float, default=1.0)
|
|
100
|
+
p.add_argument("--seed", type=int, default=-1)
|
|
101
|
+
p.add_argument("--sampler", default="er_sde")
|
|
102
|
+
p.add_argument("--scheduler", default="simple")
|
|
103
|
+
p.add_argument("--unet", default="krea2_turbo_fp8_scaled.safetensors")
|
|
104
|
+
p.add_argument("--lora", action="append")
|
|
105
|
+
p.add_argument("--prefix", default="krea/test")
|
|
106
|
+
p.add_argument("--latent", default="sd3", choices=["sd3", "plain"])
|
|
107
|
+
p.add_argument("--init", help="local image to img2img from (uploads it)")
|
|
108
|
+
p.add_argument("--denoise", type=float, default=1.0)
|
|
109
|
+
p.add_argument("--out", default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "out"))
|
|
110
|
+
p.add_argument("--no-wait", action="store_true")
|
|
111
|
+
a = p.parse_args()
|
|
112
|
+
if a.seed < 0:
|
|
113
|
+
a.seed = int(time.time() * 1000) % (2 ** 31)
|
|
114
|
+
|
|
115
|
+
graph = build(a)
|
|
116
|
+
cid = str(uuid.uuid4())
|
|
117
|
+
try:
|
|
118
|
+
r = post("/prompt", {"prompt": graph, "client_id": cid})
|
|
119
|
+
except urllib.error.HTTPError as e:
|
|
120
|
+
print("ENQUEUE FAILED", e.code, e.read().decode()[:4000])
|
|
121
|
+
return
|
|
122
|
+
pid = r["prompt_id"]
|
|
123
|
+
print(f"prompt_id={pid} seed={a.seed}")
|
|
124
|
+
if a.no_wait:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
t0 = time.time()
|
|
128
|
+
while time.time() - t0 < 900:
|
|
129
|
+
h = get(f"/history/{pid}")
|
|
130
|
+
if pid in h:
|
|
131
|
+
entry = h[pid]
|
|
132
|
+
st = entry.get("status", {})
|
|
133
|
+
if st.get("status_str") == "error" or not st.get("completed", True):
|
|
134
|
+
print("EXECUTION ERROR:", json.dumps(st, ensure_ascii=False)[:4000])
|
|
135
|
+
imgs = []
|
|
136
|
+
for node in entry.get("outputs", {}).values():
|
|
137
|
+
imgs += node.get("images", [])
|
|
138
|
+
os.makedirs(a.out, exist_ok=True)
|
|
139
|
+
for im in imgs:
|
|
140
|
+
q = urllib.parse.urlencode({"filename": im["filename"],
|
|
141
|
+
"subfolder": im.get("subfolder", ""),
|
|
142
|
+
"type": im.get("type", "output")})
|
|
143
|
+
data = urllib.request.urlopen(f"{BASE}/view?{q}").read()
|
|
144
|
+
path = os.path.join(a.out, im["filename"])
|
|
145
|
+
with open(path, "wb") as f:
|
|
146
|
+
f.write(data)
|
|
147
|
+
print("SAVED", path)
|
|
148
|
+
print(f"elapsed={time.time() - t0:.1f}s")
|
|
149
|
+
return
|
|
150
|
+
time.sleep(1.5)
|
|
151
|
+
print("TIMEOUT (job may still be running)")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: visual-asset-production
|
|
3
|
+
description: 制作 UI 图标、面板、角色、背景和纹理时选择生成、程序绘图与排版,并在目标尺寸及真实场景验收。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 素材工具选择与场景验收
|
|
7
|
+
|
|
8
|
+
## 按约束选工具
|
|
9
|
+
|
|
10
|
+
读既有视觉参考及素材,确认尺寸、透明、留白、缩放、锚点、颜色和文字需求。
|
|
11
|
+
|
|
12
|
+
- 精确边框、九宫格、排版、文字和数值图形优先 SVG、Canvas、程序绘图或排版。
|
|
13
|
+
- 插画、纹理和允许变化的造型可用生成模型,先核对模型、节点和授权。
|
|
14
|
+
- 已有合适素材优先复用或小幅加工,不擅自重做全部素材。
|
|
15
|
+
|
|
16
|
+
分清可随机与必须精确的内容。伪文字不能做正式文案;需要准确文字时生成无字底图,再以真实字体排版。
|
|
17
|
+
|
|
18
|
+
## 小样到定稿
|
|
19
|
+
|
|
20
|
+
先用小样确认构图,记录模型、工作流、输入和 seed;同 seed 不保证跨模型、尺寸和版本一致。历史实测不写成永久保证。
|
|
21
|
+
|
|
22
|
+
从既有素材取色,固定轮廓、材质、光向和视角。角色复用描述与参考,每张仍检查面部、服装、比例。复杂持物和组件可分层制作,批量生成不证明一致性。
|
|
23
|
+
|
|
24
|
+
使用 Krea 2 时再读 [Krea 技能](../krea2-image-prompt/SKILL.md)。其中机器事实使用前重新核验,不照抄路径或假定模型存在。先读当前 MCP schema;不擅自下载模型、安装节点或消费付费资源。
|
|
25
|
+
|
|
26
|
+
## 后处理
|
|
27
|
+
|
|
28
|
+
保留原图和编辑源,输出新路径。去底前检查主体与背景是否同色,阈值从保守起点调整。白底处理按需加载 white-background-to-alpha;预览白色不证明透明。
|
|
29
|
+
|
|
30
|
+
检查解码格式、尺寸、alpha 和边缘,在浅、深、彩色底及目标尺寸查看。描边留边距,缩放后检查细线、白边、色溢出。
|
|
31
|
+
|
|
32
|
+
放回实际界面或场景,检查对比度、锚点、落脚点、遮挡、接缝及真实文案。HTML 视觉参考不代替应用验收。报告源文件、产物、已验证尺寸和残留差异。
|