feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能

This commit is contained in:
sgp
2026-10-03 21:44:18 +08:00
commit 9e222877ea
497 changed files with 43611 additions and 0 deletions
+394
View File
@@ -0,0 +1,394 @@
/**
* 画个标 · 技能库:构图类(layout)
* 注册:HGB_Skills.register([...])
*/
(function () {
'use strict';
const U = HGB_Skills.utils;
const { estTextW, elBox } = U;
HGB_Skills.register([
{
id: 'composition',
name: '构图质量评估',
category: 'layout',
emoji: '📐',
scene: 'LOGO 构图设计与质量检查(生成后自动审计)',
summary: '依据"极简、充足负空间、精确比例"专业原则,检测方案的元素越界、重叠与文字过宽,输出质量评分与修正建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '待检查的方案对象 {elements, bg}' }
],
source: 'GitHub: op7418/logo-generator-skill(设计原则)+ 自研几何审计',
run: function (args) {
const plan = args.plan || {};
const els = plan.elements || [];
const CW = 600, CH = 600;
const issues = [], suggestions = [];
for (const el of els) {
if (el.visible === false) continue;
const b = elBox(el);
if (b.x < 0 || b.y < 0 || b.x + b.w > CW || b.y + b.h > CH) {
issues.push(`"${el.name}" 超出画布边界`);
}
}
for (let i = 0; i < els.length; i++) {
for (let j = i + 1; j < els.length; j++) {
const a = els[i], b = els[j];
const types = [a.type, b.type].sort().join('|');
if (types !== 'icon|text') continue;
// 圆环/装饰条是有意的背景元素(徽章/印章中文字位于环内),不参与重叠判定
if (['ring', 'deco-bar'].includes(a.iconId) || ['ring', 'deco-bar'].includes(b.iconId)) continue;
const ba = elBox(a), bb = elBox(b);
const ox = Math.min(ba.x + ba.w, bb.x + bb.w) - Math.max(ba.x, bb.x);
const oy = Math.min(ba.y + ba.h, bb.y + bb.h) - Math.max(ba.y, bb.y);
if (ox > 0 && oy > 0 && ox * oy > (ba.w * ba.h) * 0.08) {
issues.push(`"${a.name}" 与 "${b.name}" 存在重叠(建议加大间距)`);
}
}
}
for (const el of els) {
if (el.type === 'text' && el.fontSize && el.fontSize >= 36) {
const b = elBox(el);
if (b.x + b.w > CW - 20) {
suggestions.push(`"${el.name}" 文字偏宽(约 ${Math.round(b.w)}px),建议缩小字号或缩短文字`);
}
}
}
const score = issues.length ? Math.max(40, 95 - issues.length * 20) : (suggestions.length ? 88 : 96);
return { score, pass: issues.length === 0, issues, suggestions };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '测试品牌名' });
const r = this.run({ plan });
return { score: r.score, pass: r.pass, issues: r.issues, suggestions: r.suggestions, elements: plan.elements.length };
}
},
{
id: 'layout-choice',
name: '布局匹配推荐',
category: 'layout',
emoji: '🧱',
scene: '根据品牌名长度与行业选择最合适的布局模板',
summary: '按品牌名长度(中文字数/西文字符数)匹配布局:短名用徽章/印章/双图标,长名用上下/左右式,避免破版。',
params: [
{ name: 'brand', type: 'string', required: true, desc: '品牌名' },
{ name: 'industry', type: 'string', required: false, desc: '行业 id' }
],
source: 'GitHub: neonwatty/logo-designer-skill(interview 阶段)+ 自研',
run: function (args) {
const brand = String(args.brand || '');
const def = HGB_Generator.defaultLayout(brand);
const cn = (brand.match(/[\u4e00-\u9fa5]/g) || []).length;
const len = cn || brand.length;
const pool = HGB_Generator.LAYOUTS.filter(l => {
if (['badge', 'seal', 'duo'].includes(l.id)) return len <= 4;
return true;
});
return { brand, len, recommend: def.id, recommendName: def.name, candidates: pool.map(l => l.id + ':' + l.name) };
},
test: function () {
return { short: this.run({ brand: '远渡' }).recommend, medium: this.run({ brand: '远渡教育咨询' }).recommend, long: this.run({ brand: 'FAR CROSS EDUCATION' }).recommend };
}
},
{
id: 'align-check',
name: '对齐与居中检查',
category: 'layout',
emoji: '📏',
scene: 'LOGO 元素对齐与居中规范审计',
summary: '检查方案元素是否围绕画布中心对齐(水平/垂直居中),并检测同级元素是否存在明显错位,输出对齐修正建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: 'GitHub: op7418/logo-generator-skill(对齐规范)+ 自研',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const issues = [];
const main = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (!main.length) return { pass: true, issues: ['无可见主体元素'], centered: true };
// 主体元素中心应落在画布中心附近(容差 20px)
let cxSum = 0, cySum = 0, n = 0;
for (const el of main) {
const b = elBox(el);
cxSum += b.x + b.w / 2;
cySum += b.y + b.h / 2;
n++;
}
const cx = cxSum / n, cy = cySum / n;
if (Math.abs(cx - CW / 2) > 20) issues.push(`主体水平中心偏移 ${Math.round(cx - CW / 2)}px(建议居中)`);
if (Math.abs(cy - CH / 2) > 20) issues.push(`主体垂直中心偏移 ${Math.round(cy - CH / 2)}px(建议居中)`);
return { pass: issues.length === 0, issues, centerX: Math.round(cx), centerY: Math.round(cy), canvasCenter: CW / 2 };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '对齐测试' });
const r = this.run({ plan });
return { pass: r.pass, issues: r.issues };
}
},
{
id: 'negative-space',
name: '负空间评估',
category: 'layout',
emoji: '🕳️',
scene: 'LOGO 负空间(留白)充足性评估',
summary: '计算主体元素在画布中的占比:占比过高说明负空间不足(拥挤),占比过低说明主体偏小(空洞),给出建议区间。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(基于"极简 + 充足负空间"设计原则量化)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const area = CW * CH;
let used = 0;
for (const el of els) {
if (el.visible === false) continue;
const b = elBox(el);
const w = Math.max(0, Math.min(b.x + b.w, CW) - Math.max(b.x, 0));
const h = Math.max(0, Math.min(b.y + b.h, CH) - Math.max(b.y, 0));
used += w * h;
}
const ratio = Math.round((used / area) * 1000) / 10;
const verdict = ratio > 55 ? '负空间不足,建议缩小主体或加大留白'
: (ratio < 12 ? '主体偏小,建议放大增强存在感'
: '负空间充足,比例健康');
return { ratio, verdict, suggest: ratio > 55 ? '缩小主体或精简元素' : (ratio < 12 ? '放大主体或增加细节' : '无需调整') };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '留白测试' });
return this.run({ plan });
}
},
{
id: 'spacing-check',
name: '间距节奏检查',
category: 'layout',
emoji: '↔️',
scene: '元素间距与画布边距的一致性检查',
summary: '检查主体元素与画布边缘的留白是否充足一致,以及图标与文字间距是否过近(<8px 视为贴边),保证构图不挤不散。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(间距节奏规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const issues = [];
const main = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (!main.length) return { pass: true, issues: ['无可见主体元素'] };
for (const el of main) {
const b = elBox(el);
const minPad = Math.min(b.x, CW - (b.x + b.w), b.y, CH - (b.y + b.h));
if (minPad < 16) issues.push(`"${el.name}" 距画布边缘仅 ${Math.round(minPad)}px,建议留白 ≥ 24px`);
}
const icon = main.find(el => el.type === 'icon');
const text = main.find(el => el.type === 'text');
if (icon && text) {
const bi = elBox(icon), bt = elBox(text);
// 仅当水平相邻(垂直中心距小于图标高)时检查水平间距
const verticalGap = Math.abs((bi.y + bi.h / 2) - (bt.y + bt.h / 2));
if (verticalGap < (bi.h + bt.h) / 2) {
const gap = Math.abs((bi.x + bi.w / 2) - (bt.x + bt.w / 2)) - (bi.w + bt.w) / 2;
if (gap < 8) issues.push('图标与文字水平间距过近(<8px),建议加大间距');
}
}
return { pass: issues.length === 0, issues, ok: issues.length ? [] : ['间距节奏协调,留白充足'] };
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '间距测试' });
return this.run({ plan });
}
},
{
id: 'proportion-check',
name: '黄金比例评估',
category: 'layout',
emoji: '⚖️',
scene: '主体占比与黄金分割的匹配度评估',
summary: '计算主体元素宽度与画布宽度之比:接近 0.618 黄金分割视觉最舒适,过高拥挤、过低空泛,输出调整方向。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(黄金分割比例规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600;
const main = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (!main.length) return { pass: true, note: '无可见主体元素' };
let maxW = 0;
for (const el of main) {
const b = elBox(el);
if (b.w > maxW) maxW = b.w;
}
const ratio = Math.round((maxW / CW) * 1000) / 1000;
const pass = Math.abs(ratio - 0.618) < 0.15;
return {
pass,
ratio, golden: 0.618,
verdict: pass ? '主体占比接近黄金分割(' + ratio + '),比例协调' : '主体占比 ' + ratio + ',偏离黄金分割,建议' + (ratio > 0.768 ? '缩小主体' : '放大主体'),
suggest: pass ? '无需调整' : (ratio > 0.768 ? '缩小主体或精简元素' : '放大主体增强存在感')
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '比例测试' });
return this.run({ plan });
}
},
{
id: 'visual-balance',
name: '视觉重量平衡',
category: 'layout',
emoji: '⚖️',
scene: '画布左右/上下的视觉重量均衡检查',
summary: '计算所有可见元素的包围盒质心与画布中心的偏移:偏移过大说明构图偏一侧、视觉失衡,输出偏移方向与调整建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(视觉重量质心评估)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const vis = els.filter(el => el.visible !== false);
if (!vis.length) return { pass: true, note: '无可见元素' };
let sx = 0, sy = 0, sw = 0;
for (const el of vis) {
const b = elBox(el);
const w = Math.max(b.w * b.h, 1);
sx += ((b.x + b.w / 2) - CW / 2) * w;
sy += ((b.y + b.h / 2) - CH / 2) * w;
sw += w;
}
const dx = Math.round((sx / sw) * 10) / 10;
const dy = Math.round((sy / sw) * 10) / 10;
const off = Math.sqrt(dx * dx + dy * dy);
const pass = off < 40;
const dir = Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? '右' : '左') : (dy > 0 ? '下' : '上');
return {
pass,
offsetX: dx, offsetY: dy, offset: Math.round(off),
verdict: pass ? '视觉重心接近中心,构图平衡' : '视觉重心偏移 ' + Math.round(off) + 'px,构图偏' + dir,
suggest: pass ? '无需调整' : '调整元素位置或补充留白,让视觉重心回归中心'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '平衡测试' });
return this.run({ plan });
}
},
{
id: 'focal-point',
name: '视觉焦点检查',
category: 'layout',
emoji: '🎯',
scene: 'LOGO 是否有明确视觉焦点(主次分明)',
summary: '找出面积最大的主体元素,判断其是否显著大于其他元素:主次分明有助于快速识别,多个等权元素会让视线分散。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(主次层级量化)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const vis = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (vis.length < 2) return { pass: true, note: '元素过少,无需焦点检查' };
const areas = vis.map(el => { const b = elBox(el); return { el, area: b.w * b.h }; }).sort((a, b) => b.area - a.area);
const top = areas[0], second = areas[1];
const ratio = Math.round((top.area / Math.max(second.area, 1)) * 100) / 100;
const pass = ratio >= 1.5;
return {
pass,
focusRatio: ratio,
main: top.el.text || top.el.iconId || '主体',
verdict: pass ? '主元素面积是次元素的 ' + ratio + ' 倍,主次分明' : '主次面积接近(' + ratio + ' 倍),视觉焦点不明确',
suggest: pass ? '无需调整' : '放大主元素或缩小次要元素,强化视觉焦点'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '焦点测试' });
return this.run({ plan });
}
},
{
id: 'rule-of-thirds',
name: '构图位置分析',
category: 'layout',
emoji: '📍',
scene: '主体构图位置(居中 / 三分 / 偏移)分析',
summary: '计算主体元素重心相对画布中心与三分线交点的位置,判定构图类型:居中构图适合徽章/印章类经典版式,三分构图更具视觉张力,随机偏移则需要纠正。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(居中/三分构图规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const main = els.filter(el => el.visible !== false && !['ring', 'deco-bar'].includes(el.iconId));
if (!main.length) return { pass: true, note: '无可见主体元素' };
let sx = 0, sy = 0, n = 0;
for (const el of main) {
const b = elBox(el);
sx += b.x + b.w / 2; sy += b.y + b.h / 2; n++;
}
const cx = sx / n, cy = sy / n;
const dc = Math.hypot(cx - CW / 2, cy - CH / 2);
const xs = [CW / 3, CW * 2 / 3], ys = [CH / 3, CH * 2 / 3];
let dt = Infinity;
for (const gx of xs) for (const gy of ys) dt = Math.min(dt, Math.hypot(cx - gx, cy - gy));
const type = dc < 60 ? '居中构图(经典稳健,适合徽章/印章版式)'
: (dt < 60 ? '三分构图(主体落在分割线交点,视觉有张力)' : '偏移构图(主体既不在中心也不在分割点)');
const pass = dc < 60 || dt < 60;
return {
pass, type,
centerOffset: Math.round(dc), thirdsOffset: Math.round(dt),
centerX: Math.round(cx), centerY: Math.round(cy),
verdict: pass ? '构图位置合理:' + type : '构图位置偏散:' + type,
suggest: pass ? '无需调整' : '建议把主体重心移回画布中心或三分线交点附近,避免随意偏移'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '构图测试' });
return this.run({ plan });
}
},
{
id: 'circle-crop-safety',
name: '圆形裁切安全区',
category: 'layout',
emoji: '⭕',
scene: '头像/贴纸/印章等圆形裁切场景的构图安全检查',
summary: '以画布内切圆为裁切边界,检查各元素包围盒是否落在圆内:越界的元素在圆形裁切(头像、印章、贴纸)时会被切边,输出越界清单与缩放建议。',
params: [
{ name: 'plan', type: 'object', required: true, desc: '方案对象 {elements}' }
],
source: '自研(圆形裁切安全区规范)',
run: function (args) {
const els = (args.plan || {}).elements || [];
const CW = 600, CH = 600;
const cx = CW / 2, cy = CH / 2, r = CW / 2;
const vis = els.filter(el => el.visible !== false);
if (!vis.length) return { pass: true, note: '无可见元素' };
const risky = [];
let maxD = 0;
for (const el of vis) {
const b = elBox(el);
const corners = [[b.x, b.y], [b.x + b.w, b.y], [b.x, b.y + b.h], [b.x + b.w, b.y + b.h]];
let d = 0;
for (const [px, py] of corners) d = Math.max(d, Math.hypot(px - cx, py - cy));
maxD = Math.max(maxD, d);
if (d > r) risky.push({ name: el.text || el.iconId || '元素', maxDist: Math.round(d) });
}
const pass = risky.length === 0;
return {
pass, risky, radius: r, maxDist: Math.round(maxD),
verdict: pass ? '所有元素均在内切圆内,圆形裁切安全' : risky.length + ' 个元素超出内切圆(' + risky.map(x => x.name).join('、') + '),圆形裁切时会被切边',
suggest: pass ? '无需调整' : '建议缩小越界元素或整体向圆心内缩 ' + Math.max(10, Math.round(maxD - r)) + 'px,保证圆形裁切后主体完整'
};
},
test: function () {
const plan = HGB_Generator.generateVariant({ industry: HGB_Industries[0], brand: '圆裁测试' });
const edge = { plan: { elements: [{ type: 'icon', iconId: 'x', x: 585, y: 300, w: 30, h: 30 }] } };
return { generated: this.run({ plan }).pass !== undefined, edge: this.run(edge).risky.length };
}
}
]);
})();