A => tools/array2d.js +65 -0
@@ 0,0 1,65 @@
+const { checkTypes } = require("./utils");
+
+class Array2D {
+ constructor(width, height, getter) {
+ checkTypes(arguments, "number", "number", Function);
+ this.width = width;
+ this.height = height;
+ this.getter = getter;
+ }
+
+ get(x, y) {
+ checkTypes(arguments, "number", "number");
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height)
+ throw new Error("Coordinates out of range.");
+ return this.getter(x, y);
+ }
+
+ static ofArray(array, width, height) {
+ checkTypes(arguments, Array, "number", "number");
+ if (array.length != width * height)
+ throw new Error("Array data incomplete.");
+ return new Array2D(width, height, (x, y) => array[y * width + x]);
+ }
+
+ toArray() {
+ const array = [];
+ for (let y = 0; y < this.height; y++) {
+ for (let x = 0; x < this.width; x++) {
+ array.push(this.get(x, y));
+ }
+ }
+ return array;
+ }
+
+ apply() {
+ return Array2D.ofArray(this.toArray(), this.width, this.height);
+ }
+
+ map(mapFunction) {
+ checkTypes(arguments, Function);
+ return new Array2D(this.width, this.height, (x, y) => mapFunction(this.get(x, y), x, y));
+ }
+
+ tile(width, height) {
+ checkTypes(arguments, "number", "number");
+ if (this.width % width != 0 || this.height % height != 0)
+ throw new Error("Not an integer multiple of tile dimensions.");
+ const tiles = [];
+ for (let y = 0; y < this.height; y += height) {
+ for (let x = 0; x < this.width; x += width) {
+ tiles.push(this.slice(x, y, width, height));
+ }
+ }
+ return Array2D.ofArray(tiles, Math.floor(this.width / width), Math.floor(this.height / height));
+ }
+
+ slice(xOffset, yOffset, width, height) {
+ checkTypes(arguments, "number", "number", "number", "number");
+ if (xOffset < 0 || (xOffset + width) > this.width || yOffset < 0 || (yOffset + height) > this.height)
+ throw new Error("Slice coordinates out of range.");
+ return new Array2D(width, height, (x, y) => this.get(xOffset + x, yOffset + y));
+ }
+}
+
+exports.Array2D = Array2D;
M tools/images.js +28 -66
@@ 2,48 2,48 @@
const fs = require("fs");
const PNG = require("png-js");
const { checkTypes } = require("./utils");
+const { Array2D } = require("./array2d");
class Image {
- constructor(width, height, palette) {
- checkTypes(arguments, "number", "number", Palette);
- this.width = width;
- this.height = height;
+ constructor(pixels, palette) {
+ checkTypes(arguments, Array2D, Palette);
+ this.pixels = pixels;
this.palette = palette;
}
- getPixel() {
- throw new Error("Not implemented.");
+ getPixel(x, y) {
+ return this.pixels.get(x, y);
}
getPixelData(screenMode) {
- if (this.width & 1)
+ if (this.pixels.width & 1)
throw new Error("Image width is not even.");
const pixelData = [];
if (screenMode >= 5 && screenMode <= 6) {
- for (let y = 0; y < this.height; y++) {
- for (let x = 0; x < this.width; x += 2) {
- pixelData.push((this.getPixel(x, y).index & 15) << 4 | (this.getPixel(x + 1, y).index & 15));
+ for (let y = 0; y < this.pixels.height; y++) {
+ for (let x = 0; x < this.pixels.width; x += 2) {
+ pixelData.push((this.pixels.get(x, y).index & 15) << 4 | (this.pixels.get(x + 1, y).index & 15));
}
}
} else if (screenMode == 7) {
- for (let y = 0; y < this.height; y++) {
- for (let x = 0; x < this.width; x++) {
- const pixel = this.getPixel(x, y);
+ for (let y = 0; y < this.pixels.height; y++) {
+ for (let x = 0; x < this.pixels.width; x++) {
+ const pixel = this.pixels.get(x, y);
pixelData.push((pixel.index & 15) << 4 | (pixel.index & 15));
}
}
} else if (screenMode == 8) {
- for (let y = 0; y < this.height; y++) {
- for (let x = 0; x < this.width; x++) {
- const pixel = this.getPixel(x, y).project(7).round();
+ for (let y = 0; y < this.pixels.height; y++) {
+ for (let x = 0; x < this.pixels.width; x++) {
+ const pixel = this.pixels.get(x, y).project(7).round();
pixelData.push(pixel.g << 5 | pixel.r << 2 | pixel.b >> 1);
}
}
} else if (screenMode >= 10 && screenMode <= 11) {
- for (let y = 0; y < this.height; y++) {
- for (let x = 0; x < this.width; x++) {
- const pixel = this.getPixel(x, y);
+ for (let y = 0; y < this.pixels.height; y++) {
+ for (let x = 0; x < this.pixels.width; x++) {
+ const pixel = this.pixels.get(x, y);
pixelData.push((pixel.index & 15) << 4 | 8);
}
}
@@ 51,20 51,19 @@ class Image {
return pixelData;
}
- toTiles(width, height) {
- checkTypes(arguments, "number", "number");
- const tiles = [];
- for (let y = 0; y < this.height; y += height)
- for (let x = 0; x < this.width; x += width)
- tiles.push(new ImageSlice(this, x, y, width, height));
- return tiles;
+ tile(width, height) {
+ return this.pixels.tile(width, height).map(pixels => new Image(pixels, this.palette)).apply();
+ }
+
+ slice(x, y, width, height) {
+ return new Image(this.pixels.slice(x, y, width, height), this.palette);
}
toAsm(screenMode) {
checkTypes(arguments, "number");
const lines = [];
if (this.name) {
- lines.push(`${this.name}_instance: Image ${this.width}, ${this.height}, ${this.name}_palette, ${this.name}_pixelData`);
+ lines.push(`${this.name}_instance: Image ${this.pixels.width}, ${this.pixels.height}, ${this.name}_palette, ${this.name}_pixelData`);
lines.push(`${this.name}_palette: ${this.palette.toAsm()}`);
lines.push("\tSECTION ROM_DATA");
lines.push(`${this.name}_pixelData:`);
@@ 77,43 76,6 @@ class Image {
}
}
-class ImageData extends Image {
- constructor(pixels, width, height, palette) {
- checkTypes(arguments, Array, "number", "number", Palette);
- if (pixels.length != width * height)
- throw new Error("Pixel data incomplete.");
- super(width, height, palette);
- this.pixels = pixels;
- this.name = null;
- }
-
- getPixel(x, y) {
- checkTypes(arguments, "number", "number");
- if (x < 0 || x >= this.width || y < 0 || y >= this.height)
- throw new Error("Coordinates out of range.");
- return this.pixels[y * this.width + x];
- }
-}
-
-class ImageSlice extends Image {
- constructor(image, x, y, width, height) {
- checkTypes(arguments, Image, "number", "number", "number", "number");
- if (x < 0 || (x + width) > image.width || y < 0 || (y + height) > image.height)
- throw new Error("Slice coordinates out of range.");
- super(width, height, image.palette);
- this.image = image;
- this.x = x;
- this.y = y;
- }
-
- getPixel(x, y) {
- checkTypes(arguments, "number", "number");
- if (x < 0 || x >= this.width || y < 0 || y >= this.height)
- throw new Error("Coordinates out of range.");
- return this.image.getPixel(this.x + x, this.y + y);
- }
-}
-
class Palette {
constructor() {
this.colors = [];
@@ 208,7 170,7 @@ class PNGLoader {
for (const index of pngPixels)
pixels.push(palette.getColor(index));
- return new ImageData(pixels, png.width, png.height, palette);
+ return new Image(Array2D.ofArray(pixels, png.width, png.height), palette);
}
toPalette(palette8bit) {
@@ 223,4 185,4 @@ class PNGLoader {
}
}
-module.exports = { Image, ImageData, ImageSlice, Palette, PaletteColor, PNGLoader };
+module.exports = { Image, Palette, PaletteColor, PNGLoader };
M tools/sprites.js +5 -11
@@ 2,7 2,7 @@
const fs = require("fs");
const path = require("path");
const { checkTypes } = require("./utils");
-const { Image, ImageSlice, PNGLoader } = require("./images");
+const { Image, PNGLoader } = require("./images");
class Sprite {
constructor(name, sheet) {
@@ 38,14 38,12 @@ class Sprite {
class SpriteFrame {
constructor(name, image, duration) {
checkTypes(arguments, "string", Image, "number");
- if (image.width % 16 != 0 || image.height % 16 != 0)
- throw new Error(`Unsupported sprite dimensions .`);
this.name = name;
this.image = image;
this.duration = duration;
this.patterns = [];
- for (const tile of image.toTiles(16, 16)) {
+ for (const tile of image.tile(16, 16).toArray()) {
this.patterns.push(new SpritePattern(tile, 1));
this.patterns.push(new SpritePattern(tile, 2));
this.patterns.push(new SpritePattern(tile, 4));
@@ 74,7 72,8 @@ class SpritePattern {
checkTypes(arguments, Image, "number");
this.data = [];
- const addPatternData = image => {
+ const images = image.tile(8, 8);
+ for (const image of [images.get(0, 0), images.get(0, 1), images.get(1, 0), images.get(1, 1)]) {
for (let y = 0; y < 8; y++) {
let byte = 0;
for (let x = 0; x < 8; x++) {
@@ 84,11 83,6 @@ class SpritePattern {
this.data.push(byte);
}
}
-
- addPatternData(new ImageSlice(image, 0, 0, 8, 8));
- addPatternData(new ImageSlice(image, 0, 8, 8, 8));
- addPatternData(new ImageSlice(image, 8, 0, 8, 8));
- addPatternData(new ImageSlice(image, 8, 8, 8, 8));
}
toAsm() {
@@ 122,7 116,7 @@ class AsepriteSheet {
checkTypes(arguments, Object, Sprite);
for (const jsonFrameName in jsonFrames) {
const json = jsonFrames[jsonFrameName];
- const image = new ImageSlice(sprite.sheet, json.frame.x, json.frame.y, json.frame.w, json.frame.h);
+ const image = sprite.sheet.slice(json.frame.x, json.frame.y, json.frame.w, json.frame.h);
sprite.addFrame(new SpriteFrame(jsonFrameName, image, json.duration / 1000));
}
}
M tools/tilemaps.js +16 -35
@@ 4,36 4,30 @@ const path = require("path");
const xml2js = require("xml2js");
const { checkTypes } = require("./utils");
const { Image, Palette, PNGLoader } = require("./images");
+const { Array2D } = require("./array2d");
class TileMap {
- constructor(name, width, height, tileSet) {
- checkTypes(arguments, "string", "number", "number", TileSet);
+ constructor(name, tiles, tileSet) {
+ checkTypes(arguments, "string", Array2D, TileSet);
this.name = name;
- this.width = width;
- this.height = height;
+ this.tiles = tiles;
this.tileSet = tileSet;
- this.tiles = [];
- }
-
- addTileByIndex(index) {
- checkTypes(arguments, "number");
- this.tiles.push(this.tileSet.getTileByIndex(index));
}
optimize() {
- this.tileSet.optimize(this.tiles);
+ this.tileSet.optimize(this.tiles.toArray());
}
toAsm(screenMode) {
checkTypes(arguments, "number");
const lines = [];
lines.push(`${this.name}_instance:`)
- lines.push(`\tTileMap ${this.width}, ${this.height}, ${this.tileSet.name}_palette, ${this.name}_data`);
+ lines.push(`\tTileMap ${this.tiles.width}, ${this.tiles.height}, ${this.tileSet.name}_palette, ${this.name}_data`);
lines.push("");
lines.push("\tSECTION ROM_DATA");
lines.push("\tALIGN ROMMapper_BANK_SIZE");
lines.push(`${this.name}_data:`);
- for (const tile of this.tiles) {
+ for (const tile of this.tiles.toArray()) {
lines.push(`\tdw ${this.tileSet.name}_${tile.getName()}`);
}
lines.push("\tENDS");
@@ 44,18 38,11 @@ class TileMap {
}
class TileSet {
- constructor(name, width, height, palette) {
- checkTypes(arguments, "string", "number", "number", Palette);
+ constructor(name, tiles, palette) {
+ checkTypes(arguments, "string", Array, Palette);
this.name = name;
- this.width = width;
- this.height = height;
+ this.tiles = tiles;
this.palette = palette;
- this.tiles = [];
- }
-
- addTile(tile) {
- checkTypes(arguments, Tile);
- this.tiles.push(tile);
}
getTileByIndex(index) {
@@ 137,25 124,19 @@ class Tmx {
async parseMap(mapXml, gamma) {
checkTypes(arguments, Object, "number");
const tileSet = await this.parseSet(mapXml.tileset[0], gamma);
- const tileMap = new TileMap(this.name, ~~mapXml.$.width, ~~mapXml.$.height, tileSet);
const indices = mapXml.layer[0].data[0]._.replace(/\s/g, "").split(",").map(i => ~~i - 1);
- for (const index of indices) {
- tileMap.addTileByIndex(index);
- }
- return tileMap;
+ const indexMap = Array2D.ofArray(indices, ~~mapXml.$.width, ~~mapXml.$.height);
+ return new TileMap(this.name, indexMap.map(index => tileSet.getTileByIndex(index)).apply(), tileSet);
}
async parseSet(setXml, gamma) {
checkTypes(arguments, Object, "number");
const imagepath = path.resolve(path.dirname(this.path), setXml.image[0].$.source);
const image = await new PNGLoader(imagepath, gamma).loadImage();
- const tileSet = new TileSet(setXml.$.name, ~~setXml.$.tilewidth, ~~setXml.$.tileheight, image.palette);
- const tileImages = image.toTiles(tileSet.width, tileSet.height);
- for (let i = 0; i < tileImages.length; i++) {
- const tileXml = (setXml.tile || []).find(t => t.$.id == i) || { $: { id: i } };
- tileSet.addTile(this.parseTile(tileXml, tileImages[i]));
- }
- return tileSet;
+ const tileImages = image.tile(~~setXml.$.tilewidth, ~~setXml.$.tileheight);
+ const tiles = tileImages.toArray().map((image, index) =>
+ this.parseTile((setXml.tile || []).find(t => t.$.id == index) || { $: { id: index } }, image));
+ return new TileSet(setXml.$.name, tiles, image.palette);
}
parseTile(tileXml, image) {