sillypacker/index.ts
2025-05-21 03:24:35 +07:00

119 lines
3.6 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import packer, { packAsync, type TexturePackerOptions } from "free-tex-packer-core"
const args = process.argv.slice(2);
if (args.length < 2) {
console.log('Usage: sillypacker <inputFolder> <outputFolder>');
process.exit(1);
}
const inputFolder = args[0];
const outputFolder = args[1];
console.log(`Input Folder: ${inputFolder}`);
console.log(`Output Folder: ${outputFolder}`);
type FileImage = {
path: string,
contents: Buffer<ArrayBufferLike>,
}
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff'];
const isImageFile = (fileName: string) => imageExtensions.some(ext => fileName.toLowerCase().endsWith(ext));
const getImageFiles = (dirPath: string): FileImage[] => {
const files:FileImage[] = [];
const items = fs.readdirSync(dirPath);
items.forEach((item) => {
const fullPath = path.join(dirPath, item);
const stats = fs.statSync(fullPath);
const basename = path.basename(fullPath);
if (stats.isDirectory())
files.push(...getImageFiles(fullPath));
else if (stats.isFile() && isImageFile(item))
files.push({path: basename, contents: fs.readFileSync(fullPath)});
});
return files;
};
const images = getImageFiles(inputFolder);
const sorter = (a, b) => {
let prefixA = a.match(/[a-zA-Z]+/)[0];
let numA = parseInt(a.match(/\d+/)[0]);
let prefixB = b.match(/[a-zA-Z]+/)[0];
let numB = parseInt(b.match(/\d+/)[0]);
if (prefixA === prefixB) {
return numA - numB;
} else {
return prefixA.localeCompare(prefixB);
}
}
let options:TexturePackerOptions = {
textureName: path.basename(inputFolder),
removeFileExtension: true,
prependFolderName: false,
width: 128,
height: 128,
powerOfTwo: true,
padding: 2,
extrude: 0,
allowRotation: true,
allowTrim: true,
detectIdentical: true,
trimMode: "trim",
packer: "OptimalPacker",
exporter: "JsonArray"
};
packer(images, options, (files, error) => {
if (error) {
console.error('Packaging failed', error);
} else {
const meta = files.filter(i => i.name.endsWith(".json"))
.map(i => JSON.parse(i.buffer.toString("utf-8")))
.map(i => {
const images = i.frames.map(frame => {
const name = frame.filename;
const texcord_x = frame.frame.x
const texcord_y = frame.frame.y
const texcord_w = frame.frame.w
const texcord_h = frame.frame.h
const offset_x = frame.spriteSourceSize.x
const offset_y = frame.spriteSourceSize.y
const width = frame.sourceSize.w
const height = frame.sourceSize.h
const rotated = (frame.rotated) ? 1 : 0;
return {name, texcord_x, texcord_y, texcord_w, texcord_h, offset_x, offset_y, width, height, rotated}
}).toSorted();
const name = i.meta.image;
return {name, images}
})
// fs.writeFileSync(
// path.join("dist", options.textureName + ".json"),
// JSON.stringify(meta, null, 2),
// "utf-8");
const out = [`sillyatl ${meta.length}`, meta.map(tex => [`${path.basename(tex.name, path.extname(tex.name))} ${tex.images.length}`, ...tex.images.map(img => {
return `${img.name} ${img.texcord_x} ${img.texcord_y} ${img.texcord_w} ${img.texcord_h} ${img.offset_x} ${img.offset_y} ${img.width} ${img.height} ${img.rotated}`
}).toSorted((a, b) => sorter(a,b))].join("\n"))].flat().join("\n");
fs.writeFileSync(path.join(outputFolder, options.textureName + ".txt"), out, "utf-8");
const texture = files.filter(i => i.name.endsWith(".png"))
.map(i => fs.writeFileSync(path.join(outputFolder, i.name), i.buffer));
}
});