118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
|
import type { PathLike } from "fs";
|
||
|
import { access } from "fs/promises";
|
||
|
import { dirname, resolve } from "path";
|
||
|
import sharp, { type OverlayOptions } from "sharp";
|
||
|
import { parseArgs } from "util";
|
||
|
import { formatImage, makeKZ } from "./common";
|
||
|
import { readFile } from "fs/promises";
|
||
|
import { writeFile } from "fs/promises";
|
||
|
import { rm } from "fs/promises";
|
||
|
import { mkdir } from "fs/promises";
|
||
|
import { stat } from "fs/promises";
|
||
|
import { readdir } from "fs/promises";
|
||
|
|
||
|
const { values: args } = parseArgs({
|
||
|
args: Bun.argv,
|
||
|
options: {
|
||
|
images: {
|
||
|
type: "string",
|
||
|
short: "i",
|
||
|
default: "images/images.json"
|
||
|
},
|
||
|
imagedir: {
|
||
|
type: "string",
|
||
|
short: "d",
|
||
|
default: "images"
|
||
|
},
|
||
|
outdir: {
|
||
|
type: "string",
|
||
|
short: "o",
|
||
|
default: "data"
|
||
|
},
|
||
|
basefile: {
|
||
|
type: "string",
|
||
|
short: "b",
|
||
|
default: "base.png"
|
||
|
},
|
||
|
throw: {
|
||
|
type: "boolean",
|
||
|
short: "t",
|
||
|
default: false
|
||
|
}
|
||
|
},
|
||
|
strict: true,
|
||
|
allowPositionals: true
|
||
|
});
|
||
|
|
||
|
const dirExists = async(path: PathLike) => access(path).then(() => true, () => false);
|
||
|
const imagesPath = resolve(args.images ?? "images.json");
|
||
|
const imageDir = resolve(args.imagedir ?? dirname(imagesPath));
|
||
|
const baseFile = resolve(args.basefile ?? "base.png");
|
||
|
const outDir = resolve(args.outdir ?? "data");
|
||
|
const throwOnMissing = !!args.throw;
|
||
|
if (!await dirExists(imageDir)) {
|
||
|
console.error("Image directory %s does not exist.", imageDir);
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
const ap = (p: string) => resolve(p, "assets/minecraft/textures/entity/bed");
|
||
|
// const ap = (p: string) => resolve(p, "../img");
|
||
|
await rm(`${outDir}/assets`, { recursive: true, force: true });
|
||
|
await mkdir(ap(outDir), { recursive: true });
|
||
|
|
||
|
export interface Image {
|
||
|
animation?: { frametime?: number; };
|
||
|
frames?: { start: number; end?: number; } | Array<number>;
|
||
|
name: string;
|
||
|
sizeMultiplier?: number;
|
||
|
rotate?: number;
|
||
|
flip?: "horizontal" | "vertical";
|
||
|
}
|
||
|
|
||
|
export interface Detail {
|
||
|
pos: [number, number];
|
||
|
size: [number, number];
|
||
|
cut: [number, number];
|
||
|
rotate?: number;
|
||
|
flip?: "horizontal" | "vertical";
|
||
|
}
|
||
|
|
||
|
|
||
|
export interface Details {
|
||
|
top: Detail;
|
||
|
middle: Detail;
|
||
|
bottom: Detail;
|
||
|
}
|
||
|
|
||
|
export interface Config {
|
||
|
defaultSize: [width: number, height: number];
|
||
|
details: Details;
|
||
|
images: Array<Image>;
|
||
|
}
|
||
|
|
||
|
const config = await Bun.file(imagesPath).json() as Config;
|
||
|
|
||
|
for (const image of config.images) {
|
||
|
let img: string | Array<string>;
|
||
|
if (await stat(`${imageDir}/${image.name}`).then(s => s.isDirectory(), () => false)) {
|
||
|
img = await readdir(`${imageDir}/${image.name}`).then(files => files.map(f => `${imageDir}/${image.name}/${f}`).sort());
|
||
|
} else {
|
||
|
const [file] = await Array.fromAsync(new Bun.Glob(`${image.name}.*`).scan({ onlyFiles: true, cwd: imageDir }));
|
||
|
if (!file) {
|
||
|
console.error("Image %s does not exist.", image.name);
|
||
|
if (throwOnMissing) {
|
||
|
process.exit(1);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
img = `${imageDir}/${file}`
|
||
|
}
|
||
|
console.log("Processing %s", image.name);
|
||
|
|
||
|
const { isImage, result } = await formatImage(image.name, baseFile, config, img);
|
||
|
await result.toFile(`${ap(outDir)}/${image.name}.png`);
|
||
|
if (!isImage) {
|
||
|
await writeFile(`${ap(outDir)}/${image.name}.png.mcmeta`, JSON.stringify({ animation: image.animation ?? {} }));
|
||
|
}
|
||
|
}
|