@theatrejs/loader-aseprite
Version:
⚙️ A Webpack Loader for Aseprite files.
155 lines (113 loc) • 5.6 kB
JavaScript
const subprocess = require('child_process');
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const pngjs = require('pngjs').PNG.sync;
/**
* @type {webpack.RawLoaderDefinition}
*/
module.exports = function loader() {
/**
* @typedef {Object} TypeColorswap A swap of two colors.
* @property {Array<number>} TypeColorswap.source The source color to swap from (in rgba).
* @property {Array<number>} TypeColorswap.target The target color to swap to (in rgba).
* @private
*/
/**
* @typedef {Object} TypeOptions The options for the loader.
* @property {string} TypeOptions.aseprite The path to the Aseprite executable.
* @property {Object} [TypeOptions.prepare] The options for the Aseprite CLI.
* @property {('colums' | 'horizontal' | 'packed' | 'rows' | 'vertical')} [TypeOptions.prepare.sheet] The output sheet type ('rows' by default).
* @property {boolean} [TypeOptions.prepare.trim] The 'trim cels' option (false by default).
* @property {Object} [TypeOptions.processing] The options for processing the output files.
* @property {Array<TypeColorswap>} [TypeOptions.processing.colorswap] The swaps of colors.
* @private
*/
const context = /** @type {webpack.LoaderContext<TypeOptions>} */(this);
const file = context.resourcePath;
const options = context.getOptions();
const aseprite = options.aseprite;
const prepare = options.prepare;
const processing = options.processing;
try {
require.resolve('@theatrejs/plugin-aseprite');
}
catch ($error) {
throw $error;
}
if (typeof aseprite === 'undefined') {
throw new Error('Aseprite executable path is missing in the loader options.');
}
if (fs.existsSync(aseprite) === false) {
throw new Error('Aseprite executable not found reading path: ' + (typeof aseprite !== 'undefined' || aseprite !== '' ? '\'' + aseprite + '\'' : '<empty>') + '.');
}
const location = path.dirname(file);
const filename = path.basename(file, '.aseprite');
const sourceTexture = filename + '.png';
const sourceData = filename + '.json';
const trim = (typeof prepare !== 'undefined' && prepare.trim === true) ? ' --trim' : '';
const sheetType = (typeof prepare !== 'undefined' && ['colums', 'horizontal', 'packed', 'rows', 'vertical'].indexOf(prepare.sheet) !== -1) ? prepare.sheet : 'rows';
try {
if (fs.existsSync(path.resolve(location, sourceTexture)) === false
|| fs.existsSync(path.resolve(location, sourceData)) === false
|| fs.statSync(path.resolve(location, sourceTexture)).mtime < fs.statSync(file).mtime
|| fs.statSync(path.resolve(location, sourceData)).mtime < fs.statSync(file).mtime) {
subprocess.execSync(
'cd "' + location + '"' +
' && "' + aseprite + '"' +
' --batch "' + file + '"' +
trim +
' --sheet "' + sourceTexture + '"' +
' --sheet-type ' + sheetType +
' --split-tags' +
' --data "' + sourceData + '"' +
' --list-tags' +
' --format json-array' +
' --filename-format {tag}#{tagframe001}@{title}.{extension}'
);
if (typeof processing !== 'undefined'
&& Array.isArray(processing.colorswap)
&& processing.colorswap.length > 0) {
const bufferSource = fs.readFileSync(path.resolve(location, sourceTexture));
const image = pngjs.read(bufferSource);
const pixels = image.data;
const height = image.height;
const width = image.width;
processing.colorswap.forEach(({source, target}) => {
const [redSource, greenSource, blueSource, alphaSource] = source;
const [redTarget, greenTarget, blueTarget, alphaTarget] = target;
for (let y = 0; y < height; y += 1) {
for (let x = 0; x < width; x += 1) {
const index = (width * y + x) * 4;
const indexRed = index;
const indexGreen = index + 1;
const indexBlue = index + 2;
const indexAlpha = index + 3;
if (pixels[indexRed] === redSource
&& pixels[indexGreen] === greenSource
&& pixels[indexBlue] === blueSource
&& pixels[indexAlpha] === alphaSource) {
pixels[indexRed] = redTarget;
pixels[indexGreen] = greenTarget;
pixels[indexBlue] = blueTarget;
pixels[indexAlpha] = alphaTarget;
}
}
}
});
const bufferTarget = pngjs.write(image);
fs.writeFileSync(path.resolve(location, sourceTexture), bufferTarget);
}
}
return (
'import {Aseprite} from \'@theatrejs/plugin-aseprite\';' +
'import data from \'./' + sourceData + '\';' +
'import texture from \'./' + sourceTexture + '\';' +
'export default new Aseprite(texture, data);'
);
}
catch ($error) {
throw $error;
}
};
module.exports.raw = true;