
Wechat Red Envelope Cover Designer
- 34 installs
- 33 repo stars
- Updated July 5, 2026
- wuchubuzai2018/expert-skills-hub
Helps with design & ui/ux tasks.
About
wechat-red-envelope-cover-designer is a Claude Code skill for design & ui/ux. It helps solo builders move faster with AI-assisted development.
- wechat-red-envelope-cover-designer
- Design & UI/UX
- AI-coding skill
Wechat Red Envelope Cover Designer by the numbers
- 34 all-time installs (skills.sh)
- Ranked #1,305 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wuchubuzai2018/expert-skills-hub --skill wechat-red-envelope-cover-designerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 34 |
|---|---|
| repo stars | ★ 33 |
| Last updated | July 5, 2026 |
| Repository | wuchubuzai2018/expert-skills-hub ↗ |
What it does
Helps with design & ui/ux tasks.
Files
技能概述
技能名称: wechat-red-envelope-cover-designer 核心功能: 生成符合微信红包封面开放平台规范的封面设计图片,包括封面图、封面挂件、气泡挂件和封面故事素材。 适用场景:
- 春节、中秋、情人节、七夕、圣诞、生日、元宵、端午、元旦、国庆等节日主题红包封面设计
- 自定义品牌活动主题红包封面设计
- 企业或个人定制红包封面需求
完整工作流程
需求确认 → 制定设计方案 → 生成封面图片 → 交付与审核提醒阶段一:需求确认
在开始设计前,必须与用户确认以下关键信息:
1. 基础信息确认
封面主题:
- 节日类型选择:春节、中秋、情人节、七夕、圣诞、生日、元宵、端午、元旦、国庆
- 或品牌活动主题
封面简称:
- 最多8个字
- 显示在红包封面上
- 可以是:企业名称字号、公众号名称、视频号名称、注册商标
是否需要品牌logo:
- 是/否
- 如果是,需要提供logo文件(200×200px,≤100KB)
目标受众:
- 普通用户
- 企业客户
- 特定群体
2. 设计风格确认
整体风格选择:
- 国潮(传统元素+现代设计)
- 简约
- 插画
- 3D立体
- 扁平化
- 手绘
- Q萌可爱
- 复古
- 其他自定义风格
主色调偏好:
- 红金色系(适合春节、国庆等喜庆节日)
- 粉紫色系(适合情人节、七夕)
- 蓝白色系(适合中秋、冰雪主题)
- 绿色系(适合端午、环保主题)
- 自定义颜色描述
元素偏好:
- 传统元素(祥云、纹样、窗花等)
- 现代元素(几何图形、抽象设计等)
- 吉祥物
- 抽象图案
- 特定IP形象
3. 素材需求确认
用户选择需要生成的素材:
- ☑️ 封面图(必需素材)
- ☐ 封面挂件(可选素材)
- ☐ 气泡挂件(可选素材)
- ☐ 封面故事图片(可选素材,最多5张)
4. 特殊需求确认
是否包含人物肖像:
- 如果是,需要提供肖像权授权书(授权有效期需≥提审后6个月)
是否使用注册商标:
- 如果是,需要提供商标注册证
是否包含版权素材:
- 如果是,需要提供版权授权书(授权有效期需≥提审后6个月)
阶段二:制定设计方案
根据用户确认的需求,向用户展示完整的设计方案:
方案展示格式
【设计方案】
━━━━━━━━━━━━━━━━━━━━━━━━
🎨 主题:[主题名称]
📝 封面简称:[简称](≤8字)
🎯 设计风格:[风格描述]
🎨 主色调:[色彩方案]
📦 素材清单:
1. ☑️ 封面图(必需)- 957×1278像素
2. ☐ 封面挂件(可选)- 1053×1746像素
3. ☐ 气泡挂件(可选)- 480×384像素
4. ☐ 封面故事图片(可选)- 750×1250像素 × [数量]
━━━━━━━━━━━━━━━━━━━━━━━━方案确认
- 用户确认设计方案无误后,进入下一阶段
- 如有修改需求,调整后再次确认
- 确认后方可开始生成图片
阶段三:生成封面图片
严格按照以下顺序和规范生成各类素材:
生成顺序
1. 封面图(必需)- 957×1278px 2. 封面挂件(可选)- 1053×1746px 3. 气泡挂件(可选)- 480×384px 4. 封面故事图(可选)- 750×1250px
图片生成步骤
步骤1:选择提示词模板
如果是节日主题:
- 使用
references/festival-prompts.md中对应的节日专属提示词 - 确保使用与节日匹配的元素和配色方案
如果是自定义主题:
- 使用
references/prompt-templates.md中的通用模板 - 根据用户需求替换变量,补充细节
步骤2:构建完整提示词
根据选择的模板,替换以下变量:
[用户选择的风格]:替换为用户确认的具体风格[主色调]:替换为用户确认的配色方案[主题描述]:替换为具体的主题名称[核心视觉元素描述]:替换为用户确认的核心元素(如生肖形象、动物等)[背景设计]:替换为具体的背景描述[装饰性元素]:替换为用户确认的装饰元素
步骤3:强调关键构图规则
- 使用
references/dimension-specs.md中的尺寸规范文件的详细说明
在提示词中必须包含尺寸规范文件中的相关规则,例如以下说明:
封面图构图规则:
- 主要视觉元素集中在画面中部和下部
- 上方区域避免复杂图案,确保白色文字清晰可读
- 画面底部15%可能被遮挡,避免放置关键元素
封面挂件构图规则:
- 说明:挂件应是一个图案或图标,具备一定的造型和装饰纹样,不应是单一的线条或线框。封面挂件会和封面图一起展示在拆红包页。
- 尺寸:1053×1746像素,宽高比约3:5
- 可编辑区域:仅顶部约324px区域可编辑(其余区域为不可编辑区域)
- 设计元素必须 100% 位于顶部324px区域内(包括阴影、光晕、粒子等细节)
- 顶部324px以下区域必须保持完全空白/透明(不可出现任何不透明像素)
- 挂件应是一个图案或图标,具备一定的造型和装饰纹样,不应是单一的线条或线框。
气泡挂件构图规则:
- 挂件应是一个图案或图标,具备一定的造型和装饰纹样,不应是单一的线条或线框。气泡挂件会和封面图一起展示在红包气泡上。
- 尺寸:480×384像素,宽高比5:4
- 可编辑区域(按平台编辑器示意图):
- 顶部96px为可编辑区域(可出现元素)
- 中间216px为不可编辑区域(必须完全透明留空,不可出现任何不透明像素)
- 底部72px为可编辑区域(可出现元素)
- 元素不得越界:包括阴影、描边、发光、粒子等都不得进入中间216px不可编辑区域
封面故事图构图规则:
- 说明:封面故事将在红包详情页展示,用于讲述品牌、团队、封面创作背后的故事,让品牌文化更好地传递。
- 尺寸:750×1250像素,宽高比3:5
- 画面顶部和底部各10%可能在部分设备上被裁切
- 关键元素集中在画面中央80%区域
- 底部预留空间用于可选的故事描述文字叠加
步骤4:强调禁止元素
在提示词中必须包含以下禁止内容:
- 不包含任何文字、水印或logo
- 不包含二维码、条形码或联系方式
- 不包含真实人物照片或肖像
- 不包含品牌商品图片或广告元素
- 不包含政治、宗教或争议性符号
- 内容适合全年龄段用户
步骤5:调用图片生成工具
- 使用
scripts/generate_image工具生成初始图片,使用命令样例如下:
node scripts/generate_image.js -p "日落山脉风景" -f "sunset.png" -a 1:1 -r 2K命令解释如下:
| 参数 | 必填 | 说明 |
|---|---|---|
-p / --prompt | 是 | 图片描述(文生图)或编辑指令(图生图)。保留用户原始完整输入。 |
-f / --filename | 否 | 输出图片路径/文件名;不传则自动生成带时间戳的 PNG 文件名,并写入当前目录。 |
-a / --aspect-ratio | 否 | 图片比例:1:1、16:9、9:16、4:3、3:4、3:2、2:3、5:4、4:5、21:9。 |
-r / --resolution | 否 | 图片分辨率:1K / 2K / 4K(必须大写)。不传则不在请求中指定,由 API 侧决定。 |
-i / --input-image | 否 | 图生图输入图片路径;可传多张(最多 14 张)。传入该参数即进入编辑模式。 |
- 根据素材类型设置合适的描述
- 每次调用可以生成多张图片以提高质量
步骤6:调整图片尺寸并自动压缩
- 使用
scripts/readme.md中的说明内容,理解图片尺寸规范和智能压缩策略
使用 scripts/resize_cover.js 脚本对生成的图片进行精确裁剪、尺寸调整和智能压缩:
脚本功能:
- 自动居中裁剪,保持目标比例
- 智能压缩:自动调整质量直到满足微信大小限制
- 支持批量生成所有尺寸
- 自动检查文件大小限制
- 挂件类自动强制安全区域透明
- 显示安全区域提示
- 注意:本脚本不处理背景去除,背景去除请使用 remove_bg_enhanced.js
微信大小限制:
- 封面图:≤500KB
- 封面挂件:≤300KB(PNG透明)
- 气泡挂件:≤300KB(PNG透明)
- 封面故事:≤300KB
使用方法:
1. 安装依赖
cd scripts
npm install2. 单类型裁剪(自动压缩)
# 生成封面图(自动压缩到≤500KB)
node resize_cover.js <输入图片> cover
# 生成封面挂件(自动裁剪到标准尺寸,仅保留顶部324px可编辑区域)
node resize_cover.js <输入图片> hang
# 生成气泡挂件(自动裁剪到标准尺寸,保留顶部96px和底部72px可编辑区域)
node resize_cover.js <输入图片> bubble
# 生成封面故事(自动压缩到≤300KB)
node resize_cover.js <输入图片> story3. 批量生成所有尺寸
node resize_cover.js <输入图片> all [输出目录]4. 指定输出路径
node resize_cover.js <输入图片> <类型> <输出路径>支持的尺寸:
- 封面图:957×1278px(3:4)
- 封面挂件:1053×1746px(3:5,仅顶部324px可编辑,PNG透明)
- 气泡挂件:480×384px(5:4,顶部96px+底部72px可编辑,中间216px透明留空,PNG透明)
- 封面故事:750×1250px(3:5)
智能压缩策略:
脚本会自动尝试多种压缩策略直到满足限制:
- 非透明图片(封面图、故事图):
- JPEG质量调整:90% → 85% → ... → 55%
- 使用mozjpeg优化编码
- 必要时降低分辨率
- 透明图片(挂件类):
- 8-bit调色板PNG
- 降低调色板质量
- 限制颜色数量(128色)
- 最大压缩级别
- 必要时降低分辨率
输出示例:
📸 封面图
原始: 1920×1080px, 2450.50KB
目标: 957×1278px, ≤500KB
裁剪: 1438×1080px → 957×1278px
✅ 已保存: ./output/cover.jpg
大小: 485.32KB / 500KB
压缩: jpeg, 质量75%步骤7:处理透明背景(仅挂件)
对于封面挂件和气泡挂件,需要确保背景为透明(PNG格式),元素必须位于规定的安全区域内。
使用 remove_bg_enhanced.js(AI生成图专用)
适用于AI生成的复杂背景图片,具有智能的背景检测算法:
脚本特点:
- 智能背景检测(支持白色/灰色/自动模式)
- 基于亮度和颜色距离的双重检测算法
- 边缘羽化和抗锯齿处理
- 噪点过滤(自动去除小杂质)
- 针对AI生成图片优化
使用方法:
1. 先生成白底图片
# 生成封面挂件(提示词中包含 "PURE WHITE BACKGROUND")
node generate_image.js -p "... PURE WHITE BACKGROUND ..." -i "封面.png" -f "封面挂件_白底.png" -a "2:3"
# 生成气泡挂件
node generate_image.js -p "... PURE WHITE BACKGROUND ..." -i "封面.png" -f "气泡挂件_白底.png" -a "5:4"2. 使用增强脚本去除背景
# 处理封面挂件
node remove_bg_enhanced.js 封面挂件_白底.png 封面挂件.png --mode white --tolerance 35 --feather 4
# 处理气泡挂件
node remove_bg_enhanced.js 气泡挂件_白底.png 气泡挂件.png --mode white --tolerance 35 --feather 33. 调整尺寸到规范要求
# 封面挂件调整到标准尺寸(已透明,直接裁剪即可)
node resize_cover.js 封面挂件.png hang 封面挂件.png
# 气泡挂件调整到标准尺寸(已透明,直接裁剪即可)
node resize_cover.js 气泡挂件.png bubble 气泡挂件.png参数说明:
| 参数 | 说明 | 默认值 | 建议值 |
|---|---|---|---|
--mode | 背景检测模式:white/gray/auto | auto | white(白底图) |
--tolerance | 背景容差(0-100),越大越激进 | 30 | 35-50 |
--feather | 边缘羽化像素(0-20) | 3 | 3-5 |
--smooth | 边缘平滑度(0-10) | 2 | 2-4 |
--min-area | 最小保留区域像素数 | 100 | 100-200 |
完整工作流示例:
# 1. 生成白底挂件图片
node generate_image.js \
-p "A decorative pendant design on PURE WHITE BACKGROUND..." \
-i "封面.png" \
-f "封面挂件_白底.png" \
-a "2:3"
# 2. 去除背景
node remove_bg_enhanced.js \
封面挂件_白底.png \
封面挂件_透明.png \
--mode white \
--tolerance 35 \
--feather 4 \
--min-area 200
# 3. 调整尺寸到微信规范
node resize_cover.js \
封面挂件_透明.png \
hang \
封面挂件.png \
--no-remove-bg注意事项:
- 生成图片时务必在提示词中加入
PURE WHITE BACKGROUND - 容差值根据实际效果调整,复杂背景可适当增大
- 羽化值越大边缘越柔和,但可能损失细节
- 处理完成后必须使用
resize_cover.js调整至标准尺寸
阶段四:交付与审核提醒
完成所有素材生成后,向用户提供完整的交付清单和审核提醒:
交付清单格式
【交付清单】
━━━━━━━━━━━━━━━━━━━━━━━━
✅ 封面图 (957×1278px),文件名称
└─ 格式:PNG/JPG,≤500KB
☐ 封面挂件 (1053×1746px),文件名称
└─ 格式:PNG透明背景,≤300KB
☐ 气泡挂件 (480×384px),文件名称
└─ 格式:PNG透明背景,≤300KB
☐ 封面故事图片 (750×1250px) × [数量],文件名称
└─ 格式:PNG/JPG,≤300KB每张
📋 提交审核提醒:
- [ ] 准备封面简称(≤8字)
- [ ] 准备品牌logo(如需,200×200px,≤100KB)
- [ ] 准备证明材料(如涉及商标/肖像/版权)
- [ ] 授权有效期需≥提审后6个月
━━━━━━━━━━━━━━━━━━━━━━━━审核前自检提醒
提醒用户在提交审核前进行以下检查:
1. 内容合规性检查
- 不包含任何法律禁止内容
- 不包含政治敏感内容
- 不包含联系方式和引流信息
- 不包含商业推广内容
- 不侵犯他人版权、商标、肖像权
2. 设计质量检查
- 画面清晰锐利,无模糊、锯齿
- 色彩搭配和谐统一
- 构图平衡,焦点明确
- 文件大小符合要求
- 尺寸精确无误
3. 安全区域检查
- 封面图上方25%区域简洁干净
- 封面图底部15%无关键元素
- 封面挂件元素仅在顶部324px可编辑区域内(其余区域透明留空)
- 气泡挂件中间216px不可编辑区域完全透明(元素仅出现在顶部96px与底部72px)
4. 格式要求检查
- 封面图:PNG/JPG,≤500KB,957×1278px
- 封面挂件:PNG透明,≤300KB,1053×1746px
- 气泡挂件:PNG透明,≤300KB,480×384px
- 封面故事:PNG/JPG,≤300KB/张,750×1250px
工作流程的关键原则
1. 循序渐进
- 每个阶段完成后才能进入下一阶段
- 必须在用户确认设计方案后才能开始生成图片
- 确保每个步骤都符合规范要求
2. 遵循规范
- 严格遵循
references/dimension-specs.md中的尺寸规范 - 严格遵循
references/prompt-templates.md和references/festival-prompts.md中的提示词模板 - 严格遵循
references/audit-standards.md中的审核标准
3. 风格统一
- 所有素材(封面图、挂件、故事图)必须使用统一的设计语言
- 色彩、风格、元素选择要协调一致
- 形成完整的视觉体系
4. 安全优先
- 始终以审核通过率为首要目标
- 任何不确定的内容都不要使用
- 保持内容积极向上、健康正面
5. 用户确认
- 每个关键阶段都需要用户确认
- 用户的满意度是最终目标
- 根据用户反馈及时调整
常见场景处理
场景1:用户选择节日主题
1. 从 references/festival-prompts.md 中选择对应的节日模板 2. 使用节日专属的元素和配色方案 3. 按照节日主题的设计要求生成素材
场景2:用户有自定义品牌需求
1. 使用 references/prompt-templates.md 中的通用模板 2. 融入用户的品牌元素(logo、品牌色等) 3. 提醒用户准备商标注册证等证明材料
场景3:用户需要所有素材
1. 按照顺序生成:封面图 → 封面挂件 → 气泡挂件 → 封面故事图 2. 确保每张图片都符合对应的尺寸规范 3. 保持所有素材的风格统一
场景4:用户只需要封面图
1. 仅生成封面图(957×1278px) 2. 确保构图符合安全区域要求 3. 重点优化封面图的视觉效果
技能目录文件结构
核心功能: 存放技能运行所需的参考文档、模板和规范文件,为微信红包封面设计提供标准化的指导依据。
├── SKILL.md # 技能主文件,包含完整的工作流程和使用说明
└── scripts/ # 脚本文件夹
├── generate_image.js # 图片生成脚本(文生图/图生图)
├── resize_cover.js # 图片尺寸裁剪和压缩脚本
├── remove_bg_enhanced.js # 增强版背景去除脚本(AI图专用)
├── package.json # 依赖配置
└── README.md # 脚本使用说明
└── references/ # 参考文档文件夹
├── dimension-specs.md # 尺寸规范文档
├── prompt-templates.md # 通用提示词模板
├── festival-prompts.md # 节日专属提示词
└── audit-standards.md # 审核标准文档
references文件清单及用途说明
1. dimension-specs.md(尺寸规范文档)
文档作用: 定义微信红包封面各类素材的精确尺寸规格和设计要求
2. prompt-templates.md(通用提示词模板)
文档作用: 提供各类素材的通用提示词模板,确保生成的图片符合微信红包封面规范
3. festival-prompts.md(节日专属提示词)
文档作用: 为不同节日提供专属的提示词模板,包含节日特定的元素、配色方案和设计要求
4. audit-standards.md(审核标准文档)
文档作用: 详细说明微信红包封面的审核标准和禁止内容,确保生成的素材能够通过平台审核
scripts脚本清单及用途说明
1. generate_image.js(图片生成脚本)
文件作用: 调用AI接口生成图片,支持文生图和图生图
核心功能:
- 文生图:根据提示词生成全新图片
- 图生图:基于已有图片进行编辑和优化
- 支持多种比例和分辨率
使用场景:
- 生成封面图、挂件、故事图等素材
- 基于已有图片生成配套素材(保持风格一致)
2. resize_cover.js(尺寸裁剪脚本)
文件作用: 调整图片尺寸至微信红包封面规范,并进行智能压缩
核心功能:
- 自动居中裁剪,保持目标比例
- 智能压缩至符合大小限制
- 挂件类自动强制安全区域透明(仅保留可编辑区域内容)
使用场景:
- 将图片调整到微信红包封面的标准尺寸
- 压缩文件至符合微信大小限制
- 强制挂件类图片的安全区域(自动裁剪掉不可编辑区域)
注意事项:
- 本脚本不负责背景去除,仅处理尺寸和压缩
- 如需去背景,请先使用 remove_bg_enhanced.js 处理
3. remove_bg_enhanced.js(增强版背景去除脚本)⭐ 推荐
文件作用: 专为AI生成图片设计的智能背景去除工具
核心功能:
- 智能背景检测(白色/灰色/自动模式)
- 亮度和颜色距离双重检测算法
- 边缘羽化和抗锯齿处理
- 噪点过滤,保留主体细节
使用场景:
- AI生成挂件图片的背景去除
- 复杂背景的专业级抠图
- 需要精细边缘处理的场景
脚本分工:
| 功能 | resize_cover.js | remove_bg_enhanced.js |
|---|---|---|
| 主要职责 | 尺寸裁剪和压缩 | 背景去除和透明处理 |
| 适用场景 | 所有图片的尺寸标准化 | AI生成图片的背景去除 |
| 核心能力 | 居中裁剪、智能压缩、安全区域限制 | 智能背景检测、边缘羽化、噪点过滤 |
| 使用时机 | 最后一步尺寸标准化 | 去背景后,resize_cover.js之前 |
| 典型工作流 | 最后执行 | 先生成白底图→去背景→resize_cover.js调整尺寸 |
references文件使用原则
1. 优先使用节日专属模板
- 当用户选择节日主题时,优先使用 festival-prompts.md 中的对应模板
- 确保节日元素和配色方案符合节日特色
2. 通用模板作为补充
- 当用户有自定义需求或节日专属模板不完全匹配时,使用 prompt-templates.md 中的通用模板
- 根据用户需求替换变量,补充细节
3. 严格遵守尺寸规范
- 所有素材必须严格按照 dimension-specs.md 中的尺寸要求生成
- 特别注意构图安全区域,确保文字可读性
4. 始终遵循审核标准
- 所有设计必须符合 audit-standards.md 中的要求
- 避免使用任何禁止内容
- 确保内容适合全年龄段用户
5. 风格保持一致
- 所有素材(封面图、挂件、故事图)必须使用统一的设计语言
- 色彩、风格、元素选择要协调
4. audit-standards.md(审核标准文件)
文件作用: 详细说明微信红包封面的审核标准和禁止内容
主要内容包含:
- 法律法规禁止内容
- 国家安全与统一相关禁止内容
- 违法行为相关内容(恐怖主义、邪教、淫秽色情、赌博、毒品等)
- 违法广告相关内容
- 平台规范禁止内容
- 联系方式与引流(二维码、电话、网址等)
- 商业推广(广告语、促销信息、价格信息)
- 版权与商标(未经授权的商标、版权素材、知名IP)
- 政治敏感内容(国旗、国徽、天安门、中国地图、领导人肖像)
- 封禁人物(负面舆论人物)
- 重复内容
- 未成年人保护要求
- 禁止未成年人不当行为的内容
- 避免过于暴露或性暗示的设计
- 避免暴力、血腥、恐怖元素
- 设计规范禁止项
- 封面图设计问题(上方25%过于复杂、背景色与文字对比不足等)
- 封面挂件设计问题(简单边框、元素延伸到禁止区域等)
- 气泡挂件设计问题(元素过大、延伸到禁止区域等)
- 品牌logo问题(尺寸过大、位置不当、质量低劣)
- 审核最佳实践
- 设计前的自检清单(内容合规性、设计质量、安全区域、格式要求)
- 常见审核失败原因及解决方法
- 提高审核通过率的建议
- 特殊场景审核要点
- 节日主题(春节、国庆节、情人节、圣诞节的特别注意事项)
- 企业品牌(商标注册证要求)
- IP形象(版权授权书要求)
- 人物肖像(肖像权授权书要求)
- 常见问题解答
- 审核不通过的常见原因
- 真实人物照片的使用限制
- 品牌logo的要求
- 封面故事图片的限制
- 国庆节可用元素的说明
- 审核流程时间
- 常规审核:1-3个工作日
- 高峰期:3-5个工作日
- 申诉联系方式和方法
1. dimension-specs.md(尺寸规范文件)
文档类型: 尺寸规范参考文档
文档作用: 定义微信红包封面各类素材的精确尺寸规格、文件格式要求和构图安全区域,确保生成的素材符合微信红包封面开放平台的标准。
主要内容包含:
- 封面图规范
- 封面图和挂件的风格与布局应协调统一。
- 尺寸:957×1278像素,宽高比3:4
- 文件大小:≤500KB
- 格式:PNG或JPG
- 构图安全区域:中下部50%为主要视觉区域,底部15%避免关键元素
- 封面挂件规范
- 说明:挂件应是一个图案或图标,具备一定的造型和装饰纹样,不应是单一的线条或线框。封面挂件会和封面图一起展示在拆红包页。
- 尺寸:1053×1746像素,宽高比约3:5
- 文件大小:≤300KB
- 格式:PNG(必须透明背景)
- 可编辑区域:仅顶部约324px为可编辑区域(其余区域不可编辑,必须透明留空)
- 气泡挂件规范
- 挂件应是一个图案或图标,具备一定的造型和装饰纹样,不应是单一的线条或线框。气泡挂件会和封面图一起展示在红包气泡上。
- 尺寸:480×384像素,宽高比5:4
- 文件大小:≤300KB
- 格式:PNG(必须透明背景)
- 可编辑区域(按平台编辑器示意图):顶部96px可编辑,中间216px不可编辑(必须透明),底部72px可编辑
- 封面故事图片规范
- 说明:封面故事将在红包详情页展示,用于讲述品牌、团队、封面创作背后的故事,让品牌文化更好地传递。
- 尺寸:750×1250像素,宽高比3:5
- 文件大小:≤300KB(每张)
- 数量:最多5张
- 格式:PNG或JPG
- 安全区域:中央80%为关键元素区域
- 品牌logo规范
- 尺寸:200×200像素
- 文件大小:≤100KB
- 格式:PNG(透明背景)或JPG
- 通用规范
- 文件格式要求
- 色彩模式(RGB)
- 分辨率要求(72 DPI)
- 质量要求和测试建议
文件结构
目录概览
文档包含以下六个主要章节:
1. 封面图 2. 封面挂件 3. 气泡挂件 4. 封面故事图片 5. 品牌logo 6. 文件格式要求
第一章:封面图规范
基本信息
- 类型: 必需素材(每个红包封面必须包含)
- 尺寸: 957×1278 像素
- 宽高比: 3:4(竖版)
- 文件大小: ≤500KB
- 格式: PNG 或 JPG
构图安全区域(关键)
上方25%(约320px高度):
- 用途:该区域将显示用户头像、昵称和祝福语
- 要求:
- 保持简洁
- 避免纯白或极浅色背景(影响白色文字可读性)
- 确保与白色文字有足够对比度
中下部50%:
- 用途:主要视觉元素区域
- 要求:放置核心视觉元素(如生肖、吉祥物等)
底部15%(约190px高度):
- 用途:可能被遮挡
- 要求:避免放置关键元素
设计建议
- 使用渐变或精致纹理背景
- 主视觉元素集中在画面中部和下部
- 节日主题推荐使用饱和度较高的喜庆色彩
- 保持优雅喜庆的美学风格
第二章:封面挂件规范
基本信息
- 类型: 可选素材
- 尺寸: 1053×1746 像素
- 宽高比: 约3:5
- 文件大小: ≤300KB
- 格式: PNG(必须透明背景)
安全区域(关键)
顶部可编辑区域(约324px高度):
- 用途:挂件的所有可见元素只能出现在该区域
- 要求:
- 设计元素必须 100% 位于顶部324px内(包括阴影、描边、光晕、粒子等细节)
- 建议主体尽量居中,避免贴边(防止不同机型/渲染裁切)
顶部324px以下区域:
- 用途:不可编辑区域
- 要求:必须保持完全空白/透明(不可出现任何不透明像素)
设计要求
- 必须有实质性的造型和视觉层次
- 不能是单一线条或简单边框
- 不应完全填满画框(保持红包轮廓辨识度)
- 不包含任何文字
- 必须为透明背景
- 与封面图风格协调统一
常见错误
- ❌ 简单的线条或边框作为挂件
- ❌ 完全填满整个画面的设计
- ❌ 元素延伸到顶部324px以下的不可编辑区域
- ❌ 不透明背景
第三章:气泡挂件规范
基本信息
- 类型: 可选素材
- 尺寸: 480×384 像素
- 宽高比: 5:4
- 文件大小: ≤300KB
- 格式: PNG(必须透明背景)
安全区域(关键)
顶部可编辑区域(96px高度):
- 用途:可以放置气泡挂件的设计元素
- 要求:所有元素(含阴影、发光等)必须完全位于顶部96px内
中间不可编辑区域(216px高度):
- 用途:不可编辑区域
- 要求:必须保持完全空白/透明(不可出现任何不透明像素)
底部可编辑区域(72px高度):
- 用途:可放置补充性装饰元素(更适合小体量点缀)
- 要求:所有元素必须完全位于底部72px内
设计要求
- 小巧、易识别的图标或装饰元素
- 在小尺寸显示时仍清晰可辨
- 不包含文字
- 透明背景
- 喜庆、欢乐的氛围
常见错误
- ❌ 元素过大影响文字显示
- ❌ 元素进入中间216px不可编辑区域
- ❌ 复杂的设计在小尺寸下不清晰
- ❌ 不透明背景
第四章:封面故事图片规范
基本信息
- 类型: 可选素材
- 尺寸: 750×1250 像素
- 宽高比: 3:5
- 文件大小: ≤300KB(每张)
- 数量: 最多5张
- 格式: PNG 或 JPG
安全区域
- 顶部10%: 可能在部分设备上被裁切
- 底部10%: 可能在部分设备上被裁切,也用于可选的故事描述文字叠加
- 中央80%: 关键元素集中区域
设计要求
- 具有叙事性的视觉画面,诠释封面的含义
- 可比封面图更加细节丰富
- 保持与封面图一致的设计风格
- 不包含广告或促销内容
内容建议
- 品牌故事
- 创作背景
- 祝福寓意
- 节日场景
- 温馨氛围
第五章:品牌logo规范
基本信息
- 类型: 可选素材(仅企业账号使用)
- 尺寸: 200×200 像素
- 文件大小: ≤100KB
- 格式: PNG(必须透明背景)或 JPG
设计要求
- 尺寸适中,位置得当
- 不影响整体设计美感
- 与封面风格协调统一
- 必须为已注册商标或品牌标识
第六章:文件格式要求
通用规则
1. 文件大小限制: 严格按照各类素材的大小上限 2. 透明背景: 挂件必须使用PNG格式的透明背景 3. 色彩模式: RGB色彩模式 4. 分辨率: 72 DPI
文件命名建议
- 封面图:
cover.png或cover.jpg - 封面挂件:
pendant.png - 气泡挂件:
bubble.png - 封面故事:
story-01.png、story-02.png等 - 品牌logo:
logo.png或logo.jpg
质量要求
1. 图片清晰度: 所有素材必须清晰锐利,无模糊、锯齿 2. 色彩准确: 色彩还原准确,无色偏 3. 细节完整: 细节丰富,无明显压缩痕迹 4. 无水印: 不得包含任何水印、版权标识
注意事项
尺寸适配
- 所有素材必须严格按照指定尺寸制作
- 不要拉伸或压缩图片导致变形
- 建议在设计软件中精确设置画布尺寸
文件大小控制
- 优化图片质量,控制文件大小在限制内
- 使用合理的压缩比例
- 避免过度压缩导致质量下降
格式选择
- 封面图和封面故事:推荐PNG(无损),也可用JPG
- 挂件:必须PNG(透明背景)
- 品牌logo:推荐PNG(透明背景),可用JPG
测试建议
- 在微信红包封面开放平台预览工具中测试显示效果
- 在不同设备上测试显示效果(手机、平板等)
- 确保文字区域清晰可读
文件使用场景
在技能的工作流程中,dimension-specs.md 文件在以下场景中被使用:
1. 需求确认阶段: 向用户说明各类素材的尺寸限制 2. 制定设计方案阶段: 在方案中列出各类素材的尺寸信息 3. 生成封面图片阶段: 确保生成的图片符合精确尺寸要求 4. 图片裁剪阶段: 使用图像处理工具调整到标准尺寸 5. 交付与审核提醒阶段: 在交付清单中列出尺寸规范 6. 自检阶段: 验证所有素材的尺寸是否符合要求
这个文件是微信红包封面设计技能的核心参考文档之一,确保所有产出的素材都能顺利通过平台审核并呈现出专业的视觉效果。
3. festival-prompts.md(节日专属提示词文件)
文件作用: 为不同节日提供专属的提示词模板,包含节日特定的元素和配色方案
主要内容包含:
- 春节/新年红包
- 生肖形象、传统符号(福字、灯笼、鞭炮)、植物花卉、吉祥图案
- 配色:中国红、金色、暗红色为主
- 包含封面图、封面挂件、气泡挂件、封面故事图的完整提示词
- 中秋节红包
- 圆月、玉兔、嫦娥、桂花树、月饼、灯笼等元素
- 配色:宝蓝、藏青、金色、月白色为主
- 意境悠远、典雅温馨的设计要求
- 情人节/七夕红包
- 爱心、玫瑰花、丘比特之箭、鹊桥、银河等元素
- 配色:粉红、玫红、紫罗兰为主
- 浪漫唯美、甜蜜可爱风格
- 圣诞节红包
- 圣诞树、圣诞老人、驯鹿、雪花、礼物盒等元素
- 配色:圣诞红、森林绿、金色为主
- 温馨、欢乐、童话感
- 生日红包
- 生日蛋糕、蜡烛、气球、礼物盒等元素
- 配色:糖果色系(粉色、蓝色、金色)
- 欢乐、温馨、祝福感
- 元宵节红包
- 花灯、孔明灯、汤圆、红灯笼等元素
- 配色:暖橙、金黄、朱红为主
- 热闹、温馨、团圆氛围
- 端午节红包
- 粽子、龙舟、艾草、五彩绳等元素
- 配色:粽叶绿、龙舟红、金色为主
- 清新、传统、安康寓意
- 元旦/新年红包
- 烟花、香槟、气球、时钟等元素
- 配色:金色、黑色、白色为主
- 高级、时尚、欢庆
- 国庆节红包
- 烟花、彩旗、气球、和平鸽等元素
- 配色:中国红、金色为主
- 特别强调不使用国旗、国徽、天安门等政治元素
- 使用说明
- 提示词选择原则
- 变量替换方法
- 元素选择建议
- 色彩调整建议
- 风格一致性要求
- 审核红线提醒
---
通用骨架(适用于所有节日)
说明:本文件按“节日元素/配色建议”做索引;具体可直接复制使用的提示词正文见 prompt-templates.md。
封面挂件(hang)节日骨架(重点:顶部324px可编辑)
在 prompt-templates.md 的“封面挂件模板”基础上,仅替换这些变量即可:
[主题描述]:春节/中秋/情人节等[核心视觉元素描述]:灯笼/玉兔/爱心等[装饰性元素]:祥云纹样/桂花/星光粒子等[主色调]:红金/蓝金/粉紫等[用户选择的风格]:国潮/插画/3D/扁平等
强制提醒:封面挂件只能在顶部324px区域出图,其余区域必须完全透明。
prompt-templates.md(通用提示词模板文件)
文件作用: 提供各类素材的通用提示词模板,确保生成的图片符合规范
主要内容包含:
- 封面图提示词模板
- 画布规格说明
- 主视觉元素描述
- 构图规则(特别强调上方25%简洁要求)
- 设计规范(不含文字、水印、二维码等禁止元素)
- 色彩要求
- 变量替换说明
- 封面挂件提示词模板
- 画布规格和透明背景要求
- 设计要求(必须有实质性造型)
- 可编辑区域说明(仅顶部约324px可编辑,其余区域必须完全透明)
- 风格规范
- 常见元素类型(垂吊式、环绕式、角落式、点缀式)
- 气泡挂件提示词模板
- 画布规格和透明背景要求
- 设计要求(小巧、易识别)
- 可编辑区域说明(顶部96px可编辑 + 中间216px必须透明 + 底部72px可编辑)
- 常见元素类型(图标式、角色式、装饰式)
- 封面故事图片提示词模板
- 画布规格
- 内容要求(叙事性元素)
- 构图规则(中央80%为关键区域)
- 常见主题类型(温馨团圆、美好祝福、品牌故事等)
- 提示词优化建议
- 明确性原则
- 简洁性原则
- 一致性原则
- 安全性原则
- 实用性原则
- 提示词生成工作流
- 确认需求→选择模板→替换变量→补充细节→审核检查→生成图片→效果评估
---
可直接复制的通用提示词模板
说明:以下模板按微信红包封面开放平台常见审核点编写,重点强化“无文字/无水印/挂件透明背景/挂件仅顶部可编辑区域出图”。
A. 封面图(cover)模板(957×1278,3:4)
请生成一张微信红包封面图(竖版),画布尺寸 957×1278px,比例 3:4,RGB,72dpi。
主题/风格: [主题描述],整体风格为[用户选择的风格],主色调为[主色调]。
色彩锁定(强制,建议填色值以保证一致):
- 主背景色必须为红包红:使用[主色值](HEX/RGB)作为大面积底色铺满画面。
- 若未提供[主色值],则按[主色调]生成“高饱和、偏暖的红包红”大面积铺底。
- 禁止生成白底/灰底/大面积留白的封面图。
构图规则(强制): 1) 画面上方25%区域保持简洁干净,避免复杂纹样与高频细节,保证白色文字清晰可读。 2) 核心视觉元素集中在中下部50%,形成明确焦点。 3) 底部15%避免放置关键元素。
设计要求(强制):画面精致、有层次、细节清晰;不包含任何文字/标语/数字;不包含二维码;不包含水印、署名、logo;不出现边框式海报排版。
背景与装饰:在“红包红”主背景色上叠加[背景设计](可为轻微纹理/渐变/金粉颗粒,但不能稀释主红色的占比),搭配[装饰性元素],整体喜庆但不杂乱。
输出:高质量、干净画面。
B. 封面挂件(hang/pendant)模板(1053×1746,3:5,PNG透明)
请生成一张“微信红包封面挂件”PNG图片,画布尺寸 1053×1746px(约3:5),必须透明背景(alpha透明)。
【最重要规则 - 强制】 1) 仅顶部约324px高度为可编辑区域:所有可见元素只能出现在画面顶部 324px 内。 2) 顶部324px以下区域必须 100% 完全透明留空(不可出现任何不透明像素)。 3) 元素不得延伸越界:包括阴影、描边、发光、粒子、反射等都必须完全在顶部324px内。
内容与造型(强制):
- 挂件必须是“一个具备实体造型和层次的图案/装饰图标”,可以有纹样与立体质感;不能是单一线条、简单线框、或只画一个边框。
- 不包含任何文字、数字、logo、水印、二维码。
设计建议(可选):
- 让主体在顶部可编辑区域内居中排布,适度留边(避免贴边被裁切)。
- 细节清晰、对比明确,边缘干净,适合悬浮叠加在封面图上。
主题与元素: [主题描述];核心元素为[核心视觉元素描述];装饰为[装饰性元素];风格为[用户选择的风格];主色调为[主色调]。
输出要求:透明背景PNG;画面干净无杂边;高质量。
C. 气泡挂件(bubble)模板(480×384,5:4,PNG透明)
请生成一张“微信红包气泡挂件”PNG图片,画布尺寸 480×384px(5:4),透明背景。
构图规则(强制): 1) 顶部96px为可编辑区域:可出现元素。 2) 中间216px为不可编辑区域:必须 100% 完全透明留空(不可出现任何不透明像素)。 3) 底部72px为可编辑区域:可出现元素(更适合小体量点缀)。 4) 元素不得越界:包括阴影、描边、发光、粒子等都不得进入中间216px不可编辑区域。 5) 不包含任何文字、数字、logo、水印、二维码。
造型要求:小巧、清晰、易识别;层次简洁;边缘干净;适合小尺寸显示。
主题与元素: [主题描述];元素为[核心视觉元素描述];风格为[用户选择的风格];主色调为[主色调]。
输出:透明背景PNG,高质量。
D. 封面故事图(story)模板(750×1250,3:5)
请生成一张微信红包“封面故事图片”,画布尺寸 750×1250px(3:5),RGB,72dpi。
构图规则(强制): 1) 顶部10%和底部10%避免放置关键元素(可能裁切/遮挡)。 2) 关键元素集中在中央80%区域。
内容:具有叙事感的画面,表达[主题描述]的寓意与氛围;风格与封面图一致。
禁止项(强制):不含文字、广告语、价格信息、二维码、水印、联系方式。
输出:高质量、清晰、无明显压缩痕迹。
#!/usr/bin/env node
/*
基于NanoBananaPro/Gemini 3 Pro的图片生成与编辑脚本(Node.js版)
使用API易国内代理服务
支持功能:
- 文生图:根据提示词生成图片
- 图生图:根据编辑指令修改已有图片
参数说明:
- -p, --prompt 图片描述或编辑指令文本(必需)
- -f, --filename 输出图片路径(可选,默认自动生成时间戳文件名)
- -a, --aspect-ratio 图片比例(可选)
- -r, --resolution 图片分辨率(可选:1K/2K/4K,必须大写)
- -i, --input-image 输入图片路径(可选,可多张,最多14张)
- -k, --api-key API密钥(可选,覆盖环境变量 APIYI_API_KEY)
使用示例:
【生成新图片】
node generate_image.js -p "一只可爱的橘猫"
node generate_image.js -p "日落山脉" -a 16:9 -r 4K
node generate_image.js -p "城市夜景" -a 9:16 -r 2K -f wallpaper.png
【编辑已有图片】
node generate_image.js -p "转换成油画风格" -i original.png
node generate_image.js -p "添加彩虹到天空" -i photo.jpg -f edited.png
node generate_image.js -p "将背景换成海滩" -i portrait.png -a 3:4 -r 2K
node generate_image.js -p "参考多张图片融合风格" -i ref1.png ref2.png ref3.png -f merged.png
【环境变量】
export APIYI_API_KEY="your-api-key"
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const SUPPORTED_ASPECT_RATIOS = [
'1:1',
'16:9',
'9:16',
'4:3',
'3:4',
'3:2',
'2:3',
'5:4',
'4:5',
'21:9',
];
const SUPPORTED_RESOLUTIONS = ['1K', '2K', '4K'];
function printHelpAndExit(exitCode = 0) {
const help = `usage: generate_image.js [-h] --prompt PROMPT [--filename FILENAME]
[--aspect-ratio ${SUPPORTED_ASPECT_RATIOS.join(', ')}]
[--resolution ${SUPPORTED_RESOLUTIONS.join(', ')}]
[--input-image INPUT_IMAGE [INPUT_IMAGE ...]]
[--api-key API_KEY]
基于Gemini 3 Pro的图片生成与编辑工具(Node.js版)
options:
-h, --help show this help message and exit
-p, --prompt PROMPT 图片描述或编辑指令文本(必需)
-f, --filename FILE 输出图片路径 (默认: 自动生成时间戳文件名)
-a, --aspect-ratio 图片比例 (可选)
-r, --resolution 图片分辨率 (可选: 1K, 2K, 4K,必须大写)
-i, --input-image 输入图片路径(编辑模式,可传多张,最多14张)
-k, --api-key API密钥(覆盖环境变量)
运行示例:
node scripts/generate_image.js -p "一只可爱的橘猫"
node scripts/generate_image.js -p "日落山脉" -a 16:9 -r 4K
node scripts/generate_image.js -p "城市夜景" -a 9:16 -r 2K -f wallpaper.png
node scripts/generate_image.js -p "转换成油画风格" -i original.png
node scripts/generate_image.js -p "参考多张图片融合风格" -i ref1.png ref2.png -f merged.png
`;
process.stdout.write(help);
process.exit(exitCode);
}
function exitWithError(message) {
process.stderr.write(`${message}\n`);
process.exit(1);
}
function pad2(n) {
return String(n).padStart(2, '0');
}
function formatTimestamp(dateObj) {
const d = dateObj || new Date();
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}-${pad2(d.getHours())}-${pad2(d.getMinutes())}-${pad2(d.getSeconds())}`;
}
function addTimestampToFilename(filePath, timestamp) {
const ts = timestamp || formatTimestamp(new Date());
const parsed = path.parse(filePath);
const base = parsed.name ? `${parsed.name}-${ts}` : ts;
return path.join(parsed.dir || '.', `${base}${parsed.ext || ''}`);
}
function generateFilename(prompt) {
const now = new Date();
const timestamp = formatTimestamp(now);
const keywords = String(prompt).split(/\s+/).filter(Boolean).slice(0, 3);
const keywordStrRaw = keywords.join('-') || 'image';
const keywordStr = keywordStrRaw
.split('')
.map((c) => (/^[a-zA-Z0-9\-_.]$/.test(c) ? c : '-'))
.join('')
.toLowerCase()
.slice(0, 30);
return `${timestamp}-${keywordStr}.png`;
}
function getApiKey(argsKey) {
if (argsKey) return argsKey;
const apiKey = process.env.APIYI_API_KEY;
if (!apiKey) {
exitWithError(
'错误: 未设置 APIYI_API_KEY 环境变量\n' +
'请前往 https://api.apiyi.com 注册申请API Key\n' +
'或使用 -k/--api-key 参数临时指定'
);
}
return apiKey;
}
function encodeImageToBase64(imagePath) {
try {
const bytes = fs.readFileSync(imagePath);
return bytes.toString('base64');
} catch (e) {
exitWithError(`错误: 无法读取图片文件 ${imagePath} - ${e.message || String(e)}`);
}
}
function postJson(urlString, headers, payload, timeoutMs) {
return new Promise((resolve, reject) => {
const url = new URL(urlString);
const body = Buffer.from(JSON.stringify(payload), 'utf8');
const req = https.request(
{
protocol: url.protocol,
hostname: url.hostname,
port: url.port || 443,
path: url.pathname + url.search,
method: 'POST',
headers: {
...headers,
'Content-Length': body.length,
},
},
(res) => {
const chunks = [];
res.on('data', (d) => chunks.push(d));
res.on('end', () => {
const text = Buffer.concat(chunks).toString('utf8');
const statusCode = res.statusCode || 0;
if (statusCode < 200 || statusCode >= 300) {
const err = new Error(`HTTP ${statusCode}`);
err.statusCode = statusCode;
err.responseText = text;
return reject(err);
}
try {
resolve(JSON.parse(text));
} catch (e) {
const err = new Error('响应不是有效的JSON');
err.responseText = text;
return reject(err);
}
});
}
);
req.on('error', reject);
req.setTimeout(timeoutMs, () => {
req.destroy(new Error('timeout'));
});
req.write(body);
req.end();
});
}
function parseArgs(argv) {
const args = {
prompt: null,
filename: null,
aspectRatio: null,
resolution: null,
inputImages: null,
apiKey: null,
};
const knownFlags = new Set([
'-h',
'--help',
'-p',
'--prompt',
'-f',
'--filename',
'-a',
'--aspect-ratio',
'-r',
'--resolution',
'-i',
'--input-image',
'-k',
'--api-key',
]);
function requireValue(i, flag) {
const v = argv[i + 1];
if (!v || (v.startsWith('-') && knownFlags.has(v))) {
exitWithError(`错误: 参数 ${flag} 需要一个值`);
}
return v;
}
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '-h' || a === '--help') {
printHelpAndExit(0);
}
if (a === '-p' || a === '--prompt') {
args.prompt = requireValue(i, a);
i++;
continue;
}
if (a === '-f' || a === '--filename') {
args.filename = requireValue(i, a);
i++;
continue;
}
if (a === '-a' || a === '--aspect-ratio') {
args.aspectRatio = requireValue(i, a);
i++;
continue;
}
if (a === '-r' || a === '--resolution') {
args.resolution = requireValue(i, a);
i++;
continue;
}
if (a === '-k' || a === '--api-key') {
args.apiKey = requireValue(i, a);
i++;
continue;
}
if (a === '-i' || a === '--input-image') {
const images = [];
let j = i + 1;
while (j < argv.length) {
const v = argv[j];
if (v.startsWith('-') && knownFlags.has(v)) break;
images.push(v);
j++;
}
if (images.length === 0) {
exitWithError(`错误: 参数 ${a} 需要至少一个图片路径`);
}
args.inputImages = images;
i = j - 1;
continue;
}
if (a.startsWith('-')) {
exitWithError(`错误: 未知参数 ${a},请使用 --help 查看帮助`);
}
}
if (!args.prompt) {
exitWithError('错误: 缺少必需参数 -p/--prompt');
}
return args;
}
async function main() {
const argv = process.argv.slice(2);
const args = parseArgs(argv);
const runTimestamp = formatTimestamp(new Date());
let checkProgress = null;
const clearProgressTimer = () => {
if (checkProgress) {
clearInterval(checkProgress);
checkProgress = null;
}
};
if (args.aspectRatio != null && !SUPPORTED_ASPECT_RATIOS.includes(args.aspectRatio)) {
exitWithError(
`错误: 不支持的比例 '${args.aspectRatio}'\n支持的比例: ${SUPPORTED_ASPECT_RATIOS.join(', ')}`
);
}
if (args.resolution != null && !SUPPORTED_RESOLUTIONS.includes(args.resolution)) {
exitWithError(
`错误: 不支持的分辨率 '${args.resolution}'\n支持的分辨率: ${SUPPORTED_RESOLUTIONS.join(', ')} (必须大写)`
);
}
if (!args.filename) {
args.filename = generateFilename(args.prompt);
} else {
const resolved = path.resolve(args.filename);
if (fs.existsSync(resolved)) {
const adjusted = addTimestampToFilename(args.filename, runTimestamp);
process.stdout.write(`⚠️ 输出文件已存在,将避免覆盖并改为: ${adjusted}\n`);
args.filename = adjusted;
}
}
const apiKey = getApiKey(args.apiKey);
const url =
'https://api.apiyi.com/v1beta/models/gemini-3-pro-image-preview:generateContent';
const headers = {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
};
const parts = [{ text: args.prompt }];
let modeStr = '生成图片';
if (args.inputImages && args.inputImages.length > 0) {
if (args.inputImages.length > 14) {
exitWithError(`错误: 输入图片最多支持14张,当前为 ${args.inputImages.length} 张`);
}
for (const imgPath of args.inputImages) {
if (!fs.existsSync(imgPath)) {
exitWithError(`错误: 输入图片不存在: ${imgPath}`);
}
const imageBase64 = encodeImageToBase64(imgPath);
parts.push({
inlineData: {
mimeType: 'image/png',
data: imageBase64,
},
});
}
modeStr = '编辑图片';
}
const generationConfig = {
responseModalities: ['IMAGE'],
};
const imageConfig = {};
if (args.aspectRatio != null) imageConfig.aspectRatio = args.aspectRatio;
if (args.resolution != null) imageConfig.imageSize = args.resolution;
if (Object.keys(imageConfig).length > 0) generationConfig.imageConfig = imageConfig;
const payload = {
contents: [{ parts }],
generationConfig,
};
// 生成前通知 + 生成中实时日志(避免长时间无输出导致体验不佳)
const resolutionHint = args.resolution;
const etaText = resolutionHint === '4K' ? '1-6分钟' : '30-120秒';
process.stdout.write('🎨 图片生成已启动!\n');
process.stdout.write(`⏱️ 预计时间: ${etaText}\n`);
process.stdout.write('📊 我会定期给您发送进度更新\n');
process.stdout.write(`正在${modeStr}...\n`);
process.stdout.write(`提示词: ${args.prompt}\n`);
if (generationConfig.imageConfig && generationConfig.imageConfig.aspectRatio) {
process.stdout.write(`比例: ${generationConfig.imageConfig.aspectRatio}\n`);
}
if (generationConfig.imageConfig && generationConfig.imageConfig.imageSize) {
process.stdout.write(`分辨率: ${generationConfig.imageConfig.imageSize}\n`);
}
// 输出请求参数(脱敏:不直接输出base64图片数据,避免刷屏)
const payloadLog = {
generationConfig,
contents: [],
};
for (const content of payload.contents || []) {
const partsLog = [];
for (const part of content.parts || []) {
if (part && typeof part === 'object' && part.inlineData && typeof part.inlineData === 'object') {
const inlineData = { ...part.inlineData };
if (typeof inlineData.data === 'string') {
inlineData.data = `<omitted base64: ${inlineData.data.length} chars>`;
}
partsLog.push({ inlineData });
} else {
partsLog.push(part);
}
}
payloadLog.contents.push({ parts: partsLog });
}
process.stdout.write(`输出请求参数: ${JSON.stringify(payloadLog, null, 2)}\n`);
process.stdout.write('image generation in progress...\n');
const startTime = Date.now();
checkProgress = setInterval(() => {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
process.stdout.write(`🔄 已进行 ${elapsed}秒...\n`);
}, 5000);
let data;
try {
data = await postJson(url, headers, payload, 120_000);
} catch (e) {
clearProgressTimer();
if (e && e.message === 'timeout') {
exitWithError('错误: 请求超时,请稍后重试');
}
if (e && e.statusCode) {
process.stderr.write(`错误: 请求失败 - HTTP ${e.statusCode}\n`);
if (e.responseText) {
try {
const detail = JSON.parse(e.responseText);
process.stderr.write(`错误详情: ${JSON.stringify(detail, null, 2)}\n`);
} catch {
process.stderr.write(`响应内容: ${e.responseText}\n`);
}
}
process.exit(1);
}
exitWithError(`错误: 请求失败 - ${e.message || String(e)}`);
}
clearProgressTimer();
const imageData =
data &&
data.candidates &&
Array.isArray(data.candidates) &&
data.candidates[0] &&
data.candidates[0].content &&
data.candidates[0].content.parts &&
data.candidates[0].content.parts[0] &&
data.candidates[0].content.parts[0].inlineData &&
data.candidates[0].content.parts[0].inlineData.data;
if (!imageData) {
process.stderr.write('错误: 响应中未找到图片数据\n');
process.stderr.write(`完整响应: ${JSON.stringify(data, null, 2)}\n`);
process.exit(1);
}
const imageBytes = Buffer.from(imageData, 'base64');
const outputFile = path.resolve(args.filename);
const outputDir = path.dirname(outputFile);
fs.mkdirSync(outputDir, { recursive: true });
fs.writeFileSync(outputFile, imageBytes);
process.stdout.write(`✓ 图片已成功${modeStr}并保存到: ${args.filename}\n`);
process.stdout.write('✅ 生成完成!\n');
}
main().catch((e) => {
exitWithError(`错误: ${e && e.message ? e.message : String(e)}`);
});
{
"name": "wechat-red-envelope-resize",
"version": "1.0.0",
"description": "微信红包封面图片尺寸裁剪工具 - 支持封面图、封面挂件、气泡挂件、封面故事的尺寸转换",
"main": "resize_cover.js",
"scripts": {
"resize": "node resize_cover.js"
},
"dependencies": {
"sharp": "^0.32.6"
},
"keywords": [
"wechat",
"red-envelope",
"cover",
"image",
"resize",
"crop"
],
"author": "",
"license": "MIT"
}
微信红包封面图片尺寸裁剪工具
自动将图片裁剪为微信红包封面开放平台要求的标准尺寸,并智能压缩以满足文件大小限制。
功能特性
✅ 自动尺寸裁剪 - 居中裁剪,保持目标比例 ✅ 智能压缩 - 自动调整质量直到满足大小限制 ✅ 多格式支持 - 自动选择最佳格式(PNG/JPEG) ✅ 透明背景 - 挂件类自动使用PNG透明背景 ✅ 批量处理 - 一键生成所有尺寸 ✅ 安全提示 - 显示微信安全区域规范
支持尺寸
| 类型 | 尺寸 | 比例 | 格式 | 大小限制 | 特殊要求 |
|---|---|---|---|---|---|
| 封面图 (cover) | 957×1278px | 3:4 | PNG/JPG | ≤500KB | 上方25%保持简洁 |
| 封面挂件 (hang) | 1053×1746px | 3:5 | PNG | ≤300KB | 仅顶部324px可编辑,其余透明背景 |
| 气泡挂件 (bubble) | 480×384px | 5:4 | PNG | ≤300KB | 顶部96px+底部72px可编辑,中间216px透明 |
| 封面故事 (story) | 750×1250px | 3:5 | PNG/JPG | ≤300KB | 顶部/底部各10%可能被裁切 |
安装
cd scripts
npm install使用方法
单类型裁剪
# 生成封面图(自动压缩到≤500KB)
node resize_cover.js <输入图片> cover
# 生成封面挂件(自动压缩到≤300KB,透明背景)
node resize_cover.js <输入图片> hang
# 生成气泡挂件(自动压缩到≤300KB,透明背景)
node resize_cover.js <输入图片> bubble
# 生成封面故事(自动压缩到≤300KB)
node resize_cover.js <输入图片> story指定输出路径
node resize_cover.js <输入图片> <类型> <输出路径>示例:
node resize_cover.js myimage.png cover ./output/cover.png
node resize_cover.js myimage.png hang ./output/hang.png批量生成所有尺寸
node resize_cover.js <输入图片> all [输出目录]示例:
# 输出到当前目录
node resize_cover.js myimage.png all
# 输出到指定目录
node resize_cover.js myimage.png all ./output智能压缩策略
脚本会自动尝试以下压缩策略,直到满足大小限制:
非透明图片(封面图、故事图)
1. JPEG 质量调整: 90% → 85% → 80% → ... → 55% 2. mozjpeg 优化: 使用优化的JPEG编码器 3. 分辨率降级: 如果质量调整仍不满足,降低分辨率
透明图片(挂件类)
1. PNG 调色板: 使用8-bit调色板模式(质量80%) 2. 降低调色板质量: 60%质量 3. 限制颜色数: 减少到128色 4. 最大压缩: effort=10的极致压缩 5. 分辨率降级: 最后的降级手段
挂件类“假背景”清理(可选)
有些AI生成的挂件(封面挂件/气泡挂件)会带黑底/灰底/近似纯色底,看起来像“假透明背景”。脚本支持在裁剪/遮罩完成后,自动把“接近背景色”的像素转为透明:
# 开启自动抠底(默认 hang/bubble 已开启;这里显式写出来)
node resize_cover.js input.png hang --remove-bg
node resize_cover.js input.png bubble --remove-bg
# 调整容差与羽化(容差越大越容易抠掉背景,但也可能误伤主体)
node resize_cover.js input.png hang --remove-bg --bg-tolerance 26 --bg-feather 10参数说明:
--remove-bg/--no-remove-bg: 开/关自动背景清理--bg-tolerance N: 背景色容差(默认22)--bg-feather N: 边缘羽化(默认8)
输出示例
📸 封面图
原始: 1920×1080px, 2450.50KB
目标: 957×1278px, ≤500KB
裁剪: 1438×1080px → 957×1278px
✅ 已保存: ./output/myimage_cover.jpg
大小: 485.32KB / 500KB
压缩: jpeg, 质量75%输出文件
批量处理时会自动生成以下文件:
myimage_cover.jpg/png- 封面图 (957×1278px, ≤500KB)myimage_hang.png- 封面挂件 (1053×1746px, ≤300KB, 透明)myimage_bubble.png- 气泡挂件 (480×384px, ≤300KB, 透明)myimage_story.jpg/png- 封面故事 (750×1250px, ≤300KB)
完整示例
# 1. 安装依赖
cd scripts
npm install
# 2. 批量生成所有尺寸(推荐)
node resize_cover.js ../my_design.png all ../output
# 3. 查看结果
ls -lh ../output/
# -rw-r--r-- 1 user staff 485K my_design_cover.jpg
# -rw-r--r-- 1 user staff 298K my_design_hang.png
# -rw-r--r-- 1 user staff 156K my_design_bubble.png
# -rw-r--r-- 1 user staff 287K my_design_story.jpg
# 4. 单独生成封面图
node resize_cover.js ../my_design.png cover ../output/cover_final.png注意事项
1. 自动压缩: 无需手动调整,脚本会自动压缩到符合微信规范 2. 格式选择: 非透明图片默认使用JPEG(压缩率更好),透明图片使用PNG 3. 质量优先: 脚本会尽量保持高质量,只有在必要时才降低质量 4. 极端情况: 如果原图过大,脚本可能会降低分辨率以满足限制 5. 安全区域: 处理完成后会显示微信安全区域提示,帮助设计审核
故障排除
如果文件仍然超出限制
可能是因为原图过于复杂或尺寸过大。脚本已包含极端降级策略:
- 降低分辨率
- 使用最大压缩级别
- 减少颜色数量(挂件类)
如果仍不满足,建议: 1. 使用更简单的原图设计 2. 减少原图的细节和颜色数量 3. 手动使用专业工具(如Photoshop)先优化原图
依赖安装失败
# 如果npm install sharp失败,可以尝试:
npm install sharp --platform=darwin --arch=arm64 # Mac M系列
npm install sharp --platform=darwin --arch=x64 # Mac Intel
npm install sharp --platform=linux --arch=x64 # Linux
npm install sharp --platform=win32 --arch=x64 # Windows#!/usr/bin/env node
/**
* 微信红包封面挂件背景去除增强工具
*
* 特点:
* - 智能背景检测(支持白色、灰色、纯色背景)
* - 边缘羽化和抗锯齿处理
* - 保留主体细节,去除背景杂质
* - 针对AI生成图片优化
*
* 使用方法:
* node remove_bg_enhanced.js <输入图片> [输出图片] [选项]
*
* 选项:
* --mode <white|gray|auto> 背景检测模式 (默认: auto)
* --tolerance <0-100> 背景容差 (默认: 30)
* --feather <0-20> 边缘羽化像素 (默认: 3)
* --smooth <0-10> 平滑度 (默认: 2)
* --min-area <pixels> 最小保留区域 (默认: 100)
*
* 示例:
* node remove_bg_enhanced.js input.png output.png --mode white --tolerance 40
* node remove_bg_enhanced.js input.png --mode auto --tolerance 35
*/
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
function parseArgs(argv) {
const args = {
input: null,
output: null,
mode: 'auto',
tolerance: 30,
feather: 3,
smooth: 2,
minArea: 100
};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === '--mode') {
args.mode = argv[++i];
} else if (arg === '--tolerance') {
args.tolerance = parseInt(argv[++i], 10);
} else if (arg === '--feather') {
args.feather = parseInt(argv[++i], 10);
} else if (arg === '--smooth') {
args.smooth = parseInt(argv[++i], 10);
} else if (arg === '--min-area') {
args.minArea = parseInt(argv[++i], 10);
} else if (!arg.startsWith('--') && !args.input) {
args.input = arg;
} else if (!arg.startsWith('--') && !args.output) {
args.output = arg;
}
}
if (!args.output) {
const ext = path.extname(args.input);
const base = args.input.slice(0, -ext.length);
args.output = `${base}_transparent${ext}`;
}
return args;
}
function showHelp() {
console.log(`
微信红包封面挂件背景去除增强工具
使用方法:
node remove_bg_enhanced.js <输入图片> [输出图片] [选项]
选项:
--mode <white|gray|auto> 背景检测模式 (默认: auto)
white: 针对白色背景优化
gray: 针对灰色背景优化
auto: 自动检测
--tolerance <0-100> 背景容差,越大越激进 (默认: 30)
--feather <0-20> 边缘羽化像素 (默认: 3)
--smooth <0-10> 边缘平滑度 (默认: 2)
--min-area <pixels> 最小保留区域像素数 (默认: 100)
示例:
# 处理白色背景图片
node remove_bg_enhanced.js input.png output.png --mode white --tolerance 40
# 自动检测背景
node remove_bg_enhanced.js input.png --mode auto --tolerance 35
# 高容差处理(适合复杂背景)
node remove_bg_enhanced.js input.png --tolerance 50 --feather 5
`);
}
// 计算像素亮度
function getLuminance(r, g, b) {
return 0.299 * r + 0.587 * g + 0.114 * b;
}
// 计算颜色距离
function colorDistance(r1, g1, b1, r2, g2, b2) {
const dr = r1 - r2;
const dg = g1 - g2;
const db = b1 - b2;
return Math.sqrt(dr * dr + dg * dg + db * db);
}
// 检测背景颜色
async function detectBackground(imageData, width, height, mode) {
const samples = [];
const borderWidth = Math.max(5, Math.floor(Math.min(width, height) * 0.05));
// 从四边采样
for (let y = 0; y < height; y += 5) {
for (let x = 0; x < borderWidth; x += 2) {
const idx = (y * width + x) * 4;
if (imageData[idx + 3] > 200) {
samples.push({
r: imageData[idx],
g: imageData[idx + 1],
b: imageData[idx + 2],
lum: getLuminance(imageData[idx], imageData[idx + 1], imageData[idx + 2])
});
}
}
for (let x = width - borderWidth; x < width; x += 2) {
const idx = (y * width + x) * 4;
if (imageData[idx + 3] > 200) {
samples.push({
r: imageData[idx],
g: imageData[idx + 1],
b: imageData[idx + 2],
lum: getLuminance(imageData[idx], imageData[idx + 1], imageData[idx + 2])
});
}
}
}
for (let x = borderWidth; x < width - borderWidth; x += 5) {
for (let y = 0; y < borderWidth; y += 2) {
const idx = (y * width + x) * 4;
if (imageData[idx + 3] > 200) {
samples.push({
r: imageData[idx],
g: imageData[idx + 1],
b: imageData[idx + 2],
lum: getLuminance(imageData[idx], imageData[idx + 1], imageData[idx + 2])
});
}
}
for (let y = height - borderWidth; y < height; y += 2) {
const idx = (y * width + x) * 4;
if (imageData[idx + 3] > 200) {
samples.push({
r: imageData[idx],
g: imageData[idx + 1],
b: imageData[idx + 2],
lum: getLuminance(imageData[idx], imageData[idx + 1], imageData[idx + 2])
});
}
}
}
if (samples.length < 20) return null;
// 分析样本统计
let rSum = 0, gSum = 0, bSum = 0, lumSum = 0;
for (const s of samples) {
rSum += s.r; gSum += s.g; bSum += s.b; lumSum += s.lum;
}
const rMean = rSum / samples.length;
const gMean = gSum / samples.length;
const bMean = bSum / samples.length;
const lumMean = lumSum / samples.length;
// 计算颜色方差
let colorVariance = 0;
for (const s of samples) {
colorVariance += colorDistance(s.r, s.g, s.b, rMean, gMean, bMean);
}
colorVariance /= samples.length;
// 判断背景类型
let bgType = 'mixed';
if (colorVariance < 30) {
if (lumMean > 200) bgType = 'white';
else if (lumMean > 100 && lumMean < 180) bgType = 'gray';
else bgType = 'dark';
}
// 如果用户指定了模式,优先使用用户指定
if (mode !== 'auto') bgType = mode;
return {
r: Math.round(rMean),
g: Math.round(gMean),
b: Math.round(bMean),
luminance: lumMean,
type: bgType,
variance: colorVariance
};
}
// 创建透明度蒙版
function createAlphaMask(imageData, width, height, bgInfo, tolerance) {
const mask = new Uint8Array(width * height);
const tolVal = tolerance * 2.55; // 转换为0-255范围
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = (y * width + x) * 4;
const r = imageData[idx];
const g = imageData[idx + 1];
const b = imageData[idx + 2];
let isBackground = false;
let alpha = 255;
if (bgInfo.type === 'white' || bgInfo.type === 'gray') {
// 基于亮度的检测
const lum = getLuminance(r, g, b);
const bgLum = bgInfo.luminance;
const lumDiff = Math.abs(lum - bgLum);
// 同时考虑颜色距离
const colorDist = colorDistance(r, g, b, bgInfo.r, bgInfo.g, bgInfo.b);
// 综合判断
if (lumDiff < tolerance && colorDist < tolVal * 1.5) {
isBackground = true;
alpha = 0;
} else if (lumDiff < tolerance * 1.5 || colorDist < tolVal * 2) {
// 过渡区域
const factor = Math.min(lumDiff / (tolerance * 1.5), colorDist / (tolVal * 2));
alpha = Math.round(255 * Math.max(0, factor - 0.3) / 0.7);
}
} else {
// 通用颜色距离检测
const dist = colorDistance(r, g, b, bgInfo.r, bgInfo.g, bgInfo.b);
if (dist < tolVal) {
isBackground = true;
alpha = 0;
} else if (dist < tolVal * 1.8) {
alpha = Math.round(255 * (dist - tolVal) / (tolVal * 0.8));
}
}
mask[y * width + x] = alpha;
}
}
return mask;
}
// 应用羽化
function applyFeather(mask, width, height, feather) {
if (feather <= 0) return mask;
const result = new Uint8Array(mask);
const temp = new Uint8Array(width * height);
// 多次迭代实现羽化效果
for (let iter = 0; iter < feather; iter++) {
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const idx = y * width + x;
// 3x3平均
let sum = 0;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
sum += mask[(y + dy) * width + (x + dx)];
}
}
temp[idx] = Math.round(sum / 9);
}
}
// 复制回mask
for (let i = 0; i < width * height; i++) {
mask[i] = temp[i];
}
}
return mask;
}
// 去除小噪点
function removeNoise(mask, width, height, minArea) {
const visited = new Uint8Array(width * height);
const newMask = new Uint8Array(mask);
function floodFill(startX, startY) {
const stack = [[startX, startY]];
const pixels = [];
visited[startY * width + startX] = 1;
while (stack.length > 0) {
const [x, y] = stack.pop();
pixels.push([x, y]);
const neighbors = [[x-1,y], [x+1,y], [x,y-1], [x,y+1]];
for (const [nx, ny] of neighbors) {
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
const idx = ny * width + nx;
if (!visited[idx] && mask[idx] > 128) {
visited[idx] = 1;
stack.push([nx, ny]);
}
}
}
}
return pixels;
}
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = y * width + x;
if (!visited[idx] && mask[idx] > 128) {
const pixels = floodFill(x, y);
if (pixels.length < minArea) {
// 太小的区域设为透明
for (const [px, py] of pixels) {
newMask[py * width + px] = 0;
}
}
}
}
}
return newMask;
}
async function processImage(inputPath, outputPath, options) {
console.log(`\n🎨 开始处理: ${path.basename(inputPath)}`);
console.log(` 模式: ${options.mode}, 容差: ${options.tolerance}, 羽化: ${options.feather}`);
// 读取图片
const { data, info } = await sharp(inputPath)
.ensureAlpha()
.raw()
.toBuffer({ resolveWithObject: true });
const { width, height } = info;
console.log(` 尺寸: ${width}×${height}px`);
// 检测背景
const bgInfo = await detectBackground(data, width, height, options.mode);
if (!bgInfo) {
console.log(' ⚠️ 无法检测背景,跳过处理');
await sharp(inputPath).png().toFile(outputPath);
return;
}
console.log(` 检测到背景: ${bgInfo.type} (RGB: ${bgInfo.r},${bgInfo.g},${bgInfo.b})`);
// 创建透明度蒙版
let mask = createAlphaMask(data, width, height, bgInfo, options.tolerance);
// 去除噪点
if (options.minArea > 0) {
mask = removeNoise(mask, width, height, options.minArea);
}
// 应用羽化
if (options.feather > 0) {
mask = applyFeather(mask, width, height, options.feather);
}
// 应用蒙版到原图
const outputData = Buffer.from(data);
for (let i = 0; i < width * height; i++) {
outputData[i * 4 + 3] = mask[i];
}
// 保存结果
await sharp(outputData, {
raw: { width, height, channels: 4 }
})
.png({ compressionLevel: 9 })
.toFile(outputPath);
const stats = fs.statSync(outputPath);
console.log(` ✅ 已保存: ${outputPath}`);
console.log(` 大小: ${(stats.size / 1024).toFixed(2)}KB`);
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
showHelp();
process.exit(0);
}
try {
require.resolve('sharp');
} catch (e) {
console.error('❌ 错误: 缺少依赖 "sharp"');
console.error(' 请先安装: npm install sharp');
process.exit(1);
}
const options = parseArgs(args);
if (!fs.existsSync(options.input)) {
console.error(`❌ 错误: 找不到文件 "${options.input}"`);
process.exit(1);
}
await processImage(options.input, options.output, options);
console.log('\n🎉 处理完成!');
}
main().catch(err => {
console.error(`\n❌ 错误: ${err.message}`);
process.exit(1);
});
#!/usr/bin/env node
/**
* 微信红包封面图片尺寸裁剪工具
*
* 支持微信红包封面开放平台的所有尺寸规范:
* - 封面图: 957×1278px (3:4) ≤500KB
* - 封面挂件: 1053×1746px (3:5) ≤300KB
* - 气泡挂件: 480×384px (5:4) ≤300KB
* - 封面故事: 750×1250px (3:5) ≤300KB
*
* 自动压缩功能:如果文件超出限制,会自动降低质量直到符合要求
*
* 注意:本脚本仅负责尺寸裁剪和压缩,背景去除请使用 remove_bg_enhanced.js
*
* 使用方法:
* node resize_cover.js <输入图片路径> <类型> [输出路径]
* node resize_cover.js <输入图片路径> all [输出目录]
*
* 示例:
* node resize_cover.js myimage.png cover
* node resize_cover.js myimage.png all ./output
*/
let sharp;
const path = require('path');
const fs = require('fs');
// 微信红包封面尺寸规范
const DIMENSIONS = {
cover: {
name: '封面图',
width: 957,
height: 1278,
ratio: '3:4',
sizeLimit: 500, // KB
safeZone: {
top: 0.25,
middle: 0.50,
bottom: 0.15
}
},
hang: {
name: '封面挂件',
width: 1053,
height: 1746,
ratio: '3:5',
sizeLimit: 300, // KB
transparent: true,
// 微信红包封面挂件:仅顶部可编辑区域允许出现内容,其余区域应完全透明
// 这里按"顶部 324px 可编辑"约束(来自平台编辑器示意图/规范)
editableRegions: [{ top: 0, height: 324 }],
safeZone: {
top: 324 / 1746,
bottom: 1 - 324 / 1746,
}
},
bubble: {
name: '气泡挂件',
width: 480,
height: 384,
ratio: '5:4',
sizeLimit: 300, // KB
transparent: true,
// 气泡挂件(480×384):可编辑区域为上下两段,中间为不可编辑区域
// 按平台编辑器示意图:顶部96px可编辑,中间216px不可编辑(必须透明),底部72px可编辑
editableRegions: [
{ top: 0, height: 96 },
{ top: 384 - 72, height: 72 },
],
},
story: {
name: '封面故事',
width: 750,
height: 1250,
ratio: '3:5',
sizeLimit: 300, // KB
safeZone: {
top: 0.10,
middle: 0.80,
bottom: 0.10
}
}
};
function showHelp() {
console.log(`
微信红包封面图片尺寸裁剪工具 (带自动压缩)
使用方法:
node resize_cover.js <输入图片路径> <类型> [输出路径]
node resize_cover.js <输入图片路径> all [输出目录]
参数说明:
输入图片路径 - 原始图片路径
类型 - cover|hang|bubble|story|all
输出路径 - 可选,默认为输入文件名_类型.png
尺寸与大小限制:
cover - 封面图: 957×1278px, ≤500KB
hang - 封面挂件: 1053×1746px, ≤300KB, PNG透明
bubble - 气泡挂件: 480×384px, ≤300KB, PNG透明
story - 封面故事: 750×1250px, ≤300KB
注意:
本脚本仅处理尺寸裁剪和压缩,不处理背景去除。
如需去除背景,请使用 remove_bg_enhanced.js 脚本。
特性:
✓ 自动居中裁剪,保持目标比例
✓ 智能压缩,自动调整质量满足大小限制
✓ 挂件类自动强制安全区域透明
✓ 显示安全区域提示
示例:
node resize_cover.js myimage.png cover
node resize_cover.js myimage.png all ./output
node resize_cover.js cover.png hang pendant.png
node resize_cover.js bubble.png bubble bubble_final.png
`);
}
function parseArgs(argv) {
const positionals = [];
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (!a.startsWith('--')) {
positionals.push(a);
}
}
return positionals;
}
async function getImageInfo(inputPath) {
try {
const metadata = await sharp(inputPath).metadata();
return {
width: metadata.width,
height: metadata.height,
format: metadata.format,
size: fs.statSync(inputPath).size
};
} catch (error) {
throw new Error(`无法读取图片: ${error.message}`);
}
}
function calculateCropDimensions(imgWidth, imgHeight, targetWidth, targetHeight) {
const targetRatio = targetWidth / targetHeight;
const currentRatio = imgWidth / imgHeight;
let cropWidth, cropHeight, left, top;
if (currentRatio > targetRatio) {
cropHeight = imgHeight;
cropWidth = Math.round(imgHeight * targetRatio);
left = Math.round((imgWidth - cropWidth) / 2);
top = 0;
} else {
cropWidth = imgWidth;
cropHeight = Math.round(imgWidth / targetRatio);
left = 0;
top = Math.round((imgHeight - cropHeight) / 2);
}
return { cropWidth, cropHeight, left, top };
}
// 智能压缩函数:尝试不同质量设置直到满足大小限制
async function compressToLimit(buffer, type, sizeLimitKB, isTransparent) {
const sizeLimitBytes = sizeLimitKB * 1024;
// 如果已经符合要求,直接返回
if (buffer.length <= sizeLimitBytes) {
return { buffer, quality: null, format: 'original' };
}
console.log(` 原始大小: ${(buffer.length / 1024).toFixed(2)}KB,需要压缩...`);
// 对于非透明图片,使用JPEG压缩(压缩率更好)
if (!isTransparent && type !== 'hang' && type !== 'bubble') {
// 尝试JPEG不同质量
const qualities = [90, 85, 80, 75, 70, 65, 60];
for (const quality of qualities) {
const jpegBuffer = await sharp(buffer)
.jpeg({ quality, progressive: true, mozjpeg: true })
.toBuffer();
if (jpegBuffer.length <= sizeLimitBytes) {
return { buffer: jpegBuffer, quality, format: 'jpeg' };
}
}
// 如果JPEG还是太大,尝试更低质量
const lowQualityBuffer = await sharp(buffer)
.jpeg({ quality: 55, progressive: true, mozjpeg: true })
.toBuffer();
if (lowQualityBuffer.length <= sizeLimitBytes) {
return { buffer: lowQualityBuffer, quality: 55, format: 'jpeg' };
}
}
// 对于透明图片(挂件),使用PNG优化
if (isTransparent) {
// 尝试使用8-bit调色板PNG
const paletteBuffer = await sharp(buffer)
.png({
compressionLevel: 9,
adaptiveFiltering: true,
palette: true,
quality: 80
})
.toBuffer();
if (paletteBuffer.length <= sizeLimitBytes) {
return { buffer: paletteBuffer, quality: 80, format: 'png-palette' };
}
// 尝试更低质量的调色板
const lowPaletteBuffer = await sharp(buffer)
.png({
compressionLevel: 9,
adaptiveFiltering: true,
palette: true,
quality: 60,
effort: 10
})
.toBuffer();
if (lowPaletteBuffer.length <= sizeLimitBytes) {
return { buffer: lowPaletteBuffer, quality: 60, format: 'png-palette' };
}
// 尝试颜色数量限制
const colorsBuffer = await sharp(buffer)
.png({
compressionLevel: 9,
palette: true,
colours: 128,
effort: 10
})
.toBuffer();
if (colorsBuffer.length <= sizeLimitBytes) {
return { buffer: colorsBuffer, quality: null, format: 'png-128colors' };
}
}
// 通用降级策略:降低分辨率
const img = sharp(buffer);
const metadata = await img.metadata();
const scale = Math.sqrt(sizeLimitBytes / buffer.length) * 0.95; // 留一些余量
const newWidth = Math.floor(metadata.width * scale);
const newHeight = Math.floor(metadata.height * scale);
const resizedBuffer = await img
.resize(newWidth, newHeight, {
fit: 'inside',
withoutEnlargement: false
})
.png({ compressionLevel: 9 })
.toBuffer();
return {
buffer: resizedBuffer,
quality: null,
format: `resized-${newWidth}x${newHeight}`
};
}
async function processImage(inputPath, type, outputPath) {
const config = DIMENSIONS[type];
const imgInfo = await getImageInfo(inputPath);
console.log(`\n📸 ${config.name}`);
console.log(` 原始: ${imgInfo.width}×${imgInfo.height}px, ${(imgInfo.size / 1024).toFixed(2)}KB`);
console.log(` 目标: ${config.width}×${config.height}px, ≤${config.sizeLimit}KB`);
// 计算裁剪参数
const { cropWidth, cropHeight, left, top } = calculateCropDimensions(
imgInfo.width,
imgInfo.height,
config.width,
config.height
);
// 裁剪和调整尺寸
let processedBuffer = await sharp(inputPath)
.extract({ left, top, width: cropWidth, height: cropHeight })
.resize(config.width, config.height, { fit: 'fill' })
.toBuffer();
console.log(` 裁剪: ${cropWidth}×${cropHeight}px → ${config.width}×${config.height}px`);
// 挂件类需要透明背景:统一强制输出PNG+alpha,避免输入是JPG时丢失透明信息
if (config.transparent) {
processedBuffer = await sharp(processedBuffer).ensureAlpha().png().toBuffer();
}
// 强制限制可编辑区域:仅允许指定区域内出现任何不透明像素。
// 这样即便生成图"画满了整张",最终交付也会自动裁掉不可编辑区域的内容。
if (config.transparent && Array.isArray(config.editableRegions) && config.editableRegions.length > 0) {
const composites = [];
for (const region of config.editableRegions) {
const regionTop = Math.max(0, Math.min(region.top, config.height - 1));
const regionHeight = Math.max(0, Math.min(region.height, config.height - regionTop));
if (!regionHeight) continue;
const extracted = await sharp(processedBuffer)
.extract({ left: 0, top: regionTop, width: config.width, height: regionHeight })
.toBuffer();
composites.push({ input: extracted, left: 0, top: regionTop });
}
processedBuffer = await sharp({
create: {
width: config.width,
height: config.height,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 },
},
})
.composite(composites)
.png()
.toBuffer();
}
// 压缩到符合大小限制
const { buffer: finalBuffer, quality, format } = await compressToLimit(
processedBuffer,
type,
config.sizeLimit,
config.transparent
);
// 确定输出格式
let finalOutputPath = outputPath;
if (format === 'jpeg' && !outputPath.toLowerCase().endsWith('.jpg')) {
finalOutputPath = outputPath.replace(/\.png$/i, '.jpg');
}
// 确保输出目录存在
const outputDir = path.dirname(finalOutputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// 保存文件
fs.writeFileSync(finalOutputPath, finalBuffer);
const finalSizeKB = finalBuffer.length / 1024;
const status = finalSizeKB <= config.sizeLimit ? '✅' : '⚠️';
console.log(` ${status} 已保存: ${finalOutputPath}`);
console.log(` 大小: ${finalSizeKB.toFixed(2)}KB / ${config.sizeLimit}KB`);
if (quality) {
console.log(` 压缩: ${format}, 质量${quality}%`);
} else if (format !== 'original') {
console.log(` 压缩: ${format}`);
}
if (finalSizeKB > config.sizeLimit) {
console.warn(` ⚠️ 警告: 仍超出限制,建议手动检查或优化原图`);
}
return finalOutputPath;
}
async function batchResize(inputPath, outputDir) {
console.log(`\n🚀 批量处理 - 生成所有尺寸\n`);
if (!outputDir) {
outputDir = path.dirname(inputPath) || '.';
}
const baseName = path.basename(inputPath, path.extname(inputPath));
const results = [];
for (const [type, config] of Object.entries(DIMENSIONS)) {
const outputPath = path.join(outputDir, `${baseName}_${type}.png`);
try {
await processImage(inputPath, type, outputPath);
results.push({ type, status: 'success', path: outputPath });
} catch (error) {
results.push({ type, status: 'error', error: error.message });
console.error(` ❌ 失败: ${error.message}`);
}
}
// 打印汇总
console.log(`\n📊 处理完成`);
console.log('='.repeat(60));
results.forEach(result => {
const config = DIMENSIONS[result.type];
if (result.status === 'success') {
const stats = fs.statSync(result.path);
const sizeKB = (stats.size / 1024).toFixed(2);
const statusIcon = stats.size <= config.sizeLimit * 1024 ? '✅' : '⚠️';
console.log(`${statusIcon} ${config.name}: ${sizeKB}KB / ${config.sizeLimit}KB`);
console.log(` ${result.path}`);
} else {
console.log(`❌ ${config.name}: ${result.error}`);
}
});
console.log('='.repeat(60));
return results;
}
async function main() {
const args = parseArgs(process.argv.slice(2));
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
showHelp();
process.exit(0);
}
// 检查sharp是否安装
try {
require.resolve('sharp');
sharp = require('sharp');
} catch (e) {
console.error('❌ 错误: 缺少依赖 "sharp"');
console.error(' 请先安装: npm install sharp');
process.exit(1);
}
const inputPath = args[0];
if (!fs.existsSync(inputPath)) {
console.error(`❌ 错误: 找不到文件 "${inputPath}"`);
process.exit(1);
}
// 批量处理
if (args[1] === 'all' || args[1] === '--all') {
await batchResize(inputPath, args[2]);
return;
}
if (args.length < 2) {
console.error('❌ 错误: 缺少类型参数');
console.error(' 使用: node resize_cover.js <图片> <类型> [输出]');
process.exit(1);
}
const type = args[1];
if (!DIMENSIONS[type]) {
console.error(`❌ 错误: 不支持的类型 "${type}"`);
console.error(' 支持: cover, hang, bubble, story');
process.exit(1);
}
let outputPath = args[2];
if (!outputPath) {
const dir = path.dirname(inputPath) || '.';
const baseName = path.basename(inputPath, path.extname(inputPath));
outputPath = path.join(dir, `${baseName}_${type}.png`);
}
await processImage(inputPath, type, outputPath);
// 显示安全区域提示
const config = DIMENSIONS[type];
console.log(`\n💡 安全区域提示:`);
if (Array.isArray(config.editableRegions) && config.editableRegions.length > 0) {
if (type === 'hang') {
console.log(` - 仅顶部${config.editableRegions[0].height}px可编辑/可出现元素`);
console.log(` - 其余区域必须完全透明留空`);
return;
}
if (type === 'bubble') {
const top = config.editableRegions[0];
const bottom = config.editableRegions[1];
const middle = config.height - top.height - bottom.height;
console.log(` - 顶部${top.height}px可编辑/可出现元素`);
console.log(` - 中间${middle}px必须完全透明留空(不可编辑区域)`);
console.log(` - 底部${bottom.height}px可编辑/可出现元素`);
return;
}
}
if (config.safeZone.top && config.safeZone.middle && config.safeZone.bottom) {
console.log(` - 上方${(config.safeZone.top * 100).toFixed(0)}%: 保持简洁,确保文字可读`);
console.log(` - 中部${(config.safeZone.middle * 100).toFixed(0)}%: 主要视觉元素区域`);
console.log(` - 底部${(config.safeZone.bottom * 100).toFixed(0)}%: 避免放置关键元素`);
} else if (config.safeZone.top && config.safeZone.bottom) {
console.log(` - 上方${(config.safeZone.top * 100).toFixed(0)}%: 设计元素区域`);
console.log(` - 下方${(config.safeZone.bottom * 100).toFixed(0)}%: 必须留白/透明`);
}
}
main().catch(error => {
console.error(`\n❌ 处理失败: ${error.message}`);
process.exit(1);
});