Last active
March 3, 2024 08:41
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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