feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 画个标 · 核心:头像生成(Avatar)
|
||||
* 头像 = 复用 Mark 纯图形引擎 + 头像专属约束 + 平台规格导出
|
||||
* - 仅纯图形:过滤带文字布局(monogram 首字 / ribbon 绶带区),保证小尺寸清晰
|
||||
* - 圆形构图优先:头像布局以圆形/圆角/对称构图为倾向,缩小不变形
|
||||
* - 圆形裁切预览与导出:社交平台头像均以圆形展示
|
||||
* - 平台规格一键导出:微信 / QQ / 抖音 / YouTube / App 图标 / Favicon + 自定义
|
||||
*/
|
||||
const HGB_AvatarGen = (function () {
|
||||
'use strict';
|
||||
|
||||
/** 适合头像的纯图形布局(Mark 层,全部无文字) */
|
||||
const AVATAR_LAYOUTS = [
|
||||
'orb', // 圆标容器(径向渐变圆)
|
||||
'badge', // 双环徽章
|
||||
'seal', // 双环印章
|
||||
'burst', // 光芒徽章(射线环)
|
||||
'duo', // 双图标对称
|
||||
'emblem', // 渐变盾徽
|
||||
'hexagon', // 六边形徽章
|
||||
'diamond', // 菱形徽章
|
||||
'arch', // 拱门徽章
|
||||
'geo', // 几何构形
|
||||
'techcorner', // 科技角标
|
||||
'fullbleed', // 头像专属:全出血大图标(无容器)
|
||||
'concentric', // 头像专属:同心圆环
|
||||
'centroid' // 头像专属:中心单环
|
||||
];
|
||||
|
||||
/** 平台头像导出规格预设 */
|
||||
const AVATAR_SIZES = [
|
||||
{ id: 'wechat', name: '微信头像', size: 300, note: '微信/微博 300' },
|
||||
{ id: 'qq', name: 'QQ 头像', size: 640, note: 'QQ 640' },
|
||||
{ id: 'douyin', name: '抖音头像', size: 400, note: '抖音/快手 400' },
|
||||
{ id: 'youtube', name: 'YouTube 头像', size: 800, note: '频道头像 800' },
|
||||
{ id: 'app', name: 'App 图标', size: 1024, note: 'iOS/安卓 1024' },
|
||||
{ id: 'favicon', name: '网站 Favicon', size: 64, note: '浏览器标签 64' }
|
||||
];
|
||||
|
||||
/** 头像词根扩充的主力单色源(懒加载优先预载;多色 emoji/品牌源不参与词根搜索) */
|
||||
const AVATAR_CORE_SOURCES = [
|
||||
'lucide', 'tabler', 'feather', 'mdi', 'fluent', 'solar', 'msymbols',
|
||||
'iconpark', 'hugeicons', 'mingcute', 'carbon', 'majesticons', 'pixelarticons', 'akar-icons'
|
||||
];
|
||||
|
||||
/** 布局 id 是否适合头像(在头像白名单内) */
|
||||
function isAvatarLayout(layoutId) {
|
||||
return AVATAR_LAYOUTS.includes(layoutId);
|
||||
}
|
||||
|
||||
/** 洗牌(保证多套头像图标各异) */
|
||||
function shuffle(arr) {
|
||||
const a = arr.slice();
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[a[i], a[j]] = [a[j], a[i]];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/** 内置库关键词搜索:图标名包含任一英文词根即命中(返回带库前缀 id 的图标数组;懒加载适配:先预载核心源再搜已加载源) */
|
||||
async function searchBuiltin(roots, limit) {
|
||||
// 词根搜索依赖已加载源 → 预载头像核心源(容忍单个失败)
|
||||
await HGB_IconLib.preload(AVATAR_CORE_SOURCES);
|
||||
const out = [];
|
||||
const seen = new Set();
|
||||
const kws = roots.filter(k => k.length >= 3);
|
||||
if (!kws.length) return out;
|
||||
for (const kw of kws) {
|
||||
for (const ic of HGB_IconLib.searchLoaded(kw, limit)) {
|
||||
const fullId = ic.id;
|
||||
if (seen.has(fullId)) continue;
|
||||
seen.add(fullId);
|
||||
out.push({ id: fullId, name: ic.name, svg: ic.svg, source: ic.source, type: ic.type });
|
||||
if (out.length >= limit) return out;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 头像素材池:用户选中 + 行业图标优先,内置库按行业图标词根扩充(解决"素材不够/重复") */
|
||||
async function avatarIconPool(ind, userIcons) {
|
||||
const picked = (userIcons || []).filter(Boolean).slice(0, 5);
|
||||
const indIcons = (ind.icons || []);
|
||||
const poolMap = new Map();
|
||||
// 本地段:用户选中 + 行业图标(保持插入顺序)
|
||||
for (const ic of picked) poolMap.set(ic.id, ic);
|
||||
for (const ic of indIcons) if (!poolMap.has(ic.id)) poolMap.set(ic.id, ic);
|
||||
const localCount = poolMap.size;
|
||||
// 词根:行业图标 id 按 '-' 拆分(英文语义词根,如 chef-hat → chef/hat)
|
||||
const roots = [];
|
||||
for (const ic of indIcons) {
|
||||
for (const part of String(ic.id || '').split('-')) {
|
||||
const k = part.toLowerCase();
|
||||
if (k.length >= 3 && !roots.includes(k)) roots.push(k);
|
||||
}
|
||||
}
|
||||
if (roots.length) {
|
||||
for (const ic of await searchBuiltin(roots, 60)) {
|
||||
if (!poolMap.has(ic.id)) poolMap.set(ic.id, ic);
|
||||
}
|
||||
}
|
||||
const all = Array.from(poolMap.values());
|
||||
// 分段洗牌:行业/用户图标在前(高度相关),内置库扩充在后;各自洗牌保证多套各异
|
||||
return shuffle(all.slice(0, localCount)).concat(shuffle(all.slice(localCount)));
|
||||
}
|
||||
|
||||
/** 批量生成头像(复用 Mark 生成引擎 + 头像布局白名单 + 内置库素材扩充) */
|
||||
async function generateAvatars(opt) {
|
||||
const ind = opt.industry || {};
|
||||
const count = Math.min(Math.max(opt.count || 5, 1), 20);
|
||||
// 1) 基础批量生成(风格全覆盖 + 头像布局白名单)
|
||||
const base = HGB_Generator.generatePlans({ ...opt, count, layoutWhitelist: AVATAR_LAYOUTS });
|
||||
// 2) 头像素材池扩充(行业/用户图标在前 + 内置库词根扩充;懒加载预载核心源)
|
||||
const pool = await avatarIconPool(ind, opt.icons);
|
||||
if (pool.length < 2) return base;
|
||||
// 3) 逐套替换为池中不同图标(保持风格/布局/配色不变,图标素材多样)
|
||||
return base.map((p, i) => {
|
||||
const ic1 = pool[i % pool.length];
|
||||
const ic2 = pool[(i + Math.ceil(pool.length / 2)) % pool.length];
|
||||
if (!ic1) return p;
|
||||
const v = HGB_Generator.generateVariant({
|
||||
...opt,
|
||||
layoutWhitelist: AVATAR_LAYOUTS,
|
||||
layoutId: p.layoutId,
|
||||
styleId: p.styleId,
|
||||
icons: ic2 && ic2.id !== ic1.id ? [ic1, ic2] : [ic1],
|
||||
lockup: p.lockup
|
||||
});
|
||||
return Object.assign({}, v, {
|
||||
id: p.id, name: p.name, concept: p.concept, aiIcon: ic1.id
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 生成单个头像变体(预览弹窗微调) */
|
||||
function generateVariant(opt) {
|
||||
return HGB_Generator.generateVariant({
|
||||
...opt,
|
||||
layoutWhitelist: AVATAR_LAYOUTS
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
AVATAR_LAYOUTS,
|
||||
AVATAR_SIZES,
|
||||
isAvatarLayout,
|
||||
generateAvatars,
|
||||
generateVariant
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user