53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
import sharp, { type OverlayOptions } from 'sharp';
|
|
import { LAYERS, weightedChoice } from './layers';
|
|
|
|
const BASE_SHAPE_PATH = './assets/base_shape.png';
|
|
|
|
export type Trait = 'skin' | 'nose' | 'eyes' | 'mouth' | 'detail';
|
|
|
|
export type Layer = {
|
|
trait: Trait,
|
|
path: string,
|
|
chance?: number,
|
|
options: TraitOption[]
|
|
};
|
|
|
|
export type TraitOption = {
|
|
fileName: string,
|
|
weight?: number
|
|
};
|
|
|
|
async function generateRandomImage(fileName: string, orderedLayers: Layer[]) {
|
|
const layers: { input: string }[] = [];
|
|
|
|
for (const layer of orderedLayers) {
|
|
if (layer.chance && Math.random() > layer.chance) {
|
|
continue;
|
|
}
|
|
|
|
const choice = weightedChoice(layer.options);
|
|
|
|
layers.push({ input: `${layer.path}${choice}` });
|
|
}
|
|
|
|
const image = sharp(BASE_SHAPE_PATH).composite(layers);
|
|
|
|
await Bun.write(`./output/${fileName}`, await image.toBuffer());
|
|
}
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
await generateRandomImage(`characters/${i}.png`, LAYERS);
|
|
}
|
|
|
|
// combine into a single grid image
|
|
const characters: OverlayOptions[] = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
characters.push({ input: `./output/characters/${i}.png`, left: (i % 10) * 16, top: Math.floor(i / 10.0) * 16 });
|
|
}
|
|
|
|
const grid = sharp({ create: { width: 160, height: 160, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } } }).composite(characters).png();
|
|
|
|
await Bun.write('./output/grid.png', await grid.toBuffer());
|
|
|
|
console.log('Finished generation');
|