Skip to content

Instantly share code, notes, and snippets.

@3vorp
Last active March 3, 2024 08:41
Show Gist options
  • Save 3vorp/39ee18b798a1f43436b6e31626a28d8a to your computer and use it in GitHub Desktop.
Save 3vorp/39ee18b798a1f43436b6e31626a28d8a to your computer and use it in GitHub Desktop.
Extend a tilesheet with ping-ponging and frame duplication. Useful for manipulating Minecraft atlases.
const { loadImage, createCanvas } = require("@napi-rs/canvas");
const { writeFileSync } = require("fs");
const { readFile } = require("fs/promises");
/**
* @typedef TileSheetOptions
* @property {string | URL | Buffer | ArrayBufferLike | Uint8Array | import("@napi-rs/canvas").Image | import("stream").Readable} src - What to load
* @property {number} [duplicate] - How many duplicates to create (default 2)
* @property {boolean} [pingPong] - Whether to ping-pong the animation or not (default false)
*/
/**
* Extend a tilesheet with ping-ponging and frame duplication
* - Useful for backporting Minecraft atlases
* @author Evorp
* @param {TileSheetOptions} params - Options for what to do
* @returns {Buffer} PNG buffer of the new tilesheet atlas
*/
async function extendTileSheet({ src, duplicate, pingPong } = {}) {
duplicate ||= 2;
pingPong ??= false; // force undefined or null to false
const buf = await readFile(src).catch(() => {
console.log(`This file needs to be in the same folder as your ${src}!`);
process.exit(1);
});
const img = await loadImage(buf);
const newHeight = img.height * duplicate * (+pingPong + 1); // yes I'm casting a boolean as a number
const canvas = createCanvas(img.width, newHeight);
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;
for (let i = 0; i < img.height / img.width; ++i) {
const origPos = img.width * i;
for (let j = 0; j < duplicate; ++j) {
const newPos = img.width * (j + i * duplicate);
// subtract one frame because it's drawn from top coordinate
const invPos = newHeight - img.width - newPos;
ctx.drawImage(img, 0, origPos, img.width, img.width, 0, newPos, img.width, img.width);
if (pingPong)
ctx.drawImage(
img,
0,
origPos,
img.width,
img.width,
0,
invPos,
img.width,
img.width
);
}
}
return canvas.toBuffer("image/png");
}
// sample usage
extendTileSheet({ src: "./lava.png", duplicate: 1, pingPong: false }).then((buf) =>
writeFileSync("./out.png", buf)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment