Files
drawlogo/scripts/gen-tech-avatars-png.js

80 lines
3.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 🤖 简洁科技感头像 PNG 批量生成(DiceBear bottts-neutral 机器人中性)
* 从资源库读取风格定义,seed 确定性渲染;黑灰机器人反白成线框感,
* 配深蓝渐变圆形底 + 细亮蓝圆环,Chrome headless 导出 1024×1024 PNG。
*
* 用法:node scripts/gen-tech-avatars-png.js
*/
'use strict';
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const { execFileSync } = require('child_process');
const ROOT = path.resolve(__dirname, '..');
const OUT_DIR = path.join(ROOT, 'output', 'agent-avatars-png');
const TMP_DIR = path.join(ROOT, 'output', '.tmp-tech');
const CHROME = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe';
// —— 1. 加载资源库风格定义 + 渲染引擎 ——
global.window = global;
const styleFile = path.join(ROOT, 'js', 'data', 'sources', 'dicebear-bottts-neutral.js');
vm.runInThisContext(fs.readFileSync(styleFile, 'utf8'), { filename: styleFile });
vm.runInThisContext(fs.readFileSync(path.join(ROOT, 'js', 'core', 'dicebear.js'), 'utf8'), { filename: 'dicebear.js' });
// —— 2. 渲染 10 个 seed,反白(黑→白线框感),剥离 svg 外壳取 inner ——
const COUNT = 10;
const S = 120; // bottts-neutral 画布 120
const ZOOM = 5.4; // 放大到 ~648px
const BG_FROM = '#0b1220', BG_TO = '#16294a'; // 深蓝科技渐变
const RING = '#3b82f6'; // 亮蓝圆环
function renderInner(seed) {
const { svg } = HGB_Dicebear.renderAvatar('bottts-neutral', seed);
const inner = svg.replace(/^<\?xml[^>]*\?>\s*<svg[^>]*>/, '').replace(/<\/svg>\s*$/, '');
return inner.replace(/fill="black"/g, 'fill="white"'); // 反白
}
function buildHtml(inner) {
return `<!DOCTYPE html>
<html><head><meta charset="utf-8">
<style>html,body{margin:0;padding:0;width:1024px;height:1024px;overflow:hidden;background:transparent}</style>
</head><body>
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="${BG_FROM}"/><stop offset="100%" stop-color="${BG_TO}"/>
</linearGradient>
<clipPath id="circle"><circle cx="512" cy="512" r="512"/></clipPath>
</defs>
<rect width="1024" height="1024" fill="url(#bg)" clip-path="url(#circle)"/>
<circle cx="512" cy="512" r="452" fill="none" stroke="${RING}" stroke-width="5" opacity=".45"/>
<g transform="translate(512 512) scale(${ZOOM}) translate(${-S / 2} ${-S / 2})">${inner}</g>
</svg>
</body></html>`;
}
// —— 3. 导出 PNG ——
fs.mkdirSync(OUT_DIR, { recursive: true });
fs.mkdirSync(TMP_DIR, { recursive: true });
for (let i = 1; i <= COUNT; i++) {
const idx = String(i).padStart(2, '0');
const htmlFile = path.join(TMP_DIR, `avatar-${idx}.html`);
const pngFile = path.join(OUT_DIR, `agent-${idx}.png`);
fs.writeFileSync(htmlFile, buildHtml(renderInner('agent-' + i)), 'utf8');
try {
execFileSync(CHROME, [
'--headless=new', '--disable-gpu', '--hide-scrollbars',
'--force-device-scale-factor=1', '--default-background-color=00000000',
'--window-size=1024,1024', '--virtual-time-budget=1000',
`--screenshot=${pngFile}`,
'file:///' + htmlFile.replace(/\\/g, '/')
], { stdio: 'pipe' });
console.log(`✅ agent-${idx}.png seed=agent-${i}`);
} catch (e) {
console.error(`❌ agent-${idx}.png 失败:`, e.message.split('\n')[0]);
}
}
fs.rmSync(TMP_DIR, { recursive: true, force: true });
console.log('\n🎉 完成 → output/agent-avatars-png/');