cast-avatar
Version:
Dependency-free deterministic SVG avatar generator for browsers.
83 lines (71 loc) • 2.73 kB
JavaScript
import { createRandom, pick } from '../hash.js';
import { resolvePalette } from '../palettes.js';
import { colorAt, svgFrame } from './common.js';
// 8-bit pixel-art character (inspired by DiceBear's pixel-art): a blocky face
// built on a 12x12 grid with seeded skin/hair/clothing colors and variation in
// hair style and facial hair. CELL/ORIGIN center the grid inside the 128 frame.
const CELL = 8;
const ORIGIN = 16;
function px(col, row, cols, rows, fill) {
return `<rect x="${ORIGIN + col * CELL}" y="${ORIGIN + row * CELL}" width="${cols * CELL}" height="${rows * CELL}" fill="${fill}"/>`;
}
export function renderPixelAvatar(config) {
const random = createRandom(`${config.seed}:pixel`);
const palette = resolvePalette(config.palette);
const skin = palette.skinTones[pick(Object.keys(palette.skinTones), random)];
const hair = palette.hairColors[pick(Object.keys(palette.hairColors), random)];
const clothing = colorAt(palette.shapeColors, random, 2);
const hatColor = colorAt(palette.shapeColors, random, 5);
const hairStyle = pick(['flat', 'tall', 'side', 'long', 'none'], random);
const hat = pick(['none', 'none', 'none', 'beanie', 'cap'], random);
const hasBeard = random() > 0.65;
const hasGlasses = random() > 0.5;
const parts = [];
// face + ears
parts.push(px(2, 3, 8, 7, skin));
parts.push(px(1, 5, 1, 2, skin));
parts.push(px(10, 5, 1, 2, skin));
// hair
if (hairStyle !== 'none') {
parts.push(px(2, 1, 8, 2, hair));
parts.push(px(2, 3, 8, 1, hair));
if (hairStyle === 'tall') {
parts.push(px(3, 0, 6, 1, hair));
}
if (hairStyle === 'side') {
parts.push(px(2, 4, 1, 2, hair));
}
if (hairStyle === 'long') {
parts.push(px(2, 4, 1, 4, hair));
parts.push(px(9, 4, 1, 4, hair));
}
}
// hat (drawn over the hair crown)
if (hat === 'beanie') {
parts.push(px(2, 1, 8, 2, hatColor));
parts.push(px(2, 3, 8, 1, hatColor));
} else if (hat === 'cap') {
parts.push(px(3, 1, 6, 2, hatColor));
parts.push(px(1, 3, 8, 1, hatColor));
}
// beard (drawn under the mouth so the mouth stays visible)
if (hasBeard) {
parts.push(px(2, 8, 8, 2, hair));
parts.push(px(3, 7, 1, 1, hair));
parts.push(px(8, 7, 1, 1, hair));
}
// eyes (or glasses) + mouth
if (hasGlasses) {
parts.push(px(3, 6, 6, 1, '#1f2937'));
parts.push(px(4, 6, 1, 1, '#e5e7eb'));
parts.push(px(7, 6, 1, 1, '#e5e7eb'));
} else {
parts.push(px(4, 6, 1, 1, '#1f2937'));
parts.push(px(7, 6, 1, 1, '#1f2937'));
}
parts.push(px(5, 8, 2, 1, '#7f1d1d'));
// clothing + neck
parts.push(px(1, 10, 10, 2, clothing));
parts.push(px(5, 10, 2, 1, skin));
return svgFrame(config, parts.join(''));
}