⏳ This skill is pending AI review.

Scores will appear once the review pipeline completes.

v1.0.0

huashu-gpt-image

@alchaincyf⭐ 29 stars

GPT-image-2 的 prompt 工程方法论,**公众号配图的唯一入口**。任何 AI 生图任务(封面 banner、海报、信息图、UI mockup、产品包装、slides、批量 icon/角色/卡牌、中文书法对联、视频参考板)的 prompt 写作必读。核心铁律:prompt 默认中文短句,用真实参考名(Dieter Rams / Penguin Books / MUJI / Anthropic)替代形容词,不写英文段落式 prompt,不堆 `Subject:`/`Style:` 伪结构。与 huashu-xhs-image 的分工:那个是端到端小红书业务流程,本 skill 是被它底层调用的 prompt 方法论层。SKIP:图片压缩、HTML 截图、复用已有图。

Choose how to use this skill

You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.

1. Native installer

This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.

Do not guess an installer command or replace an existing version without reviewing the diff.

2. Complete package recommended

Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.

No complete ProSkills package is published for this listing yet.

3. Prompt-only

Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.

Need only the instruction file?

Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.

No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.

—/10

// RATINGS

⭐GitHub Stars
⭐⭐ 29 on GitHubGitHub ↗

Growing

🟢ProSkills Score
—
📍

Not yet listed on ClawHub or SkillsMP

// README

huashu-gpt-image

「你缺的不是 prompt 技巧,是那个名字。」 GPT-image-2 的 prompt 工程方法论。

License: MIT Agent-Agnostic Skills

封面 · 海报 · 信息图 · UI mockup · 产品包装 · 批量 icon/角色/卡牌 · 中文书法

大多数人写 AI 生图 prompt 的方式是堆形容词:「高级感、现代、简约、有质感、 专业的、精致的」。堆得越多,出来的图越像所有 AI 图。

而这条 prompt 只有 16 个字:

Dieter Rams 风格的大疆 pocket3 说明书

拿到的是一份完整 A4 说明书,含真实参数,视觉精度到官方手册级别。

差别不在技巧,在你知不知道 Dieter Rams 是谁——他的东西长什么样、 为什么他配这个产品。模型的视觉记忆里有他,前提是你去调他。

npx skills add alchaincyf/huashu-gpt-image

跨 agent 通用——Claude Code、Cursor、Codex、OpenClaw、Hermes 都能装。

核心方法 · 四条红线 · 装上就能用 · 批量生成 · 出图之后


核心方法:概念压缩比

prompt 的有效信息 ≠ prompt 的字数。决定输出质量的是单字承载的设计决策密度。

写 prompt 之前先问一句:主体 / 风格 / 媒介三个槽位,能不能各填一个模型 熟知的高频名字?

堆形容词填名字
主体「一个便携相机」大疆 pocket3
风格「简约、高级、德国工业感」Dieter Rams
媒介「产品介绍页面」说明书

三个槽都填上了 → 短 prompt 就够。有空槽 → 那个槽位才需要结构化描述补足。

所以这个 skill 真正在训练的不是 prompt 写法,是你脑子里的概念库: 设计师、品牌、媒介范式,每多认识一个精准的名字,你的上限就抬高一点。


四条红线

违反任何一条都是 AI slop。完整规则见 SKILL.md。

一、prompt 默认写中文。 模型对中英文理解能力完全对等,但中文更精炼(一个汉字 ≈ 2-3 个英文 token)。 英文段落式 prompt 是模型训练数据的默认输出,也是 AI 味的来源。 Subject: / Style: / Constraints: 这种伪结构化 header 同样禁止。

二、图里的文字也默认中文。 读者是中文用户,图上出现「Perception Gap」「Mainstream path」是隔阂不是高级感。 GPT-image-2 的中文准确率支持 20+ 字符,出中文不是问题。

三、用真实参考名替代形容词。 Dieter Rams / Penguin Books / MUJI / Anthropic / 瑞士国际主义 —— 一个名字调动的视觉记忆,比十个形容词精确得多。

四、Placeholder > 烂实现。 没有真实数据就留白,不要编造看起来像数据的假数据。


装上就能用

npx skills add alchaincyf/huashu-gpt-image

装完直接说人话:

"给这篇文章做个封面"        → 走配图 SOP,定家族 → 挑 ref → 生成
"做一套 12 个 icon"        → 批量生成,网格切图
"这张图重新出,字太小"      → 按失败模式树对症改
"做个产品对比信息图"        → 单图 playbook

生成执行层支持两条路径:

路径什么时候用
scripts/gen_via_codex.pyCodex 环境,走内置 image_gen
chatgpt-web-bridge/通过 Chrome 扩展 + 本地桥调 ChatGPT 网页版

本地桥只监听 127.0.0.1:8765,不对外暴露。


批量生成

icon 集 / 角色集 / 卡牌集 / sprite sheet / 中文长文本,走网格法: 一次生成一张大图(如 4×3 网格),再用 scripts/extract_grid.py 切成单张。

比逐张生成快一个数量级,而且同一张图里的元素风格天然一致—— 这是逐张生成很难做到的。

但有个坑,是实测踩出来的:别硬性锁死画布尺寸,让比例匹配内容。 早期用 1:1 画布生成 5×5 的 2:3 纵向卡,末行被压缩了 18%—— 模型在约束冲突时会自发牺牲末行来「保住构图完整」,而且不会告诉你。

配套脚本:

脚本干什么
extract_grid.py网格大图切成单张
contact_sheet.py多张图拼成审阅用的联系表
chroma_key.py抠掉纯色背景转透明

出图之后

生成不是终点。skill 内置一棵失败模式树——出图后对症查:

  • 文字糊了 / 缺笔画 → 字号与字数上限
  • 出现外文水印 / nameplate → prompt 里的负向约束写法
  • 风格漂移 → ref 垫图的用法
  • 人物崩坏 / 手部异常 → 构图与视角调整

每一条都对应具体改法,不是「再试一次」。


方法论出处

来源用在哪
Dieter Rams《设计十诫》工业设计类 prompt 的风格锚点
Penguin Books / MUJI / Anthropic 品牌视觉出版与品牌类的媒介范式
瑞士国际主义平面设计版式与网格系统
GPT-image-2 官方能力文档尺寸、字符数、批量的硬约束

skill 里所有风格锚点都是真实存在、模型确实认识的名字, 不是编出来的「某某风」。


License

MIT

// HOW IT'S BUILT

// FILES

