Donovan Daniels
a6fcb7c5c9
Also optimize some of the animated paintings, we're now 64MB total, which is just a few megabytes over what we were before previous optimizations, and we just added 20 more paintings
128 lines
4.4 KiB
TypeScript
Executable File
128 lines
4.4 KiB
TypeScript
Executable File
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"
|
|
},
|
|
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");
|
|
// const ap = (p: string) => resolve(p, "../img");
|
|
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; } | 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"];
|
|
|
|
let kz = await makeKZ(baseWidth);
|
|
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);
|
|
}
|