image-generator/SKILL.md
# Image Generator - Canvas封面图生成器 ## 💡 Skill简介 这是一个基于Canvas的封面图生成系统,无需AI绘图API,本地化、免费、快速生成风格统一的公众号配图。 **核心能力**: - 5秒生成封面图(无需等待AI) - 10套预设模板,风格统一 - 动态填充标题/数据 - 支持自定义品牌色 **技术方案**: - Node.js + Canvas - 模板+数据 = 封面图 - 完全本地化,零成本 --- ## 🎨 模板库(10套风格) ### 1. 极简风(推荐) ``` ┌─────────────────────┐ │ │ │ 如何用AI工具 │ │ 10分钟做封面图 │ │ │ │ @你的公众号 │ └─────────────────────┘ ``` **适用**:知识类、教程类 **配色**:黑白灰 ### 2. 渐变风 ``` ┌─────────────────────┐ │ ╱╲
npx skillsauth add atxinsky/skills image-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
这是一个基于Canvas的封面图生成系统,无需AI绘图API,本地化、免费、快速生成风格统一的公众号配图。
核心能力:
技术方案:
┌─────────────────────┐
│ │
│ 如何用AI工具 │
│ 10分钟做封面图 │
│ │
│ @你的公众号 │
└─────────────────────┘
适用:知识类、教程类 配色:黑白灰
┌─────────────────────┐
│ ╱╲ (渐变背景) │
│ │
│ 爆款文章的3个秘密 │
│ │
│ 📝 │
└─────────────────────┘
适用:干货类、清单类 配色:蓝紫渐变
┌─────────────────────┐
│ 10,000+ 阅读量 │
│ ──────────── │
│ │
│ 这篇文章教你 │
│ 写出爆款标题 │
└─────────────────────┘
适用:案例分析类 配色:橙色系
┌─────────────────────┐
│ 普通人 VS 高手 │
│ ──────────── │
│ │
│ 差距在这3点 │
└─────────────────────┘
适用:对比类、方法论类 配色:红蓝对比
┌─────────────────────┐
│ ❓ │
│ │
│ 为什么你的文章 │
│ 总是没人看? │
│ │
└─────────────────────┘
适用:痛点类、引导类 配色:黄色系
/image-generator "标题" --template minimalist
/image-generator "如何用AI工具10分钟做出公众号封面图" --template gradient
✅ 封面图生成成功!
文件路径:~/封面图/cover-20260123.png
尺寸:900x500px(公众号推荐尺寸)
模板:gradient(渐变风)
预览:
┌─────────────────────────────┐
│ ╱╲ (蓝紫渐变背景) │
│ │
│ 如何用AI工具 │
│ 10分钟做出公众号封面图 │
│ │
│ @atxin │
└─────────────────────────────┘
cd ~/.claude/skills/image-generator
npm init -y
npm install canvas
const { createCanvas, registerFont } = require('canvas');
const fs = require('fs');
const path = require('path');
// 注册字体(可选,使用系统字体)
// registerFont('fonts/SourceHanSansCN.ttf', { family: 'Source Han Sans CN' });
/**
* 生成封面图
* @param {string} title - 标题
* @param {string} template - 模板名称
* @param {object} options - 配置项
*/
function generateCover(title, template = 'minimalist', options = {}) {
// Canvas尺寸(公众号推荐 900x500)
const width = 900;
const height = 500;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// 根据模板选择样式
const templates = {
minimalist: drawMinimalist,
gradient: drawGradient,
data: drawData,
vs: drawVS,
qa: drawQA,
};
const drawFunc = templates[template] || templates.minimalist;
drawFunc(ctx, title, width, height, options);
// 保存图片
const outputDir = path.join(process.env.HOME, '封面图');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const filename = `cover-${Date.now()}.png`;
const filepath = path.join(outputDir, filename);
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync(filepath, buffer);
console.log(`✅ 封面图生成成功!`);
console.log(`文件路径:${filepath}`);
console.log(`尺寸:${width}x${height}px`);
return filepath;
}
// ========== 模板1:极简风 ==========
function drawMinimalist(ctx, title, width, height, options) {
// 背景
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, width, height);
// 标题(自动换行)
ctx.fillStyle = '#000000';
ctx.font = 'bold 64px "Microsoft YaHei", sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 简单换行逻辑
const lines = wrapText(ctx, title, width - 100);
const lineHeight = 80;
const startY = (height - lines.length * lineHeight) / 2;
lines.forEach((line, i) => {
ctx.fillText(line, width / 2, startY + i * lineHeight);
});
// 底部署名
ctx.font = '24px sans-serif';
ctx.fillStyle = '#999999';
ctx.fillText(options.author || '@atxin', width / 2, height - 40);
}
// ========== 模板2:渐变风 ==========
function drawGradient(ctx, title, width, height, options) {
// 渐变背景
const gradient = ctx.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// 标题(白色)
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 60px sans-serif';
ctx.textAlign = 'center';
const lines = wrapText(ctx, title, width - 100);
const lineHeight = 75;
const startY = (height - lines.length * lineHeight) / 2;
lines.forEach((line, i) => {
ctx.fillText(line, width / 2, startY + i * lineHeight);
});
// Emoji装饰
ctx.font = '80px sans-serif';
ctx.fillText('📝', width / 2, height - 80);
}
// ========== 模板3:数据风 ==========
function drawData(ctx, title, width, height, options) {
// 背景
ctx.fillStyle = '#FFF4E6';
ctx.fillRect(0, 0, width, height);
// 顶部数据
ctx.fillStyle = '#FF6B35';
ctx.font = 'bold 72px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(options.data || '10,000+', width / 2, 120);
// 分割线
ctx.strokeStyle = '#FF6B35';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(width / 2 - 150, 160);
ctx.lineTo(width / 2 + 150, 160);
ctx.stroke();
// 标题
ctx.fillStyle = '#333333';
ctx.font = 'bold 48px sans-serif';
const lines = wrapText(ctx, title, width - 100);
const lineHeight = 65;
const startY = 240;
lines.forEach((line, i) => {
ctx.fillText(line, width / 2, startY + i * lineHeight);
});
}
// ========== 模板4:对比风(VS) ==========
function drawVS(ctx, title, width, height, options) {
// 左半部分(红色)
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(0, 0, width / 2, height);
// 右半部分(蓝色)
ctx.fillStyle = '#4ECDC4';
ctx.fillRect(width / 2, 0, width / 2, height);
// 中间VS
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 96px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('VS', width / 2, height / 2 - 50);
// 底部标题
ctx.font = 'bold 40px sans-serif';
ctx.fillStyle = '#000000';
const lines = wrapText(ctx, title, width - 100);
ctx.fillText(lines[0] || title, width / 2, height - 60);
}
// ========== 模板5:问答风(QA) ==========
function drawQA(ctx, title, width, height, options) {
// 背景
ctx.fillStyle = '#FFF9C4';
ctx.fillRect(0, 0, width, height);
// 问号Emoji
ctx.font = '120px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('❓', width / 2, 140);
// 标题(问句)
ctx.fillStyle = '#333333';
ctx.font = 'bold 52px sans-serif';
const lines = wrapText(ctx, title, width - 100);
const lineHeight = 70;
const startY = 240;
lines.forEach((line, i) => {
ctx.fillText(line, width / 2, startY + i * lineHeight);
});
}
// ========== 辅助函数:文本换行 ==========
function wrapText(ctx, text, maxWidth) {
const words = text.split('');
const lines = [];
let currentLine = '';
for (const char of words) {
const testLine = currentLine + char;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine !== '') {
lines.push(currentLine);
currentLine = char;
} else {
currentLine = testLine;
}
}
lines.push(currentLine);
return lines;
}
// 导出函数
module.exports = { generateCover };
// CLI调用示例
if (require.main === module) {
const args = process.argv.slice(2);
const title = args[0] || '默认标题';
const template = args[1] || 'minimalist';
generateCover(title, template, { author: '@atxin' });
}
创建 ~/.claude/skills/image-generator/brand-colors.json:
{
"primary": "#667eea",
"secondary": "#764ba2",
"text": "#333333",
"background": "#FFFFFF",
"accent": "#FF6B35"
}
在代码中使用:
const brandColors = require('./brand-colors.json');
ctx.fillStyle = brandColors.primary;
为多篇文章一次性生成封面:
/image-generator --batch ~/文章列表.json
文章列表.json:
[
{ "title": "文章1标题", "template": "minimalist" },
{ "title": "文章2标题", "template": "gradient" },
{ "title": "文章3标题", "template": "data", "data": "5000+" }
]
从文章内容提取数据:
/image-generator "文章标题" --auto-data ~/文章.md
自动提取:
生成多个版本对比:
/image-generator "标题" --ab-test
输出:
| 位置 | 尺寸 | 比例 | |------|------|------| | 头图(推荐) | 900x500px | 16:9 | | 正文配图 | 750px宽 | 不限高 | | 缩略图 | 200x200px | 1:1 |
# 方案1:使用系统字体
ctx.font = 'bold 64px "Microsoft YaHei", sans-serif';
# 方案2:注册自定义字体
const { registerFont } = require('canvas');
registerFont('fonts/SourceHanSansCN.ttf', { family: 'Source Han Sans CN' });
ctx.font = 'bold 64px "Source Han Sans CN"';
# Windows用户
npm install --global --production windows-build-tools
npm install canvas
# Mac用户
brew install pkg-config cairo pango libpng jpeg giflib librsvg
npm install canvas
# Linux用户
sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
npm install canvas
// 提高Canvas分辨率(Retina屏)
const scale = 2;
const canvas = createCanvas(width * scale, height * scale);
ctx.scale(scale, scale);
generate-cover.js 中添加绘制函数:function drawMyTemplate(ctx, title, width, height, options) {
// 你的设计...
}
const templates = {
minimalist: drawMinimalist,
gradient: drawGradient,
mytemplate: drawMyTemplate, // 新增
};
/image-generator "标题" --template mytemplate
创建 ~/.claude/skills/image-generator/config.json:
{
"default_template": "minimalist",
"author": "@atxin",
"output_dir": "~/封面图",
"width": 900,
"height": 500,
"format": "png",
"quality": 0.95
}
Step 1: 安装依赖
cd ~/.claude/skills/image-generator
npm install canvas
Step 2: 生成第一张封面
/image-generator "如何写好公众号" --template minimalist
Step 3: 查看结果
open ~/封面图/cover-*.png
Step 4: 上传到公众号
记住:好的封面图,3秒内吸引眼球,5秒内传递信息。
"封面图是文章的脸,脸好看了,才有机会展示内涵。"
development
Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like "the xlsx in my downloads") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.
testing
Use when creating new skills, editing existing skills, or verifying skills work before deployment
development
Use when you have a spec or requirements for a multi-step task, before touching code
documentation
Create detailed implementation plan with bite-sized tasks