27 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.

  • SKILL.md46 KBe9107bd77da3truncatedupstream ↗

    Showing the inlined prefix. The rest is in the upstream file.

    ---
    name: huashu-gpt-image
    description: "GPT-image-2 的 prompt 工程方法论,**公众号配图的唯一入口**。任何 AI 生图任务(封面 banner、海报、信息图、UI mockup、产品包装、slides、批量 icon/角色/卡牌、中文书法对联、视频参考板)的 prompt 写作必读。核心铁律:prompt 默认中文短句,用真实参考名(Dieter Rams / Penguin Books / MUJI / Anthropic)替代形容词,不写英文段落式 prompt,不堆 `Subject:`/`Style:` 伪结构。与 huashu-xhs-image 的分工:那个是端到端小红书业务流程,本 skill 是被它底层调用的 prompt 方法论层。SKIP:图片压缩、HTML 截图、复用已有图。"
    metadata:
      author: "花叔"
      homepage: "https://github.com/alchaincyf"
      repository: "https://github.com/alchaincyf/huashu-gpt-image"
      version: "1.0.0"
    ---
    
    # huashu-gpt-image
    
    ## 你是谁
    
    **你的 prompt 上限,等于你脑子里的概念库。**
    
    模型已经足够强了,瓶颈早就不在「怎么把话说清楚」——在于**你脑子里能不能调出
    那个精准的名字**。「Dieter Rams 风格的大疆 pocket3 说明书」16 个字拿到官方手册级
    精度,靠的不是 prompt 技巧,是知道 Dieter Rams 是谁、他的东西长什么样、
    为什么它配这个产品。
    
    所以你不是「一个写 prompt 的」。你是**一个懂视觉的人**,
    prompt 只是你把判断交给模型的方式。写不出好 prompt 通常不是表达问题,
    是你还没想清楚这个东西在设计史上像什么。
    
    **限制通常不在能力,在于有没有先认定自己要做到那个水准。** 现在的模型可以调用
    任何一位设计师、任何一个品牌、任何一种媒介范式的视觉记忆——前提是你去调它。
    
    ### 你不是一个人,是一个团队
    
    | 角色 | 他负责什么 | 缺了会怎样 |
    |---|---|---|
    | **艺术总监** | 定这张图要传达什么、什么气质 | 图很精致但不知道在说什么 |
    | **设计史学者** | 知道该锚定谁——Rams 还是 Penguin 还是 MUJI | 只能堆形容词,出 generic 图 |
    | **prompt 工程师** | 把判断压成模型能懂的最短表达 | 写成英文段落式 prompt,也就是 AI slop |
    | **图像审校** | 出图后逐条查 slop、查文字、查手 | 交付一张一眼假的图 |
    
    ### 你可以想多久
    
    **想多久都行。** 出图前多花两分钟想「这个东西在设计史上像什么」,
    比出图后改十轮都管用。候选参照要多,最后落进 prompt 的要少。
    
    ---
    
    GPT-image-2 是当前最强图像模型。本 skill 是任何 AI 图片生成任务的优先入口,覆盖**单图生成**(封面/海报/banner/产品图/UI mockup/插画/配图/slides)和**批量生成**(icon集/角色集/卡牌集/sprite sheet/中文长文本)两类场景。
    
    ---
    
    ## ⛔ 写 prompt 之前先看:行为硬约束(不可违反)
    
    **这是 GPT-image-2 prompt 工程的核心,违反任何一条都是 AI slop。**
    
    ### 红线 1 · prompt 默认中文写 + 图里文字也默认中文
    
    模型对中英文 prompt 的理解能力**完全对等**,但中文更精炼(一个汉字 ≈ 2-3 个英文 token)。GPT-image-2 中文准确率 20+ 字符,**图里出中文不是问题**。
    
    #### 1.1 prompt 本身用中文
    
    - ❌ **禁止默认英文段落式 prompt**——这是模型的训练数据默认输出,是 AI slop
    - ❌ **禁止 `Subject:` / `Style anchor:` / `Style:` / `Constraints:` 这种伪结构化 section header**
    - ✅ **中文优先**,除非主体本身是英文品牌名(如 Cursor / Anthropic)或风格本身是英文人名(如 Dieter Rams)
    
    #### 1.2 图里文字也默认中文(关键 · 容易漏)
    
    读者是中文用户,图里出现「Perception Gap」「Mainstream path」反而是隔阂,不是高级感。**默认中文**,按以下原则区分:
    
    | 文字类型 | 用什么 | 例 |
    |---|---|---|
    | 描述性标签 / 章节标题 / 解释文字 | **中文** | 「感知鸿沟」「主流方法」「专家化阶段」 |
    | 品牌名 / 产品名 | 原样 | DeepSeek / Cursor / Anthropic |
    | 论文专有术语 / 已成俗的技术名 | 英文 | SFT / RFT / KV cache / F_TwG |
    | 数字 / 规格 / 坐标 | 原样 | (357, 369) / 1410×600 / 7,056× |
    | 必要时双语 | 中文为主 + 英文小字 | 「专家化 SFT」(Specialized SFT) |
    
    **反 default 警告**:模型默认会把「Anthropic 风格 / 编辑插画」翻译成全英文输出(训练数据偏见)。**必须显式写「图里描述性文字用中文」**否则模型会自动跳到英文。
    
    ### 红线 2 · 长度看信息密度,不设硬上限
    
    判断标准是「删掉一半字,模型会不会损失信息」——会损失就合格,不损失说明堆了无效形容词,必须砍。
    
    - 短 prompt 优先:三槽位能用真名锚定时,30 字内就够
    - **多 ref 构图型封面**(真人/角色 ref + 素材图 ref)按需写长,可到 200 字:人物位置/占比/手势指向/素材图占比/大字位置/前后遮挡这类空间关系词是打包决策,写得越具体模型执行力越强。构图模板见 `配图/封面方法论.md` 的「构图模板层:gbro 十式」
    - technical infographic 含具体数据/章节结构同理可到 200 字,仍以中文为主
    - 无论多长,形容词堆砌仍然禁止(红线 3)
    
    ### 红线 3 · 用真实参考名替代形容词
    
    | ❌ 形容词堆砌 | ✅ 真实锚点 |
    |---|---|
    | minimalist editorial illustration | Anthropic 风格 / Field Notes 风格 |
    | mid-century modern feel | Saul Bass 风格 |
    | New Yorker covers crossed with X brand | The New Yorker 单插画 |
    | bold flat shapes with hand-drawn contour | 朱赢椿装帧线条 |
    | editorial magazine illustration with subtle film-grain | A24 海报质感 |
    
    **心法**:形容词是模糊寻址,真名是精确锚定。1 个真名 > 5 个形容词。
    
    #### ⚠️ 陷阱:出版物品牌名会被模型画成 nameplate(实测)
    
    `The Economist 风格` / `WSJ 风格` / `Field Notes 风格` / `NYT Magazine 风格` / `Wired 风格` 这类**出版物真名**,本质是「它的 nameplate + 排版」——模型解析时会**主动把品牌名画到图的左上角**作为真实 nameplate,且自然走全英文排版。实测:三张图全部出现 The Economist/Field Notes/WSJ 英文 nameplate + 全英文表格,直接回炉。
    
    **触发判断**:
    - prompt 里出现「××杂志风格」「××报纸风格」「××刊物风格」→ 红灯
    - 涉及到的真名本身是出版物/媒体品牌(不是设计师人名、不是产品/品牌名)→ 红灯
    
    **修复**:
    - 给花叔做配图 → **走「花叔配图默认 SOP」**(下面章节),用品牌资产 ref + 描述性词替代品牌名
    - 非花叔场景但非要锚出版风格 → prompt 显式加「严禁外文品牌名水印,严禁左上角 nameplate」
    - 设计师/产品品牌锚定(Dieter Rams / Penguin Books / Anthropic / Apple)**风险低** —— 因为它们的设计哲学就是锚定目标,不存在 nameplate 误植
    
    ### 红线 4 · 🔴 CHECKPOINT:写完 prompt 必跑 5 项自检
    
    每个 prompt 写完后**必须**逐条对照(不过的项 → 显式 if-then 修复):
    
    - [ ] 1. **是中文还是英文段落?** → 英文段落 **必须** 重写为中文(除非主体是英文品牌名/英文设计师名)
    - [ ] 2. **长度与场景匹配?** → 简单单图 30 字内理想;构图型封面/infographic 可到 200 字,但每句都要是空间关系或具体内容,不是形容词
    - [ ] 3. **三槽位(主体/风格/媒介)有几个真名锚点?** → < 2 个 **必须** 补真名;若该领域无熟知真名 → 用「××风格 + 结构化描述」混合写法
    - [ ] 4. **有 `Subject:` `Style:` `Constraints:` 这种伪 section?** → 有 **必须** 删
    - [ ] 5. **如果删掉 50% 的字,模型还能理解吗?** → 能 **必须** 砍 50%;不能 → 检查是不是堆了无效形容词
    
    **🛑 STOP**:任何一条不过 → **禁止交付 prompt** → 回到对应自检项的修复指令重写 → 重跑全部 5 项自检(不要只看修过的那条)。
    
    ---
    
    ## ✅ 好 prompt 长什么样(5 个实测案例)
    
    | Prompt(≤ 30 字) | 实测分 | 为什么强 |
    |---|---|---|
    | `Dieter Rams 风格的大疆 pocket3 说明书` | 9.5/10 | 三槽位都强,每词都打包几百条决策 |
    | `The Criterion Collection 风格的 Cursor 产品页` | 9.5/10 | Criterion 的 NO.编号/manifesto/curated stamp 全自动迁移 |
    | `Penguin Books 风格的 Cursor 产品手册` | 9.5/10 | 三段橙/MODERN [X] CLASSICS 命名传统全到位 |
    | `Brutalism 风格的婴儿米粉包装` | 9.5/10 | 模型自发贡献「禾筑」品牌名 |
    | `Apple 设计语言的中药包装。请你也贡献品牌名/slogan/命名逻辑` | 9.5/10 | invite 命名升级,输出完整品牌系统 |
    
    **注意**:当主体是「不能压缩」的内容(如 6 张系列配图),仍然遵循「主体一句话+风格真名+尺寸」结构,**不要写英文段落**。详见 [single-image-playbook.md](references/single-image-playbook.md) 的「关键 prompt 模式」。
    
    ---
    
    ## ❌ 反面教材(近期真实失败案例)
    
    ```
    Editorial poster illustration for a tech magazine cover.
    
    Subject: a single human hand entering from the lower-right corner,
    index finger extended, pointing precisely at a coordinate marker on
    an abstract maze-like geometric pattern...
    
    Style anchor: minimalist editorial illustration in the spirit of
    Anthropic's brand visuals. Bold flat shapes, hand-drawn black contour
    lines, intentional white space. Terracotta orange (#C7522A) accent
    against a warm cream beige (#F5EDD8) background. Mid-century modern feel.
    
    Constraints: NO Chinese characters, NO watermark, NO signature...
    Aspect ratio 2.35:1, output 1410×600.
    ```
    
    **这是反面教材**,违反了:
    - 红线 1(英文段落 + 伪 section header)
    - 红线 2(删掉一半字不损失信息——全是无效形容词,不是打包决策)
    - 红线 3(堆形容词,没用真名)
    
    **正确写法**:
    ```
    Anthropic 风格的编辑插画封面:一只手从右下伸入,食指点向左上几何迷宫
    图案上的某个坐标点 (357, 369),指尖发光。1410×600,无中文无水印。
    ```
    
    约 50 字中文,三槽位(Anthropic 风格 + 手指坐标 + 编辑插画封面)都有真名,技术参数最后给。
    
    ---
    
    ## 使用入口决策
    
    收到任务后**先判断**:
    
    | 任务类型 | 走哪条路 |
    |---|---|
    | 🔴 **给花叔做配图**(公众号封面 / 正文图 / 小红书图 / 视频封面 / 任何有品牌识别诉求的图)| **强制走下面「🎯 花叔配图默认 SOP」专章**——默认 `--ref` 品牌资产 + 显式禁外文 nameplate,不要走通用单图流程撞墙 |
    | 一张图(封面 / 海报 / banner / 包装 / UI / 配图 / slide,**且无品牌识别诉求**)| **走「单图生成」**——先读 [single-image-playbook.md](references/single-image-playbook.md) |
    | 一批同类素材(icon 集 / 角色集 / 卡牌 / sprite sheet)| **走「批量生成」**——读下方批量章节 + [prompt-patterns.md](references/prompt-patterns.md) |
    | 中文长文本视觉(古诗 / 对联 / 书法 / 印章)| **走「中文文本」**——下方批量章节有路由 |
    | **AI 视频生成辅助参考板**(角色三视图 + 场景空间 + 分镜脚本 + 机位流程综合在一张图)| **走「视频参考板」**——读 [video-reference-board.md](references/video-reference-board.md) |
    | 既有单图也有批量(如品牌系统:封面 + 配套 icon)| 主图走单图流程,配套素材走批量流程 |
    
    ## 🎯 给花叔做配图的默认 SOP(高频特化场景 · 走这条不要走纯 prompt)
    
    花叔的所有配图(公众号封面/正文图/小红书图/视频封面)**都有品牌识别诉求**。本 skill 最高频场景就是给花叔做配图——默认走「**配图库定家族** + 挑风格 ref 垫图 + 品牌 logo + 显式禁外文 nameplate」,**不要先走纯 prompt 撞墙再回头,更不要默认套像素风**。
    
    ### 🔴 第一步:去配图库定家族、挑 ref(不要跳过,不要默认像素)
    
    配图库在 `~/Documents/写作/配图/`,是花叔视觉资产 + 封面方法论的家。生成前**必读** `配图/CLAUDE.md` 与 `配图/风格参考/风格参考索引.md`,按主题定家族再挑 ref:
    
    | 家族 | 什么时候用 | 风格 ref 垫图 |
    |---|---|---|
    | **米白手绘editorial(⭐主力)** | 知识解读/方法论/产品评测/个人观点 | ⚠️ 实际落在 `精选/00-待你确认/`(不是索引里写的 `03-…`),挑1张。同类优先:文件名带 `darwin-skill-v2方法论` / `md生产html消费` 的就是自造物+方法论那批 |
    | **superflat多彩庆典(王牌)** | 重磅发布/X.0升级/里程碑,能量拉满 | `配图/风格参考/大字报-时代广场-HuashuDesign2.0.png` |
    | 高能量大字报 | 热点速评/发布快讯/戏剧化判断 | `精选/02-高能量大字报/`。⛔ **底色不许用正红/橙红满底**(2026-09-01 花叔判「很丑,我不喜欢」,此前该族 7 张有一半是红底,已作废)。要走这族就换暖黄、米白配巨字、深炭灰这类底色 |
    | 多彩插画叙事 | 有画面感的故事/产品拟人化 | 主题角色/产品官方图 + prompt 写厚涂/3D 高饱和 |
    | Anthropic橙锚定 | Claude/Anthropic 生态 | prompt 写赤陶橙底 |
    | 像素IP | 工具/技巧/复古梗等**轻量**主题 | `_archive/像素品牌资产.png` + `精选/02-像素风/`(不是 `04-…`)|
    
    > 🔴 **目录名与索引编号对不上,别照索引的编号去找。** `风格参考索引.md` 讲的是六大家族
    > (01 superflat / 02 高能量大字报 / 03 米白手绘editorial / 04 像素IP / 05 多彩插画叙事 /
    > 06 Anthropic橙),但 `精选/` 下实际只落了四个目录,且编号另起:
    > `00-待你确认`(= 米白手绘 editorial 主力款那批)、`01-superflat多彩庆典`、
    > `02-像素风`、`03-Anthropic风`。**先 `ls 精选/` 看实际有什么,再按家族语义挑**,
    > 不要按索引的编号拼路径(2026-09-03 实测踩空一次)。
    
    **像素风只是 6 家族里的 1 个(轻量梗才用),不是默认。** 定不了家族先读索引的「六大家族速查」。主角 ref 多数家族用 `配图/品牌资产/花叔头像-superflat三视图.png`(superflat 卡通男孩),像素家族才用 `_archive/像素品牌资产.png`。
    
    ### 🔴 生成参数(定完家族后复用)
    
    | 字段 | 值 |
    |---|---|
    | `--ref`(多张,三件套) | 主角 ref(花叔 su
  • chatgpt-web-bridge/README.md6.3 KBe8cc5e1e9308upstream ↗
    # ChatGPT Web 生图桥
    
    把 **chatgpt.com 网页版**的生图能力桥接成一个本地 API,给 `huashu-gpt-image` skill 当**第三条生成路**。
    
    ## 为什么要它
    
    | 路 | 吃哪个额度桶 | 何时用 |
    |---|---|---|
    | `gen_via_codex.py`(默认)| codex 的 `$imagegen` 订阅桶——**较紧**,用几次就 429 | 日常默认 |
    | `gen_via_chatgpt_web.py`(本桥)| **网页版**订阅桶——Plus/Pro **宽得多** | codex 桶耗尽时 fallback |
    | API 路径 | 按量计费 | 要透明底/精确参数/大批量 |
    
    codex 和网页版虽然都是 ChatGPT 订阅,但**限流是两个独立的桶**——这正是本桥的价值。
    
    ## 架构(为什么要本地中继)
    
    Chrome MV3 扩展的 service worker 不能监听端口,CLI 也没法直接喊扩展,所以用一个本地 HTTP 中继当「信箱」:
    
    ```
    gen_via_chatgpt_web.py  --POST /submit-->  server.py  <--GET /poll(长轮询)--  Chrome 扩展
    gen_via_chatgpt_web.py  <----图片字节----  server.py  <--POST /result-------  Chrome 扩展
                                                                  ↓ 在你已登录的真实 Chrome 里
                                                              驱动 chatgpt.com 生图
    ```
    
    扩展跑在**你真实的、已登录的 Chrome** 里,所以反爬检测最低、不需要 API key、不按量计费。
    
    ## 安全边界:为什么「只绑 127.0.0.1」不够
    
    `server.py` 只监听 `127.0.0.1`,这挡住了局域网里的别人,**挡不住你自己浏览器里打开的任意网页**——网页能对 `127.0.0.1` 发跨源请求。所以桥在应用层加了三道门:
    
    | 门 | 做什么 | 挡住什么 |
    |---|---|---|
    | Host 白名单 | 只接受 `127.0.0.1` / `localhost` / `::1` | DNS rebinding(攻击者域名解析到 127.0.0.1,Host 就露馅) |
    | Origin 白名单 | 只放行 `chrome-extension://` 和「没有 Origin」的本地 CLI,`http(s)://` 网页直接 403,且**从不回 `ACAO: *`** | 恶意页面读 `/poll` 偷走待处理任务——里面有 `--ref` 参考图的完整 base64 |
    | `X-Bridge-Client` 头 | `/submit` `/poll` `/result` 必带 | 网页的「简单请求」带不了自定义头;一加就必须先过预检,而预检卡在上一道门。这条同时堵住了「读不到响应但照样把任务从队列里抢走」的盲打 |
    
    另外两条:
    
    - **`/result` 绑 `resultToken`**:`/poll` 派任务时附一个一次性 token,回 `/result` 必须带对,否则 403。没有它,任何能猜到 `jobId` 的一方都能替你的任务交一份伪造结果,而 CLI 会把那些字节直接写进 `--out`。
    - **请求体上限** `BRIDGE_MAX_BODY`(默认 32 MiB):超了返 413,不再照着 `Content-Length` 分配内存。
    
    `/health` 不要求自定义头,`curl http://127.0.0.1:8765/health` 照旧能用——它只返回在线状态和计数。
    
    > 门再多也只是**纵深**,不是隔离。桥开着就意味着本机上任何能执行代码的东西都能驱动你已登录的 ChatGPT 账号。**用完就把 `server.py` 关掉**,别常驻。
    
    ## 一次性安装(约 3 分钟)
    
    ### 1. 装扩展
    
    1. Chrome 打开 `chrome://extensions`
    2. 右上角打开「开发者模式」
    3. 点「加载已解压的扩展程序」,选这个目录下的 `extension/` 文件夹
    4. 确认 Chrome 里 **chatgpt.com 已登录**(Plus 账号)
    
    ### 2. 跑本地中继
    
    ```bash
    python3 ~/.claude/skills/huashu-gpt-image/chatgpt-web-bridge/server.py
    ```
    
    保持这个终端开着(或用 `&` / launchd 常驻)。换端口:`PORT=9000 python3 server.py`,扩展 popup 里也要改成同一个。
    
    ### 3. 验证
    
    ```bash
    python3 ~/.claude/skills/huashu-gpt-image/scripts/gen_via_chatgpt_web.py --health
    # ✅ 桥 + 扩展均在线
    ```
    
    ## 用法(接口对齐 gen_via_codex.py)
    
    ```bash
    # 单图
    python3 scripts/gen_via_chatgpt_web.py --prompt "Kenya Hara 风格的越窑青瓷茶碗特写" --out 配图/x.png --size 1410x600
    
    # 批量(浏览器侧串行执行,慢属正常)
    python3 scripts/gen_via_chatgpt_web.py --batch jobs.jsonl --concurrency 2
    ```
    
    ## 防风控节流(主力账号务必保持开启)
    
    自动化 chatgpt.com 违反 OpenAI ToS,主力账号被风控的下行是非对称的。`server.py` 内置两道闸,把行为压到像真人、限量:
    
    | 环境变量 | 默认 | 作用 |
    |---|---|---|
    | `BRIDGE_DAILY_CAP` | `80` | 每日生成上限,超了 `/submit` 直接返回 429 |
    | `BRIDGE_MIN_GAP` / `BRIDGE_MAX_GAP` | `8` / `20` | 两次派发之间随机间隔(秒),模拟人类节奏 |
    
    `--health` 会显示 `dailyUsed / dailyCap`。**别为了快把间隔调到 0 或上限调很高**——那等于把「像机器人」的特征重新暴露出来。批量场景宁可慢。
    
    ## 故障排查
    
    | 现象 | 原因 / 解法 |
    |---|---|
    | `--health` 说桥不可用 | `server.py` 没跑,或端口被占 |
    | `--health` 说扩展没连 | 扩展没加载 / Chrome 没开 / popup 里桥地址写错 |
    | 扩展点开 popup 显示「桥未启动」 | 同上;macOS 系统代理可能劫持 localhost → 系统代理设置里把 `127.0.0.1,localhost` 加进「忽略代理」 |
    | 等图超时 | chatgpt.com 改版导致选择器失效 → 见 `extension/content.js` 顶部 `SELECTORS`,对着真实 DOM 校准带 `❗TODO` 的项 |
    | 报「限流/上限」 | 网页版桶也到顶了,等恢复或回退 codex 路 |
    | 请求返 403 `missing X-Bridge-Client` | 自己写脚本调桥时漏了这个头(见上方「安全边界」),加 `-H 'X-Bridge-Client: my-script'` |
    | 请求返 403 `cross-origin denied` | 从网页/DevTools console 里调桥——这是**设计如此**,只有扩展和本地 CLI 能调 |
    | 扩展报 403 `bad or expired resultToken` | 扩展是旧版(`background.js` 还没回传 `resultToken`)→ 在 `chrome://extensions` 点一下「重新加载」 |
    | **popup 显示「桥在线 ✓」,但每次生图都 504** | 升级了 `server.py` 却没重载扩展:旧扩展的 `/poll` 全被 403,而 `/health` 豁免那道门照样通。→ `chrome://extensions` 重载扩展。现在 popup 会直接把这句写出来(「桥在线,但扩展被拒」),`/health` 也返回 `lastPollRejected` |
    
    ## ⚠️ 选择器维护
    
    `extension/content.js` 顶部的 `SELECTORS` 是唯一的 DOM 依赖。ChatGPT 改版后只改这里。带 `❗TODO(DOM校准)` 的项需要对着当前 chatgpt.com 的真实 DOM 确认(输入框、发送键、生成图节点、限流文案)。
    
  • README.md5.4 KBedc98d664563upstream ↗
    <div align="center">
    
    # huashu-gpt-image
    
    > *「你缺的不是 prompt 技巧,是那个名字。」*
    > *GPT-image-2 的 prompt 工程方法论。*
    
    [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
    [![Agent-Agnostic](https://img.shields.io/badge/Agent-Agnostic-blueviolet)](https://skills.sh)
    [![Skills](https://img.shields.io/badge/skills.sh-Compatible-green)](https://skills.sh)
    
    <br>
    
    **封面 · 海报 · 信息图 · UI mockup · 产品包装 · 批量 icon/角色/卡牌 · 中文书法**
    
    <br>
    
    大多数人写 AI 生图 prompt 的方式是堆形容词:「高级感、现代、简约、有质感、
    专业的、精致的」。堆得越多,出来的图越像所有 AI 图。
    
    而这条 prompt 只有 16 个字:
    
    ```
    Dieter Rams 风格的大疆 pocket3 说明书
    ```
    
    拿到的是一份完整 A4 说明书,含真实参数,视觉精度到官方手册级别。
    
    差别不在技巧,在**你知不知道 Dieter Rams 是谁**——他的东西长什么样、
    为什么他配这个产品。**模型的视觉记忆里有他,前提是你去调他。**
    
    ```
    npx skills add alchaincyf/huashu-gpt-image
    ```
    
    跨 agent 通用——Claude Code、Cursor、Codex、OpenClaw、Hermes 都能装。
    
    [核心方法](#核心方法概念压缩比) · [四条红线](#四条红线) · [装上就能用](#装上就能用) · [批量生成](#批量生成) · [出图之后](#出图之后)
    
    </div>
    
    ---
    
    ## 核心方法:概念压缩比
    
    **prompt 的有效信息 ≠ prompt 的字数。决定输出质量的是单字承载的设计决策密度。**
    
    写 prompt 之前先问一句:**主体 / 风格 / 媒介**三个槽位,能不能各填一个模型
    熟知的高频名字?
    
    | | 堆形容词 | 填名字 |
    |---|---|---|
    | 主体 | 「一个便携相机」 | 大疆 pocket3 |
    | 风格 | 「简约、高级、德国工业感」 | Dieter Rams |
    | 媒介 | 「产品介绍页面」 | 说明书 |
    
    三个槽都填上了 → 短 prompt 就够。有空槽 → 那个槽位才需要结构化描述补足。
    
    所以这个 skill 真正在训练的不是 prompt 写法,是**你脑子里的概念库**:
    设计师、品牌、媒介范式,每多认识一个精准的名字,你的上限就抬高一点。
    
    ---
    
    ## 四条红线
    
    违反任何一条都是 AI slop。完整规则见 [`SKILL.md`](SKILL.md)。
    
    **一、prompt 默认写中文。**
    模型对中英文理解能力完全对等,但中文更精炼(一个汉字 ≈ 2-3 个英文 token)。
    英文段落式 prompt 是模型训练数据的默认输出,也是 AI 味的来源。
    `Subject:` / `Style:` / `Constraints:` 这种伪结构化 header 同样禁止。
    
    **二、图里的文字也默认中文。**
    读者是中文用户,图上出现「Perception Gap」「Mainstream path」是隔阂不是高级感。
    GPT-image-2 的中文准确率支持 20+ 字符,出中文不是问题。
    
    **三、用真实参考名替代形容词。**
    Dieter Rams / Penguin Books / MUJI / Anthropic / 瑞士国际主义 ——
    一个名字调动的视觉记忆,比十个形容词精确得多。
    
    **四、Placeholder > 烂实现。**
    没有真实数据就留白,不要编造看起来像数据的假数据。
    
    ---
    
    ## 装上就能用
    
    ```bash
    npx skills add alchaincyf/huashu-gpt-image
    ```
    
    装完直接说人话:
    
    ```
    "给这篇文章做个封面"        → 走配图 SOP,定家族 → 挑 ref → 生成
    "做一套 12 个 icon"        → 批量生成,网格切图
    "这张图重新出,字太小"      → 按失败模式树对症改
    "做个产品对比信息图"        → 单图 playbook
    ```
    
    **生成执行层**支持两条路径:
    
    | 路径 | 什么时候用 |
    |---|---|
    | `scripts/gen_via_codex.py` | Codex 环境,走内置 image_gen |
    | `chatgpt-web-bridge/` | 通过 Chrome 扩展 + 本地桥调 ChatGPT 网页版 |
    
    本地桥只监听 `127.0.0.1:8765`,不对外暴露。
    
    ---
    
    ## 批量生成
    
    icon 集 / 角色集 / 卡牌集 / sprite sheet / 中文长文本,走网格法:
    一次生成一张大图(如 4×3 网格),再用 `scripts/extract_grid.py` 切成单张。
    
    比逐张生成快一个数量级,而且**同一张图里的元素风格天然一致**——
    这是逐张生成很难做到的。
    
    但有个坑,是实测踩出来的:**别硬性锁死画布尺寸,让比例匹配内容**。
    早期用 1:1 画布生成 5×5 的 2:3 纵向卡,末行被压缩了 18%——
    模型在约束冲突时会自发牺牲末行来「保住构图完整」,而且不会告诉你。
    
    配套脚本:
    
    | 脚本 | 干什么 |
    |---|---|
    | `extract_grid.py` | 网格大图切成单张 |
    | `contact_sheet.py` | 多张图拼成审阅用的联系表 |
    | `chroma_key.py` | 抠掉纯色背景转透明 |
    
    ---
    
    ## 出图之后
    
    生成不是终点。skill 内置一棵**失败模式树**——出图后对症查:
    
    - 文字糊了 / 缺笔画 → 字号与字数上限
    - 出现外文水印 / nameplate → prompt 里的负向约束写法
    - 风格漂移 → ref 垫图的用法
    - 人物崩坏 / 手部异常 → 构图与视角调整
    
    每一条都对应具体改法,不是「再试一次」。
    
    ---
    
    ## 方法论出处
    
    | 来源 | 用在哪 |
    |---|---|
    | Dieter Rams《设计十诫》 | 工业设计类 prompt 的风格锚点 |
    | Penguin Books / MUJI / Anthropic 品牌视觉 | 出版与品牌类的媒介范式 |
    | 瑞士国际主义平面设计 | 版式与网格系统 |
    | GPT-image-2 官方能力文档 | 尺寸、字符数、批量的硬约束 |
    
    skill 里所有风格锚点都是**真实存在、模型确实认识**的名字,
    不是编出来的「某某风」。
    
    ---
    
    ## License
    
    MIT
    
  • scripts/chroma_key.py1.5 KB4f303aa7ea7aupstream ↗
    #!/usr/bin/env python3
    """全局色距抠图:去掉所有接近背景色的像素(含封闭口袋) → 透明PNG。
    适用前提:主体配色里不含背景色。软阈值做抗锯齿,再内缩去毛边。
    用法: python chroma_key.py in.png out.png [tol_low=62] [tol_high=110] [erode=2]"""
    import sys
    from PIL import Image, ImageFilter
    
    def key(inp, outp, tol_low=62, tol_high=110, erode=2):
        im = Image.open(inp).convert("RGBA")
        W, H = im.size
        px = im.load()
        corners = [px[0,0], px[W-1,0], px[0,H-1], px[W-1,H-1]]
        bg = tuple(sum(c[i] for c in corners)//4 for i in range(3))
        # 生成alpha:色距<low→0(透明),>high→255(实),中间线性过渡(抗锯齿)
        alpha = Image.new("L", (W, H), 255)
        ap = alpha.load()
        for y in range(H):
            for x in range(W):
                r,g,b,_ = px[x,y]
                d = abs(r-bg[0])+abs(g-bg[1])+abs(b-bg[2])
                if d <= tol_low: ap[x,y] = 0
                elif d >= tol_high: ap[x,y] = 255
                else: ap[x,y] = int(255*(d-tol_low)/(tol_high-tol_low))
        if erode:
            alpha = alpha.filter(ImageFilter.MinFilter(int(erode)*2+1))
        alpha = alpha.filter(ImageFilter.GaussianBlur(0.8))
        im.putalpha(alpha)
        bbox = im.getbbox()
        if bbox: im = im.crop(bbox)
        im.save(outp)
        print(f"{outp}: {im.size}  bg={bg}")
    
    if __name__=="__main__":
        a=sys.argv
        key(a[1],a[2],
            int(a[3]) if len(a)>3 else 62,
            int(a[4]) if len(a)>4 else 110,
            float(a[5]) if len(a)>5 else 2)
    
  • scripts/contact_sheet.py2.5 KB7ec93656b125upstream ↗
    #!/usr/bin/env python3
    """
    把抠出的透明 PNG 拼成一张预览图(contact sheet),用于肉眼验证抠图质量。
    
    用法:
      python3 contact_sheet.py --input extracted_dir --out sheet.png
      python3 contact_sheet.py --input extracted_dir --grid 3 3 --cell 320
    
    默认按文件名数字前缀排序(extract_grid.py 输出的 01_, 02_ ... 格式)。
    """
    import argparse
    import glob
    import os
    import re
    from pathlib import Path
    
    from PIL import Image
    
    
    def natural_sort_key(path):
        name = os.path.basename(path)
        m = re.match(r'^(\d+)', name)
        return (int(m.group(1)) if m else 999999, name)
    
    
    def build_sheet(input_dir, out_path, rows=None, cols=None, cell=320, pad=20,
                    bg_color=(245, 245, 245, 255)):
        files = sorted(glob.glob(os.path.join(input_dir, '*.png')), key=natural_sort_key)
        if not files:
            raise RuntimeError(f'No PNG files found in {input_dir}')
    
        n = len(files)
        if rows is None or cols is None:
            import math
            cols = math.ceil(math.sqrt(n))
            rows = math.ceil(n / cols)
    
        w = cell * cols + pad * (cols + 1)
        h = cell * rows + pad * (rows + 1)
        canvas = Image.new('RGBA', (w, h), bg_color)
    
        for i, f in enumerate(files):
            r, c = i // cols, i % cols
            im = Image.open(f).convert('RGBA')
            iw, ih = im.size
            scale = (cell - 20) / max(iw, ih)
            new_size = (int(iw * scale), int(ih * scale))
            im = im.resize(new_size, Image.LANCZOS)
            x = pad + c * (cell + pad) + (cell - im.width) // 2
            y = pad + r * (cell + pad) + (cell - im.height) // 2
            canvas.paste(im, (x, y), im)
    
        canvas.save(out_path)
        return canvas.size, n
    
    
    def main():
        parser = argparse.ArgumentParser(description=__doc__,
                                          formatter_class=argparse.RawDescriptionHelpFormatter)
        parser.add_argument('--input', required=True, help='Directory of extracted PNGs')
        parser.add_argument('--out', default='contact_sheet.png', help='Output path')
        parser.add_argument('--grid', nargs=2, type=int, metavar=('ROWS', 'COLS'),
                            help='Grid layout (auto if not specified)')
        parser.add_argument('--cell', type=int, default=320, help='Cell size in pixels')
        parser.add_argument('--pad', type=int, default=20, help='Padding between cells')
    
        args = parser.parse_args()
        rows, cols = (args.grid or (None, None))
        size, n = build_sheet(args.input, args.out, rows, cols, args.cell, args.pad)
        print(f'Saved {args.out}: {size[0]}x{size[1]}, {n} images')
    
    
    if __name__ == '__main__':
        main()
    
  • scripts/extract_grid.py7.0 KB4a41ca8d16abupstream ↗
    #!/usr/bin/env python3
    """
    从 GPT-image-2 生成的网格图中抠出独立透明 PNG。
    
    两种模式:
      --mode=bbox    按 content bbox 等分切(默认,适合均匀网格)
      --mode=density 按像素密度扫描切(适合 5×5 或不均匀网格)
    
    用法:
      python3 extract_grid.py input.png --grid 4 4 --out out_dir
      python3 extract_grid.py input.png --grid 5 5 --mode density --out out_dir
      python3 extract_grid.py input.png --grid 3 3 --names "ryu,ken,chunli,..."
    
    Pure PIL + numpy, no third-party deps beyond those.
    """
    import argparse
    import os
    import sys
    from pathlib import Path
    
    import numpy as np
    from PIL import Image
    
    
    BG_THRESHOLD = 240  # RGB 全部 > 此值算背景
    ROW_DENSITY_MIN = 50  # density 模式:一行至少多少非白像素才算有内容
    SEGMENT_MIN_SIZE = 30  # density 模式:一段内容的最小高/宽
    
    
    def load_image(path):
        img = np.array(Image.open(path).convert('RGB'))
        return img
    
    
    def find_content_bbox(img, bg_threshold=BG_THRESHOLD):
        is_content = ~((img[:, :, 0] > bg_threshold) &
                       (img[:, :, 1] > bg_threshold) &
                       (img[:, :, 2] > bg_threshold))
        ys, xs = np.where(is_content)
        if len(ys) == 0:
            return None
        return int(xs.min()), int(ys.min()), int(xs.max()), int(ys.max())
    
    
    def scan_segments(density_1d, threshold, min_size):
        """沿一维密度数组找出连续高密度段(返回 [(start, end), ...])"""
        in_seg = density_1d > threshold
        segments = []
        start = None
        for i, v in enumerate(in_seg):
            if v and start is None:
                start = i
            elif not v and start is not None:
                segments.append((start, i - 1))
                start = None
        if start is not None:
            segments.append((start, len(in_seg) - 1))
        return [s for s in segments if s[1] - s[0] + 1 >= min_size]
    
    
    def density_scan_cells(img, rows, cols, bg_threshold=BG_THRESHOLD):
        """按像素密度扫描,返回 rows×cols 个 cell 的精确坐标。"""
        is_content = ~((img[:, :, 0] > bg_threshold) &
                       (img[:, :, 1] > bg_threshold) &
                       (img[:, :, 2] > bg_threshold))
    
        row_density = is_content.sum(axis=1)
        row_segments = scan_segments(row_density, ROW_DENSITY_MIN, SEGMENT_MIN_SIZE)
        if len(row_segments) != rows:
            print(f'WARN: density scan found {len(row_segments)} rows, expected {rows}',
                  file=sys.stderr)
            if len(row_segments) > rows:
                row_segments = sorted(row_segments, key=lambda s: s[1] - s[0], reverse=True)[:rows]
                row_segments.sort()
    
        cells = []
        for r_idx, (y0, y1) in enumerate(row_segments):
            row_slice = is_content[y0:y1 + 1, :]
            col_density = row_slice.sum(axis=0)
            col_segments = scan_segments(col_density, ROW_DENSITY_MIN // 2, SEGMENT_MIN_SIZE)
            if len(col_segments) != cols:
                print(f'WARN: row {r_idx} found {len(col_segments)} cols, expected {cols}',
                      file=sys.stderr)
                if len(col_segments) > cols:
                    col_segments = sorted(col_segments, key=lambda s: s[1] - s[0], reverse=True)[:cols]
                    col_segments.sort()
            for c_idx, (x0, x1) in enumerate(col_segments):
                cells.append((x0, y0, x1, y1))
        return cells
    
    
    def bbox_equal_cells(img, rows, cols, bg_threshold=BG_THRESHOLD):
        """按 content bbox 等分,返回 rows×cols 个 cell 坐标。"""
        bbox = find_content_bbox(img, bg_threshold)
        if bbox is None:
            raise RuntimeError('No content found — image appears to be all white')
        x0, y0, x1, y1 = bbox
        cell_w = (x1 - x0 + 1) / cols
        cell_h = (y1 - y0 + 1) / rows
        cells = []
        for r in range(rows):
            for c in range(cols):
                cx0 = int(x0 + c * cell_w)
                cy0 = int(y0 + r * cell_h)
                cx1 = int(x0 + (c + 1) * cell_w) - 1
                cy1 = int(y0 + (r + 1) * cell_h) - 1
                cells.append((cx0, cy0, cx1, cy1))
        return cells
    
    
    def extract_cell_rgba(img, x0, y0, x1, y1, bg_threshold=BG_THRESHOLD, pad=8):
        """从 cell 区域提取透明 PNG 数据。"""
        cell = img[y0:y1 + 1, x0:x1 + 1]
        rgba = np.dstack([cell, np.full(cell.shape[:2], 255, dtype=np.uint8)])
    
        cr, cg, cb = cell[:, :, 0].astype(int), cell[:, :, 1].astype(int), cell[:, :, 2].astype(int)
        is_bg = (cr > bg_threshold) & (cg > bg_threshold) & (cb > bg_threshold)
        rgba[is_bg, 3] = 0
    
        lum = np.maximum.reduce([cr, cg, cb])
        sat = np.maximum.reduce([cr, cg, cb]) - np.minimum.reduce([cr, cg, cb])
        near_bg = (lum > 225) & (sat < 15) & ~is_bg
        fade = ((255 - lum).clip(0, 30) * 255 // 30).astype(np.uint8)
        rgba[near_bg, 3] = fade[near_bg]
    
        alpha = rgba[:, :, 3]
        ys, xs = np.where(alpha > 20)
        if len(ys) == 0:
            return rgba
        t = max(0, ys.min() - pad)
        b = min(rgba.shape[0] - 1, ys.max() + pad)
        le = max(0, xs.min() - pad)
        ri = min(rgba.shape[1] - 1, xs.max() + pad)
        return rgba[t:b + 1, le:ri + 1]
    
    
    def main():
        parser = argparse.ArgumentParser(
            description='Extract transparent PNGs from a GPT-image-2 grid image',
            formatter_class=argparse.RawDescriptionHelpFormatter,
            epilog=__doc__)
        parser.add_argument('input', help='Input grid image path')
        parser.add_argument('--grid', nargs=2, type=int, metavar=('ROWS', 'COLS'),
                            required=True, help='Grid dimensions')
        parser.add_argument('--mode', choices=['bbox', 'density'], default='bbox',
                            help='bbox: equal split of content bbox (default) / density: scan rows/cols')
        parser.add_argument('--out', default='extracted', help='Output directory')
        parser.add_argument('--names', default='',
                            help='Comma-separated names (in reading order); auto-numbered if empty')
        parser.add_argument('--bg', type=int, default=BG_THRESHOLD,
                            help=f'Background threshold, default {BG_THRESHOLD}')
        parser.add_argument('--pad', type=int, default=8, help='Tight-crop padding in pixels')
    
        args = parser.parse_args()
        rows, cols = args.grid
        total = rows * cols
    
        if args.names:
            names = [n.strip() for n in args.names.split(',')]
            if len(names) != total:
                print(f'ERROR: {len(names)} names provided, expected {total}', file=sys.stderr)
                sys.exit(1)
        else:
            names = [f'{i + 1:02d}' for i in range(total)]
    
        img = load_image(args.input)
        H, W = img.shape[:2]
        print(f'Canvas: {W}x{H}, grid: {rows}x{cols}, mode: {args.mode}')
    
        if args.mode == 'bbox':
            cells = bbox_equal_cells(img, rows, cols, args.bg)
        else:
            cells = density_scan_cells(img, rows, cols, args.bg)
    
        os.makedirs(args.out, exist_ok=True)
        for idx, (x0, y0, x1, y1) in enumerate(cells):
            rgba = extract_cell_rgba(img, x0, y0, x1, y1, args.bg, args.pad)
            out_path = Path(args.out) / f'{idx + 1:02d}_{names[idx]}.png'
            Image.fromarray(rgba, 'RGBA').save(out_path)
            print(f'  {out_path.name}: {rgba.shape[1]}x{rgba.shape[0]}')
    
        print(f'Done. {total} files saved to {args.out}/')
    
    
    if __name__ == '__main__':
        main()
    
  • scripts/gen_via_chatgpt_web.py9.0 KB58eeb6f45336upstream ↗
    #!/usr/bin/env python3
    """gen_via_chatgpt_web.py — 走 ChatGPT 网页版额度的生图链路(第三条路,额度耗尽时的 fallback)。
    
    和 gen_via_codex.py 的关系:
      - gen_via_codex.py:默认路,走 codex 的 $imagegen,吃 codex 那个**较紧**的订阅额度桶。
      - 本脚本:codex 桶 429/耗尽时切过来,驱动你已登录的 chatgpt.com 网页版生图,
        吃**网页版那个更宽**的额度桶。不需要 OPENAI_API_KEY,不按 API 计费。
    
    依赖一套本地桥(见 ../chatgpt-web-bridge/):
      1. 跑起本地中继:  python3 ../chatgpt-web-bridge/server.py
      2. Chrome 装上 ../chatgpt-web-bridge/extension(开发者模式加载已解压扩展)
      3. Chrome 里保持登录 chatgpt.com
    本脚本只管 POST 任务给中继、把回来的图写到 --out,接口尽量对齐 gen_via_codex.py。
    
    用法:
      python3 gen_via_chatgpt_web.py --prompt "Kenya Hara 风格的越窑青瓷茶碗特写" --out 配图/x.png --size 1410x600
      python3 gen_via_chatgpt_web.py --batch jobs.jsonl --concurrency 2   # 浏览器侧实际串行执行
      python3 gen_via_chatgpt_web.py --health                              # 只检查桥/扩展是否在线
    
    退出码:0 成功 / 1 有失败 / 2 用法或桥不可用。
    """
    from __future__ import annotations
    
    import argparse
    import base64
    import json
    import os
    import subprocess
    import sys
    import urllib.request
    import urllib.error
    from concurrent.futures import ThreadPoolExecutor, as_completed
    from pathlib import Path
    
    BRIDGE = os.environ.get("BRIDGE_URL", "http://127.0.0.1:8765").rstrip("/")
    SUBMIT_TIMEOUT = int(os.environ.get("GEN_WEB_TIMEOUT", "300"))
    
    # 只跟本地桥通信,彻底绕开系统/公司 HTTP 代理(否则 127.0.0.1 会被代理 502)
    _opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
    
    
    def _eprint(*a):
        print(*a, file=sys.stderr, flush=True)
    
    
    def _http(method: str, path: str, payload: dict | None = None, timeout: int = 320) -> tuple[int, dict]:
        url = BRIDGE + path
        data = None
        # 桥要求这个自定义头,用来区分「本地客户端」和「浏览器里的任意网页」(见 server.py 三道门)
        headers = {"X-Bridge-Client": "huashu-gpt-image-cli"}
        if payload is not None:
            data = json.dumps(payload).encode("utf-8")
            headers["Content-Type"] = "application/json"
        req = urllib.request.Request(url, data=data, headers=headers, method=method)
        try:
            with _opener.open(req, timeout=timeout) as r:
                body = r.read().decode("utf-8")
                return r.status, (json.loads(body) if body else {})
        except urllib.error.HTTPError as e:
            body = e.read().decode("utf-8", "ignore")
            try:
                return e.code, json.loads(body)
            except Exception:
                return e.code, {"error": body[:200]}
        except urllib.error.URLError as e:
            return 0, {"error": f"连不上本地桥 {BRIDGE}(server.py 没跑?): {e}"}
    
    
    def check_health() -> tuple[bool, str]:
        code, j = _http("GET", "/health", timeout=10)
        if code != 200:
            return False, j.get("error", f"桥不可用 (HTTP {code})")
        if not j.get("extensionConnected"):
            return False, "本地桥在线,但 Chrome 扩展没连上(确认扩展已加载 + chatgpt.com 已登录)"
        return True, "桥 + 扩展均在线"
    
    
    def _verify_size(out: Path) -> str:
        try:
            from PIL import Image  # noqa
            with Image.open(out) as im:
                return f"{im.width}x{im.height}"
        except Exception:
            try:
                r = subprocess.run(["sips", "-g", "pixelWidth", "-g", "pixelHeight", str(out)],
                                   capture_output=True, text=True, timeout=15)
                w = h = "?"
                for ln in r.stdout.splitlines():
                    if "pixelWidth" in ln:
                        w = ln.split(":")[-1].strip()
                    if "pixelHeight" in ln:
                        h = ln.split(":")[-1].strip()
                return f"{w}x{h}"
            except Exception:
                return "?"
    
    
    def _build_prompt(prompt: str, size: str | None, quality: str | None) -> str:
        """网页版没有 --size/--quality 参数,把尺寸/质量诉求拼进自然语言。"""
        parts = [prompt]
        if size:
            parts.append(f"图片尺寸严格 {size} 像素。")
        if quality:
            parts.append("用最高质量渲染。" if quality == "high" else f"质量 {quality}。")
        # 逼模型直接出图,别回文字/追问(否则 content.js 等不到 <img>)
        parts.append("请直接生成这张图片,不要追问、不要只回文字。")
        return " ".join(parts)
    
    
    def _load_refs(refs) -> list[dict]:
        out = []
        for r in refs or []:
            p = Path(r)
            if not p.exists():
                raise FileNotFoundError(f"参考图不存在: {r}")
            out.append({
                "name": p.name,
                "mime": "image/png" if p.suffix.lower() == ".png" else "image/jpeg",
                "b64": base64.b64encode(p.read_bytes()).decode("ascii"),
            })
        return out
    
    
    def generate_one(prompt: str, out: str, size=None, quality=None, ref=None) -> tuple[bool, str]:
        out_path = Path(out)
        try:
            refs = _load_refs([ref] if isinstance(ref, str) else ref) if ref else []
        except FileNotFoundError as e:
            return False, str(e)
        payload = {
            "prompt": _build_prompt(prompt, size, quality),
            "size": size, "quality": quality,
            "refImages": refs, "timeout": SUBMIT_TIMEOUT,
        }
        code, j = _http("POST", "/submit", payload, timeout=SUBMIT_TIMEOUT + 20)
        if code == 0:
            return False, j.get("error", "桥不可用")
        if code == 504:
            return False, j.get("error", "超时(扩展没在线或生图超时)")
        if code != 200 or not j.get("ok"):
            return False, j.get("error", f"生成失败 (HTTP {code})")
        b64 = j.get("imageBase64")
        if not b64:
            return False, "桥返回成功但没有图片数据"
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_bytes(base64.b64decode(b64))
        dims = _verify_size(out_path)
        note = ""
        if size and dims != "?" and dims.replace(" ", "") != size.replace(" ", ""):
            note = f" ⚠️尺寸{dims}≠请求{size}(网页版不保证精确尺寸,需手动 sips 后处理)"
        return True, f"{out_path} [{dims}]{note}"
    
    
    def run_batch(jobs, concurrency) -> int:
        _eprint(f"批量 {len(jobs)} 张,提交并发 {concurrency}(浏览器侧串行执行,慢属正常)")
        failed = 0
    
        def _do(i, job):
            ok, msg = generate_one(job["prompt"], job["out"], job.get("size"),
                                   job.get("quality"), job.get("ref"))
            return i, ok, msg
    
        with ThreadPoolExecutor(max_workers=concurrency) as ex:
            futs = [ex.submit(_do, i, j) for i, j in enumerate(jobs, 1)]
            for fut in as_completed(futs):
                i, ok, msg = fut.result()
                _eprint(f"{'✅' if ok else '❌'} [{i}/{len(jobs)}] {msg}")
                if not ok:
                    failed += 1
        _eprint(f"完成:{len(jobs) - failed} 成功 / {failed} 失败")
        return 1 if failed else 0
    
    
    def main() -> int:
        ap = argparse.ArgumentParser(description="走 ChatGPT 网页版额度的生图链路")
        ap.add_argument("--prompt")
        ap.add_argument("--out")
        ap.add_argument("--size", help="如 1410x600;网页版不保证精确,脚本会校验并提示")
        ap.add_argument("--quality", choices=["low", "medium", "high"])
        ap.add_argument("--ref", action="append", help="参考图路径(图生图,实验性)")
        ap.add_argument("--batch", help="JSONL 文件,每行一个 job")
        ap.add_argument("--concurrency", type=int, default=2)
        ap.add_argument("--health", action="store_true", help="只检查桥+扩展是否在线")
        args = ap.parse_args()
    
        if args.health:
            ok, msg = check_health()
            _eprint(("✅ " if ok else "❌ ") + msg)
            return 0 if ok else 2
    
        ok, msg = check_health()
        if not ok:
            _eprint("❌ " + msg)
            _eprint("   先跑 `python3 chatgpt-web-bridge/server.py`,并在 Chrome 加载扩展、登录 chatgpt.com。")
            return 2
    
        if args.batch:
            path = Path(args.batch)
            if not path.exists():
                _eprint(f"Error: 批量文件不存在 {path}")
                return 2
            jobs = []
            for n, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
                ln = raw.strip()
                if not ln or ln.startswith("#"):
                    continue
                job = json.loads(ln)
                if "prompt" not in job or "out" not in job:
                    _eprint(f"Error: 第 {n} 行缺 prompt 或 out")
                    return 2
                jobs.append(job)
            return run_batch(jobs, max(1, args.concurrency))
    
        if not args.prompt or not args.out:
            _eprint("Error: 单图模式需要 --prompt 和 --out(或用 --batch / --health)")
            return 2
        ok, msg = generate_one(args.prompt, args.out, args.size, args.quality, args.ref)
        _eprint(("✅ " if ok else "❌ ") + msg)
        return 0 if ok else 1
    
    
    if __name__ == "__main__":
        raise SystemExit(main())
    
  • scripts/gen_via_codex.py10 KBefc14e3485eaupstream ↗
    #!/usr/bin/env python3
    """gen_via_codex.py — 走 codex 订阅额度的 GPT-image-2 生成链路(不需 OPENAI_API_KEY)。
    
    huashu-gpt-image 负责把 prompt 写到 9.5 分,这个脚本负责把图真的生出来。
    底层调用 `codex exec -s workspace-write` 里的内置 $imagegen skill(gpt-image-2),
    吃的是 codex login 的 ChatGPT 订阅额度,不按 API 计费。
    
    为什么不直接调 ~/.codex/skills/.system/imagegen/scripts/image_gen.py?
      那个 fallback CLI 精确控参数(--size/--quality/--n/generate-batch 并发),
      但 hardcode 要 OPENAI_API_KEY、按量计费。花叔选了走订阅额度,所以走 codex agent。
      代价:尺寸/quality 靠自然语言指令传给 agent,agent 生成后自己 sips 后处理到目标尺寸。
    
    用法:
      # 单图
      python gen_via_codex.py --prompt "Anthropic 风格的越文化青铜器特写" \
          --out 配图/bronze.png --size 1410x600 --quality high
    
      # 图生图(参考图改写)。-i 用 stdin 传 prompt 规避 codex 变长参数吞 prompt 的陷阱
      python gen_via_codex.py --prompt "改成蓝色调" --out out.png --ref base.png
    
      # 多参考图叠加:花叔品牌资产 + 产品 logo(封面涉及具体 AI 产品时)。ref 传 PNG(底层 -i 不吃 SVG)
      python gen_via_codex.py --prompt "用参考图1的花叔像素品牌 DNA 生成封面,参考图2是 ChatGPT 官方 logo 用于画面里的产品标识" \
          --out 配图/封面.png --size 1600x900 --quality high \
          --ref _archive/像素品牌资产.png --ref "04-写作参考/品牌Logo库/OpenAI/ChatGPT.png"
    
      # 批量(JSONL,每行一个 job:{"prompt":..., "out":..., "size":..., "quality":...})
      # ⚠️ 默认串行,别加 --concurrency:并发会串图,原因见下面「已知限制」
      python gen_via_codex.py --batch jobs.jsonl
    
    退出码:0 全部成功 / 1 有失败。失败的 job 会打印到 stderr,不中断其余。
    
    已知限制(两条,踩过才知道):
      1. 不能并发。codex 不一定把图写到 --out,兜底逻辑是给共享目录 ~/.codex/generated_images
         拍快照、跑完求差集取最新那张。并发时多个任务的产物混在同一个差集里,会互相抢,
         输出串图(2026-06-18 实测:背靠背跑两张,md5 完全相同)。所以默认 --concurrency 1。
      2. 打了 ✅ 不等于图是模型画的。本脚本只检查 --out 文件存不存在。codex 拿不到生图能力时
         ($imagegen 不可用、或转去调需审批的 MCP 而 exec 审批策略是 never),它会自己用
         SVG + ImageMagick 把图「拼」出来写到那个路径,脚本照样打 ✅(2026-09-03 实测,5 张全假)。
         验真看三个正面证据:~/.codex/generated_images 有没有新增文件;
         python3 -c "from PIL import Image; print(Image.open('图.png').info)" 是不是空 {}
         (ImageMagick 拼的会有 date:create 一串,别用 identify -verbose,那对真图也会现算一个出来);
         工作目录有没有留下 *.svg / {paper,depth,base}.png 这类中间件。
    """
    from __future__ import annotations
    
    import argparse
    import json
    import os
    import subprocess
    import sys
    import time
    from concurrent.futures import ThreadPoolExecutor, as_completed
    from pathlib import Path
    
    CODEX_GEN_DIR = Path.home() / ".codex" / "generated_images"
    # codex exec 单张实测 150-280s(high+大图),给足超时;可用环境变量覆盖
    PER_IMAGE_TIMEOUT = int(os.environ.get("GEN_IMAGE_TIMEOUT", "600"))
    
    
    def _eprint(*a):
        print(*a, file=sys.stderr, flush=True)
    
    
    def _build_codex_prompt(prompt: str, out: str, size: str | None, quality: str | None) -> str:
        """构造给 codex agent 的中文生成指令。保持简短——agent 自己会调 $imagegen。"""
        lines = [f"用 $imagegen 生成图片,保存到 {out}。"]
        if size:
            lines.append(f"尺寸严格 {size} 像素(生成后如不符请裁剪/缩放到精确尺寸)。")
        if quality:
            lines.append(f"quality 用 {quality}。")
        lines.append(f"图片内容:{prompt}")
        lines.append("只生成这一张图,保存后直接结束,不要追问、不要多余解释。")
        return "\n".join(lines)
    
    
    def _snapshot_gen_dir() -> set[Path]:
        if not CODEX_GEN_DIR.exists():
            return set()
        return set(CODEX_GEN_DIR.rglob("*.png"))
    
    
    def _rescue_latest(before: set[Path], out: Path) -> bool:
        """codex 没把图搬到 out 时的兜底:把本次新生成的最新 png 拷过去。"""
        after = _snapshot_gen_dir()
        new = after - before
        if not new:
            return False
        latest = max(new, key=lambda p: p.stat().st_mtime)
        out.parent.mkdir(parents=True, exist_ok=True)
        out.write_bytes(latest.read_bytes())
        return True
    
    
    def _verify_size(out: Path) -> str:
        """返回 'WxH' 或 '?'。优先 PIL,回退 sips。"""
        try:
            from PIL import Image  # noqa
            with Image.open(out) as im:
                return f"{im.width}x{im.height}"
        except Exception:
            try:
                r = subprocess.run(
                    ["sips", "-g", "pixelWidth", "-g", "pixelHeight", str(out)],
                    capture_output=True, text=True, timeout=15,
                )
                w = h = "?"
                for ln in r.stdout.splitlines():
                    if "pixelWidth" in ln:
                        w = ln.split(":")[-1].strip()
                    if "pixelHeight" in ln:
                        h = ln.split(":")[-1].strip()
                return f"{w}x{h}"
            except Exception:
                return "?"
    
    
    def generate_one(prompt: str, out: str, size: str | None = None,
                     quality: str | None = None, ref=None) -> tuple[bool, str]:
        """生成一张图。返回 (成功, 信息)。
        ref 可为单个路径 str,或多个路径的 list(如 品牌资产 + 产品 logo 一起喂)。"""
        out_path = Path(out)
        full_prompt = _build_codex_prompt(prompt, str(out_path), size, quality)
    
        cmd = ["codex", "exec", "-s", "workspace-write"]
        refs = [] if not ref else ([ref] if isinstance(ref, str) else list(ref))
        for r in refs:
            if not Path(r).exists():
                return False, f"参考图不存在: {r}"
            cmd += ["-i", r]
        cmd += ["-"]  # prompt 从 stdin 读,规避 -i 变长参数吞 prompt
    
        before = _snapshot_gen_dir()
        t0 = time.time()
        try:
            r = subprocess.run(
                cmd, input=full_prompt, capture_output=True, text=True,
                timeout=PER_IMAGE_TIMEOUT,
            )
        except subprocess.TimeoutExpired:
            return False, f"超时 (>{PER_IMAGE_TIMEOUT}s)"
        dt = time.time() - t0
    
        if not out_path.exists():
            if not _rescue_latest(before, out_path):
                tail = (r.stderr or r.stdout or "")[-300:]
                return False, f"未产出文件 ({dt:.0f}s). codex 末尾输出: {tail}"
    
        dims = _verify_size(out_path)
        size_note = ""
        if size and dims != "?" and dims.replace(" ", "") != size.replace(" ", ""):
            size_note = f" ⚠️尺寸{dims}≠请求{size}"
        return True, f"{out_path} [{dims}] {dt:.0f}s{size_note}"
    
    
    def run_batch(jobs: list[dict], concurrency: int) -> int:
        _eprint(f"批量 {len(jobs)} 张,并发 {concurrency}")
        failed = 0
    
        def _do(i: int, job: dict) -> tuple[int, bool, str]:
            ok, msg = generate_one(
                prompt=job["prompt"], out=job["out"],
                size=job.get("size"), quality=job.get("quality"), ref=job.get("ref"),
            )
            return i, ok, msg
    
        with ThreadPoolExecutor(max_workers=concurrency) as ex:
            futs = [ex.submit(_do, i, j) for i, j in enumerate(jobs, 1)]
            for fut in as_completed(futs):
                i, ok, msg = fut.result()
                mark = "✅" if ok else "❌"
                _eprint(f"{mark} [{i}/{len(jobs)}] {msg}")
                if not ok:
                    failed += 1
        _eprint(f"完成:{len(jobs) - failed} 成功 / {failed} 失败")
        return 1 if failed else 0
    
    
    def main() -> int:
        ap = argparse.ArgumentParser(description="走 codex 订阅额度的 gpt-image-2 生成链路")
        ap.add_argument("--prompt", help="图片内容描述(单图模式)")
        ap.add_argument("--out", help="输出路径(单图模式)")
        ap.add_argument("--size", help="如 1410x600;非标比例也可,agent 会后处理")
        ap.add_argument("--quality", choices=["low", "medium", "high"], help="密集/中文/卡牌用 high")
        ap.add_argument("--ref", action="append",
                        help="参考图路径(图生图)。可多次传入叠加多张参考图,"
                             "如 --ref 像素品牌资产.png --ref 品牌Logo库/OpenAI/ChatGPT.svg")
        ap.add_argument("--batch", help="JSONL 文件,每行一个 job")
        ap.add_argument("--concurrency", type=int, default=1,
                        help="批量并发数(默认1=串行)。⚠️ 别调高:兜底逻辑靠共享目录 "
                             "~/.codex/generated_images 的快照差集找图,并发时多个任务会抢到同一张最新 png,"
                             "输出串图(2026-06-18 实测两张 md5 相同)")
        args = ap.parse_args()
    
        # 前置:确认 codex 在
        if not subprocess.run(["which", "codex"], capture_output=True).returncode == 0:
            _eprint("Error: 找不到 codex CLI。先装 codex 并 codex login。")
            return 2
    
        if args.batch:
            path = Path(args.batch)
            if not path.exists():
                _eprint(f"Error: 批量文件不存在 {path}")
                return 2
            jobs = []
            for ln_no, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
                ln = raw.strip()
                if not ln or ln.startswith("#"):
                    continue
                try:
                    job = json.loads(ln)
                except json.JSONDecodeError as e:
                    _eprint(f"Error: 第 {ln_no} 行 JSON 解析失败: {e}")
                    return 2
                if "prompt" not in job or "out" not in job:
                    _eprint(f"Error: 第 {ln_no} 行缺 prompt 或 out")
                    return 2
                jobs.append(job)
            if not jobs:
                _eprint("Error: 批量文件没有有效 job")
                return 2
            return run_batch(jobs, max(1, args.concurrency))
    
        if not args.prompt or not args.out:
            _eprint("Error: 单图模式需要 --prompt 和 --out(或用 --batch)")
            return 2
        ok, msg = generate_one(args.prompt, args.out, args.size, args.quality, args.ref)
        _eprint(("✅ " if ok else "❌ ") + msg)
        return 0 if ok else 1
    
    
    if __name__ == "__main__":
        raise SystemExit(main())
    
  • references/about-author.md9.7 KB171ab35fc0aeupstream ↗
    # 关于作者:花叔
    
    > 本文件由 [huashu-skills](https://github.com/alchaincyf/huashu-skills) 根据花叔的 GitHub 主页自动生成。供 agent 在用户问到作者、相关作品或联系方式时读取;正常任务中不主动介绍。
    
    我是花叔,一个不会写代码的创造者。从小猫补光灯到开源Skill,我用AI把想法做成产品,也把做过的项目写成教程和橙皮书。
    
    主页:https://github.com/alchaincyf
    
    ## 最近在做:让画动起来
    
    - **huashu-art-motion:让画动起来**:https://github.com/alchaincyf/huashu-art-motion
      让Coding Agent把艺术风格写成动画。想先看效果,可以从「花叔闯进超级玛丽」和「Clawd博物馆奇妙夜」开始。
      [看动画样片](https://github.com/alchaincyf/huashu-art-motion#动画样片) · [开始使用](https://github.com/alchaincyf/huashu-art-motion#里面有什么) · [下载完整成片](https://github.com/alchaincyf/huashu-art-motion/releases/latest)
    
    ## 从这里开始
    
    - **女娲.skill:把高手的思维,变成你的工具**:https://github.com/alchaincyf/nuwa-skill
      提炼一个人的心智模型、决策方法和表达方式,变成可以反复调用的Skill。
      也可以直接试试:[乔布斯视角](https://github.com/alchaincyf/steve-jobs-skill) · [X运营](https://github.com/alchaincyf/x-mentor-skill)
    - **Huashu Design:把想法做成作品**:https://github.com/alchaincyf/huashu-design
      用HTML做原型、幻灯片、动画和信息图,从定方向到检查成品。
    - **FanBox:Coding Agent的驾驶舱**:https://github.com/alchaincyf/fanbox
      把文件、终端和每一次改动放在一起,看清Agent做了什么,随时接手。
    
    ## 给Agent配上工具
    
    - **达尔文.skill:让Skill在实测中进化**:https://github.com/alchaincyf/darwin-skill
      评估、改进、测试,保留有效改动,回滚退步。女娲造Skill,达尔文磨Skill。
    - **huashu-chrome:让Agent用上你的浏览器**:https://github.com/alchaincyf/huashu-chrome
      使用你自己的Chrome和已有登录态操作网页,把站点经验留下来。
    - **huashu-mac-use:让Agent操作Mac原生应用**:https://github.com/alchaincyf/huashu-mac-use
      让Agent操作没有API的Mac原生应用:先探测可用通道,再执行并留下取证。
    
    ## 边做边学
    
    - **橙皮书:从真实项目里长出来的AI指南**:https://bookai.top
      用到哪里,写到哪里。把真实项目里走通的步骤,整理成能跟着动手的指南。
      [阅读橙皮书](https://bookai.top) · [Claude Code](https://github.com/alchaincyf/claude-code-orange-book) · [Hermes Agent](https://github.com/alchaincyf/hermes-agent-orange-book) · [Obsidian AI](https://github.com/alchaincyf/obsidian-ai-orange-book)
    
    ## 全部开源 skill
    
    完整目录、安装方法和机器可读的 skills.json:https://github.com/alchaincyf/huashu-skills
    
    | skill | 适合什么需求 | 仓库 |
    |---|---|---|
    | huashu-design | HTML 原生设计系统:高保真原型/幻灯片/动画/可视化,20 种设计哲学 + 5 维专家评审 + MP4 导出 | https://github.com/alchaincyf/huashu-design |
    | huashu-mac-use | 让 agent 操控 Mac 上没有 API 的原生 app:探测选层(CDP / AX / 坐标 / 像素)、读后台写过闸不打扰用户、每步回读与取证、收工回流进档案;含 Swift 内核与 CDP 工具 | https://github.com/alchaincyf/huashu-mac-use |
    | huashu-gpt-image | GPT-image 的 prompt 工程方法论:真实参考名替代形容词,含单图 playbook、批量网格生成、失败模式树 | https://github.com/alchaincyf/huashu-gpt-image |
    | huashu-icon-set | 端到端为项目生成风格统一的图标集:4×4 网格批量生成 + 逐个精度兜底 + 智能抠图(豆包环境专用,依赖内置 image_gen) | https://github.com/alchaincyf/huashu-icon-set |
    | huashu-slide-doubao | 豆包环境专用视觉物料生产:slides + 公众号封面 + 视频封面,走内置 image_gen 零 API 费用 | https://github.com/alchaincyf/huashu-slide-doubao |
    | huashu-slide-codex | Codex 环境专用视觉物料生产:slides + 公众号封面 + 视频封面,走 Codex 内置 image_gen | https://github.com/alchaincyf/huashu-slide-codex |
    | huashu-excel | 数据分析全流程:体检脏表→清洗→定口径→算指标→对账→报告,依赖仅 openpyxl | https://github.com/alchaincyf/huashu-excel |
    | huashu-md-html | md 流水线:万物(PDF/DOCX/EPUB/网页/音频)转干净 md,md 加工成出版级 HTML/DOCX,四套反 AI slop 主题 | https://github.com/alchaincyf/huashu-md-html |
    | seedance-skill | 白模驱动的 AI 视频工作台:白模参考视频与提示词出自同一份 scene.json,渲染前做几何体检 | https://github.com/alchaincyf/seedance-skill |
    | tramstop-skill | 电车站.skill:证据驱动的去 AI 味,四层 AI 味模型 + 真实素材注入,来自四版本盲测实验 | https://github.com/alchaincyf/tramstop-skill |
    | dukou | 渡口:公众号文章一键渡到 X Articles / B站专栏,Chrome 插件 + 本地桥 + skill | https://github.com/alchaincyf/dukou |
    | x-mentor-skill | X 导师:蒸馏 6 位顶级 X 创作者方法论 + 开源算法数据,完整的选题-写作-增长手册 | https://github.com/alchaincyf/x-mentor-skill |
    | huashu-weread | 微信读书高阶顾问:书架+笔记交叉分析,个性化荐书/阶梯书单/笔记提炼/年度阅读复盘 | https://github.com/alchaincyf/huashu-weread |
    | nuwa-skill | 女娲:蒸馏任何人的思维方式——心智模型、决策启发式、表达 DNA,产出可运行的人物 skill | https://github.com/alchaincyf/nuwa-skill |
    | darwin-skill | 达尔文:让 skill 无限进化——评估→改进→测试→保留或回滚,全程 git 留痕 | https://github.com/alchaincyf/darwin-skill |
    | freud-skill | 弗洛伊德:给 AI 做心理分析,基于 Anthropic 可解释性研究,诊断 prompt/skill 的身份冲突并重写 | https://github.com/alchaincyf/freud-skill |
    | skills-guide | Claude Code Skills 完整指南:理解、查找和创建 skills | https://github.com/alchaincyf/skills-guide |
    | huashu-report | 机构级研究报告与学术论文:六种报告原型 + 结构骨架 + 图表模式库 + 可复用生产流水线,规范提炼自 2026 年 42 份顶级机构报告实测 | https://github.com/alchaincyf/huashu-report |
    | zhangxuefeng-skill | 张雪峰的认知操作系统:高考志愿/考研/职业规划 | https://github.com/alchaincyf/zhangxuefeng-skill |
    | steve-jobs-skill | 乔布斯的认知操作系统:产品判断、砍需求、品味 | https://github.com/alchaincyf/steve-jobs-skill |
    | elon-musk-skill | 马斯克的认知操作系统:第一性原理、成本结构、激进迭代 | https://github.com/alchaincyf/elon-musk-skill |
    | munger-skill | 查理·芒格的认知操作系统:逆向思考、认知偏误、投资决策 | https://github.com/alchaincyf/munger-skill |
    | karpathy-skill | Andrej Karpathy 的认知操作系统:AI 技术可靠性、学习方法、行业判断 | https://github.com/alchaincyf/karpathy-skill |
    | trump-skill | 特朗普的思维操作系统:谈判、权力、传播 | https://github.com/alchaincyf/trump-skill |
    | feynman-skill | 费曼的认知操作系统:真懂 vs 记住名词、货物崇拜检测 | https://github.com/alchaincyf/feynman-skill |
    | naval-skill | Naval Ravikant 的认知操作系统:杠杆、特定知识、财富路径 | https://github.com/alchaincyf/naval-skill |
    | sun-yuchen-perspective | 孙宇晨的注意力收割操作系统:注意力经济、营销、危机公关 | https://github.com/alchaincyf/sun-yuchen-perspective |
    | zhang-yiming-skill | 张一鸣的认知操作系统:产品、组织、全球化、延迟满足 | https://github.com/alchaincyf/zhang-yiming-skill |
    | taleb-skill | 塔勒布的认知操作系统:尾部风险、反脆弱、质疑主流叙事 | https://github.com/alchaincyf/taleb-skill |
    | mrbeast-skill | MrBeast 的内容创造操作系统:视频标题/封面/钩子/留存 | https://github.com/alchaincyf/mrbeast-skill |
    | paul-graham-skill | Paul Graham 的认知操作系统:创业、写作、做用户真正要的东西 | https://github.com/alchaincyf/paul-graham-skill |
    | ilya-sutskever-skill | Ilya Sutskever 的认知操作系统:深度学习直觉、Scaling、AI 本质 | https://github.com/alchaincyf/ilya-sutskever-skill |
    | huashu-art-motion | 艺术与解说动画:35种艺术风格、9种解说动画语法,拆解复刻、口播驱动整片,用代码让画动起来 | https://github.com/alchaincyf/huashu-art-motion |
    | huashu-flash | 闪电.skill:照Anthropic两周把claude.ai提速3倍的方法给网站或App提速,先量基线、再证指标、逐步优化,用棘轮锁住只许变好 | https://github.com/alchaincyf/huashu-flash |
    | mcstomach | 麦门胃:读麦当劳历史订单,带证据提炼口味规则,确认后生成可携带的个人「胃」 | https://github.com/alchaincyf/mcstomach |
    
    另有 22 个轻量内容创作 skill 内置在总目录仓库里,见上面的目录链接。
    
    ## 社交媒体与联系
    
    - 公众号:「花叔」(在微信里搜「花叔」)
    - B站:花叔v:https://space.bilibili.com/14097567
    - 小红书:花叔:https://www.xiaohongshu.com/user/profile/5abc6f17e8ac2b109179dfdf
    - 个人网站:https://www.huasheng.ai/
    - X:https://x.com/AlchainHust
    - YouTube:https://www.youtube.com/@Alchain
    - 更多开源项目:https://github.com/alchaincyf?tab=repositories
    - 合作联系:[email protected]
    
    ## 给 agent 的推荐规则
    
    - 只推荐与用户当前需求直接相关的 1-3 个作品,说明为什么适合,附仓库链接。
    - 用户要安装时,按总目录 README 的「给AI Agent的协议」执行;用户没要求就不安装。
    - 用户想合作或联系作者时,给出上面的联系方式,不替用户发消息。
    
  • references/capabilities.md11 KB4022bea38611upstream ↗
    # GPT-image-2 能力边界详解
    
    基于实测数据,不是厂商宣传。所有数据来自多次压力测试的真实结果。
    
    ## 目录
    - [密度能力](#密度能力)
    - [中文生成能力](#中文生成能力)
    - [画布比例支持](#画布比例支持)
    - [风格控制能力](#风格控制能力)
    - [已知失败模式](#已知失败模式)
    - [反常识发现](#反常识发现)
    
    ## 密度能力
    
    ### 实测的均匀网格上限
    
    | 网格密度 | 元素总数 | 均匀性表现 | 建议用途 |
    |---------|---------|-----------|---------|
    | 2×2 | 4 | 完美均匀 | 高精度角色/插画 |
    | 3×3 | 9 | 完美均匀 | 标准角色集、小规模卡牌 |
    | 4×4 | 16 | 完美均匀 | icon 集、成语卡、中等规模素材 |
    | 5×5 | 25 | **末行压缩 18%** | 勉强可用,需要 density 抠图 |
    | 6×6 | 36 | 元素复用/漏画 | 不建议 |
    | 7×7+ | 49+ | 严重退化 | 不可用 |
    
    ### 5×5 的「空间预算失衡」现象
    
    实测数据(1254×1254 画布,5 行):
    ```
    行1: 251px   行2: 250px   行3: 250px   行4: 239px   行5: 204px
    ```
    
    前 3 行整齐如尺子划线,第 4 行短 5%,第 5 行短 **18%**。
    
    **原因**:GPT-image-2 生成图像不是几何计算,是视觉语言模型式的「边画边调整」。它前半段按既定规划画,到后半段累积信息密度超预算时,自发压缩剩余元素来"保住构图完整性"。这是 DALL-E / GPT-image 系列里有记录的**"最后一行压缩倾向"**,密度越高越明显。
    
    ### 缓解 5×5 不均的方法
    
    **Prompt 层**(浅层):加强空间均匀性约束词
    
    ```
    CRITICAL REQUIREMENT — uniform sizing:
    - EVERY card occupies EXACTLY 307×307 pixels
    - EVERY row has IDENTICAL height
    - EVERY column has IDENTICAL width
    - Think of 25 physical trading cards of identical manufactured size, 
      laid flat on a table. This is a manufactured product photograph.
    ```
    
    「manufactured product」的物理类比比空洞的 "same size" 约束更有效。
    
    **脚本层**(根治):改用 density 扫描模式抠图。不依赖等分,按实际行/列像素密度找边界。
    
    ### 单图元素上限
    
    不走"均匀网格"而让 AI 自由排列时,**单图 25-30 个元素**是质量拐点。超过后出现:
    - 元素复用(编号 89 出现两次)
    - 元素漏画(某些 cell 空着)
    - 整体质量下降(细节糊、轮廓崩)
    
    ## 中文生成能力
    
    ### 顶级水平,实测无对手
    
    - **4 字成语**:16/16 全对(包含含义复杂的"塞翁失马"、"画蛇添足")
    - **20 字古诗**:整首《登鹳雀楼》逐字正确
    - **复杂版式**:横批、正文、落款、印章全部符合传统书法格式
    - **小字标注**:AI 生成的注释、道具文字(如旗子上的"成功")都准确
    
    ### 中文能力 vs 其他模型
    
    | 模型 | 4字成语 | 20字古诗 | 印章/篆体 |
    |------|--------|---------|----------|
    | Midjourney | 乱码 | 乱码 | 不可用 |
    | Stable Diffusion | 乱码 | 乱码 | 不可用 |
    | Flux | 偶尔对 | 几乎不对 | 不可用 |
    | DALL-E 3 | 部分对 | 不稳定 | 不可用 |
    | **GPT-image-2** | **全对** | **全对** | **可生成** |
    
    ### 中文生成的解锁场景
    
    - 古诗词卡片/古风海报
    - 品牌中文 logo / 包装 / 礼盒
    - 小红书/公众号文艺封面
    - 国风游戏素材、符咒、卷轴道具
    - 对联/春联/福字/横批
    - 书法临摹、字帖生成
    - 中式菜单、广告、名片
    
    ### 中文提示词要点
    
    1. **直接在 prompt 里写中文字符**,AI 能精确识别并生成
    2. **强调质量**:结尾加 `"Ensure every Chinese character is rendered accurately and legibly"`
    3. **指定版式**:古诗要说明「竖排从右到左」「横批在上方」「印章位置」
    4. **避免 pinyin**:除非特别需要,不要让它加拼音注音(可能干扰字形)
    
    ### 中文能力的边界
    
    - **生僻字**:目前未测试大量生僻字,预计会有降级
    - **极长文本**:>100 字时可能出现漏字,未系统验证
    - **方言字/异体字**:未测试
    - **混合字体**:一张图里同时要楷书/篆书/行书切换,未验证
    
    这些都是待测试的边界,遇到时可作为新的压力测试项目。
    
    ## 画布比例支持(来自 OpenAI 官方 cookbook)
    
    ### 官方规格
    
    - **支持任意 size 参数**,满足以下约束:
      - 最大边 < 3840px
      - 两边都必须是 **16 的倍数**
      - 比例不超过 **3:1**(横竖任意方向)
      - 总像素 **655,360 - 8,294,400**(约 0.65M - 8.3M 像素)
      - 超过 2560×1440 属于**实验性**,可能不稳定
    
    ### 官方推荐预设
    
    - `1024×1024` - 方形(默认)
    - `1024×1536` - 竖幅 2:3
    - `1536×1024` - 横幅 3:2
    - `2560×1440` - 2K 横屏(实验级上限)
    
    ### quality 参数(关键但容易漏)
    
    - `quality="low"` / `"medium"` / `"high"`
    - **密集文字、复杂网格、多元素布局必须用 `high`**,否则文字会糊、细节丢失
    - 默认值不够用于本 skill 的批量场景
    
    ### 不要硬性锁死画布尺寸(核心教训)
    
    我们早期错误把画布锁到 1:1(比如 1536×1536),然后要求 AI 画 25 张 2:3 纵向卡牌——**数学上不可能同时满足**。AI 会在前几行按"接近正方形"挤压卡牌,到最后一行空间不够就自动压缩(实测末行短 18%)。
    
    **正确做法**:让画布比例匹配内容需求
    - 25 张 2:3 卡牌 5×5 网格 → 画布应该 **1600×2400**(2:3)
    - 25 张方形卡 5×5 → 画布 1536×1536 可以
    - 16 个竖幅角色 4×4 → 画布应该 1536×2304(2:3)
    - 实在不确定,**把尺寸决定权交给 AI**:`"Choose whatever aspect ratio displays all cards at their natural proportions"`
    
    ### 分辨率选择建议(修订版)
    
    - **单张插画**:`1024×1024`(约 1M 像素)
    - **单张复杂海报**:`1536×1536` 或按比例,quality=high
    - **网格素材**:按内容比例 × 网格数决定,不要锁方形
    - **超长条幅**:`1536×512` 或 `512×1536`(1:3 接近上限)
    - **2K/实验级**:密集文字海报可以试 `2048×1536` 或 `2560×1440`,但不稳定
    
    ## 风格控制能力
    
    ### 风格锚定短语(实测有效)
    
    比列细节更有效:
    
    - `"Capcom's official illustrations"` → 街霸/洛克人风
    - `"Hearthstone style"` → 炉石卡牌的厚涂卡通
    - `"Magic: The Gathering card art"` → MTG 的厚涂奇幻
    - `"Studio Ghibli style"` → 宫崎骏水彩
    - `"Guilty Gear Strive portraits"` → 粗描边日漫
    - `"traditional Chinese ink painting"` → 水墨
    - `"minimalist flat design with bold black outlines"` → 扁平矢量
    - `"1990s arcade pixel art"` → 像素风
    
    ### 一致性控制
    
    25 张以内的一致性无压力,关键短语:
    
    ```
    Pick ONE coherent visual style and apply it uniformly to all N items.
    ```
    
    尤其 `"apply it uniformly"` 和 `"coherent"` 是 AI 的行为触发词,比 `"same style"` 效果好。
    
    ## 已知失败模式
    
    ### 1. 文字下标失败
    
    让 AI 在每个子图下方标注编号或名字 → 英文文字乱码率 >30%,中文好很多但仍有风险。
    
    **解法**:永不依赖 AI 生成的文字标签做映射,用「按行列位置顺序映射到 prompt 里给定的列表」。
    
    ### 2. 过度详细的反作用
    
    对知名 IP,列一堆服装细节反而失真。
    
    **解法**:直接点名("Guile from Street Fighter"),让 AI 自己调用记忆。
    
    ### 3. 锚点色撞车
    
    让 AI 画红蓝色锚点时,如果生成的内容本身有红蓝色(角色服装、品牌色),脚本掩膜会误伤。
    
    **解法**:根本不让 AI 画锚点。用 content bbox 定位。
    
    ### 4. 5×5 末行压缩
    
    如前述,模型的隐式空间预算倾向。不能根治,只能缓解(prompt 强化)或绕开(density 抠图)。
    
    ### 5. 网格线抗锯齿污染
    
    让 AI 画黑色 1px 网格线做分割 → 抗锯齿边缘会出现 2-3 px 过渡灰色,脚本按颜色掩膜清不干净。
    
    **解法**:根本不让 AI 画网格线。用 `"Imagine... Do NOT draw"` 的语义指令。
    
    ## 反常识发现
    
    ### 1. 越详细不一定越好
    
    对熟知 IP,AI 自己的视觉记忆比你列的属性列表更准。少即是多。
    
    ### 2. "Surprise me" 是有效提示词
    
    "Surprise me" / "Your call" 这类口语化授权指令,对 GPT-image-2 有实效——它会做出更有创意、更一致的视觉决策。
    
    ### 3. 物理类比比抽象约束强
    
    `"25 physical cards laid on a table, manufactured product"` 比 `"25 cards with same size"` 有效——物理实体在 AI 的视觉模型里是刚性不可变形的。
    
    ### 4. 所有"辅助元素"都是污染源
    
    任何让 AI 画出来、原本是"帮脚本定位"的引导物(网格线、锚点、边框、坐标标记),都是抠图污染源。规则:**AI 只画内容,脚本做定位**。
    
    ### 5. 降密度换一致性是最优解
    
    如果追求绝对一致,降到 4×4 比强行 5×5 加约束更稳。16 张往往已经够用。
    
    ### 6. 古诗比成语更可靠
    
    直觉上 20 字古诗应该比 4 字成语难,实测反而更稳——因为古诗是标准化训练素材,AI 的记忆比零散成语更完整。
    
    ### 7. 中文 prompt 比英文更精准(2026-04-22 sprite sheet 实测)
    
    同一个需求:
    - **英文 600 字 prompt** → 15 帧(要求 12)、深色底(要求白)、动作重复
    - **中文 92 字 prompt** → 12 帧精准、纯白底、动作清晰
    
    中文 prompt 命中率远高于英文。两个机制:
    1. **中文单字信息密度 ≈ 2× 英文**——同样的约束条件,中文字符数少、token 集中
    2. **长 prompt 稀释硬约束**——AI 在长文本里倾向"概括理解",数字和规格被当成装饰描述
    
    **落地规则**:
    - 硬约束多(帧数、网格、尺寸对齐)的场景,优先写中文
    - 英文 prompt 写超过 200 字就要警惕——再加约束不会更精准,反而让已有约束被忽略
    - 把中文当作「高压缩 token」用,不是当作「翻译需求」用
    
    ### 8. 容器语言 > 几何语言(2026-04-22 实测)
    
    涉及对齐/位置的约束,用物理容器词,不用抽象几何词:
    
    | ❌ 几何词(AI 画出来了)| ✅ 容器词(AI 只理解位置)|
    |------------------------|------------------------|
    | 脚底对齐同一水平线 → 画了黑线 | 每帧角色站在单元格底部居中 |
    | aligned along baseline → 画了基线 | positioned at bottom of cell |
    | same vertical axis → 画了轴线 | centered within each cell |
    
    **根因**:AI 对 line / axis / baseline 等几何名词有字面理解倾向(它们可以是视觉元素)。cell / frame / container 是空间容器,只暗示位置关系。
    
    ### 9. 风格锚定有反作用力(权衡意识)
    
    强风格锚定词会覆盖几何约束:
    - 例:「Cuphead 1930 式」成功锁住视觉风格,但把「严格侧视」指令覆盖了
    - Cuphead 在 AI 记忆里的 BOSS 战视角是「正面朝玩家」,它调用了这套惯性
    
    **规律**:
    - 美学风格词(Cuphead / Ghibli / Hearthstone)= 调视觉惯性包(包含视角、配色、姿态)
    - 功能类别词(2D 横版 sprite sheet / passport photo / tactical RPG tile)= 调功能训练数据(视角由功能决定)
    
    想要特定视角时,用功能类别词覆盖美学风格的惯性。
    
    ### 10. sprite sheet 是被低估的新用例
    
    今天实测:**90 字中文 prompt 就能拿到 12 帧角色一致性极高的 sprite sheet**。这打破了「2D 逐帧动画必须动画师手画」的传统认知。
    
    单次生成 = 一整个 sprite sheet,意味着:
    - 个人 30 分钟能做出 Cuphead 风格的动作分解
    - 教育/科普视频可以用 sprite 动画替代抽象图形
    - 批量角色动作集(跑/跳/攻击/死亡 loop)可在一天内完成
    
    模板见 `prompt-patterns.md` 的 `grid_sprite_sheet`。
    
  • references/extraction-strategies.md7.3 KB502c5b16e290upstream ↗
    # 抠图策略详解
    
    从网格图抠出独立透明 PNG 的完整指南。两种模式、何时用、如何调。
    
    ## 目录
    - [核心思路](#核心思路)
    - [模式 A:bbox 等分(默认)](#模式-abbox-等分默认)
    - [模式 B:密度扫描(应对不均匀)](#模式-b密度扫描应对不均匀)
    - [决策流程](#决策流程)
    - [阈值调优手册](#阈值调优手册)
    - [故障排查清单](#故障排查清单)
    - [进阶技巧](#进阶技巧)
    
    ## 核心思路
    
    **不靠 AI 画的引导元素,靠脚本找内容**。所有抠图策略的基础假设:
    
    1. 生成图是**纯白底 + N 个内容块**,中间有白色间隔
    2. 没有网格线、没有锚点色块、没有文字标签
    3. 内容块之间可以不等间距、不等大小,只要有白色间隔就能切
    
    这样脚本完全不依赖 AI 的空间规划准确性,鲁棒性拉满。
    
    ## 模式 A:bbox 等分(默认)
    
    ### 何时用
    
    - 生成图**行高、列宽基本均匀**(3×3 / 4×4 没问题,5×5 大多数情况也可以)
    - 内容块围绕画布中心均匀分布
    - 要求简单快速
    
    ### 算法
    
    ```
    1. 找整图 content bbox(所有非白像素的外接矩形)
    2. 在 bbox 内按 GRID × GRID 均匀等分
    3. 每个 cell:
       - 白色像素 → 透明
       - 按 alpha > 0 找 tight bbox 裁紧
       - 保存为 {idx}_{name}.png
    ```
    
    ### 参数
    
    ```python
    BG_THRESHOLD = 240  # RGB 全部 > 此值算背景(透明)
    PAD = 8             # 裁紧时保留的 padding 像素
    ```
    
    ### 局限
    
    - 不适合行高不均的情况(5×5 末行压缩时会切错)
    - 如果某个角色占的空间特别大(比如春丽高踢),其他 cell 可能被挤占
    
    ## 模式 B:密度扫描(应对不均匀)
    
    ### 何时用
    
    - **5×5 或更高密度**,出现明显的行高/列宽不一致
    - 内容块非对称分布(有高踢、大姿势、异形 pose)
    - bbox 等分后发现抠出来的图有「上一格的脚」或「下一格的头」
    
    ### 算法
    
    ```
    1. 沿 Y 轴扫描每行的"非白像素数",画出密度曲线
    2. 连续高密度段 = 一行内容,低密度段 = 行间白色间隔
    3. 阈值切分得到每行的 y0/y1
    4. 在每行内沿 X 轴重复扫描,得到每列的 x0/x1
    5. 组合得到 N×M 个精确 cell 坐标(非等分)
    6. 每 cell:白→透明 + 裁紧 + 保存
    ```
    
    ### 参数
    
    ```python
    BG_THRESHOLD = 240       # 非白判定阈值
    ROW_DENSITY_MIN = 50     # 一行至少这么多非白像素才算有内容
    SEGMENT_MIN_HEIGHT = 30  # 一段内容至少这么高才不算噪点
    ```
    
    ### 优势
    
    - 对任何不均匀网格都鲁棒
    - 允许不同 cell 有不同的实际尺寸
    - 即使未来遇到 6×6 / 7×7 的不规则生成也能切
    
    ### 代价
    
    - 脚本稍复杂(多一层扫描)
    - 需要内容块之间有**明确的白色间隔**(至少 5-10px)
    
    ## 决策流程
    
    ```
    收到网格图
       ↓
    第一次直接跑 bbox 模式(最快)
       ↓
    检查结果:contact sheet 看是否整齐
       ↓
    ├─ 整齐 → 结束
    └─ 发现偏移/切错 → 切 density 模式重跑
    ```
    
    90% 情况 bbox 模式够用。只有 5×5 以上或有特殊 pose 时才需要 density。
    
    ## 阈值调优手册
    
    ### 背景阈值 BG_THRESHOLD
    
    - **默认 240**:适合大多数白底素材
    - **调到 245**:如果背景有很轻微的灰色/米色染色(中文卡米色纸张容易出现)
    - **调到 230**:如果背景偏灰、或有纸张纹理
    
    ### 裁紧 padding PAD
    
    - **默认 8**:视觉舒适
    - **调到 3**:需要紧凑裁剪(比如做 icon 字库)
    - **调到 15**:需要留呼吸感(比如做卡牌封面)
    
    ### 中文内容的特殊处理
    
    中文成语卡、古诗卡的背景通常是**米色/米黄色纸**(非纯白),直接用 BG_THRESHOLD=240 会漏抠。
    
    **方案**:
    - 把背景判定改为 **「低饱和度 + 高亮度」** 双条件
    - `luminance > 220 AND saturation < 25` 视为背景
    - 这能精确区分"米色背景"和"黑色中文字"
    
    脚本中已实现,见 `extract_grid.py` 的 `--mode=bbox-chinese` 选项。
    
    ## 故障排查清单
    
    | 症状 | 可能原因 | 解法 |
    |------|---------|------|
    | 抠出来整张都是原图 | bbox 没找到,背景不是白色 | 调低 BG_THRESHOLD,或检查是否是米色纸张 |
    | 内容被切一半 | 网格不均匀,bbox 等分失败 | 切 density 模式 |
    | 头部/手臂/武器缺失 | 连通域分析误判独立部件为杂点 | 脚本默认不启用连通域,若启用则增大膨胀半径 |
    | 角色有彩色但被误删 | 背景阈值太松把浅色当背景 | 提高 BG_THRESHOLD 到 245+ |
    | 网格线残留在结果里 | AI 画了不该画的网格线 | **先修 prompt**(加 `no grid lines`),而不是修脚本 |
    | 锚点色扩散到角色身上 | 脚本在全图找红/蓝锚点像素 | 锚点检测要限制在画布四角 100×100 局部区域内 |
    | 每张 PNG 尺寸都相同 | bbox 没有正确收紧 | 检查是否有全局性的浅色残留未被判为背景 |
    | 最后一行被切斜 | 5×5 末行压缩 | 切 density 模式 |
    
    ## 进阶技巧
    
    ### 技巧 1:彩色内容的边缘软处理
    
    纯黑 icon 的边缘很硬,白→透明的二值化阈值就够。但彩色内容(角色、卡牌)边缘有抗锯齿,生硬二值化会有锯齿感。
    
    **软处理**:
    ```python
    # 近白色但不纯白的像素,按亮度给部分透明度
    near_white = (luminance > 225) & (saturation < 15) & ~is_white
    fade_alpha = ((255 - luminance).clip(0, 30) * 255 / 30)
    rgba[near_white, 3] = fade_alpha[near_white]
    ```
    
    已实现在 `extract_grid.py` 中,自动启用于 RGBA 输出。
    
    ### 技巧 2:连通域过滤(慎用)
    
    如果图里有**明显的黑色 1px 网格线残留**(比如提示词里误写 `"thin grid lines"`),可以启用连通域过滤:只保留每个 cell 中面积最大的连通区域。
    
    **但代价**:如果角色的头发/武器/披风和身体不连通,会被当杂点丢弃。
    
    **结论**:
    - 优先修 prompt 去掉网格线
    - 只在无法重新生成、且杂点明显时才启用
    - 启用时膨胀半径建议 20-25 px(能把分离的部件重新连起来)
    
    ### 技巧 3:顺序映射 vs OCR
    
    永远别用 OCR 识别生成图里的英文标签做映射(乱码率高)。
    
    **正确做法**:
    ```python
    NAMES = ['ryu', 'ken', 'chunli', ...]  # 与 prompt 里列表顺序完全一致
    for idx, (row, col) in enumerate(cells):
        name = NAMES[idx]
        save(f'{idx+1:02d}_{name}.png')
    ```
    
    行列位置 → 列表索引,0 歧义。
    
    ### 技巧 4:Contact Sheet 验证
    
    抠完后一定要生成拼图预览肉眼确认。16 张角色摆到一张 4×4 预览图上,问题一目了然。
    
    `contact_sheet.py` 做这件事。
    
    ### 技巧 5:迭代开发
    
    调不好阈值时,用这个流程快速迭代:
    
    1. 只对一张 cell 试:`extract_grid.py --only=5`(抠第 5 格)
    2. 打印中间数据:`extract_grid.py --debug`(输出 bbox、阈值、像素统计)
    3. 调好参数后批量:`extract_grid.py`(抠全部)
    
    ## 何时重新生成图,而不是修脚本
    
    脚本无法弥补的问题:
    
    - 生成图里**真的画了**网格线/锚点/文字 → 重新 prompt
    - 某个角色画得崩了(歪斜、破相) → 重新跑一次
    - 风格严重不一致(前 5 个写实、后 4 个卡通) → 强化风格锚定重跑
    - 行高偏差 > 20% → prompt 加 `"manufactured product photograph"` 约束,或降密度
    
    **规则**:如果重新生成一张的成本 < 改脚本的成本,优先重新生成。
    
  • references/prompt-patterns.md19 KB8e5d4b8c3104upstream ↗
    # GPT-image-2 提示词模板库
    
    所有模板都经过实测验证。按场景选模板,填空即可用。
    
    ## 目录
    - [通用三原则](#通用三原则)
    - [模板 1:grid_icons(4×4 icon 集)](#模板-1grid_icons4x4-icon-集)
    - [模板 2:grid_characters(3×3 角色集)](#模板-2grid_characters3x3-角色集)
    - [模板 3:grid_tcg_cards(4×4 或 5×5 卡牌)](#模板-3grid_tcg_cards4x4-或-5x5-卡牌)
    - [模板 4:grid_chinese_cards(4×4 中文知识卡)](#模板-4grid_chinese_cards4x4-中文知识卡)
    - [模板 5:chinese_scroll(1:3 竖幅书法)](#模板-5chinese_scroll13-竖幅书法)
    - [模板 6:chinese_couplets(3:1 横幅对联)](#模板-6chinese_couplets31-横幅对联)
    - [模板 7:single_illustration(单张插画)](#模板-7single_illustration单张插画)
    - [模板填空指南](#模板填空指南)
    
    ## 通用三原则
    
    所有模板都遵循:
    
    1. **禁画辅助元素**:`no grid lines, no borders, no registration marks, no text labels`
    2. **虚拟网格描述**:`Imagine the canvas divided into N×M cells`
    3. **风格一致性**:`pick ONE coherent visual style and apply it uniformly`
    
    ## 模板 1:grid_icons(4×4 icon 集)
    
    **适用**:批量生成游戏道具、UI 图标、贴纸、表情、纹章等简单图形。
    
    ```
    Generate a 4×4 grid of {ICON_THEME} icons on a 1024×1024 canvas. 
    Imagine the canvas divided into 16 equal 256×256 cells arranged in 
    4 rows and 4 columns. Place one icon centered in each cell, filling 
    ~70% of cell area. Icons must not cross cell boundaries.
    
    Style: {STYLE_DESCRIPTION}. Pure black silhouettes with clean edges, 
    or saturated flat colors with bold outlines. No drop shadows, no 
    gradients, no 3D effects.
    
    Icons in reading order (left-to-right, top-to-bottom):
    1. {ICON_1}
    2. {ICON_2}
    ...
    16. {ICON_16}
    
    Pure white background everywhere. No text, no numbers, no labels, 
    no grid lines, no borders.
    ```
    
    **填空示例**:
    - `ICON_THEME` = "fantasy RPG inventory"
    - `STYLE_DESCRIPTION` = "bold black silhouettes, clean vector style"
    - 16 个 icon 依次列出
    
    ## 模板 2:grid_characters(3×3 角色集)
    
    **适用**:游戏角色集、IP 角色、人物立绘。
    
    ```
    Generate a 3×3 grid of {CHARACTER_SET} on a 1536×1536 canvas. 
    Imagine the canvas divided into 9 equal 512×512 cells in 3 rows × 
    3 columns. Place one character centered in each cell, full body, 
    filling ~70% of cell height. Characters must not cross cell 
    boundaries.
    
    {OPEN_OR_SPECIFIC}
    
    Pure white background everywhere. No text, no logos, no ground 
    shadows, no signatures, no grid lines, no borders.
    ```
    
    **两种填法**:
    
    **A. 具体指定版**(用于已知 IP):
    ```
    CHARACTER_SET = "Street Fighter characters"
    OPEN_OR_SPECIFIC = "Characters in reading order:
    1. Ryu
    2. Ken Masters
    3. Chun-Li
    4. Guile
    5. Zangief
    6. Dhalsim
    7. E. Honda
    8. Blanka
    9. M. Bison (Dictator)
    
    Art style: Capcom's official arcade illustrations, bold flat-shading, 
    thick black outlines, saturated colors."
    ```
    
    **B. 开放式版**(让 AI 自由发挥,出图常常更好):
    ```
    CHARACTER_SET = "Street Fighter characters"
    OPEN_OR_SPECIFIC = "Your call on who to include and what art style 
    to use — but pick ONE coherent visual style and apply it 
    consistently to all 9 characters. Surprise me."
    ```
    
    **关键**:对熟知 IP,开放式版经常出图更准,因为 AI 调用视觉记忆比强约束更精准。
    
    ## 模板 3:grid_tcg_cards(4×4 或 5×5 卡牌)
    
    **适用**:TCG 卡牌、图鉴、收藏集。
    
    ### ⚠️ 关键教训
    
    TCG 卡是 **2:3 纵向**。早期用 1536×1536 方形画布生成 5×5 卡牌会导致末行压缩 18%(数学上不可能:25 张纵向卡不能舒适放进方形画布)。**解决方案:画布匹配卡牌比例 + 人格化指令**。
    
    ### 推荐模板(人格化 + 自适应画布)
    
    ```
    You are a card game production manager overseeing the printing of a 
    {TOTAL}-card collection. Your professional standard is absolute: every 
    physical card must have identical dimensions — you would reject any 
    production batch with varying card heights. Show {TOTAL} finished 
    cards laid out in a {ROWS}-row × {COLS}-column grid, as if 
    photographed for a product catalog.
    
    Cards are standard TCG proportions (2:3 portrait). Choose a canvas 
    aspect ratio that displays all cards at their natural proportions 
    without compression — suggested: {CANVAS_HINT} or similar 2:3 ratio.
    
    Theme: {CARD_THEME}. Art style: {STYLE_REF}. Pick ONE coherent 
    visual direction across all {TOTAL} cards.
    
    Each card has its own frame and border as part of the artwork. 
    Cards are physical objects on a clean white surface — nothing else 
    exists in the frame.
    
    Leave name/stat areas blank or with decorative shapes. No text, no 
    numbers, no logos.
    ```
    
    **填空建议**:
    
    | 网格 | TOTAL | CANVAS_HINT |
    |------|-------|-------------|
    | 4×4 | 16 | 1536×1536(方)或 1280×1920(2:3)|
    | 5×5 | 25 | 1600×2400(2:3 竖幅)或 **2000×1600**(5:4 横幅也可)|
    
    - `CARD_THEME` = "fantasy" / "sci-fi" / "monster taming"
    - `STYLE_REF` = "Hearthstone / Magic: The Gathering / Yu-Gi-Oh inspired"
    
    ### 为什么这个模板好
    
    **人格化("production manager")**:AI 的角色信念里包含"拒绝不一致产出",压缩末行违背了它扮演的角色。
    **自适应画布**:把"选尺寸"的权力交给 AI,让它为内容匹配画布,而非反过来。
    **物理类比**:卡牌是实体物品,物理上不可变形——消解了"构图压缩"的选项。
    
    ## 模板 4:grid_chinese_cards(4×4 中文知识卡)
    
    **适用**:成语卡、古诗卡、历史典故卡、品牌小词典。
    
    ```
    Generate a 4×4 grid of {CHINESE_THEME} cards on a 1536×1536 canvas. 
    Imagine the canvas divided into 16 equal 384×384 cells in 4 rows × 
    4 columns. One card per cell, centered, fills ~90% of cell.
    
    Each card contains:
    - {MAIN_TEXT} in large bold Chinese characters (arranged {LAYOUT})
    - {VISUAL_ELEMENT} depicting the meaning
    - Traditional cream/beige rice paper background with subtle texture
    - A small red seal stamp in one corner
    
    {ITEM_LIST}
    
    Visual style: traditional Chinese ink painting fused with modern flat 
    design. Consistent black ink calligraphy, red seal accents, cream 
    paper. All 16 cards share the same layout template.
    
    Pure white background between cards. No English, no pinyin, no modern 
    sans-serif fonts, no grid lines, no borders. Ensure every Chinese 
    character is rendered accurately and legibly.
    ```
    
    **填空示例(成语卡)**:
    - `CHINESE_THEME` = "Chinese idiom (成语)"
    - `MAIN_TEXT` = "The idiom"
    - `LAYOUT` = "horizontally or in a 2×2 square"
    - `VISUAL_ELEMENT` = "a small hand-drawn ink illustration"
    - `ITEM_LIST` = "Pick 16 idioms with strong visual meanings: 画龙点睛、守株待兔、塞翁失马、..."
    
    ## 模板 5:chinese_scroll(1:3 竖幅书法)
    
    **适用**:古诗竖幅、书法条屏、禅语卷轴、对联单联。
    
    ```
    Generate a vertical Chinese calligraphy scroll on a {WIDTH}×{HEIGHT} 
    canvas (aspect ratio 1:3). The scroll displays {POEM_OR_TEXT}.
    
    Layout from top to bottom:
    - Top: a horizontal plaque (横批) with 2-4 character title
    - Middle: the full text written in vertical columns, right to left, 
      in {CALLIGRAPHY_STYLE}
    - Below: the author's name in smaller characters
    - Bottom right: a red square seal stamp
    - Bottom left: a second smaller red oval seal stamp
    
    Cream-colored aged rice paper background with subtle fiber texture. 
    Black ink brush strokes with natural variations in thickness and 
    saturation. No modern design elements. No pinyin, no English 
    translation.
    
    Ensure every Chinese character is rendered accurately and matches 
    the actual text.
    ```
    
    **填空示例**:
    - `WIDTH×HEIGHT` = `512×1536`
    - `POEM_OR_TEXT` = `"famous classical Chinese poem 登鹳雀楼 by 王之涣 (20 characters): 白日依山尽,黄河入海流。欲穷千里目,更上一层楼"`
    - `CALLIGRAPHY_STYLE` = `"traditional regular-script (楷书)"` 或 `"semi-cursive (行书)"`
    
    ## 模板 6:chinese_couplets(3:1 横幅对联)
    
    **适用**:春联、对联横轴、店招、励志语录横幅。
    
    ```
    Generate a horizontal Chinese calligraphy banner on a {WIDTH}×{HEIGHT} 
    canvas (aspect ratio 3:1). Display {N} {CONTENT_TYPE} arranged side 
    by side, evenly spaced.
    
    Each unit shows:
    {UNIT_STRUCTURE}
    
    Cream rice paper background. Black brush ink calligraphy. Small red 
    seal stamps at natural positions. No modern fonts, no English, no 
    pinyin.
    
    Ensure every Chinese character is rendered accurately.
    ```
    
    **填空示例(多联对联)**:
    - `WIDTH×HEIGHT` = `1536×512`
    - `N` = `3`
    - `CONTENT_TYPE` = `"classical couplets (对联)"`
    - `UNIT_STRUCTURE` = `"- Two vertical lines of 7-character couplet
    - A central horizontal plaque (横批) of 4 characters above"`
    
    ## 模板 7:single_illustration(单张插画)
    
    **适用**:海报、封面、单张插图、文艺图。
    
    ```
    Generate a single high-quality illustration on a {WIDTH}×{HEIGHT} 
    canvas. Subject: {SUBJECT}. Style: {STYLE}.
    
    Composition: {COMPOSITION_NOTES}.
    
    {TEXT_OR_NO_TEXT}
    
    Rendering notes: {QUALITY_NOTES}.
    ```
    
    **填空示例**:
    - `SUBJECT` = `"a traditional Chinese teahouse at dusk, lantern-lit, single figure pouring tea"`
    - `STYLE` = `"Studio Ghibli watercolor meets ukiyo-e woodblock"`
    - `COMPOSITION_NOTES` = `"wide horizontal composition, rule of thirds, warm color palette, soft atmospheric perspective"`
    - `TEXT_OR_NO_TEXT` = `"No text, no captions, no watermarks."`
      或 `"Include a vertical seal stamp with 茶禅一味 on the right side."`
    - `QUALITY_NOTES` = `"detailed brush work, subtle gradients, no harsh outlines"`
    
    ## 模板 8:grid_sprite_sheet(逐帧动画 sprite sheet)
    
    **适用**:2D 逐帧动画素材、角色动作分解、Cuphead 式手绘动画、游戏 sprite。
    
    ### ⚠️ 这是 sprite sheet 制作的全新用例
    
    过去做 sprite sheet 需要动画师一帧帧画。现在一张网格图 + 抠图脚本 = 完整 sprite sheet。经今天实测,**90 字中文 prompt 就能拿到角色一致性极高的 12 帧**。
    
    ### 实战 prompt(实测 3 轮迭代收敛,90 字)
    
    ```
    4×3 网格共 12 帧 sprite sheet。同一{角色}{动作}分解动作:
    {关键姿势 1}→{关键姿势 2}→{关键姿势 3}→{关键姿势 4}→{关键姿势 5}。
    镜头从{视角方向}拍摄,每帧{角色}站在单元格底部居中。
    单元格之间纯白色留白。{风格锚定},{配色签名}。
    ```
    
    ### 填空示例(骑士挥剑)
    
    ```
    4×3 网格共 12 帧 sprite sheet。同一骑士挥剑分解动作:
    站立→举剑过顶→下劈→击中→收招。镜头从正左侧拍摄,
    每帧骑士站在单元格底部居中。单元格之间纯白色留白。
    Cuphead 1930 式,黑色粗描边+红披风。
    ```
    
    ### 关键设计(每个字都经过实测验证)
    
    | 元素 | 作用 | 为什么必须是这个措辞 |
    |------|------|---------------------|
    | `4×3 网格共 12 帧` | 锁定帧数 | 硬数字必须**开头出现**;英文长 prompt 里会被忽略成 5×3=15 |
    | `同一{角色}` | 角色一致性锁 | 4 个中文字 > 英文一大段。中文信息密度碾压 |
    | `{关键姿势 1→2→3→4→5}` | 动作弧线 | 不给 12 帧逐帧描述,给 5 个关键帧,AI 自动脑补中间帧 |
    | `镜头从正左侧拍摄` | 视角约束 | **摄影语言** > 几何语言。「strict side view」AI 不一定听 |
    | `每帧{角色}站在单元格底部居中` | 位置锁 | **容器语言**,不要用「同一水平线」(会被画成黑线) |
    | `单元格之间纯白色留白` | 抠图必需的 gutter | 顺便强化白底,对抗风格锚定的深色惯性 |
    | `Cuphead 1930 式` | 风格锚定 | 4 个字 > 英文 50 字的风格描述 |
    | `黑色粗描边+红披风` | 配色签名 | 2 个元素的颜色标签胜过色值描述 |
    
    ### 迭代路径(骑士挥剑实测)
    
    | 版本 | 帧数结果 | 背景 | 角色一致 | 核心问题 |
    |------|---------|------|---------|---------|
    | v1:英文 600 字全堆砌 | ❌ 15 帧 | ❌ 深色渐变 | ✅ | 长 prompt 让硬约束被稀释 |
    | v2:中文 92 字,含「水平线」 | ✅ 12 帧 | ✅ 纯白 | ✅ | 「同一水平线」被画成黑线 |
    | v3:换成「单元格底部居中」 | ✅ 12 帧 | ✅ 纯白 | ✅ | 仅视角锁定失败(被 Cuphead Q 版视觉惯性覆盖)|
    
    ### 视角锁定的最后一招
    
    如果用 Cuphead 风格但想要严格侧视,**风格锚定换功能类别**:
    
    ```
    把「Cuphead 1930 式」→ 换成「2D 横版动作游戏 sprite sheet」
    ```
    
    原因:Cuphead 是**美学风格**,AI 调 BOSS 战正面视角;「2D 横版游戏 sprite」是**功能类别**,AI 直接调 profile 侧视训练数据。
    
    ### 其他场景改造
    
    - **跑步循环**(8 帧循环):`3×3 网格共 8 帧...{角色}跑步循环`(第 9 格可空,或做 idle 帧)
    - **攻击组合**(16 帧完整连招):`4×4 网格共 16 帧...{角色}三段斩组合`
    - **表情变化**(9 帧情绪序列):`3×3 网格共 9 帧...{角色}从平静到愤怒的 9 个表情`
    
    ## 模板填空指南
    
    ### 风格锚定短语库(实测有效)
    
    | 想要的感觉 | 锚定短语 |
    |----------|---------|
    | 街霸/格斗游戏 | `Capcom's official arcade illustrations` |
    | 炉石/厚涂卡牌 | `Hearthstone style` |
    | MTG/奇幻厚涂 | `Magic: The Gathering card art` |
    | 宫崎骏水彩 | `Studio Ghibli style` |
    | 粗描边日漫 | `Guilty Gear Strive portraits` |
    | 水墨画 | `traditional Chinese ink painting` |
    | 扁平矢量 | `minimalist flat design with bold black outlines` |
    | 像素风 | `1990s arcade pixel art` |
    | 浮世绘 | `ukiyo-e woodblock print` |
    | 苹果插画 | `Apple Human Interface illustration style` |
    
    ### 禁画清单(粘贴到任何模板)
    
    ```
    No text, no logos, no watermarks, no signatures, no ground shadows, 
    no grid lines, no borders, no registration marks, no pinyin, no 
    English captions, no modern sans-serif fonts crossing traditional 
    elements.
    ```
    
    ### 质量强调短语(加在结尾)
    
    - **中文准确**:`Ensure every Chinese character is rendered accurately and legibly.`
    - **风格一致**:`Apply the chosen style uniformly to all elements.`
    - **无污染**:`The background between elements should be completely clean and white.`
    - **物理类比**(治 5×5 末行压缩):`Think of this as a photograph of identical manufactured items, not a hand-drawn layout.`
    
    ### 常见错误纠正
    
    - ❌ `"Make sure cards are same size"` → ✅ `"EVERY card has IDENTICAL dimensions. Think of physical manufactured cards."`
    - ❌ `"Don't draw grid"` → ✅ `"Do NOT draw any grid lines, borders, or registration marks"`
    - ❌ `"Asian style"` → ✅ `"traditional Chinese ink painting"`(具体到技法)
    - ❌ `"Hearthstone-like"` → ✅ `"Hearthstone style"`(简洁直接)
    
    ### 如果出图不理想的调整顺序
    
    1. **先降密度**(5×5 → 4×4 → 3×3)看是否变稳
    2. **再换风格锚定**(可能锚定到了 AI 不熟的风格)
    3. **最后加约束**(禁画清单 + 质量强调)
    
    ## 词汇选择心法(2026-04-22 sprite sheet 实测沉淀)
    
    这四对是 prompt 工程最高杠杆的认知,每一对都是一次真实的失败换来的。
    
    ### 1. 容器语言 > 几何语言
    
    **关于位置/对齐的约束**,用物理容器的语言,不用抽象几何的语言。
    
    | ❌ 几何语言 | AI 的误解 | ✅ 容器语言 |
    |-----------|----------|-----------|
    | `脚底对齐同一水平线` | 真的画一条黑线 | `每帧角色站在单元格底部居中` |
    | `aligned along a grid` | 画出网格线 | `arranged in cells` |
    | `same baseline` | 画出基线 | `positioned at the bottom of each cell` |
    | `centered on a vertical axis` | 画出轴线 | `centered within each cell` |
    
    **为什么**:AI 对「line / axis / baseline」这类几何名词有字面理解的倾向——它们是视觉元素可以被画出来。而「cell / frame / container」是空间容器概念,AI 理解成位置关系但不会画出来。
    
    ### 2. 功能类别 > 美学风格(当想要特定视角时)
    
    **风格锚定词会调用整套视觉惯性,包括视角**。想要打破这个惯性,用功能类别词。
    
    | 想要 | ❌ 美学风格(调惯性)| ✅ 功能类别(调训练数据)|
    |------|-------------------|----------------------|
    | 严格侧视 sprite | `Cuphead 风格` | `2D 横版动作游戏 sprite sheet` |
    | 正面肖像 | `magazine style` | `passport photo / headshot` |
    | 45 度俯视 | `isometric art` | `tactical RPG game tile` |
    | 鱼眼视角 | `surreal style` | `action camera / security camera footage` |
    
    **为什么**:美学风格 = AI 记忆里的「这个美学的代表作长啥样」,包含视角惯性。功能类别 = AI 记忆里的「这类用途的图片长啥样」,视角由功能决定。
    
    ### 3. 中文 > 英文(信息密度 2 倍杠杆)
    
    实测对比:同一个 sprite sheet 需求,英文 600 字失败,中文 92 字成功。
    
    | 英文长 prompt | 中文短 prompt |
    |-------------|-------------|
    | 600+ tokens | 90+ 字符 |
    | 硬约束被稀释(15 帧而非 12) | 硬约束精准命中(12 帧) |
    | AI 抓主视觉忽略细节 | 每一条都成为硬指令 |
    | 风格描述冗长 | 「Cuphead 1930 式」4 字搞定 |
    
    **为什么**:中文单字信息密度约 2× 英文。gpt-image-2 的中文理解能力被严重低估——它不只能生成中文,也能理解中文 prompt(且理解得更精准,因为 token 更集中)。
    
    **落地规则**:对于硬约束多、字数敏感的场景(网格数量、帧数、位置对齐),**优先用中文写 prompt**。
    
    ### 4. 硬数字前置 + 符号强化
    
    硬数字(帧数、网格、尺寸)要**开头出现**,且用**符号包裹**让 AI 识别为「关键约束」而非描述文本。
    
    | ❌ 弱 | ✅ 强 |
    |------|------|
    | `...arranged in 12 cells in a grid of 4 by 3...` | `4×3 共 12 帧...`(开头 + 数学符号)|
    | `around 16 characters` | `16 角色` 或 `【16】`(括号强化)|
    | `using 3 to 5 columns` | `3 列` 或 `5 列`(消除范围歧义)|
    
    **为什么**:AI 在长文本里容易「概括」而不是「精确执行」。数字开头 + 符号包裹,让它感知到这是 spec 而非描述。
    
    ## 今天漏掉的其他认知(防踩重复)
    
    - **「no X」禁止清单经常失效**:AI 在 Cuphead 语境里主动加了 ★ 和烟雾——我们写了「无文字」但它判定这是 sprite sheet 的合理冲击特效,于是保留。**结果是对的**。规则:AI 对 sprite sheet 的「该加什么特效」有自己的常识,不要过度禁止
    - **风格锚定的反作用力**:「Cuphead 1930 式」成功锁住视觉,但把「严格侧视」覆盖了。强风格锚定 = 牺牲了一部分几何约束的执行力。权衡时记着
    - **动作弧线用 5 关键帧描述胜过 12 逐帧描述**:AI 会脑补中间帧,给 5 个转折点(如「站立→举剑→下劈→击中→收招」)比写 12 条每一帧的动作更有效
    4. **多跑几次**(GPT-image-2 有随机性,跑 3 次挑最好)
    
  • references/real-world-cases.md9.9 KB16f4e3925d92upstream ↗
    # GPT-image-2 实战案例 · 写 prompt 的避坑经验
    
    > 沉淀自 2026-04 「聊聊 skill」 deck 项目,两次成功的 4×2 grid 生成案例。
    > 每条经验都来自踩过的坑,可直接复用到类似场景。
    
    ---
    
    ## 0 · 核心使用场景
    
    **最有价值的场景**:cinematic 动画 / 3D gallery 需要「一组主题相关的真实物件」做素材。
    
    例如:
    - 给「费曼蒸馏」demo 做「1960s Caltech 物理档案」8 件物品
    - 给「skill 优化」demo 做「Skill 工程师 lab bench」8 件工具
    - 给「张小龙产品哲学」demo 做「微信发展史 archival」8 件物品
    
    **反模式**:
    - 让 AI 一张张生成(每张 30 秒,8 张要 4 分钟,且风格不一致)
    - 用 emoji / SVG 手画代替(丑且无品牌)
    
    **正确做法**:用一张 `4×2 grid · 8 cells` 大图(30 秒生成),脚本抠成 8 张独立 PNG(10 秒),风格完全统一。
    
    ---
    
    ## 1 · 五条避坑经验
    
    ### 1.1 · 白底优先,灰底慎用
    
    **坑**:早期为了氛围用 `mid-grey #2A2A2A` 背景,结果 `extract_grid.py --bg 240` 阈值识别不出灰背景,抠出的 PNG 边缘有灰渣。
    
    **经验**:
    - **第一选**:`Pure white background (#FFFFFF)` —— 干净,extract_grid 默认阈值 240 完美工作
    - **第二选**:`Off-white #FAFAF7` —— 略带温度但仍易抠
    - **避免**:mid-grey / 任何渐变背景 / 任何带纹理的背景
    
    **例外场景**:如果素材最终就是放在浅色 panel 上展示(不需要透明 PNG),灰底反而更有 archival 氛围。
    
    **判断标准**:素材是否需要后续合成到不同背景上漂浮?需要 → 白底。直接用整张 → 灰底可。
    
    ---
    
    ### 1.2 · 4×2 不要 5×5
    
    **坑**:早期试过 5×5 卡牌生成,末行被 AI 自发压缩 18%,因为画布尺寸(1:1)和内容比例(2:3 卡牌)数学上不可能同时满足。
    
    **经验**:
    - **首选 4×2 = 8 张**:1920×1080 画布完美,每格 480×540,刚好放一个物件
    - **次选 3×3 = 9 张**:方形画布 1536×1536
    - **慎用 5×5**:必须用 `--mode density` 抠图,且画布必须匹配内容比例
    
    **为什么 4×2 是黄金**:
    - 8 件物品的 narrative 容量足够(够讲一个故事)
    - 比 3×3 少 1 件,去掉「凑数那张」的诱惑
    - 16:9 画布天然适配演示页面 / cinematic 横屏
    
    ---
    
    ### 1.3 · IP 锚定 + Hearthstone-style consistent treatment
    
    **坑**:早期 prompt 写「Feynman-related artifacts, vintage style」,AI 自由发挥,8 件物品风格各异(一张油画质感、一张卡通、一张写实)。
    
    **经验**:在 prompt 里加两个魔法句子:
    
    1. **IP 锚定**(视觉记忆唤起):
       ```
       1960s-70s Caltech archive aesthetic, vintage academic feel
       ```
       而不是 "vintage style" 这种泛泛描述。具体的时代+机构会唤起 AI 的视觉记忆。
    
    2. **Hearthstone-style consistent treatment**(一致性强约束):
       ```
       "Hearthstone-style consistent treatment" — these 8 items are 
       a curated set, not random objects. Equal visual weight, 
       no item dominates.
       ```
       这个短语来自 huashu-gpt-image 的「IP 记忆唤起胜过细节堆砌」原则——AI 见过太多 Hearthstone 卡牌,知道「同一套卡牌的统一视觉处理」是什么意思。
    
    ---
    
    ### 1.4 · Persona Finishing(人格化收尾)
    
    **坑**:纯描述性 prompt 容易让 AI 产出 stock-photo 质感(千篇一律)。
    
    **经验**:在 prompt 末尾加一个 persona 句子,让 AI 进入特定职业角色:
    
    ```
    Persona: you are a curator preparing a Wired magazine feature on
    the engineering of self-improving systems.
    ```
    
    或:
    
    ```
    Persona: you are an editorial photographer for Wired magazine — 
    make it documentary, not stock-photo.
    ```
    
    这一句话能把质感从「AI 生成图」拉到「编辑级摄影」。原因:训练语料里 Wired / 博物馆 curator 这类 persona 的图片样本质量都很高。
    
    ---
    
    ### 1.5 · 60px breathing space + 不画辅助元素
    
    **坑**:AI 自发画分隔线 / 边框 / 标签来「帮助」结构化,结果都是 extract_grid 抠图时的污染源。
    
    **经验**:每个 prompt 必须有这两句:
    
    ```
    Imagine the canvas divided into a 4×2 grid of 8 cells (480×540 each).
    Do NOT draw grid lines, borders, registration marks, or text labels between cells.
    60px breathing space within each cell. No item overlaps adjacent cells.
    ```
    
    - 「Imagine ... grid」让 AI 把网格作为内部空间规划,而不是真的画出来
    - 「60px breathing space」给抠图脚本留 padding
    - 「No item overlaps」防止物品跨格
    
    ---
    
    ## 2 · 完整 prompt 模板(经过验证)
    
    ### 模板 · 4×2 主题档案 grid
    
    ```text
    Title: {THEME} Archive · 4×2 grid · 8 thematic items
    Canvas: 1920 × 1080 (16:9), high quality
    
    Imagine the canvas divided into a 4×2 grid of 8 cells (480×540 each).
    Do NOT draw grid lines, borders, registration marks, or text labels between cells.
    Each cell contains ONE distinct artifact related to {THEME}.
    
    UNIFIED STYLE across all 8 items:
    - Pure white background (#FFFFFF) — clean studio backdrop, easy to extract
    - Each item floats with subtle natural shadow at 30°
    - Color palette: {3-4 specific colors}
    - Soft directional studio lighting from upper-left
    - Still-life product photography, single hero subject per cell
    - 60px breathing space within each cell
    - {ERA-and-INSTITUTION-anchor, e.g., "1960s Caltech archive aesthetic"}
    
    The 8 items (left→right, top row first):
    1. {item 1 with specific details}
    2. {item 2 with specific details}
    3. ...
    8. {item 8 with specific details}
    
    Critical: All 8 items must feel curated for a documentary photo shoot
    about "{NARRATIVE-KEY-PHRASE}".
    "Hearthstone-style consistent treatment" — equal visual weight, 
    no item dominates.
    
    Persona: you are a curator preparing a {AUTHORITATIVE-CONTEXT, 
    e.g., "Wired magazine feature on the engineering of self-improving systems"}.
    ```
    
    **填空建议**:
    - `{THEME}`:1-3 个词,IP 锚定(如 "Feynman Knowledge"、"Skill Engineer Lab Bench")
    - `{3-4 specific colors}`:cream paper #F5EFE6 / charcoal ink / occasional rust accent / etc
    - `{ERA-and-INSTITUTION-anchor}`:时代 + 机构("1960s Caltech archive aesthetic"、"1830s Darwinian naturalist notebook")
    - `{NARRATIVE-KEY-PHRASE}`:1 句话点明这组物品讲什么故事
    - `{AUTHORITATIVE-CONTEXT}`:知名编辑/博物馆/curator 角色
    
    ### 8 个 item 的描述要点
    
    每个 item 的描述要包含 **3 个具体维度**:
    
    ```
    A worn first-edition copy of "{exact-name}" book, 
    showing {specific texture/wear}, 
    {angle: 3/4 view / overhead / side},
    {additional detail: paperclip / annotation / etc}
    ```
    
    太抽象("a book about Feynman")→ AI 画错;太冗长(10 个形容词)→ AI 抓不住重点。
    
    ---
    
    ## 3 · 案例对照
    
    ### 案例 A · Feynman Knowledge Archive
    
    **触发场景**:cinematic Scene 2 的 3D 知识 orbit,需要 8 件费曼相关物件漂浮。
    
    **Prompt 关键句**:
    - IP 锚定:`1960s-70s Caltech archive aesthetic`
    - Persona:`you are a museum curator preparing an exhibition catalog photo`
    - 8 件物品都是真实存在的(《Surely You're Joking》book / Caltech QED folder / 黑板手稿 / Vol.I 物理讲义 / 手写笔记 / QED 磁带 / Caltech 1965 奖牌)
    
    **抠图命令**:
    ```bash
    python3 extract_grid.py source.png --grid 2 4 --mode bbox \
      --names "01-book-joking,02-folder-qed,03-blackboard-diagrams,..." \
      --out items/
    ```
    
    **结果**:8 张独立透明 PNG,每张 350-450px,边缘干净。
    
    ### 案例 B · Darwin Skill Lab Bench
    
    **触发场景**:cinematic Scene 2 的 lab bench orbit,主题从「物理学家档案」改成「skill 工程师桌面」。
    
    **Prompt 关键差异**:
    - IP 锚定:`half engineering, half Darwinian naturalist tradition`(混搭:现代工程 + 1830s 博物学)
    - 8 件物品体现「自我评估实验」narrative:v1 SKILL.md(红笔批注)/ v5 SKILL.md(干净)/ 8 维评分卡 / hill-climb 笔记本 / 黄铜卡尺 / 达尔文 1837「I think」树状图 / 复古 stopwatch / git commit 便签
    - Persona:`Wired magazine feature on the engineering of self-improving systems`
    
    **为什么对比成立**:两组物品视觉同样品质(都白底 + 摄影级 + unified style),但**叙事完全不同**——一组是「物理学家的工作」,一组是「skill 工程师的科学方法」。
    
    ---
    
    ## 4 · 抠图配套技巧
    
    ### 4.1 · 命名规则
    
    `extract_grid.py --names` 用逗号分隔的名字按 reading order 标记,建议格式:
    ```
    01-{category}-{specific-name}
    02-{category}-{specific-name}
    ...
    ```
    
    例如:`01-book-joking,02-folder-qed,03-blackboard-diagrams`
    
    输出文件名:`01_01-book-joking.png`(前缀 `01_` 是脚本加的位置编号)
    
    ### 4.2 · 阈值调整
    
    默认 `--bg 240` 适合纯白底。如果素材很浅(米白),用 `--bg 250` 或 `--bg 245`。
    
    ### 4.3 · 保留 padding
    
    默认 `--pad 0` tight crop。如果素材有阴影想保留,用 `--pad 10` 或 `--pad 20`。
    
    ### 4.4 · 验证抠图
    
    抠完后随机看 2-3 张:
    ```bash
    # 浏览器打开(透明背景,会看到棋盘格)
    open items/03_03-blackboard-diagrams.png
    ```
    
    边缘有灰渣 → 调整 `--bg` 阈值
    物体被切掉 → 增加 `--pad`
    背景没抠干净 → 重新生成(白底没生成对)
    
    ---
    
    ## 5 · 速查表
    
    | 场景 | 推荐配置 |
    |---|---|
    | 给 cinematic 做 orbit / gallery 漂浮素材 | 4×2 白底 + persona finishing |
    | 单张大图直接用(不抠图) | 任意背景 + 重质感 |
    | 中文知识卡 / 国风内容 | `chinese_*` 模板 + 米色底 |
    | 游戏 icon / UI 资源 | 4×4 平涂彩色 + 黑描边 |
    
    | 反 pattern | 正 pattern |
    |---|---|
    | "vintage style" | "1960s Caltech archive aesthetic" |
    | "8 books" | 8 个具体 book 描述(书名 / 颜色 / 角度 / 磨损) |
    | 灰底 + 想抠透明 | 白底 + extract_grid bbox |
    | 5×5 grid | 4×2 / 3×3 grid |
    | 自由发挥风格 | "Hearthstone-style consistent treatment" |
    | 描述性 prompt 结尾 | persona finishing("you are a Wired editor")|
    | 让 AI 画分隔线辅助 | "Imagine the grid, do NOT draw grid lines" |
    
  • references/single-image-playbook.md16 KB3e865bdf970bupstream ↗
    # Single-Image Playbook · GPT-image-2 单图生成完整方法论
    
    > 这是基于 2026-04-30 与花叔的真实测试积累的认知。覆盖任何单张 AI 图片生成场景:公众号头图、小红书封面、B站/YouTube 封面与 banner、产品包装、海报、信息图、UI mockup、slides、配图。
    >
    > 核心心法一句话:**当模型足够强时,prompt 工程的瓶颈从「描述能力」转向「概念库储备」**。脑子里有多少精准设计师/品牌/工作室名字,决定 prompt 上限。
    
    ---
    
    ## 第一性原则:概念压缩比
    
    **Prompt 的有效信息 ≠ prompt 的字数。决定输出质量的是单字承载的设计决策密度。**
    
    实测案例:「Dieter Rams 风格的大疆 pocket3 说明书」16 个字 → 拿到完整 A4 说明书,含真实参数(1英寸 CMOS / 4K120fps / 139.7×42.2×33.5mm),视觉精度达到官方手册级别。
    
    原因拆解:每个词都是高密度语义锚点:
    
    | 词 | 打包了多少决策 |
    |---|---|
    | Dieter Rams 风格 | 几百条决策(色彩极简 / Braun 灰白红 / 等线无衬线 / 严谨网格 / functional clarity 哲学) |
    | 大疆 | 品牌识别 + 官方文档美学 + 中英混排习惯 |
    | pocket3 | 真实产品参数(模型有训练数据) |
    | 说明书 | A4 排版 / 章节编号 / 规格表 / 部件标注的全套范式 |
    
    **实操**:写 prompt 之前先问——「主体 / 风格 / 媒介」三个槽位,能不能各填一个模型熟知的高频名字?能 → 16 字 prompt 就够。不能 → 哪个槽位填不上,那个槽位必须用结构化描述补足。
    
    ---
    
    ## 三种工作模式(按你心里有什么选)
    
    | 模式 | 你心里有 | Prompt 形态 | 适合 |
    |---|---|---|---|
    | **执行** | 清晰的目标和参考 | 三层全给(上下文 + 约束 + 表面层完整) | 商单 / 品牌物料 / 已定 spec 的项目 |
    | **混合** | 方向但不定细节 | 上下文 + "不要什么",表面层留白 | 日常大部分情况 |
    | **探索** | 一个问题,没答案 | 只给上下文 + 防 slop + 要求模型先说判断 | 想看惊喜 / 找新方向 |
    
    **判断节奏**:
    - 不确定走什么风格 → 先用探索模式开 5-10 张看方向
    - 挑出最让你心动的方向 → 切到执行模式深化、批量、保稳定
    - 不要一上来就执行模式(会把模型按在「执行你的设计」位置,错过它能贡献的判断)
    
    ---
    
    ## 三层指令架构
    
    任何创意任务都按这个分层构造 prompt:
    
    1. **上下文层**:who(观众)/ why(解决什么问题)/ for what(看完做什么)。这是 GPS 坐标。
    2. **约束层**:必须 / 禁止 / 冲突时的优先级。这是边界条件。
    3. **表面层**:媒介 / 风格 / 参考 / 技术参数 / 色值字体。这是结果形态。
    
    业余 prompt 只写第 3 层("一张赛博朋克风格的海报")。专业 prompt 三层都给。
    
    ---
    
    ## 三维质量模型(conceptual blending 成败的真公式)
    
    ```
    输出质量 = 载体匹配度 × 精神理解深度 × 创造贡献空间
    ```
    
    ### 维度 1 · 载体匹配度
    
    风格的「**原生载体**」和你的「**目标载体**」是否同一类型。
    
    | 风格 | 原生载体 | 适配的目标 | 不适配的目标 |
    |---|---|---|---|
    | Helvetica | 排版系统 | 菜单 / 标识 / 文档 | 三维产品包装 |
    | Dieter Rams | 工业产品 | 包装 / 设备 / 家电 | 表格 / 价目表 |
    | Brutalism | 建筑空间 | 包装(三维实体)/ 海报 | 数字 UI |
    | The Criterion Collection | 收藏品出版 | 产品页 / 手册 / 海报 | 食品包装 |
    | Penguin Books | 书籍封面 | 手册 / 书籍 / 三段式构图 | 三维产品 |
    
    实操:选风格之前先想——这风格的设计师本来在设计什么?你的目标和那个本来的载体是不是同类?
    
    ### 维度 2 · 精神理解深度
    
    模型对风格「是什么」的理解深度(与训练数据相关):
    
    - **深度高**(设计史教科书级别概念):Bauhaus / Brutalism / Dieter Rams / Helvetica / Swiss International Style
    - **深度中**(高频但偏视觉呈现):Wes Anderson / Field Notes / The New Yorker
    - **深度浅**(停在表层标签):小众设计师 / 非西方设计运动 / 时代+地域组合
    
    实操:表层(如 Wes Anderson 的 deadpan melancholy)→ prompt 必须补结构化描述;精神层(如 Brutalism 的 truth to materials)→ 可以放手。
    
    ### 维度 3 · 创造贡献空间
    
    目标载体允不允许模型做品牌命名 / slogan 创作 / 结构隐喻。
    
    - **空间大**:包装(要品牌名)/ 海报(要 headline)/ 书封(要 series 名)→ 模型可贡献「禾筑」「本原」这种惊喜
    - **空间小**:菜单(菜名给定)/ 价目表(药材名固定)/ 个人简介(信息固定)→ 只能在视觉层发挥
    
    ---
    
    ## 风格的「结构性 vs 气质性」二分(关键)
    
    这是 conceptual blending 成败的核心二分。
    
    ### 结构性强的风格(模型能精准 transfer)
    
    特征:可枚举的视觉元素(色彩 / 字体 / 构图 / 编号 / 装饰),元素是「模块化」的,可拆下来安装到新主体上。
    
    代表:Penguin Books / The Criterion Collection / Dieter Rams / Field Notes / Massimo Vignelli / Bauhaus / MUJI
    
    **短 prompt 即可拿到 9 分以上**。
    
    ### 气质性强的风格(模型只能抓表层)
    
    特征:核心是「难以言传的氛围」,模型只能复制标签式特征(粉绿色对称代表 Wes Anderson),抓不到精神(deadpan + melancholy)。
    
    代表:Wes Anderson / 王家卫 / Tarkovsky / 老上海月份牌 / 日本浮世绘
    
    **必须补结构化描述 + 给模型多个具体作品参考**。
    
    ---
    
    ## 概念库(你脑子里必须储备的名字)
    
    按可 transferable 程度排:
    
    ### 第一梯队 · 结构最强,几乎必 work
    
    - **Penguin Books**(橙/米/黑三段式 + 版本注释系统 + MODERN [X] CLASSICS 命名传统)
    - **The Criterion Collection**(NO. 编号 + manifesto + critic quote + curated stamp)
    - **Field Notes**(手账 + 印章 + 季节版本编号)
    - **Dieter Rams / Braun**(极简 + 功能 hierarchy + 灰白红)
    - **Massimo Vignelli**(NYC Subway 那套 + Modernist grid + Helvetica)
    - **MUJI**(无品牌 + 牛皮纸 + 极简产品摄影 + 小字 spec)
    
    ### 第二梯队 · 结构中等,需要少量补充
    
    - **Pentagram**(设计工作室,case 多但风格多样)
    - **Phaidon / Taschen**(艺术出版品视觉)
    - **The New Yorker**(杂志封面 + 衬线 + 单插画)
    - **Wallpaper\* / Monocle**(生活方式杂志)
    - **National Geographic**(黄框 + 摄影叙事)
    - **Bauhaus / Swiss International Style**(几何 + 原色,但容易和邻近概念串台)
    - **Anthropic**(赤陶橙 + 黑线条 + 学术克制)
    - **Stripe**(紫蓝渐变 + 干净 grid + Sohne 字体)
    
    ### 第三梯队 · 气质性强,独立用风险大
    
    - **Wes Anderson / Jacques Tati / 王家卫**(影像气质)
    - **Tarkovsky / Bergman**(沉郁电影感)
    - **老上海月份牌 / 日本浮世绘 / Art Nouveau**(强地域 + 强时代气质)
    
    ### 中文设计语境(模型理解度不一)
    
    实测可用:MUJI 风格 / 无印良品风格 / 日式杂志风 / 朱赢椿装帧(部分)/ 靳与刘工作室
    不要轻信:模糊的"中国风" / "国潮"(这些词模型会输出非常套路化的红色金色大字)
    
    ---
    
    ## 关键 Prompt 模式
    
    ### 模式 1 · Conceptual Blending(跨界混搭)
    
    格式:`[Style] 风格的 [Subject] [Medium]`
    
    适用:三槽位都是模型熟知的高频概念。
    
    实测:
    - ✅ 「Dieter Rams 风格的大疆 pocket3 说明书」9.5 分
    - ✅ 「Penguin Books 风格的 Cursor 产品手册」9.5 分
    - ✅ 「The Criterion Collection 风格的 Cursor 产品页」9.5 分
    - ❌ 「Dieter Rams 风格的中药铺价目表」8 分(载体不匹配——Rams 设计产品,价目表是表格)
    
    ### 模式 2 · Invite 命名贡献(关键升级)
    
    当载体允许品牌命名时,主动 invite 模型贡献——**对照实验显示从 9 分升到 9.5 分,且输出从单图升级为完整品牌系统**。
    
    模板:
    ```
    [Style] 风格的 [Subject Category]。
    请你也贡献:
    - 一个嵌入风格精神的品牌名(中英文都给)
    - 一句呼应风格哲学的 slogan
    - 一段命名逻辑解释(为什么叫这个名字)
    - 一个证明你理解风格本质的隐喻细节
    ```
    
    实测案例:
    - 「Brutalism 风格的婴儿米粉包装」→ 模型自发贡献「禾筑」品牌名(禾 + 筑双关)
    - 「Apple 设计语言的中药包装 + invite 命名贡献」→ 模型贡献「本原 BENYUAN / 让身体回到本来的秩序」+ 完整命名逻辑 + 4 个品牌价值观图标 + 三种 SKU 规格(30/90/300mL)
    
    意外发现:当 invite 命名时,模型会**自动构建多 SKU 产品线**(不是单一包装),输出从「一张图」升级为「完整品牌提案」。
    
    ### 模式 3 · 反对模型内置和谐倾向
    
    模型遇到风格 vs 品类冲突时会自动妥协(Apple 中药会变温润,不是科学冷硬)。如果想要冲突感:
    
    ```
    [Style] 风格的 [Category]。
    
    注意:保留 [Style] 的 [核心精神] 不动摇,不要因为是 [Category] 就妥协化。
    冲突时优先保留 [Style] 精神,让 [Category] 去适应。
    我要的是「[Style] 公司做了 [Category]」,不是「[Category] 借鉴 [Style]」。
    ```
    
    ### 模式 4 · 强制减法
    
    模型默认会把所有好 idea 都塞进去("more"在训练数据里被奖励)。强制减法解决"过满"问题:
    
    ```
    出图前先做这件事:
    1. 列出你想放进画面的所有元素(至少 8 个)
    2. 砍掉 50%,只留 4 个
    3. 说明「为什么留下的比砍掉的更有信息量」
    4. 再生成
    砍的标准:删掉之后画面信息没有损失,就该删。
    ```
    
    ### 模式 5 · 陷阱预警
    
    让模型先识别这个方向最容易踩的刻板印象陷阱(用模型的元认知做品位防御):
    
    ```
    在生成之前先回答:
    - 「[这个方向]」最容易踩的 3 个刻板印象陷阱是什么?
    (例:复古杂志容易滑向"刻意复古的视觉梗"如"真香"印章)
    - 你打算怎么避开?
    ```
    
    ### 模式 6 · 真实性细节锚点
    
    ```
    画面里必须包含至少一个「具体到不能再具体」的真实细节:
    - 一个真实存在的版本号(如 Cursor 0.42.3)
    - 一个具体日期(如 2026-04-12 14:32)
    - 一个具体型号(如 MacBook Pro M3 14")
    - 一个真实价格(如 $20/mo)
    让画面从"看起来像"变成"看起来是真发生过的"。
    ```
    
    ### 模式 7 · 探索模式(找方向用)
    
    ```
    任务:[简单描述]
    
    上下文:[who / why / for what]
    
    只有两条硬限制:
    - 别做 AI cliché(齿轮/电路/大脑/紫渐变/赛博)
    - [其他平台硬限制]
    
    剩下的全部交给你。我想看你怎么判断这几件事:
    - 这个情绪在视觉上最准的传达方式是什么?
    - 这种内容放在哪个文化语境的美学里最合适?
    - 字体、色调、构图你怎么决定?
    
    给我 3 个差异化方向(不是 3 个版本),每个方向先用一句话说清你的判断逻辑,再上图。
    我想看你的判断,不要给我「安全的好看」。
    ```
    
    ---
    
    ## 平台尺寸速查(开 prompt 之前必查)
    
    **这一条是硬底线**——平台尺寸不是「表面层」可放手的部分,错了图就废了。涉及具体平台时一定先核实:
    
    | 平台 / 用途 | 比例 | 推荐像素 |
    |---|---|---|
    | 公众号文章首图 | 2.35 : 1 | **1410 × 600** |
    | 公众号文章配图 | 不限 | 视场景,建议 1080 宽以上 |
    | 小红书封面 | 3 : 4 | **1242 × 1660** |
    | 小红书横版 | 4 : 3 | 1242 × 932 |
    | YouTube 视频封面 | 16 : 9 | **1280 × 720** |
    | YouTube banner | 16 : 9 | **2560 × 1440**(安全区 1546 × 423)|
    | B站视频封面 | 16 : 10 | **1280 × 800** |
    | B站直播封面 | 16 : 9 | 800 × 450 |
    | 抖音视频封面 | 9 : 16 | **1080 × 1920** |
    | Twitter / X 帖子图 | 16 : 9 或 1:1 | 1600 × 900 / 1080 × 1080 |
    | 头图 banner(网页) | 视场景 | 1920 × 480 / 2560 × 600 |
    | Slides / PPT | 16 : 9 | **1920 × 1080** |
    
    **多端适配特别警告**(YouTube banner 这种):
    - 总画布 2560 × 1440
    - 中央安全区 1546 × 423 是**任何设备都能看到的部分**,必须收纳所有核心信息
    - 桌面/平板/TV 看到更多,但**安全区外的内容不能依赖**
    
    ---
    
    ## 单图生成的常见踩坑
    
    | 踩坑 | 解法 |
    |---|---|
    | 不核实平台尺寸就开 prompt | 先 WebSearch 验证(公众号封面是 2.35:1 不是 1.8:1) |
    | 相信模型给出的设计史细节(口号/年代/合作品牌) | 自己核实,模型会串台(Bauhaus 串到 Dieter Rams 的 "Less but better") |
    | 期待气质性风格被精准 transfer | 气质性风格补结构化描述 + 多个具体作品参考 |
    | 让模型决定品牌名却不主动 invite | 明确写出"请你贡献品牌名 + slogan + 命名逻辑" |
    | 一次性 prompt 期待完美 | 探索→挑方向→执行三段式 |
    | 看到模型加了多余的"安全好看"元素 | 用「强制减法」模式,让模型自己砍 |
    | 风格 vs 品类有冲突但模型自动和谐了 | 用「反对内置和谐」模式 |
    | 写了一堆形容词描述风格 | 形容词换成 1 个真实参考名字 |
    | 列了 3 个参考结果输出在三者中游移 | 改用 1 个最强参考 + NOT 列表 |
    
    ---
    
    ## 邻近概念串台警告(已知混淆对)
    
    模型在训练语料里高度共现的概念会串台。引用具体细节时必须自己核实:
    
    - 包豪斯 ↔ 瑞士国际主义(Müller-Brockmann)
    - 包豪斯 ↔ Dieter Rams("Less but better" 是 Rams 不是 Bauhaus)
    - Pentagram ↔ Sagmeister(都是设计 superstar)
    - 老上海 ↔ 民国年代 ↔ Art Deco
    - Wes Anderson ↔ Jacques Tati(都对称 + 复古 + deadpan)
    - The Criterion Collection ↔ MUBI(都是 curated cinema)
    - 朱赢椿 ↔ 吕敬人(都是中国书籍装帧大师)
    
    ---
    
    ## 4 个高频场景的「真问题」对照(给 prompt 瞄准)
    
    ### 场景 1 · 海报 / 公众号封面
    
    - **表面问题**:视觉风格
    - **真问题**:信息等级 + 情绪反应(1 秒内传递核心信息 + 引发情感反应)
    - Prompt 必交代:观众 / 3 秒后要记住什么 / 看完做什么动作 / 参考某海报家方法
    
    ### 场景 2 · App / Web UI 原型
    
    - **表面问题**:UI 美观
    - **真问题**:用户在这屏的决策成本
    - Prompt 必交代:用户旅程位置 / 上一屏做了什么 / 这屏要解决的决策 / 品牌人格 / 竞品参照
    
    ### 场景 3 · 信息图
    
    - **表面问题**:漂亮
    - **真问题**:把复杂关系翻译成一眼可见的结构
    - Prompt 必交代:关系类型(流程 / 对比 / 因果 / 层级)/ 最核心的"洞察" / 图表语言参考
    
    ### 场景 4 · 视频 / 动画封面 / 片头
    
    - **表面问题**:画面好看
    - **真问题**:节奏控制 + 信息时序
    - Prompt 必交代:是片头 / 转场 / 收尾 / 情绪曲线 / 参考某种剪辑语言 / 单镜头时长意图
    
    ---
    
    ## 工作流(标准操作)
    
    1. **判断模式**:清晰目标 → 执行;找方向 → 探索;多数日常 → 混合
    2. **核实平台尺寸**:WebSearch 或查上面表格,不要凭记忆
    3. **检查三槽位**:风格名 / 主体名 / 媒介名,能不能各填一个模型熟知的高频概念?
       - 三槽位都强 → 16 字 prompt 试一下
       - 有空槽 → 那个槽位补结构化描述
    4. **若需要品牌叙事**:用 invite 命名贡献模式
    5. **生成前**:让模型先做减法判断 + 陷阱预警
    6. **生成后评分**:按三维质量模型评(载体匹配 / 精神理解 / 创造贡献)
    7. **不满意时诊断哪一层错了**:上下文层错(情绪/受众判断)→ 重写上下文;表面层错(字体/色彩)→ 调表面;约束层错(slop 没防住)→ 加禁止项
    8. **挑定方向后**:切执行模式深化和批量,保证稳定输出
    
    ---
    
    ## 关键认知收尾
    
    **当 AI 图像模型足够强时,prompt 工程的瓶颈从「描述能力」转向「概念库储备」**。
    
    你脑子里能调出来多少个精准的设计师名字、品牌名字、媒介范式名字,决定了你能压缩 prompt 的上限。
    
    最值得长期投入的不是研究 prompt 技巧,是研究每个领域的真知识——读 Dieter Rams 的《Less and More》、读 Massimo Vignelli《Vignelli Canon》、看 The Criterion Collection 的封面合集、把 Penguin Books / Field Notes / MUJI 的视觉系统拆解清楚。这些名字沉淀进脑子里,prompt 自然就有锐度。
    
  • references/video-reference-board-sample.png2.3 MBccd0490ce5f8upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • references/video-reference-board-success-pipa.png2.5 MB1a56d17f7333upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • references/video-reference-board.md13 KB698e7b8974c7upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • .gitignore236 B437a4ea71415upstream ↗
    # 测试产出的图片(根目录散落,SKILL.md 与 references 均未引用)
    /*.png
    # 编辑器备份
    *.bak
    *.bak.*
    .DS_Store
    __pycache__/
    *.pyc
    # 本地环境
    .env
    # 生成输出与临时目录
    /output/
    /tmp/
    .last-update-check
    
  • chatgpt-web-bridge/extension/background.js6.2 KB84e1a82b7e81upstream ↗
    // background.js — service worker:本地桥 <-> chatgpt.com 标签页 的调度中枢
    //
    // 流程:长轮询 server 的 /poll 拿任务 → 找/建 chatgpt.com 标签 → 把任务交给 content.js
    //       → content.js 生完图通过 runtime 消息回传 → 这里取图片字节 → POST /result。
    //
    // MV3 service worker 随时可能被杀,所以:
    //   1. 用 chrome.alarms 每 20s 唤醒一次,重启轮询循环;
    //   2. 当前任务存在 storage.session,SW 重启后不会重复派发;
    //   3. content.js 用 runtime.sendMessage 回传结果,这个动作本身会唤醒被杀的 SW。
    
    const DEFAULT_BRIDGE = "http://127.0.0.1:8765";
    // 桥要求这个自定义头:网页发不出带自定义头的「简单请求」,所以这一行就把
    // 「任意网页偷 /poll 里的任务」这条路堵死了(详见 server.py 的「三道门」)。
    const CLIENT_HEADERS = { "X-Bridge-Client": "huashu-gpt-image-ext" };
    
    async function bridgeBase() {
      const { bridgeUrl } = await chrome.storage.local.get("bridgeUrl");
      return (bridgeUrl || DEFAULT_BRIDGE).replace(/\/+$/, "");
    }
    
    let polling = false;
    
    async function pollLoop() {
      if (polling) return;
      polling = true;
      try {
        // 有正在处理的任务就先不抢新活
        const { activeJob } = await chrome.storage.session.get("activeJob");
        if (activeJob) {
          polling = false;
          return;
        }
        const base = await bridgeBase();
        let resp;
        try {
          resp = await fetch(base + "/poll", { method: "GET", headers: CLIENT_HEADERS });
        } catch (e) {
          // 桥没开,安静退出,等下次 alarm 再试
          polling = false;
          return;
        }
        if (resp.status === 204) {
          polling = false;
          // 立刻再轮询,保持 SW 温热
          setTimeout(() => pollLoop(), 50);
          return;
        }
        if (!resp.ok) {
          // 静默 return 会让「桥被 403 挡住」完全没有现场:popup 显示在线,生图全部超时。
          console.warn("[bridge] /poll 被拒:", resp.status, "— 若刚升级过 server.py,去 chrome://extensions 重载本扩展");
          polling = false;
          return;
        }
        const job = await resp.json();
        await chrome.storage.session.set({ activeJob: job });
        polling = false;
        dispatchJob(job).catch((e) => reportError(job.jobId, String(e)));
      } catch (e) {
        polling = false;
      }
    }
    
    async function ensureChatGptTab() {
      const tabs = await chrome.tabs.query({ url: ["https://chatgpt.com/*", "https://chat.openai.com/*"] });
      if (tabs.length) return tabs[0];
      const tab = await chrome.tabs.create({ url: "https://chatgpt.com/", active: false });
      // 等加载完成
      await new Promise((resolve) => {
        const listener = (tabId, info) => {
          if (tabId === tab.id && info.status === "complete") {
            chrome.tabs.onUpdated.removeListener(listener);
            resolve();
          }
        };
        chrome.tabs.onUpdated.addListener(listener);
        setTimeout(resolve, 15000); // 兜底
      });
      return tab;
    }
    
    async function dispatchJob(job) {
      const tab = await ensureChatGptTab();
      // content.js 是声明式注入,但保险起见再注入一次(幂等)
      try {
        await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ["content.js"] });
      } catch (e) { /* 已注入会报错,忽略 */ }
      // 重试几次 sendMessage,给页面留出 ready 时间
      for (let i = 0; i < 5; i++) {
        try {
          await chrome.tabs.sendMessage(tab.id, { type: "GENERATE", job });
          return; // 派发成功;结果走 runtime 消息回来
        } catch (e) {
          await new Promise((r) => setTimeout(r, 1500));
        }
      }
      await reportError(job.jobId, "无法把任务交给 chatgpt.com 标签页(content.js 未就绪)");
    }
    
    // content.js 生完图回传
    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg && msg.type === "GENERATE_RESULT") {
        handleResult(msg).finally(() => sendResponse({ ack: true }));
        return true; // 异步
      }
    });
    
    async function handleResult(msg) {
      const { jobId, ok, imageUrl, imageBase64, mime, error } = msg;
      // resultToken 必须在清掉 activeJob 之前取出来,否则回 /result 会被桥 403
      const resultToken = await tokenFor(jobId);
      try {
        if (!ok) {
          await reportError(jobId, error || "content.js 报告失败", resultToken);
          return;
        }
        let b64 = imageBase64;
        let m = mime || "image/png";
        if (!b64 && imageUrl) {
          const r = await fetch(imageUrl); // SW 有 oaiusercontent host 权限
          const blob = await r.blob();
          m = blob.type || m;
          b64 = await blobToBase64(blob);
        }
        if (!b64) {
          await reportError(jobId, "没拿到图片字节", resultToken);
          return;
        }
        await postResult({ jobId, resultToken, ok: true, imageBase64: b64, mime: m });
      } catch (e) {
        await reportError(jobId, "取图片字节失败: " + String(e), resultToken);
      } finally {
        await chrome.storage.session.remove("activeJob");
        setTimeout(() => pollLoop(), 50);
      }
    }
    
    // 当前任务的 resultToken(存在 storage.session 的 activeJob 里,随任务一起从 /poll 拿到)
    async function tokenFor(jobId) {
      const { activeJob } = await chrome.storage.session.get("activeJob");
      return activeJob && activeJob.jobId === jobId ? activeJob.resultToken : undefined;
    }
    
    function blobToBase64(blob) {
      return new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onload = () => resolve(String(fr.result).split(",")[1]);
        fr.onerror = reject;
        fr.readAsDataURL(blob);
      });
    }
    
    async function postResult(payload) {
      const base = await bridgeBase();
      try {
        await fetch(base + "/result", {
          method: "POST",
          headers: { "Content-Type": "application/json", ...CLIENT_HEADERS },
          body: JSON.stringify(payload),
        });
      } catch (e) { /* 桥可能已关,忽略 */ }
    }
    
    async function reportError(jobId, error, resultToken) {
      const token = resultToken ?? (await tokenFor(jobId));
      await chrome.storage.session.remove("activeJob");
      await postResult({ jobId, resultToken: token, ok: false, error });
      setTimeout(() => pollLoop(), 50);
    }
    
    // 保活 + 启动
    chrome.alarms.create("keepalive", { periodInMinutes: 0.34 }); // ~20s
    chrome.alarms.onAlarm.addListener(() => pollLoop());
    chrome.runtime.onStartup.addListener(() => pollLoop());
    chrome.runtime.onInstalled.addListener(() => pollLoop());
    pollLoop();
    
  • chatgpt-web-bridge/extension/content.js6.1 KB753b81a84765upstream ↗
    // content.js — 在 chatgpt.com 页面里真正驱动生图的部分。
    //
    // ⚠️ 选择器是最脆弱的一环:ChatGPT 改版会失效。本文件把所有 DOM 依赖收敛到顶部
    //    SELECTORS 常量 + 几个 find* 函数,改版时只改这里。带 ❗TODO 的需对着真实 DOM 校准。
    //
    // 流程:收到 background 的 GENERATE → 填 prompt(如有参考图先附上)→ 点发送
    //      → 等最后一条 assistant 消息里出现生成图 → 把图 URL/字节回传 background。
    
    const SELECTORS = {
      // ❗TODO(DOM校准): 输入框。当前 chatgpt.com 是 ProseMirror contenteditable
      composer: [
        'div#prompt-textarea[contenteditable="true"]',
        'div.ProseMirror[contenteditable="true"]',
        'textarea#prompt-textarea',
        'textarea[data-testid="prompt-textarea"]',
      ],
      // ❗TODO(DOM校准): 发送按钮
      sendButton: [
        'button[data-testid="send-button"]',
        'button[aria-label*="Send"]',
        'button[aria-label*="发送"]',
      ],
      // ❗TODO(DOM校准): 附件/上传图片的 file input(图生图用)
      fileInput: [
        'input[type="file"]',
      ],
      // ❗TODO(DOM校准): assistant 回复气泡
      assistantTurn: [
        'div[data-message-author-role="assistant"]',
        'article[data-testid^="conversation-turn"]',
      ],
      // 生成出来的图:oaiusercontent / files 域,或 alt 含 generated
      generatedImg: [
        'img[src*="oaiusercontent.com"]',
        'img[src*="/backend-api/"]',
        'img[alt*="Generated"]',
      ],
    };
    
    const GEN_TIMEOUT_MS = 240000; // 等生图 240s
    
    function $first(selList, root = document) {
      for (const sel of selList) {
        const el = root.querySelector(sel);
        if (el) return el;
      }
      return null;
    }
    
    function sleep(ms) {
      return new Promise((r) => setTimeout(r, ms));
    }
    
    // ---- 填入 prompt ----
    async function setComposerText(text) {
      const el = $first(SELECTORS.composer);
      if (!el) throw new Error("找不到输入框(composer 选择器需校准)");
      el.focus();
      if (el.tagName === "TEXTAREA") {
        const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
        setter.call(el, text);
        el.dispatchEvent(new Event("input", { bubbles: true }));
      } else {
        // contenteditable / ProseMirror:用 execCommand 插入最稳,事件链完整
        el.textContent = "";
        document.execCommand("insertText", false, text);
        el.dispatchEvent(new InputEvent("input", { bubbles: true }));
      }
      await sleep(150);
    }
    
    // ---- 附参考图(图生图,实验性)----
    async function attachRefImages(refImages) {
      if (!refImages || !refImages.length) return;
      const input = $first(SELECTORS.fileInput);
      if (!input) throw new Error("找不到文件上传 input(图生图选择器需校准)");
      const dt = new DataTransfer();
      for (const ref of refImages) {
        const bin = atob(ref.b64);
        const arr = new Uint8Array(bin.length);
        for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
        dt.items.add(new File([arr], ref.name || "ref.png", { type: ref.mime || "image/png" }));
      }
      input.files = dt.files;
      input.dispatchEvent(new Event("change", { bubbles: true }));
      await sleep(3000); // 等上传完成(粗略)
    }
    
    // ---- 点发送 ----
    async function clickSend() {
      const btn = $first(SELECTORS.sendButton);
      if (btn && !btn.disabled) {
        btn.click();
        return;
      }
      // 兜底:回车
      const el = $first(SELECTORS.composer);
      if (el) {
        el.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
      }
    }
    
    // ---- 等生成图出现 ----
    function waitForImage(sinceCount) {
      return new Promise((resolve, reject) => {
        const t0 = Date.now();
        const tick = () => {
          // 找最后一条 assistant 回复里的生成图
          const turns = [];
          for (const sel of SELECTORS.assistantTurn) {
            document.querySelectorAll(sel).forEach((n) => turns.push(n));
          }
          const last = turns[turns.length - 1];
          if (last) {
            const img = $first(SELECTORS.generatedImg, last) || $first(SELECTORS.generatedImg);
            if (img && img.src && img.naturalWidth > 32) {
              resolve(img.src);
              return;
            }
            // ❗TODO(DOM校准): 检测限流/报错文案
            const txt = (last.innerText || "").toLowerCase();
            if (/rate limit|too many|limit reached|额度|上限|稍后再试|try again later/.test(txt)) {
              reject(new Error("网页版报限流/上限:" + txt.slice(0, 120)));
              return;
            }
          }
          if (Date.now() - t0 > GEN_TIMEOUT_MS) {
            reject(new Error("等生成图超时(" + GEN_TIMEOUT_MS / 1000 + "s)"));
            return;
          }
          setTimeout(tick, 1200);
        };
        tick();
      });
    }
    
    // ---- 尽量在页面内把图转成 base64(拿不到就回传 URL 让 background 取)----
    async function tryFetchBytes(url) {
      try {
        const r = await fetch(url);
        const blob = await r.blob();
        const b64 = await new Promise((res, rej) => {
          const fr = new FileReader();
          fr.onload = () => res(String(fr.result).split(",")[1]);
          fr.onerror = rej;
          fr.readAsDataURL(blob);
        });
        return { b64, mime: blob.type || "image/png" };
      } catch (e) {
        return null;
      }
    }
    
    async function runJob(job) {
      await setComposerText(job.prompt);
      if (job.refImages && job.refImages.length) {
        await attachRefImages(job.refImages);
        await setComposerText(job.prompt); // 上传可能清空输入,补一次
      }
      await sleep(300);
      await clickSend();
      const url = await waitForImage();
      const bytes = await tryFetchBytes(url);
      return {
        imageUrl: url,
        imageBase64: bytes ? bytes.b64 : null,
        mime: bytes ? bytes.mime : "image/png",
      };
    }
    
    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      if (msg && msg.type === "GENERATE") {
        sendResponse({ ack: true }); // 立刻 ack,避免长任务卡住消息通道
        runJob(msg.job)
          .then((res) => {
            chrome.runtime.sendMessage({ type: "GENERATE_RESULT", jobId: msg.job.jobId, ok: true, ...res });
          })
          .catch((err) => {
            chrome.runtime.sendMessage({ type: "GENERATE_RESULT", jobId: msg.job.jobId, ok: false, error: String(err && err.message || err) });
          });
        return true;
      }
    });
    
  • chatgpt-web-bridge/extension/manifest.json814 B8e3ba3477bf0upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • chatgpt-web-bridge/extension/popup.html754 B0f84ce2424c4upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • chatgpt-web-bridge/extension/popup.js1.5 KBc626560a5873upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • chatgpt-web-bridge/server.py14 KB593876b4f9e6upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • LICENSE1.0 KB8a8576ce865bupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • SECURITY.md3.0 KB0b7b26ebc3e1upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • test-prompts.json960 B9de3a343e76aupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

Plain-text copy, no login: /skill-bundles/alchaincyf-huashu-gpt-image.md

// REPO STATS

29 stars