import { createCanvas } from "canvas";
const FLAG_VERTICAL = 0b01;
const FLAG_ALPHA = 0b10;
/**
* @class Sprite
* @category Definitions
* @hideconstructor
*/
export class Sprite {
/**
* The ID of this Sprite
* @type {number}
*/
id;
/**
* Frame index for this sprite
* @type {number}
*/
frame;
/** @type {number} */
offsetX;
/** @type {number} */
offsetY;
/** @type {number} */
width;
/** @type {number} */
height;
/**
* RGB Pixel data
* @type {number}
*/
pixels = [];
/** @type {number} */
maxWidth;
/** @type {number} */
maxHeight;
/** @type {Array<number>} */
pixelIdx = [];
/** @type {Array<number>} */
palette = [];
getWidth() {
return this.width;
}
getHeight() {
return this.height;
}
setPixels(pixels) {
this.pixels = pixels;
}
async createImageUrl(width, height) {
if (width == undefined) width = this.getWidth();
if (height == undefined) height = this.getHeight();
return (await this.createImage(width, height)).toDataURL();
}
async createImage(width, height) {
if (width == undefined) width = this.getWidth();
if (height == undefined) height = this.getHeight();
const canvas = createCanvas(this.getWidth(), this.getHeight());
const ctx = canvas.getContext('2d');
let imageData = this.createImageData(ctx);
ctx.putImageData(imageData, 0, 0);
let image = new Image();
image.src = canvas.toDataURL();
let loadPromise = new Promise(resolve => {
image.onload = () => {
canvas.width = height;
canvas.height = width;
ctx.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), 0, 0, width, height);
resolve(canvas);
}
}, reject => { })
return loadPromise;
}
createImageData(ctx) {
if (ctx == undefined) {
const canvas = createCanvas(this.getWidth(), this.getHeight());
ctx = canvas.getContext('2d');
}
let imageData = ctx.createImageData(this.getWidth(), this.getHeight());
for (let i = 0; i < imageData.data.byteLength; i += 4) {
let pixel = this.pixels[Math.floor(i / 4)];
imageData.data[i + 0] = (pixel & 0x00ff0000) >> 16;
imageData.data[i + 1] = (pixel & 0x0000ff00) >> 8;
imageData.data[i + 2] = pixel & 0x000000ff;
imageData.data[i + 3] = 254 - ((pixel & 0xff000000) >> 24);
}
return imageData;
}
}
/**
* @class SpriteDefinition
* @category Definitions
* @hideconstructor
*/
export class SpriteDefinition {
/**
* The ID of this Sprite
* @type {number}
*/
id;
/**
* Sprites that make up this SpriteDefinition. There can be multiple sprites for animated SpriteDefinitions.
* @type {Array<Sprite>}
*/
sprites = [];
}
export default class SpriteLoader {
load(bytes, id) {
let def = new SpriteDefinition();
def.id = id;
let dataview = new DataView(bytes.buffer, 0, bytes.length);
dataview.setPosition(dataview.byteLength - 2);
let spriteCount = dataview.readUint16();
let sprites = new Array(spriteCount);
def.sprites = sprites;
dataview.setPosition(dataview.byteLength - 7 - spriteCount * 8);
let width = dataview.readUint16();
let height = dataview.readUint16();
let paletteLength = dataview.readUint8() + 1;
for (let i = 0; i < spriteCount; ++i) {
sprites[i] = new Sprite();
sprites[i].id = id;
sprites[i].frame = i;
sprites[i].maxWidth = width;
sprites[i].maxHeight = height;
}
for (let i = 0; i < spriteCount; ++i) {
sprites[i].offsetX = dataview.readUint16();
}
for (let i = 0; i < spriteCount; ++i) {
sprites[i].offsetY = dataview.readUint16();
}
for (let i = 0; i < spriteCount; ++i) {
sprites[i].width = dataview.readUint16();
}
for (let i = 0; i < spriteCount; ++i) {
sprites[i].height = dataview.readUint16();
}
// same as above + 3 bytes for each palette entry, except for the first one (which is transparent)
dataview.setPosition(dataview.byteLength - 7 - spriteCount * 8 - (paletteLength - 1) * 3);
let palette = new Array(paletteLength);
for (let i = 1; i < paletteLength; ++i) {
palette[i] = dataview.readInt24();
if (palette[i] == 0) {
palette[i] = 1;
}
}
dataview.setPosition(0);
for (let i = 0; i < spriteCount; ++i) {
let sprite = sprites[i];
let spriteWidth = sprite.getWidth();
let spriteHeight = sprite.getHeight();
let dimension = spriteWidth * spriteHeight;
let pixelPaletteIndicies = new Array(dimension);
let pixelAlphas = new Array(dimension);
sprite.pixelIdx = pixelPaletteIndicies;
sprite.palette = palette;
let flags = dataview.readUint8();
if ((flags & FLAG_VERTICAL) == 0) {
// read horizontally
for (let j = 0; j < dimension; ++j) {
pixelPaletteIndicies[j] = dataview.readInt8();
}
}
else {
// read vertically
for (let j = 0; j < spriteWidth; ++j) {
for (let k = 0; k < spriteHeight; ++k) {
pixelPaletteIndicies[spriteWidth * k + j] = dataview.readInt8();
}
}
}
// read alphas
if ((flags & FLAG_ALPHA) != 0) {
if ((flags & FLAG_VERTICAL) == 0) {
// read horizontally
for (let j = 0; j < dimension; ++j) {
pixelAlphas[j] = dataview.readInt8();
}
}
else {
// read vertically
for (let j = 0; j < spriteWidth; ++j) {
for (let k = 0; k < spriteHeight; ++k) {
pixelAlphas[spriteWidth * k + j] = dataview.readInt8();
}
}
}
}
else {
// everything non-zero is opaque
for (let j = 0; j < dimension; ++j) {
let index = pixelPaletteIndicies[j];
if (index != 0)
pixelAlphas[j] = 0xFF;
}
}
let pixels = new Array(dimension);
// build argb pixels from palette/alphas
for (let j = 0; j < dimension; ++j) {
let index = pixelPaletteIndicies[j] & 0xFF;
pixels[j] = palette[index] | (pixelAlphas[j] << 24);
}
sprite.setPixels(pixels);
}
//ctx.putImageData
return def;
}
}
Source