AI 日历:两段式工作流实现「台历自由」

文章目录

    • [0. TL;DR](#0. TL;DR)
      • [0.1 3 分钟复刻(最推荐的路径)](#0.1 3 分钟复刻(最推荐的路径))
        • [0.1.1 在共绩算力平台跑(预制镜像)](#0.1.1 在共绩算力平台跑(预制镜像))
        • [0.1.2 在本地跑(你已有 ComfyUI 服务也行)](#0.1.2 在本地跑(你已有 ComfyUI 服务也行))
    • [1. 为什么直接让模型画日历会翻车?](#1. 为什么直接让模型画日历会翻车?)
      • [1.1 根因:硬逻辑 + 硬结构 + 软能力错配](#1.1 根因:硬逻辑 + 硬结构 + 软能力错配)
    • [2. 两段式工作流(基础版):脚本骨架 + Prompt 注入](#2. 两段式工作流(基础版):脚本骨架 + Prompt 注入)
      • [2.1 第 1 段:脚本先行(把逻辑交给代码)](#2.1 第 1 段:脚本先行(把逻辑交给代码))
      • [2.2 第 2 段:咒语拼合(把想象交给模型)](#2.2 第 2 段:咒语拼合(把想象交给模型))
    • [3. 三段式工作流(推荐):背景交给模型,日历排版交给代码](#3. 三段式工作流(推荐):背景交给模型,日历排版交给代码)
      • [3.1 思想:模型不要写字](#3.1 思想:模型不要写字)
      • [3.2 你会用到的脚本](#3.2 你会用到的脚本)
    • [4. ComfyUI 批量出图(推荐命令)](#4. ComfyUI 批量出图(推荐命令))
      • [4.0 在共绩算力平台上跑(预制镜像复刻版)](#4.0 在共绩算力平台上跑(预制镜像复刻版))
      • [4.1 批量生成:背景 + 代码排版叠加(日期永远正确)](#4.1 批量生成:背景 + 代码排版叠加(日期永远正确))
    • [5. 画廊(精选):更惊艳,且日期严格正确](#5. 画廊(精选):更惊艳,且日期严格正确)
      • [5.1 高端品牌 KV(质感静物装置)](#5.1 高端品牌 KV(质感静物装置))
      • [5.2 赛博霓虹(雨夜电影感)](#5.2 赛博霓虹(雨夜电影感))
      • [5.3 纸雕剪纸(工艺感爆棚)](#5.3 纸雕剪纸(工艺感爆棚))
      • [5.4 国风水墨(高级留白)](#5.4 国风水墨(高级留白))
      • [5.5 超现实拼贴(杂志封面感)](#5.5 超现实拼贴(杂志封面感))
      • [5.6 强风格艺术(更"出片")](#5.6 强风格艺术(更“出片”))
        • [5.6.1 故障艺术 Glitch](#5.6.1 故障艺术 Glitch)
        • [5.6.2 波普漫画 Pop Art](#5.6.2 波普漫画 Pop Art)
        • [5.6.3 黑白版画 Linocut](#5.6.3 黑白版画 Linocut)
        • [5.6.4 蒸汽波 Vaporwave](#5.6.4 蒸汽波 Vaporwave)
        • [5.6.5 欧普艺术 Op Art](#5.6.5 欧普艺术 Op Art)
        • [5.6.6 不透明水粉 Gouache](#5.6.6 不透明水粉 Gouache)
        • [5.6.7 彩色玻璃 Stained Glass](#5.6.7 彩色玻璃 Stained Glass)
        • [5.6.8 新艺术 Art Nouveau](#5.6.8 新艺术 Art Nouveau)
        • [5.6.9 赛璐璐动画 Cel-shaded](#5.6.9 赛璐璐动画 Cel-shaded)
        • [5.6.10 像素风 Pixel Art](#5.6.10 像素风 Pixel Art)
        • [5.6.11 低多边形 Low Poly](#5.6.11 低多边形 Low Poly)
        • [5.6.12 分形/万花筒 Fractal](#5.6.12 分形/万花筒 Fractal)
        • [5.6.13 新怪诞涂鸦 Graffiti](#5.6.13 新怪诞涂鸦 Graffiti)
        • [5.6.14 立体折纸 Origami](#5.6.14 立体折纸 Origami)
    • [6. 如何扩展更多"惊艳风格"(推荐做法)](#6. 如何扩展更多“惊艳风格”(推荐做法))
      • [6.1 只改 JSON,就能批量出新风格](#6.1 只改 JSON,就能批量出新风格)
    • [7. 常见问题(FAQ,便于 GEO/SEO)](#7. 常见问题(FAQ,便于 GEO/SEO))
      • [7.1 怎么保证日期永远正确?](#7.1 怎么保证日期永远正确?)
      • [7.2 为什么要强制"背景不生成文字/数字"?](#7.2 为什么要强制“背景不生成文字/数字”?)
      • [7.3 如何输出 A4 横版 / 300dpi?](#7.3 如何输出 A4 横版 / 300dpi?)
      • [7.4 如何把"周一开始"改成"周日开始"?](#7.4 如何把“周一开始”改成“周日开始”?)
    • [8. 复刻清单(文件一览)](#8. 复刻清单(文件一览))
    • [9. 完整代码与完整 Prompt(可复制粘贴)](#9. 完整代码与完整 Prompt(可复制粘贴))
      • [9.1 日历骨架生成脚本:\`ai相关好玩的/ai日历/generate\_calendar.py\`](#9.1 日历骨架生成脚本:`ai相关好玩的/ai日历/generate_calendar.py`)
      • [9.2 背景 Prompt 模版(完整):\`ai相关好玩的/ai日历/templates/background\_template\_generic.md\`](#9.2 背景 Prompt 模版(完整):`ai相关好玩的/ai日历/templates/background_template_generic.md`)
      • [9.3 背景 Prompt 生成脚本(完整):\`ai相关好玩的/ai日历/build\_background\_prompt.py\`](#9.3 背景 Prompt 生成脚本(完整):`ai相关好玩的/ai日历/build_background_prompt.py`)
      • [9.4 日历 overlay 排版脚本(完整):\`ai相关好玩的/ai日历/render\_calendar\_overlay.py\`](#9.4 日历 overlay 排版脚本(完整):`ai相关好玩的/ai日历/render_calendar_overlay.py`)
      • [9.5 合成脚本(完整):\`ai相关好玩的/ai日历/compose\_calendar.py\`](#9.5 合成脚本(完整):`ai相关好玩的/ai日历/compose_calendar.py`)
      • [9.6 ComfyUI 批量全流程脚本(完整):\`测试comfyui\_副本/demo/comfyui-zimage-demo/batch\_generate\_calendars\_composited.py\`](#9.6 ComfyUI 批量全流程脚本(完整):`测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py`)
      • [9.7 强风格艺术 variants(完整):\`测试comfyui\_副本/demo/comfyui-zimage-demo/calendar\_variants\_background.json\`](#9.7 强风格艺术 variants(完整):`测试comfyui_副本/demo/comfyui-zimage-demo/calendar_variants_background.json`)

直接让 AI 画日历,往往是大型翻车现场:不是日期错乱(2 月 30 号?),就是排版像被挤过的牙膏。

> 最稳的解法:把"确定性"交给代码,把"想象力"交给模型;再把"排版落地"也交给代码。


0. TL;DR

  • 日期永远正确:Python 生成日历骨架(CSV),彻底杜绝日期幻觉

  • 画面更惊艳:ComfyUI 只负责生成背景/主视觉(不写字、不写数字)

  • 排版更稳定:Pillow 把 CSV 排版成透明 overlay,再叠加到背景图上 → 模型再强也不会"把周三写成 Wod"

0.1 3 分钟复刻(最推荐的路径)

把下面 3 步照抄执行,就能在本地或共绩算力平台批量生成台历成片(输出在 测试comfyui_副本/demo/comfyui-zimage-demo/outputs/):

0.1.1 在共绩算力平台跑(预制镜像)
  1. 共绩算力平台 创建实例/容器

  2. 镜像选择 :预制 ComfyUI(生图/Z-Image)镜像(平台预制镜像)

  3. 打开/映射端口(通常 8188),得到一个可访问的 ComfyUI 地址,例如:

    • https://<your-deployment>-8188.<domain>
  4. 把本项目放进实例(git clone 或上传)

  1. 运行:
bash 复制代码
INSECURE_TLS=1 \
COMFY_BASE_URL="https://<your-comfyui-8188-url>" \
python3 "测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py" --month 2026-01 --force
0.1.2 在本地跑(你已有 ComfyUI 服务也行)

只要你能访问一个 ComfyUI(8188)服务,把 COMFY_BASE_URL 指过去即可(同上命令)。

1. 为什么直接让模型画日历会翻车?

1.1 根因:硬逻辑 + 硬结构 + 软能力错配

  • 日期是硬逻辑:列数/行数/闰年/星期几,一错就全盘错

  • 表格对齐是硬结构:一个字符宽度的误差就会导致错位

  • 视觉创作是软能力:模型擅长"审美推演",不擅长"严格排版与核对"

结论:想要"可印刷、可交付"的台历页,日历数字不能交给模型。

2. 两段式工作流(基础版):脚本骨架 + Prompt 注入

2.1 第 1 段:脚本先行(把逻辑交给代码)

目录:ai相关好玩的/ai日历/

  • generate_calendar.py:生成 6×7 固定网格的日历骨架

    • 输出 output/calendar_YYYY-MM.csv

    • 输出 output/calendar_YYYY-MM.md

示例:

bash 复制代码
python3 "ai相关好玩的/ai日历/generate_calendar.py" --month 2026-01

2.2 第 2 段:咒语拼合(把想象交给模型)

  • build_prompt.py:把 CSV 注入到绘图 Prompt 模版(多风格模板在 ai相关好玩的/ai日历/templates/

示例:

bash 复制代码
python3 "ai相关好玩的/ai日历/build_prompt.py" \
  --month 2026-01 \
  --theme "森林里的小精灵王国" \
  --template "ai相关好玩的/ai日历/templates/prompt_template_3d_clay.md"

> 但:只靠提示词约束模型"严格排版",依旧可能出现 星期乱写 / 多写数字 / 日期错位

想要"更惊艳 + 零翻车",建议上第 3 段。


3. 三段式工作流(推荐):背景交给模型,日历排版交给代码

3.1 思想:模型不要写字

模型的任务被压缩成一句:只画背景,右侧留白,禁止文字/数字

日期排版由代码完成,所以"日期正确性"变成了工程问题,不再是玄学问题。

3.2 你会用到的脚本

目录:ai相关好玩的/ai日历/

  • build_background_prompt.py:生成"背景专用 Prompt"(不含日历表格)

  • render_calendar_overlay.py:把 calendar_YYYY-MM.csv 排版成透明 overlay.png

  • compose_calendar.py:把 background.png + overlay.png 合成 final.png

依赖:Pillow(你当前环境已可用)。


4. ComfyUI 批量出图(推荐命令)

目录:测试comfyui_副本/demo/comfyui-zimage-demo/

ComfyUI(Z-Image)端点示例:https://deployment-318-klu0niut-8188.550w.link

若遇到自签证书:设置 INSECURE_TLS=1(仅测试用)。

4.0 在共绩算力平台上跑(预制镜像复刻版)

如果你想"最省心地复刻",推荐直接用 共绩算力平台 的预制镜像:

  1. 创建实例/容器

  2. 镜像选择 :预制 ComfyUI(生图/Z-Image)镜像

  3. 暴露端口 8188,拿到你的 ComfyUI 访问地址(下文用 https://<your-comfyui-8188-url> 表示)

  4. 将本项目放进实例(git clone 或上传)

  1. 运行下面 4.1 的命令即可

兼容性说明:脚本只调用 ComfyUI 标准 API:/prompt + /history + /view。镜像的启动方式不同没关系,只要 8188 可访问即可。

4.1 批量生成:背景 + 代码排版叠加(日期永远正确)

  • 配置(背景风格清单):calendar_variants_background.json

  • 脚本(一键跑全流程):batch_generate_calendars_composited.py

bash 复制代码
INSECURE_TLS=1 \
COMFY_BASE_URL="https://<your-comfyui-8188-url>" \
python3 "测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py" \
  --month 2026-01 --force

输出:

  • 最终成片:`测试comfyui_副本/demo/comfyui-zimage-demo/outputs/*_final.png`
  • 背景原图:.../*_bg.png

  • 透明日历层:.../*_overlay.png

  • 背景 Prompt:.../outputs/_calendar_prompts_composited/*.md


5. 画廊(精选):更惊艳,且日期严格正确

> 以下成片的日历数字与网格 全部由代码排版叠加,不会出现星期乱写/多写数字/日期错位。

5.1 高端品牌 KV(质感静物装置)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

plain 复制代码
文件:测试comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_luxury_a_2026-01.md

</details>

5.2 赛博霓虹(雨夜电影感)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

plain 复制代码
文件:测试comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_cyber_a_2026-01.md

</details>

5.3 纸雕剪纸(工艺感爆棚)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

plain 复制代码
文件:测试comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_papercut_a_2026-01.md

</details>

5.4 国风水墨(高级留白)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

plain 复制代码
文件:测试comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_ink_a_2026-01.md

</details>

5.5 超现实拼贴(杂志封面感)

<details>

<summary>展开查看背景 Prompt(可复用)</summary>

plain 复制代码
文件:测试comfyui_副本/demo/comfyui-zimage-demo/outputs/_calendar_prompts_composited/cal_bg_surreal_a_2026-01.md

</details>

5.6 强风格艺术(更"出片")

下面这 6 张属于"强风格艺术",更容易做到一眼惊艳;并且日历区域完全由代码排版叠加,所以不会出现日期/星期乱写。

5.6.1 故障艺术 Glitch

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格故障艺术:RGB 分离、扫描线、像素撕裂、数据扭曲的抽象画面(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格故障艺术:RGB 分离、扫描线、像素撕裂、数据扭曲的抽象画面(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.2 波普漫画 Pop Art

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格波普漫画:粗黑描边、Ben-Day 网点、互补色块、高级平面构图(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格波普漫画:粗黑描边、Ben-Day 网点、互补色块、高级平面构图(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.3 黑白版画 Linocut

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格黑白版画:木刻/胶版线条、粗犷刀痕、强对比留白(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格黑白版画:木刻/胶版线条、粗犷刀痕、强对比留白(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.4 蒸汽波 Vaporwave

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格蒸汽波:粉紫渐变天空、复古 3D 几何、镜面地面、霓虹光晕(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格蒸汽波:粉紫渐变天空、复古 3D 几何、镜面地面、霓虹光晕(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.5 欧普艺术 Op Art

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格欧普艺术:黑白几何错视、波纹与棋盘扭曲、极致秩序感(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格欧普艺术:黑白几何错视、波纹与棋盘扭曲、极致秩序感(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.6 不透明水粉 Gouache

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格不透明水粉:厚涂笔触、块面构成、复古色彩、边缘干净(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格不透明水粉:厚涂笔触、块面构成、复古色彩、边缘干净(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.7 彩色玻璃 Stained Glass

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格彩色玻璃窗:铅条分割、宝石色透光、圣堂般光束与尘埃(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格彩色玻璃窗:铅条分割、宝石色透光、圣堂般光束与尘埃(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.8 新艺术 Art Nouveau

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格新艺术:藤蔓曲线、装饰边框、花卉与孔雀羽纹样(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格新艺术:藤蔓曲线、装饰边框、花卉与孔雀羽纹样(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.9 赛璐璐动画 Cel-shaded

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格赛璐璐动画:硬边阴影、清晰线稿、青春感构图与速度线(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格赛璐璐动画:硬边阴影、清晰线稿、青春感构图与速度线(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.10 像素风 Pixel Art

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格像素艺术:16-bit 游戏场景、像素网格、复古调色板(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格像素艺术:16-bit 游戏场景、像素网格、复古调色板(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.11 低多边形 Low Poly

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格低多边形:几何切面、硬朗光影、山川/动物雕塑感(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格低多边形:几何切面、硬朗光影、山川/动物雕塑感(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.12 分形/万花筒 Fractal

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格分形万花筒:对称几何、细节无限、虹彩光谱(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格分形万花筒:对称几何、细节无限、虹彩光谱(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.13 新怪诞涂鸦 Graffiti

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格街头涂鸦:喷漆肌理、怪诞涂鸦角色、强对比配色(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格街头涂鸦:喷漆肌理、怪诞涂鸦角色、强对比配色(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>

5.6.14 立体折纸 Origami

<details>

<summary>展开查看背景 Prompt(完整)</summary>

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:2026-01
- 主题(可覆写):强风格折纸装置:纸张折痕、层叠结构、柔和侧光与投影(无文字无数字,右侧留白)

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"强风格折纸装置:纸张折痕、层叠结构、柔和侧光与投影(无文字无数字,右侧留白)"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

</details>


6. 如何扩展更多"惊艳风格"(推荐做法)

6.1 只改 JSON,就能批量出新风格

编辑 测试comfyui_副本/demo/comfyui-zimage-demo/calendar_variants_background.json,新增一条:

  • id:输出文件前缀

  • title:你希望在画廊里展示的名字

  • theme:这张背景图的审美说明(越具体越稳)

  • width/height/steps/seed:控制比例与复现(seed 用于复刻同一张图)

然后重跑:

bash 复制代码
INSECURE_TLS=1 \
COMFY_BASE_URL="https://deployment-318-klu0niut-8188.550w.link" \
python3 "测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py" --month 2026-02 --force

7. 常见问题(FAQ,便于 GEO/SEO)

7.1 怎么保证日期永远正确?

generate_calendar.py 生成 CSV(真逻辑),再用 render_calendar_overlay.py + compose_calendar.py 叠加(真排版)。模型不参与日期生成与排版。

7.2 为什么要强制"背景不生成文字/数字"?

因为"文字/数字"是模型最容易乱写的内容;让它只出背景能显著提升稳定性。即便背景里偶尔出现字,最终日历区域也会被不透明白面板彻底遮住。

7.3 如何输出 A4 横版 / 300dpi?

把 variants 里的 width/height 设置成接近 A4 横版比例(例如 3508×2480 对应 300dpi),并留意显存与推理时间。

7.4 如何把"周一开始"改成"周日开始"?

generate_calendar.py 使用 --firstweekday sun;同时在 render_calendar_overlay.py 里把 weekday 标题改为 Sun...Sat 即可。


8. 复刻清单(文件一览)

  • ai相关好玩的/ai日历/generate_calendar.py

  • ai相关好玩的/ai日历/build_prompt.py

  • ai相关好玩的/ai日历/build_background_prompt.py

  • ai相关好玩的/ai日历/render_calendar_overlay.py

  • ai相关好玩的/ai日历/compose_calendar.py

  • 测试comfyui_副本/demo/comfyui-zimage-demo/calendar_variants_background.json

  • 测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py


9. 完整代码与完整 Prompt(可复制粘贴)

你可以直接把这一节复制到你的项目里跑;或者在共绩算力平台选预制 ComfyUI 镜像后,把这些脚本放进去运行。

9.1 日历骨架生成脚本:`ai相关好玩的/ai日历/generate_calendar.py`

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
两段式工作流 · 第 1 段:脚本先行(生成"准确日历骨架")

输出:
- CSV:42 格(6 行 * 7 列)的结构化日历骨架
- Markdown 表格:便于直接注入 Prompt

仅使用标准库;避免把日期逻辑交给模型,彻底杜绝"2 月 30 号"这类幻觉。
"""

from __future__ import annotations

import argparse
import calendar
import csv
import datetime as dt
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, List, Sequence


WEEKDAY_EN = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
WEEKDAY_CN = ["一", "二", "三", "四", "五", "六", "日"]


@dataclass(frozen=True)
class Cell:
    row: int
    col: int
    date: dt.date
    in_month: bool

    @property
    def iso(self) -> str:
        return self.date.isoformat()

    @property
    def day(self) -> int:
        return self.date.day

    @property
    def weekday_index(self) -> int:
        # Monday=0 ... Sunday=6
        return self.date.weekday()

    @property
    def weekday_en(self) -> str:
        return WEEKDAY_EN[self.weekday_index]

    @property
    def weekday_cn(self) -> str:
        return WEEKDAY_CN[self.weekday_index]


def parse_month(month_str: str | None) -> tuple[int, int]:
    if not month_str:
        today = dt.date.today()
        return today.year, today.month

    # 支持:YYYY-MM / YYYY/MM / YYYYMM
    s = month_str.strip()
    for fmt in ("%Y-%m", "%Y/%m", "%Y%m"):
        try:
            d = dt.datetime.strptime(s, fmt)
            return d.year, d.month
        except ValueError:
            pass
    raise SystemExit(f"无法解析月份:{month_str!r},请用 YYYY-MM(如 2026-01)")


def month_cells(year: int, month: int, *, firstweekday: int = 0) -> List[Cell]:
    cal = calendar.Calendar(firstweekday=firstweekday)  # 0=Mon ... 6=Sun
    weeks = cal.monthdatescalendar(year, month)
    # 视觉结构固定为 6 行(常见台历布局);不足补齐到 6 行
    while len(weeks) < 6:
        last_week = weeks[-1]
        next_week_start = last_week[-1] + dt.timedelta(days=1)
        weeks.append([next_week_start + dt.timedelta(days=i) for i in range(7)])
    # 如果极端情况下多于 6 行(理论上不会发生),截断
    weeks = weeks[:6]

    cells: List[Cell] = []
    for r, week in enumerate(weeks):
        for c, d in enumerate(week):
            cells.append(Cell(row=r, col=c, date=d, in_month=(d.month == month)))
    return cells


def cells_to_markdown_table(
    cells: Sequence[Cell],
    *,
    show_outside_days: bool,
    header: Sequence[str] = WEEKDAY_EN,
) -> str:
    grid: List[List[str]] = [["" for _ in range(7)] for _ in range(6)]
    for cell in cells:
        if cell.in_month:
            grid[cell.row][cell.col] = str(cell.day)
        else:
            grid[cell.row][cell.col] = str(cell.day) if show_outside_days else ""

    header_row = "| " + " | ".join(header) + " |"
    sep_row = "| " + " | ".join(["---"] * 7) + " |"
    body_rows = ["| " + " | ".join(row) + " |" for row in grid]
    return "\n".join([header_row, sep_row, *body_rows]) + "\n"


def write_csv(
    cells: Sequence[Cell],
    out_path: Path,
    *,
    month: int,
    show_outside_days: bool,
) -> None:
    out_path.parent.mkdir(parents=True, exist_ok=True)
    with out_path.open("w", encoding="utf-8", newline="") as f:
        w = csv.DictWriter(
            f,
            fieldnames=[
                "row",
                "col",
                "date",
                "day",
                "in_month",
                "weekday_index",
                "weekday_en",
                "weekday_cn",
                "display",
            ],
        )
        w.writeheader()
        for cell in cells:
            display = ""
            if cell.in_month:
                display = str(cell.day)
            elif show_outside_days:
                display = str(cell.day)
            w.writerow(
                {
                    "row": cell.row,
                    "col": cell.col,
                    "date": cell.iso,
                    "day": cell.day,
                    "in_month": int(cell.in_month),
                    "weekday_index": cell.weekday_index,
                    "weekday_en": cell.weekday_en,
                    "weekday_cn": cell.weekday_cn,
                    "display": display,
                }
            )


def main(argv: Iterable[str] | None = None) -> int:
    p = argparse.ArgumentParser(
        description="生成准确的日历骨架(CSV/Markdown),用于后续 Prompt 注入。",
    )
    p.add_argument(
        "--month",
        default=None,
        help="目标月份,格式 YYYY-MM(默认:当前系统月份)",
    )
    p.add_argument(
        "--firstweekday",
        default="mon",
        choices=["mon", "sun"],
        help="一周从哪天开始(默认:mon)",
    )
    p.add_argument(
        "--show-outside-days",
        action="store_true",
        help="表格里是否显示上/下月的日期数字(默认留空,台历更干净)",
    )
    p.add_argument(
        "--out-csv",
        default=None,
        help="CSV 输出路径(默认:ai相关好玩的/ai日历/output/calendar_YYYY-MM.csv)",
    )
    p.add_argument(
        "--out-md",
        default=None,
        help="Markdown 表格输出路径(默认:ai相关好玩的/ai日历/output/calendar_YYYY-MM.md)",
    )
    p.add_argument(
        "--only",
        default="both",
        choices=["both", "csv", "md"],
        help="只输出 csv 或 md(默认:both)",
    )

    args = p.parse_args(list(argv) if argv is not None else None)

    year, month = parse_month(args.month)
    fw = 0 if args.firstweekday == "mon" else 6
    cells = month_cells(year, month, firstweekday=fw)

    ym = f"{year:04d}-{month:02d}"
    default_csv = Path(__file__).resolve().parent / "output" / f"calendar_{ym}.csv"
    default_md = Path(__file__).resolve().parent / "output" / f"calendar_{ym}.md"
    out_csv = Path(args.out_csv) if args.out_csv else default_csv
    out_md = Path(args.out_md) if args.out_md else default_md

    if args.only in ("both", "csv"):
        write_csv(cells, out_csv, month=month, show_outside_days=args.show_outside_days)
        print(f"[ok] CSV 已生成:{out_csv}")

    if args.only in ("both", "md"):
        header = WEEKDAY_EN if args.firstweekday == "mon" else ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
        md = cells_to_markdown_table(
            cells,
            show_outside_days=args.show_outside_days,
            header=header,
        )
        out_md.parent.mkdir(parents=True, exist_ok=True)
        out_md.write_text(md, encoding="utf-8")
        print(f"[ok] Markdown 已生成:{out_md}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())

9.2 背景 Prompt 模版(完整):`ai相关好玩的/ai日历/templates/background_template_generic.md`

plain 复制代码
你是顶级视觉设计师与插画导演。你的任务是:生成一张**可印刷的横版台历背景图**,只负责"主视觉/背景",不负责日期排版。

## 变量
- 月份:{{YM}}
- 主题(可覆写):{{THEME}}

## 关键约束(必须遵守)
- 这张图只画"背景/主视觉",**禁止生成任何文字**(包括月份、招牌字、标题、UI 小字、印章字、海报字)。
- **禁止生成任何数字**(避免误伤日历数字)。
- 在画面右侧预留一个干净区域用于后续代码叠加日历:右侧约 40% 为浅色/低纹理/高对比底,留白充足。
- 主视觉集中在左侧约 60%,构图高级、干净、可印刷(低噪点、边缘清晰)。

## 风格方向
- 允许自由发挥,但要"第一眼高级":电影级光影、材质真实、构图克制、色彩统一。
- 主题解读:围绕"{{THEME}}"构建一致世界观与道具元素。

## 输出要求
- 只输出最终成图,不要解释文字。

9.3 背景 Prompt 生成脚本(完整):`ai相关好玩的/ai日历/build_background_prompt.py`

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
两段式工作流(扩展)· 背景 Prompt 生成器

目的:生成"只有背景/主视觉"的提示词,明确要求 **不生成任何文字/日期**,
并在画面中预留一个干净区域(用于后续代码精确叠加日历排版)。

这样能彻底解决:模型乱写星期、乱写数字、日期错位等问题。
"""

from __future__ import annotations

import argparse
import datetime as dt
from pathlib import Path
from typing import Dict, Iterable


def parse_month(month_str: str | None) -> tuple[int, int]:
    if not month_str:
        today = dt.date.today()
        return today.year, today.month
    s = month_str.strip()
    for fmt in ("%Y-%m", "%Y/%m", "%Y%m"):
        try:
            d = dt.datetime.strptime(s, fmt)
            return d.year, d.month
        except ValueError:
            pass
    raise SystemExit(f"无法解析月份:{month_str!r},请用 YYYY-MM(如 2026-01)")


def render_template(template_text: str, variables: Dict[str, str]) -> str:
    out = template_text
    for k, v in variables.items():
        out = out.replace("{{" + k + "}}", v)
    return out


def default_theme_for_month(year: int, month: int) -> str:
    season = (
        "冬季" if month in (12, 1, 2) else
        "春季" if month in (3, 4, 5) else
        "夏季" if month in (6, 7, 8) else
        "秋季"
    )
    return f"{year}年{month:02d}月 · {season}主题(可覆写)"


def main(argv: Iterable[str] | None = None) -> int:
    p = argparse.ArgumentParser(description="生成"背景专用"的绘图 Prompt(不含日历文字)。")
    p.add_argument("--month", default=None, help="目标月份 YYYY-MM(默认:当前系统月份)")
    p.add_argument(
        "--template",
        default=None,
        help="背景 Prompt 模版路径(默认:ai相关好玩的/ai日历/templates/background_template_generic.md)",
    )
    p.add_argument("--theme", default=None, help="主题覆写")
    p.add_argument(
        "--out",
        default=None,
        help="输出路径(默认:ai相关好玩的/ai日历/output/background_prompt_YYYY-MM.md)",
    )
    args = p.parse_args(list(argv) if argv is not None else None)

    year, month = parse_month(args.month)
    ym = f"{year:04d}-{month:02d}"

    base_dir = Path(__file__).resolve().parent
    default_template = base_dir / "templates" / "background_template_generic.md"
    default_out = base_dir / "output" / f"background_prompt_{ym}.md"

    template_path = Path(args.template) if args.template else default_template
    out_path = Path(args.out) if args.out else default_out
    if not template_path.exists():
        raise SystemExit(f"找不到模版:{template_path}")

    theme = args.theme.strip() if args.theme else default_theme_for_month(year, month)

    template_text = template_path.read_text(encoding="utf-8")
    final_text = render_template(
        template_text,
        variables={
            "YEAR": str(year),
            "MONTH": f"{month:02d}",
            "YM": ym,
            "THEME": theme,
        },
    ).rstrip() + "\n"

    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(final_text, encoding="utf-8")
    print(f"[ok] 背景 Prompt 已生成:{out_path}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

9.4 日历 overlay 排版脚本(完整):`ai相关好玩的/ai日历/render_calendar_overlay.py`

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
确定性日历排版(第三段,可选但强烈推荐)

输入:calendar_YYYY-MM.csv(第 1 段生成)
输出:一个 RGBA 透明 PNG(只包含日期排版,可叠加到任意背景图)

依赖:Pillow(已在你的环境可用)
"""

from __future__ import annotations

import argparse
import csv
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Iterable, Tuple

from PIL import Image, ImageDraw, ImageFont  # type: ignore


@dataclass(frozen=True)
class Box:
    x: int
    y: int
    w: int
    h: int


def parse_box(s: str) -> Box:
    # "x,y,w,h"
    parts = [p.strip() for p in s.split(",")]
    if len(parts) != 4:
        raise SystemExit("--box 需要格式:x,y,w,h")
    x, y, w, h = (int(float(p)) for p in parts)
    return Box(x=x, y=y, w=w, h=h)


def load_grid(csv_path: Path) -> Dict[Tuple[int, int], str]:
    grid: Dict[Tuple[int, int], str] = {}
    with csv_path.open("r", encoding="utf-8", newline="") as f:
        r = csv.DictReader(f)
        for row in r:
            rr = int(row["row"])
            cc = int(row["col"])
            grid[(rr, cc)] = (row.get("display") or "").strip()
    # ensure full 6x7
    for rr in range(6):
        for cc in range(7):
            grid.setdefault((rr, cc), "")
    return grid


def load_font(font_path: Path | None, size: int) -> ImageFont.ImageFont:
    if font_path and font_path.exists():
        try:
            return ImageFont.truetype(str(font_path), size=size)
        except Exception:
            pass
    return ImageFont.load_default()


def draw_calendar_overlay(
    *,
    width: int,
    height: int,
    ym: str,
    grid: Dict[Tuple[int, int], str],
    box: Box,
    font_path: Path | None,
    weekdays: Tuple[str, ...],
    title: bool,
    grid_lines: bool,
) -> Image.Image:
    img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
    draw = ImageDraw.Draw(img)

    # Layout inside box
    pad = max(12, int(min(box.w, box.h) * 0.04))
    x0, y0 = box.x + pad, box.y + pad
    x1, y1 = box.x + box.w - pad, box.y + box.h - pad
    inner_w, inner_h = max(1, x1 - x0), max(1, y1 - y0)

    # Title block
    title_h = int(inner_h * 0.16) if title else 0
    title_h = min(title_h, 90)
    week_h = int(inner_h * 0.12)
    week_h = min(week_h, 60)
    cells_h = inner_h - title_h - week_h
    cells_h = max(cells_h, 1)

    cols, rows = 7, 6
    cell_w = inner_w / cols
    cell_h = cells_h / rows

    # Fonts
    title_font = load_font(font_path, size=max(22, int(min(inner_w, inner_h) * 0.07)))
    week_font = load_font(font_path, size=max(14, int(min(inner_w, inner_h) * 0.035)))
    day_font = load_font(font_path, size=max(16, int(min(inner_w, inner_h) * 0.045)))

    # Colors
    fg = (20, 20, 20, 255)
    muted = (70, 70, 70, 255)
    line = (20, 20, 20, 35)

    # Title
    if title:
        tx, ty = x0, y0
        draw.text((tx, ty), ym, fill=fg, font=title_font)

    # Weekdays
    wy = y0 + title_h
    for c in range(cols):
        label = weekdays[c]
        cx = x0 + c * cell_w
        # center label in column header
        bbox = draw.textbbox((0, 0), label, font=week_font)
        tw = bbox[2] - bbox[0]
        th = bbox[3] - bbox[1]
        lx = cx + (cell_w - tw) / 2
        ly = wy + (week_h - th) / 2
        draw.text((lx, ly), label, fill=muted, font=week_font)

    # Cells
    gy0 = wy + week_h
    if grid_lines:
        # border
        draw.rectangle([x0, gy0, x0 + inner_w, gy0 + rows * cell_h], outline=line, width=1)
        # vertical lines
        for c in range(1, cols):
            x = x0 + c * cell_w
            draw.line([x, gy0, x, gy0 + rows * cell_h], fill=line, width=1)
        # horizontal lines
        for r in range(1, rows):
            y = gy0 + r * cell_h
            draw.line([x0, y, x0 + inner_w, y], fill=line, width=1)

    for r in range(rows):
        for c in range(cols):
            text = grid[(r, c)]
            if not text:
                continue
            cx = x0 + c * cell_w
            cy = gy0 + r * cell_h
            # top-left padding inside cell
            inset_x = max(6, int(cell_w * 0.08))
            inset_y = max(4, int(cell_h * 0.08))
            draw.text((cx + inset_x, cy + inset_y), text, fill=fg, font=day_font)

    return img


def main(argv: Iterable[str] | None = None) -> int:
    p = argparse.ArgumentParser(description="把 CSV 日历骨架排版成透明 PNG overlay。")
    p.add_argument("--csv", required=True, help="calendar_YYYY-MM.csv 路径")
    p.add_argument("--ym", required=True, help="月份 YYYY-MM(用于标题)")
    p.add_argument("--width", type=int, required=True, help="画布宽度")
    p.add_argument("--height", type=int, required=True, help="画布高度")
    p.add_argument("--out", required=True, help="输出 overlay PNG 路径")
    p.add_argument("--box", default=None, help="日历区域 x,y,w,h(像素)。默认:右侧 40% 区域。")
    p.add_argument(
        "--font",
        default=None,
        help="字体文件路径(可选)。默认会尝试使用 博客/assets/fonts/NotoSansSC-wght.ttf",
    )
    p.add_argument("--no-title", action="store_true", help="不渲染月份标题")
    p.add_argument("--no-grid", action="store_true", help="不画网格线(只画数字)")
    args = p.parse_args(list(argv) if argv is not None else None)

    width, height = int(args.width), int(args.height)
    csv_path = Path(args.csv)
    out_path = Path(args.out)

    if args.box:
        box = parse_box(args.box)
    else:
        # default: right 40% with margins
        margin = int(min(width, height) * 0.06)
        bw = int(width * 0.42)
        box = Box(x=width - margin - bw, y=margin, w=bw, h=height - 2 * margin)

    # default font: try blog assets if present
    font_path: Path | None
    if args.font:
        font_path = Path(args.font)
    else:
        # workspace root from this file: ai相关好玩的/ai日历/ -> parents[2] == 共绩
        root = Path(__file__).resolve().parents[2]
        font_path = root / "博客" / "assets" / "fonts" / "NotoSansSC-wght.ttf"

    grid = load_grid(csv_path)
    weekdays = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")

    overlay = draw_calendar_overlay(
        width=width,
        height=height,
        ym=str(args.ym),
        grid=grid,
        box=box,
        font_path=font_path,
        weekdays=weekdays,
        title=not bool(args.no_title),
        grid_lines=not bool(args.no_grid),
    )

    out_path.parent.mkdir(parents=True, exist_ok=True)
    overlay.save(str(out_path))
    print(f"[ok] overlay 已生成:{out_path}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

9.5 合成脚本(完整):`ai相关好玩的/ai日历/compose_calendar.py`

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
把"背景图"与"日历 overlay"合成最终台历图。

依赖:Pillow
"""

from __future__ import annotations

import argparse
from pathlib import Path
from typing import Iterable, Tuple

from PIL import Image, ImageDraw  # type: ignore


def parse_rgba(s: str) -> Tuple[int, int, int, int]:
    # "r,g,b,a"
    parts = [p.strip() for p in s.split(",")]
    if len(parts) != 4:
        raise SystemExit("--panel-color 需要格式:r,g,b,a")
    return tuple(int(float(p)) for p in parts)  # type: ignore


def main(argv: Iterable[str] | None = None) -> int:
    p = argparse.ArgumentParser(description="合成:background + overlay -> final")
    p.add_argument("--background", required=True, help="背景 PNG 路径")
    p.add_argument("--overlay", required=True, help="透明 overlay PNG 路径")
    p.add_argument("--out", required=True, help="输出 final PNG 路径")
    p.add_argument(
        "--panel",
        default="1",
        help="是否在日历区域加一块半透明底(提升可读性)。1=是 0=否(默认 1)",
    )
    p.add_argument(
        "--panel-color",
        default="255,255,255,255",
        help="面板颜色 RGBA(默认纯白不透明 255,255,255,255,用于彻底遮住背景里的误写文字/数字)",
    )
    p.add_argument(
        "--panel-box",
        default=None,
        help="面板区域 x,y,w,h(像素)。默认:自动取 overlay 的非透明区域外接矩形并扩边。",
    )
    args = p.parse_args(list(argv) if argv is not None else None)

    bg_path = Path(args.background)
    ov_path = Path(args.overlay)
    out_path = Path(args.out)

    bg = Image.open(str(bg_path)).convert("RGBA")
    ov = Image.open(str(ov_path)).convert("RGBA")

    if bg.size != ov.size:
        ov = ov.resize(bg.size, resample=Image.BICUBIC)

    if str(args.panel).strip() not in ("0", "", "false", "False", "no", "NO"):
        # compute default panel box from overlay alpha bbox
        if args.panel_box:
            x, y, w, h = (int(float(p.strip())) for p in args.panel_box.split(","))
            panel_box = (x, y, x + w, y + h)
        else:
            alpha = ov.split()[-1]
            bbox = alpha.getbbox()
            if bbox:
                # 默认扩边更大:更彻底隔绝背景"乱写的字/数字"
                pad = int(min(bg.size) * 0.035)
                panel_box = (
                    max(0, bbox[0] - pad),
                    max(0, bbox[1] - pad),
                    min(bg.size[0], bbox[2] + pad),
                    min(bg.size[1], bbox[3] + pad),
                )
            else:
                panel_box = None

        if panel_box:
            panel = Image.new("RGBA", bg.size, (0, 0, 0, 0))
            draw = ImageDraw.Draw(panel)
            color = parse_rgba(args.panel_color)
            draw.rounded_rectangle(panel_box, radius=18, fill=color)
            bg = Image.alpha_composite(bg, panel)

    out = Image.alpha_composite(bg, ov)
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out.save(str(out_path))
    print(f"[ok] final 已生成:{out_path}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

9.6 ComfyUI 批量全流程脚本(完整):`测试comfyui_副本/demo/comfyui-zimage-demo/batch_generate_calendars_composited.py`

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
更稳定、更"惊艳"的台历生成:背景交给模型,日期排版交给代码(Pillow)

流程:
1) generate_calendar.py 生成 CSV(日期绝对正确)
2) build_background_prompt.py 生成"只画背景"的 Prompt(强制无文字/无数字,右侧留白)
3) ComfyUI 生成背景图(background.png)
4) render_calendar_overlay.py 把 CSV 排版成透明 overlay.png
5) compose_calendar.py 合成 final.png(日期永远正确)
"""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional

from generate import (  # type: ignore
    create_zimage_turbo_workflow,
    get_ssl_context,
    http_bytes,
    http_json,
)


def workspace_root_from_here() -> Path:
    # 共绩/测试comfyui_副本/demo/comfyui-zimage-demo/ -> parents[3] == 共绩
    return Path(__file__).resolve().parents[3]


def comfy_generate_png(
    *,
    base_url: str,
    prompt: str,
    negative: str,
    width: int,
    height: int,
    steps: int,
    seed: Optional[int],
    out_path: Path,
) -> None:
    base = base_url.rstrip("/")
    ctx = get_ssl_context()
    wf = create_zimage_turbo_workflow(
        prompt=prompt,
        negative_prompt=negative,
        width=width,
        height=height,
        steps=steps,
        seed=seed,
    )
    resp = http_json(f"{base}/prompt", method="POST", body={"prompt": wf}, ctx=ctx, timeout=240)
    images = resp.get("images")
    if isinstance(images, list) and images and isinstance(images[0], str):
        import base64

        out_path.write_bytes(base64.b64decode(images[0]))
        return

    prompt_id = resp.get("prompt_id")
    if not prompt_id:
        raise RuntimeError(f"unexpected response keys={list(resp.keys())}")

    history_url = f"{base}/history/{prompt_id}"
    item = None
    for _ in range(120):
        data = http_json(history_url, ctx=ctx, timeout=60)
        item = data.get(prompt_id)
        outputs = (item or {}).get("outputs") or {}
        if item and outputs:
            break
        import time

        time.sleep(2)

    if not item:
        raise RuntimeError("history timeout: no record for prompt_id")

    outputs = item.get("outputs") or {}
    save_out = outputs.get("9") or {}
    out_images = save_out.get("images") or []
    if not out_images:
        for v in outputs.values():
            imgs = (v or {}).get("images") or []
            if imgs:
                out_images = imgs
                break
    if not out_images:
        raise RuntimeError(f"no images found in outputs. outputs_keys={list(outputs.keys())}")

    img0 = out_images[0]
    filename = img0.get("filename")
    subfolder = img0.get("subfolder", "")
    ftype = img0.get("type", "output")

    from urllib.parse import urlencode

    view_url = f"{base}/view?{urlencode({'filename': filename, 'subfolder': subfolder, 'type': ftype})}"
    blob = http_bytes(view_url, ctx=ctx, timeout=240)
    out_path.write_bytes(blob)


def main() -> int:
    p = argparse.ArgumentParser(description="批量生成(背景+代码排版)台历图。")
    p.add_argument("--month", default="2026-01", help="目标月份 YYYY-MM(默认 2026-01)")
    p.add_argument(
        "--base-url",
        default=os.getenv("COMFY_BASE_URL", "https://deployment-318-klu0niut-8188.550w.link"),
        help="ComfyUI Base URL",
    )
    p.add_argument(
        "--variants",
        default=str(Path(__file__).resolve().parent / "calendar_variants_background.json"),
        help="背景 variants 配置 JSON",
    )
    p.add_argument("--force", action="store_true", help="强制重跑")
    args = p.parse_args()

    root = workspace_root_from_here()

    # Paths to ai日历 scripts
    ai_dir = root / "ai相关好玩的" / "ai日历"
    gen_csv = ai_dir / "generate_calendar.py"
    build_bg = ai_dir / "build_background_prompt.py"
    render_ov = ai_dir / "render_calendar_overlay.py"
    compose = ai_dir / "compose_calendar.py"

    out_dir = Path(__file__).resolve().parent / "outputs"
    out_dir.mkdir(parents=True, exist_ok=True)

    prompt_dir = out_dir / "_calendar_prompts_composited"
    prompt_dir.mkdir(parents=True, exist_ok=True)

    variants_path = Path(args.variants)
    items: List[Dict[str, Any]] = json.loads(variants_path.read_text(encoding="utf-8"))

    # Step 1: generate calendar CSV once
    subprocess.run([sys.executable, str(gen_csv), "--month", args.month], check=True, cwd=str(root))
    csv_path = ai_dir / "output" / f"calendar_{args.month}.csv"

    # A slightly stronger negative prompt for background-only generation
    negative = os.getenv(
        "NEGATIVE_PROMPT",
        "text, letters, words, numbers, digits, watermark, logo, blurry, low quality, lowres",
    )

    for it in items:
        vid = str(it["id"])
        title = str(it.get("title", vid))
        theme = str(it.get("theme", ""))
        width = int(it.get("width", 1536))
        height = int(it.get("height", 1088))
        steps = int(it.get("steps", 20))
        seed = it.get("seed")
        seed_val = int(seed) if seed is not None else None

        bg_path = out_dir / f"{vid}_{args.month}_bg.png"
        ov_path = out_dir / f"{vid}_{args.month}_overlay.png"
        final_path = out_dir / f"{vid}_{args.month}_final.png"
        prompt_path = prompt_dir / f"{vid}_{args.month}.md"

        if final_path.exists() and not args.force:
            print(f"[skip] 已存在:{final_path} ({title})")
            continue

        # Step 2: build background prompt
        subprocess.run(
            [
                sys.executable,
                str(build_bg),
                "--month",
                args.month,
                "--theme",
                theme,
                "--template",
                str(ai_dir / "templates" / "background_template_generic.md"),
                "--out",
                str(prompt_path),
            ],
            check=True,
            cwd=str(root),
        )
        prompt_text = prompt_path.read_text(encoding="utf-8")

        # Step 3: generate background
        if args.force or (not bg_path.exists()):
            comfy_generate_png(
                base_url=str(args.base_url),
                prompt=prompt_text,
                negative=negative,
                width=width,
                height=height,
                steps=steps,
                seed=seed_val,
                out_path=bg_path,
            )

        # Step 4: render overlay
        subprocess.run(
            [
                sys.executable,
                str(render_ov),
                "--csv",
                str(csv_path),
                "--ym",
                args.month,
                "--width",
                str(width),
                "--height",
                str(height),
                "--out",
                str(ov_path),
            ],
            check=True,
            cwd=str(root),
        )

        # Step 5: compose final
        subprocess.run(
            [
                sys.executable,
                str(compose),
                "--background",
                str(bg_path),
                "--overlay",
                str(ov_path),
                "--out",
                str(final_path),
            ],
            check=True,
            cwd=str(root),
        )

        print(f"[ok] {title} -> {final_path}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())

9.7 强风格艺术 variants(完整):`测试comfyui_副本/demo/comfyui-zimage-demo/calendar_variants_background.json`

json 复制代码
[
  {
    "id": "cal_bg_luxury_a",
    "title": "高端品牌 KV(背景)A",
    "theme": "一月冷冽感:冰晶玻璃 + 香槟金金属 + 黑色石材底座(高级香氛KV)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260211
  },
  {
    "id": "cal_bg_cyber_a",
    "title": "赛博霓虹(背景)A",
    "theme": "雨夜未来都市:霓虹反射、电光蓝与品红、体积光与薄雾(无文字无数字)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260221
  },
  {
    "id": "cal_bg_papercut_a",
    "title": "纸雕剪纸(背景)A",
    "theme": "多层纸雕冬季森林:雪花、松枝、麋鹿剪影(层叠纸雕,柔光阴影)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260231
  },
  {
    "id": "cal_bg_ink_a",
    "title": "国风水墨(背景)A",
    "theme": "雪后远山云雾、松枝与溪流(水墨留白,现代版式区域留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260241
  },
  {
    "id": "cal_bg_surreal_a",
    "title": "超现实拼贴(背景)A",
    "theme": "冬季月亮像窗户,云像丝绸,山像折纸(高概念拼贴,无文字无数字)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260251
  },
  {
    "id": "cal_bg_glitch_a",
    "title": "故障艺术 Glitch(背景)A",
    "theme": "强风格故障艺术:RGB 分离、扫描线、像素撕裂、数据扭曲的抽象画面(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260311
  },
  {
    "id": "cal_bg_popart_a",
    "title": "波普漫画 Pop Art(背景)A",
    "theme": "强风格波普漫画:粗黑描边、Ben-Day 网点、互补色块、高级平面构图(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260321
  },
  {
    "id": "cal_bg_linocut_a",
    "title": "版画 Linocut(背景)A",
    "theme": "强风格黑白版画:木刻/胶版线条、粗犷刀痕、强对比留白(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260331
  },
  {
    "id": "cal_bg_vaporwave_a",
    "title": "蒸汽波 Vaporwave(背景)A",
    "theme": "强风格蒸汽波:粉紫渐变天空、复古 3D 几何、镜面地面、霓虹光晕(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260341
  },
  {
    "id": "cal_bg_opart_a",
    "title": "欧普艺术 Op Art(背景)A",
    "theme": "强风格欧普艺术:黑白几何错视、波纹与棋盘扭曲、极致秩序感(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260351
  },
  {
    "id": "cal_bg_gouache_a",
    "title": "不透明水粉 Gouache(背景)A",
    "theme": "强风格不透明水粉:厚涂笔触、块面构成、复古色彩、边缘干净(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260361
  },
  {
    "id": "cal_bg_stainedglass_a",
    "title": "彩色玻璃 Stained Glass(背景)A",
    "theme": "强风格彩色玻璃窗:铅条分割、宝石色透光、圣堂般光束与尘埃(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260411
  },
  {
    "id": "cal_bg_artnouveau_a",
    "title": "新艺术 Art Nouveau(背景)A",
    "theme": "强风格新艺术:藤蔓曲线、装饰边框、花卉与孔雀羽纹样(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260421
  },
  {
    "id": "cal_bg_celshade_a",
    "title": "赛璐璐动画 Cel-shaded(背景)A",
    "theme": "强风格赛璐璐动画:硬边阴影、清晰线稿、青春感构图与速度线(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260431
  },
  {
    "id": "cal_bg_pixelart_a",
    "title": "像素风 Pixel Art(背景)A",
    "theme": "强风格像素艺术:16-bit 游戏场景、像素网格、复古调色板(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260441
  },
  {
    "id": "cal_bg_lowpoly_a",
    "title": "低多边形 Low Poly(背景)A",
    "theme": "强风格低多边形:几何切面、硬朗光影、山川/动物雕塑感(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260451
  },
  {
    "id": "cal_bg_fractal_a",
    "title": "分形/万花筒 Fractal(背景)A",
    "theme": "强风格分形万花筒:对称几何、细节无限、虹彩光谱(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260461
  },
  {
    "id": "cal_bg_graffiti_a",
    "title": "新怪诞涂鸦 Graffiti(背景)A",
    "theme": "强风格街头涂鸦:喷漆肌理、怪诞涂鸦角色、强对比配色(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260471
  },
  {
    "id": "cal_bg_origami_a",
    "title": "立体折纸 Origami(背景)A",
    "theme": "强风格折纸装置:纸张折痕、层叠结构、柔和侧光与投影(无文字无数字,右侧留白)",
    "width": 1536,
    "height": 1088,
    "steps": 20,
    "seed": 20260481
  }
]
相关推荐
九.九8 小时前
ops-transformer:AI 处理器上的高性能 Transformer 算子库
人工智能·深度学习·transformer
春日见8 小时前
拉取与合并:如何让个人分支既包含你昨天的修改,也包含 develop 最新更新
大数据·人工智能·深度学习·elasticsearch·搜索引擎
恋猫de小郭8 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
deephub8 小时前
Agent Lightning:微软开源的框架无关 Agent 训练方案,LangChain/AutoGen 都能用
人工智能·microsoft·langchain·大语言模型·agent·强化学习
大模型RAG和Agent技术实践8 小时前
从零构建本地AI合同审查系统:架构设计与流式交互实战(完整源代码)
人工智能·交互·智能合同审核
老邋遢8 小时前
第三章-AI知识扫盲看这一篇就够了
人工智能
互联网江湖8 小时前
Seedance2.0炸场:长短视频们“修坝”十年,不如AI放水一天?
人工智能
PythonPioneer9 小时前
在AI技术迅猛发展的今天,传统职业该如何“踏浪前行”?
人工智能
冬奇Lab9 小时前
一天一个开源项目(第20篇):NanoBot - 轻量级AI Agent框架,极简高效的智能体构建工具
人工智能·开源·agent
阿里巴巴淘系技术团队官网博客9 小时前
设计模式Trustworthy Generation:提升RAG信赖度
人工智能·设计模式