feat(write-sprite): compose atlas image + UV-JSON + lock output
This commit is contained in:
58
src/write-sprite.js
Normal file
58
src/write-sprite.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// src/write-sprite.js
|
||||||
|
// Compose the diffuse atlas image, build the UV-JSON, write all three
|
||||||
|
// output files into <outDir>/<atlasId>/.
|
||||||
|
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const Jimp = require('jimp');
|
||||||
|
const { serializeSpriteLock } = require('./lock-sprite');
|
||||||
|
|
||||||
|
async function composeSpriteAtlas(boundsW, boundsH, placed) {
|
||||||
|
// Jimp 0.22: synchronous (w, h, color) constructor; color is RGBA32.
|
||||||
|
const atlas = new Jimp(boundsW, boundsH, 0x00000000);
|
||||||
|
for (const p of placed) {
|
||||||
|
// Each `p` carries the source image as `p.image` (jimp instance).
|
||||||
|
// Blit at (p.x, p.y) — the orchestrator has already pre-offset
|
||||||
|
// these coordinates by padPx, so x/y is the INNER sprite origin.
|
||||||
|
atlas.composite(p.image, p.x, p.y);
|
||||||
|
}
|
||||||
|
return atlas;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUvJson(atlasId, boundsW, boundsH, placed) {
|
||||||
|
const sprites = {};
|
||||||
|
for (const p of placed) {
|
||||||
|
sprites[p.alias] = {
|
||||||
|
x: p.x,
|
||||||
|
y: p.y,
|
||||||
|
w: p.w,
|
||||||
|
h: p.h,
|
||||||
|
source_file: p.sourceFile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return JSON.stringify({
|
||||||
|
atlas_id: atlasId,
|
||||||
|
atlas_size: [boundsW, boundsH],
|
||||||
|
sprites,
|
||||||
|
}, null, 2) + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeSpriteOutputs(opts) {
|
||||||
|
const { atlasId, outDir, placed, boundsW, boundsH, lock } = opts;
|
||||||
|
const atlasDir = path.join(outDir, atlasId);
|
||||||
|
fs.mkdirSync(atlasDir, { recursive: true });
|
||||||
|
|
||||||
|
const atlas = await composeSpriteAtlas(boundsW, boundsH, placed);
|
||||||
|
await atlas.writeAsync(path.join(atlasDir, 'sprites.diffuse.atlas.png'));
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(atlasDir, 'sprites.uv.json'),
|
||||||
|
buildUvJson(atlasId, boundsW, boundsH, placed),
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(atlasDir, 'sprites.atlas.lock.json'),
|
||||||
|
serializeSpriteLock(lock),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { composeSpriteAtlas, buildUvJson, writeSpriteOutputs };
|
||||||
46
tests/write-sprite.test.js
Normal file
46
tests/write-sprite.test.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
const { test } = require('node:test');
|
||||||
|
const assert = require('node:assert');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const os = require('node:os');
|
||||||
|
const Jimp = require('jimp');
|
||||||
|
const { writeSpriteOutputs, buildUvJson } = require('../src/write-sprite');
|
||||||
|
|
||||||
|
test('buildUvJson: emits atlas_id + atlas_size + sprites table', () => {
|
||||||
|
const placed = [
|
||||||
|
{ alias: 'bed1', x: 0, y: 0, w: 64, h: 32, sourceFile: 'Bed1.png' },
|
||||||
|
{ alias: 'sack', x: 64, y: 0, w: 32, h: 32, sourceFile: 'Sack.png' },
|
||||||
|
];
|
||||||
|
const json = buildUvJson('tcbasics', 96, 32, placed);
|
||||||
|
const parsed = JSON.parse(json);
|
||||||
|
assert.strictEqual(parsed.atlas_id, 'tcbasics');
|
||||||
|
assert.deepStrictEqual(parsed.atlas_size, [96, 32]);
|
||||||
|
assert.strictEqual(parsed.sprites.bed1.x, 0);
|
||||||
|
assert.strictEqual(parsed.sprites.bed1.w, 64);
|
||||||
|
assert.strictEqual(parsed.sprites.bed1.source_file, 'Bed1.png');
|
||||||
|
assert.strictEqual(parsed.sprites.sack.x, 64);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('writeSpriteOutputs: produces 3 files', async (t) => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'write-sprite-test-'));
|
||||||
|
t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
// Build a 2-sprite atlas in memory
|
||||||
|
const a = new Jimp(32, 32, 0xff0000ff);
|
||||||
|
const b = new Jimp(32, 32, 0x00ff00ff);
|
||||||
|
const placed = [
|
||||||
|
{ alias: 'red', x: 0, y: 0, w: 32, h: 32, sourceFile: 'Red.png', image: a, padPx: 0 },
|
||||||
|
{ alias: 'green', x: 32, y: 0, w: 32, h: 32, sourceFile: 'Green.png', image: b, padPx: 0 },
|
||||||
|
];
|
||||||
|
await writeSpriteOutputs({
|
||||||
|
atlasId: 'test',
|
||||||
|
outDir: tmp,
|
||||||
|
placed,
|
||||||
|
boundsW: 64,
|
||||||
|
boundsH: 32,
|
||||||
|
lock: { atlas_id: 'test', version: 1, aliases: ['red', 'green'], sources: { red: 'Red.png', green: 'Green.png' } },
|
||||||
|
});
|
||||||
|
assert.ok(fs.existsSync(path.join(tmp, 'test/sprites.diffuse.atlas.png')));
|
||||||
|
assert.ok(fs.existsSync(path.join(tmp, 'test/sprites.uv.json')));
|
||||||
|
assert.ok(fs.existsSync(path.join(tmp, 'test/sprites.atlas.lock.json')));
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user