2024-05-31 13:54:48 +00:00
|
|
|
import type { PathLike } from "fs";
|
|
|
|
import { access } from "fs/promises";
|
|
|
|
import { dirname, resolve } from "path";
|
2024-06-01 12:04:09 +00:00
|
|
|
import sharp, { type OverlayOptions } from "sharp";
|
2024-05-31 13:54:48 +00:00
|
|
|
import { parseArgs } from "util";
|
2024-06-01 12:04:09 +00:00
|
|
|
import { formatImage, makeKZ } from "./common";
|
2024-05-31 13:54:48 +00:00
|
|
|
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"
|
|
|
|
},
|
|
|
|
kzoutdir: {
|
|
|
|
type: "string",
|
|
|
|
short: "k",
|
|
|
|
default: "data-kz"
|
|
|
|
},
|
|
|
|
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 outDir = resolve(args.outdir ?? "data");
|
|
|
|
const kzOutDir = resolve(args.kzoutdir ?? "data-kz");
|
|
|
|
const framePercent = 0.03125;
|
|
|
|
const frameColors = [0xA47627, 0xA45226, 0x944421, 0xAC581D, 0x8C341C, 0xAC641D, 0xAB6C25, 0xA44424, 0xAC572C, 0xAC4C24, 0xA87824];
|
|
|
|
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/painting");
|
2024-06-01 12:04:09 +00:00
|
|
|
// const ap = (p: string) => resolve(p, "../img");
|
2024-05-31 13:54:48 +00:00
|
|
|
await rm(`${outDir}/assets`, { recursive: true, force: true });
|
|
|
|
await rm(`${kzOutDir}/assets`, { recursive: true, force: true });
|
|
|
|
await mkdir(ap(outDir), { recursive: true });
|
|
|
|
await mkdir(ap(kzOutDir), { recursive: true });
|
|
|
|
|
|
|
|
interface Image {
|
|
|
|
animation?: { frametime?: number; };
|
|
|
|
frames?: [start: number, end: number];
|
|
|
|
kz: [x: number, y: number] | null;
|
|
|
|
name: string;
|
|
|
|
resize?: [width: number, height: number];
|
|
|
|
size: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Images {
|
|
|
|
images: Array<Image>;
|
|
|
|
sizes: Record<string, [width: number, height: number]>;
|
|
|
|
}
|
|
|
|
|
|
|
|
const images = await Bun.file(imagesPath).json() as Images;
|
|
|
|
const [baseWidth, baseHeight] = images.sizes["1x1"];
|
|
|
|
|
2024-06-01 12:04:09 +00:00
|
|
|
let kz = await makeKZ(baseWidth);
|
2024-05-31 13:54:48 +00:00
|
|
|
const kzCleanup: string[] = [];
|
|
|
|
for (const image of images.images) {
|
|
|
|
let img: string | 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 [resizeWidth, resizeHeight] = image.resize ?? [];
|
|
|
|
const { isImage, result, kzFile } = await formatImage(image.name, img, frameColors, framePercent, ...images.sizes[image.size], resizeWidth, resizeHeight, image.frames, image.kz !== null);
|
|
|
|
await result.toFile(`${ap(outDir)}/${image.name}.png`);
|
|
|
|
if (!isImage) {
|
|
|
|
await writeFile(`${ap(outDir)}/${image.name}.png.mcmeta`, JSON.stringify({ animation: image.animation ?? {} }));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (image.kz && kzFile !== null) {
|
|
|
|
kz = await sharp(kz)
|
|
|
|
.composite([{
|
|
|
|
input: await sharp(kzFile === true ? `${ap(outDir)}/${image.name}.png` : kzFile).resize(images.sizes[image.size][0], images.sizes[image.size][1]).toBuffer(),
|
|
|
|
top: baseHeight * image.kz[1],
|
|
|
|
left: baseWidth * image.kz[0]
|
|
|
|
}])
|
|
|
|
.toBuffer();
|
|
|
|
if (kzFile !== true) {
|
|
|
|
kzCleanup.push(kzFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await sharp(kz).toFile(`${ap(kzOutDir)}/paintings_kristoffer_zetterstrand.png`);
|
|
|
|
for (const file of kzCleanup) {
|
|
|
|
await rm(file);
|
|
|
|
}
|