1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-05-28 23:41:32 +00:00
8bitworkshop/gen/ide/pixeleditor.js

1008 lines
38 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwoWayMapper = exports.Viewer = exports.MapEditor = exports.CharmapEditor = exports.ImageChooser = exports.NESNametableConverter = exports.MetaspriteCompositor = exports.Compositor = exports.PaletteFormatToRGB = exports.Palettizer = exports.Mapper = exports.Compressor = exports.TextDataNode = exports.FileDataNode = exports.PixNode = exports.convertPaletteFormat = exports.getPaletteLength = exports.convertPaletteBytes = exports.convertImagesToWords = exports.convertWordsToImages = exports.replaceHexWords = exports.parseHexWords = void 0;
const util_1 = require("../common/util");
const toolbar_1 = require("./toolbar");
/////////////////
// 0xabcd, #$abcd, 5'010101, 0b010101, etc
var pixel_re = /([0#]?)([x$%]|\d'h)([0-9a-f]+)(?:[;].*)?|(\d'b|0b)([01]+)/gim;
function convertToHexStatements(s) {
// convert 'hex ....' asm format
return s.replace(/(\shex\s+)([0-9a-f]+)/ig, function (m, hexprefix, hexstr) {
var rtn = hexprefix;
for (var i = 0; i < hexstr.length; i += 2) {
rtn += '0x' + hexstr.substr(i, 2) + ',';
}
return rtn;
});
}
function parseHexWords(s) {
var arr = [];
var m;
while (m = pixel_re.exec(s)) {
var n;
if (typeof m[4] !== 'undefined')
n = parseInt(m[5], 2);
else if (m[2].startsWith('%') || m[2].endsWith("b"))
n = parseInt(m[3], 2);
else if (m[2].startsWith('x') || m[2].startsWith('$') || m[2].endsWith('h'))
n = parseInt(m[3], 16);
else
n = parseInt(m[3]);
arr.push(n);
}
return arr;
}
exports.parseHexWords = parseHexWords;
function replaceHexWords(s, words) {
var result = "";
var m;
var li = 0;
var i = 0;
while (m = pixel_re.exec(s)) {
result += s.slice(li, pixel_re.lastIndex - m[0].length);
li = pixel_re.lastIndex;
if (typeof m[4] !== 'undefined')
result += m[4] + words[i++].toString(2);
else if (m[2].startsWith('%'))
result += m[1] + "%" + words[i++].toString(2);
else if (m[2].endsWith('b'))
result += m[1] + m[2] + words[i++].toString(2); // TODO
else if (m[2].endsWith('h'))
result += m[1] + m[2] + words[i++].toString(16); // TODO
else if (m[2].startsWith('x'))
result += m[1] + "x" + (0, util_1.hex)(words[i++]);
else if (m[2].startsWith('$'))
result += m[1] + "$" + (0, util_1.hex)(words[i++]);
else
result += m[1] + words[i++].toString();
}
result += s.slice(li);
// convert 'hex ....' asm format
result = result.replace(/(\shex\s+)([,x0-9a-f]+)/ig, (m, hexprefix, hexstr) => {
var rtn = hexprefix + hexstr;
rtn = rtn.replace(/0x/ig, '').replace(/,/ig, '');
return rtn;
});
return result;
}
exports.replaceHexWords = replaceHexWords;
function remapBits(x, arr) {
if (!arr)
return x;
var y = 0;
for (var i = 0; i < arr.length; i++) {
var s = arr[i];
if (s < 0) {
s = -s - 1;
y ^= 1 << s;
}
if (x & (1 << i)) {
y ^= 1 << s;
}
}
return y;
}
// for VCS playfields
// ;;{w:20,h:10,flip:1,reindex:[4,5,6,7,15,14,13,12,11,10,9,8,16,17,18,19]};;
function reindexMask(x, inds) {
var i = inds[x % inds.length];
return [i >> 3, i & 7];
}
function convertWordsToImages(words, fmt) {
var width = fmt.w;
var height = fmt.h;
var count = fmt.count || 1;
var bpp = fmt.bpp || 1;
var nplanes = fmt.np || 1;
var bitsperword = fmt.bpw || 8;
var wordsperline = fmt.sl || Math.ceil(width * bpp / bitsperword);
var mask = (1 << bpp) - 1;
var pofs = fmt.pofs || wordsperline * height * count;
var skip = fmt.skip || 0;
var wpimg = fmt.wpimg || wordsperline * height;
var images = [];
for (var n = 0; n < count; n++) {
var imgdata = [];
for (var y = 0; y < height; y++) {
var yp = fmt.flip ? height - 1 - y : y;
var ofs0 = wpimg * n + yp * wordsperline;
var shift = 0;
for (var x = 0; x < width; x++) {
var color = 0;
var ofs = remapBits(ofs0, fmt.remap);
if (fmt.reindex) {
[ofs, shift] = reindexMask(x, fmt.reindex);
ofs += ofs0;
}
for (var p = 0; p < nplanes; p++) {
var byte = words[ofs + p * pofs + skip];
color |= ((fmt.brev ? byte >> (bitsperword - shift - bpp) : byte >> shift) & mask) << (p * bpp);
}
imgdata.push(color);
shift += bpp;
if (shift >= bitsperword && !fmt.reindex) {
ofs0 += 1;
shift = 0;
}
}
}
images.push(new Uint8Array(imgdata));
}
return images;
}
exports.convertWordsToImages = convertWordsToImages;
function convertImagesToWords(images, fmt) {
if (fmt.destfmt)
fmt = fmt.destfmt;
var width = fmt.w;
var height = fmt.h;
var count = fmt.count || 1;
var bpp = fmt.bpp || 1;
var nplanes = fmt.np || 1;
var bitsperword = fmt.bpw || 8;
var wordsperline = fmt.sl || Math.ceil(fmt.w * bpp / bitsperword);
var mask = (1 << bpp) - 1;
var pofs = fmt.pofs || wordsperline * height * count;
var skip = fmt.skip || 0;
var wpimg = fmt.wpimg || wordsperline * height;
var words;
if (nplanes > 0 && fmt.sl) // TODO?
words = new Uint8Array(wpimg * count);
else if (bitsperword <= 8)
words = new Uint8Array(wpimg * count * nplanes);
else
words = new Uint32Array(wpimg * count * nplanes);
for (var n = 0; n < count; n++) {
var imgdata = images[n];
var i = 0;
for (var y = 0; y < height; y++) {
var yp = fmt.flip ? height - 1 - y : y;
var ofs0 = n * wpimg + yp * wordsperline;
var shift = 0;
for (var x = 0; x < width; x++) {
var color = imgdata[i++];
var ofs = remapBits(ofs0, fmt.remap);
if (fmt.reindex) {
[ofs, shift] = reindexMask(x, fmt.reindex);
ofs += ofs0;
}
for (var p = 0; p < nplanes; p++) {
var c = (color >> (p * bpp)) & mask;
words[ofs + p * pofs + skip] |= (fmt.brev ? (c << (bitsperword - shift - bpp)) : (c << shift));
}
shift += bpp;
if (shift >= bitsperword && !fmt.reindex) {
ofs0 += 1;
shift = 0;
}
}
}
}
return words;
}
exports.convertImagesToWords = convertImagesToWords;
// TODO
function convertPaletteBytes(arr, r0, r1, g0, g1, b0, b1) {
var result = [];
for (var i = 0; i < arr.length; i++) {
var d = arr[i];
var rgb = 0xff000000;
rgb |= ((d >> r0) & ((1 << r1) - 1)) << (0 + 8 - r1);
rgb |= ((d >> g0) & ((1 << g1) - 1)) << (8 + 8 - g1);
rgb |= ((d >> b0) & ((1 << b1) - 1)) << (16 + 8 - b1);
result.push(rgb);
}
return result;
}
exports.convertPaletteBytes = convertPaletteBytes;
function getPaletteLength(palfmt) {
var pal = palfmt.pal;
if (typeof pal === 'number') {
var rr = Math.floor(Math.abs(pal / 100) % 10);
var gg = Math.floor(Math.abs(pal / 10) % 10);
var bb = Math.floor(Math.abs(pal) % 10);
return 1 << (rr + gg + bb);
}
else {
var paltable = PREDEF_PALETTES[pal];
if (paltable) {
return paltable.length;
}
else {
throw new Error("No palette named " + pal);
}
}
}
exports.getPaletteLength = getPaletteLength;
function convertPaletteFormat(palbytes, palfmt) {
var pal = palfmt.pal;
var newpalette;
if (typeof pal === 'number') {
var rr = Math.floor(Math.abs(pal / 100) % 10);
var gg = Math.floor(Math.abs(pal / 10) % 10);
var bb = Math.floor(Math.abs(pal) % 10);
// TODO: n
if (pal >= 0)
newpalette = convertPaletteBytes(palbytes, 0, rr, rr, gg, rr + gg, bb);
else
newpalette = convertPaletteBytes(palbytes, rr + gg, bb, rr, gg, 0, rr);
}
else {
var paltable = PREDEF_PALETTES[pal];
if (paltable) {
newpalette = new Uint32Array(palbytes).map((i) => { return paltable[i & (paltable.length - 1)] | 0xff000000; });
}
else {
throw new Error("No palette named " + pal);
}
}
return newpalette;
}
exports.convertPaletteFormat = convertPaletteFormat;
// TODO: illegal colors?
const PREDEF_PALETTES = {
'nes': [
0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000,
0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000,
0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000,
0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000
],
'ap2lores': [
(0x000000), (0xff00ff), (0x00007f), (0x7f007f), (0x007f00), (0x7f7f7f), (0x0000bf), (0x0000ff),
(0xbf7f00), (0xffbf00), (0xbfbfbf), (0xff7f7f), (0x00ff00), (0xffff00), (0x00bf7f), (0xffffff),
],
'vcs': [
0x000000, 0x000000, 0x404040, 0x404040, 0x6c6c6c, 0x6c6c6c, 0x909090, 0x909090, 0xb0b0b0, 0xb0b0b0, 0xc8c8c8, 0xc8c8c8, 0xdcdcdc, 0xdcdcdc, 0xf4f4f4, 0xf4f4f4,
0x004444, 0x004444, 0x106464, 0x106464, 0x248484, 0x248484, 0x34a0a0, 0x34a0a0, 0x40b8b8, 0x40b8b8, 0x50d0d0, 0x50d0d0, 0x5ce8e8, 0x5ce8e8, 0x68fcfc, 0x68fcfc,
0x002870, 0x002870, 0x144484, 0x144484, 0x285c98, 0x285c98, 0x3c78ac, 0x3c78ac, 0x4c8cbc, 0x4c8cbc, 0x5ca0cc, 0x5ca0cc, 0x68b4dc, 0x68b4dc, 0x78c8ec, 0x78c8ec,
0x001884, 0x001884, 0x183498, 0x183498, 0x3050ac, 0x3050ac, 0x4868c0, 0x4868c0, 0x5c80d0, 0x5c80d0, 0x7094e0, 0x7094e0, 0x80a8ec, 0x80a8ec, 0x94bcfc, 0x94bcfc,
0x000088, 0x000088, 0x20209c, 0x20209c, 0x3c3cb0, 0x3c3cb0, 0x5858c0, 0x5858c0, 0x7070d0, 0x7070d0, 0x8888e0, 0x8888e0, 0xa0a0ec, 0xa0a0ec, 0xb4b4fc, 0xb4b4fc,
0x5c0078, 0x5c0078, 0x74208c, 0x74208c, 0x883ca0, 0x883ca0, 0x9c58b0, 0x9c58b0, 0xb070c0, 0xb070c0, 0xc084d0, 0xc084d0, 0xd09cdc, 0xd09cdc, 0xe0b0ec, 0xe0b0ec,
0x780048, 0x780048, 0x902060, 0x902060, 0xa43c78, 0xa43c78, 0xb8588c, 0xb8588c, 0xcc70a0, 0xcc70a0, 0xdc84b4, 0xdc84b4, 0xec9cc4, 0xec9cc4, 0xfcb0d4, 0xfcb0d4,
0x840014, 0x840014, 0x982030, 0x982030, 0xac3c4c, 0xac3c4c, 0xc05868, 0xc05868, 0xd0707c, 0xd0707c, 0xe08894, 0xe08894, 0xeca0a8, 0xeca0a8, 0xfcb4bc, 0xfcb4bc,
0x880000, 0x880000, 0x9c201c, 0x9c201c, 0xb04038, 0xb04038, 0xc05c50, 0xc05c50, 0xd07468, 0xd07468, 0xe08c7c, 0xe08c7c, 0xeca490, 0xeca490, 0xfcb8a4, 0xfcb8a4,
0x7c1800, 0x7c1800, 0x90381c, 0x90381c, 0xa85438, 0xa85438, 0xbc7050, 0xbc7050, 0xcc8868, 0xcc8868, 0xdc9c7c, 0xdc9c7c, 0xecb490, 0xecb490, 0xfcc8a4, 0xfcc8a4,
0x5c2c00, 0x5c2c00, 0x784c1c, 0x784c1c, 0x906838, 0x906838, 0xac8450, 0xac8450, 0xc09c68, 0xc09c68, 0xd4b47c, 0xd4b47c, 0xe8cc90, 0xe8cc90, 0xfce0a4, 0xfce0a4,
0x2c3c00, 0x2c3c00, 0x485c1c, 0x485c1c, 0x647c38, 0x647c38, 0x809c50, 0x809c50, 0x94b468, 0x94b468, 0xacd07c, 0xacd07c, 0xc0e490, 0xc0e490, 0xd4fca4, 0xd4fca4,
0x003c00, 0x003c00, 0x205c20, 0x205c20, 0x407c40, 0x407c40, 0x5c9c5c, 0x5c9c5c, 0x74b474, 0x74b474, 0x8cd08c, 0x8cd08c, 0xa4e4a4, 0xa4e4a4, 0xb8fcb8, 0xb8fcb8,
0x003814, 0x003814, 0x1c5c34, 0x1c5c34, 0x387c50, 0x387c50, 0x50986c, 0x50986c, 0x68b484, 0x68b484, 0x7ccc9c, 0x7ccc9c, 0x90e4b4, 0x90e4b4, 0xa4fcc8, 0xa4fcc8,
0x00302c, 0x00302c, 0x1c504c, 0x1c504c, 0x347068, 0x347068, 0x4c8c84, 0x4c8c84, 0x64a89c, 0x64a89c, 0x78c0b4, 0x78c0b4, 0x88d4cc, 0x88d4cc, 0x9cece0, 0x9cece0,
0x002844, 0x002844, 0x184864, 0x184864, 0x306884, 0x306884, 0x4484a0, 0x4484a0, 0x589cb8, 0x589cb8, 0x6cb4d0, 0x6cb4d0, 0x7ccce8, 0x7ccce8, 0x8ce0fc, 0x8ce0fc
],
'astrocade': [0, 2368548, 4737096, 7171437, 9539985, 11974326, 14342874, 16777215, 12255269, 14680137, 16716142, 16725394, 16734903, 16744155, 16753663, 16762879, 11534409, 13959277, 16318866, 16721334, 16730842, 16740095, 16749311, 16758783, 10420330, 12779662, 15138995, 16718039, 16727291, 16736767, 16745983, 16755199, 8847495, 11206827, 13631696, 15994612, 16724735, 16733951, 16743423, 16752639, 6946975, 9306307, 11731175, 14092287, 16461055, 16732415, 16741631, 16751103, 4784304, 7143637, 9568505, 11929087, 14297599, 16731647, 16741119, 16750335, 2425019, 4784352, 7209215, 9570047, 12004095, 14372863, 16741375, 16750847, 191, 2359523, 4718847, 7146495, 9515263, 11949311, 14318079, 16752127, 187, 224, 2294015, 4658431, 7092735, 9461247, 11895551, 14264063, 176, 213, 249, 2367999, 4736511, 7105279, 9539327, 11908095, 159, 195, 3303, 209151, 2577919, 4946431, 7380735, 9749247, 135, 171, 7888, 17140, 681983, 3050495, 5484543, 7853311, 106, 3470, 12723, 22231, 31483, 1548031, 3916799, 6285311, 73, 8557, 17810, 27318, 36570, 373759, 2742271, 5176575, 4389, 13641, 23150, 32402, 41911, 51163, 2026495, 4456447, 9472, 18724, 27976, 37485, 46737, 56246, 1834970, 4194303, 14080, 23296, 32803, 42055, 51564, 60816, 2031541, 4456409, 18176, 27648, 36864, 46116, 55624, 392556, 2752401, 5177269, 21760, 30976, 40192, 49667, 58919, 1572683, 3932016, 6291348, 24320, 33536, 43008, 52224, 716810, 3079982, 5504851, 7864183, 25856, 35328, 44544, 250368, 2619136, 4980503, 7405371, 9764703, 26624, 35840, 45312, 2413824, 4782336, 7143173, 9568041, 11927374, 26112, 35584, 2338560, 4707328, 7141376, 9502464, 11927326, 14286659, 24832, 2393344, 4762112, 7196160, 9564928, 11992832, 14352155, 16711487, 2447360, 4815872, 7250176, 9618688, 12052992, 14417664, 16776990, 16777027, 4803328, 7172096, 9606144, 11974912, 14343424, 16776965, 16777001, 16777038, 6962176, 9330688, 11764992, 14133504, 16502272, 16773655, 16777019, 16777055, 8858112, 11226880, 13660928, 16029440, 16759818, 16769070, 16777043, 16777079, 10426112, 12794624, 15163392, 16745475, 16754727, 16764235, 16773488, 16777108, 11534848, 13969152, 16337664, 16740388, 16749640, 16759148, 16768401, 16777141, 12255232, 14684928, 16725795, 16735047, 16744556, 16753808, 16763317, 16772569],
'c64': [0x000000, 0xffffff, 0x2b3768, 0xb2a470, 0x863d6f, 0x438d58, 0x792835, 0x6fc7b8, 0x254f6f, 0x003943, 0x59679a, 0x444444, 0x6c6c6c, 0x84d29a, 0xb55e6c, 0x959595],
};
var PREDEF_LAYOUTS = {
'nes': [
['Screen Color', 0x00, 1],
['Background 0', 0x01, 3],
['Background 1', 0x05, 3],
['Background 2', 0x09, 3],
['Background 3', 0x0d, 3],
['Sprite 0', 0x11, 3],
['Sprite 1', 0x15, 3],
['Sprite 2', 0x19, 3],
['Sprite 3', 0x1d, 3]
],
'astrocade': [
['Left', 0x00, -4],
['Right', 0x04, -4]
],
};
/////
function equalArrays(a, b) {
if (a == null || b == null)
return false;
if (a.length !== b.length)
return false;
if (a === b)
return true;
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i])
return false;
}
return true;
}
function equalNestedArrays(a, b) {
if (a == null || b == null)
return false;
if (a.length !== b.length)
return false;
if (a === b)
return true;
for (var i = 0; i < a.length; i++) {
if (!equalArrays(a[i], b[i]))
return false;
}
return true;
}
class PixNode {
refreshLeft() {
var p = this;
while (p) {
p.updateLeft();
p = p.left;
}
}
refreshRight() {
var p = this;
while (p) {
p.updateRight();
p = p.right;
}
}
addRight(node) {
this.right = node;
node.left = this;
return node;
}
addLeft(node) {
this.left = node;
node.right = this;
return node;
}
}
exports.PixNode = PixNode;
class CodeProjectDataNode extends PixNode {
}
class FileDataNode extends CodeProjectDataNode {
constructor(project, fileid) {
super();
this.project = project;
this.fileid = fileid;
this.label = fileid;
}
updateLeft() {
//if (equalArrays(this.words, this.right.words)) return false;
this.words = this.right.words;
if (this.project) {
this.project.updateFile(this.fileid, this.words);
}
return true;
}
updateRight() {
if (this.project) {
this.words = this.project.project.getFile(this.fileid);
}
return true;
}
}
exports.FileDataNode = FileDataNode;
class TextDataNode extends CodeProjectDataNode {
// TODO: what if file size/layout changes?
constructor(project, fileid, label, start, end) {
super();
this.project = project;
this.fileid = fileid;
this.label = label;
this.start = start;
this.end = end;
}
updateLeft() {
if (this.right.words.length != this.words.length)
throw Error("Cannot put " + this.right.words.length + " image bytes into array of " + this.words.length + " bytes");
this.words = this.right.words;
// TODO: reload editors?
var datastr = this.text.substring(this.start, this.end);
datastr = replaceHexWords(datastr, this.words);
this.text = this.text.substring(0, this.start) + datastr + this.text.substring(this.end);
if (this.project) {
this.project.updateFile(this.fileid, this.text);
//this.project.replaceTextRange(this.fileid, this.start, this.end, datastr);
}
return true;
}
updateRight() {
if (this.project) {
this.text = this.project.project.getFile(this.fileid);
}
var datastr = this.text.substring(this.start, this.end);
datastr = convertToHexStatements(datastr); // TODO?
var words = parseHexWords(datastr);
this.words = words; //new Uint8Array(words); // TODO: 16/32?
return true;
}
}
exports.TextDataNode = TextDataNode;
class Compressor extends PixNode {
updateLeft() {
// TODO: can't modify length of rle bytes
return false;
}
updateRight() {
this.words = (0, util_1.rle_unpack)(new Uint8Array(this.left.words));
return true;
}
}
exports.Compressor = Compressor;
class Mapper extends PixNode {
constructor(fmt) {
super();
this.fmt = fmt;
}
updateLeft() {
//if (equalNestedArrays(this.images, this.right.images)) return false;
this.images = this.right.images;
this.words = convertImagesToWords(this.images, this.fmt);
return true;
}
updateRight() {
if (equalArrays(this.words, this.left.words))
return false;
// convert each word array to images
this.words = this.left.words;
this.images = convertWordsToImages(this.words, this.fmt);
return true;
}
}
exports.Mapper = Mapper;
class RGBAPalette {
constructor(palcols) {
this.palcols = palcols;
}
indexOf(rgba) {
return this.palcols.indexOf(rgba);
}
}
class Palettizer extends PixNode {
// TODO: control to select palette for bitmaps
constructor(context, fmt) {
super();
this.palindex = 0;
this.context = context;
this.ncolors = 1 << ((fmt.bpp || 1) * (fmt.np || 1));
}
updateLeft() {
if (this.right) {
this.rgbimgs = this.right.rgbimgs;
} // TODO: check is for unit test, remove?
var pal = new RGBAPalette(this.palette);
var newimages = this.rgbimgs.map((im) => {
var out = new Uint8Array(im.length);
for (var i = 0; i < im.length; i++) {
out[i] = pal.indexOf(im[i]);
}
return out;
});
// have to do it this way b/c pixel editor modifies arrays
//if (equalNestedArrays(newimages, this.images)) return false;
this.images = newimages;
return true;
}
updateRight() {
if (!this.updateRefs() && equalNestedArrays(this.images, this.left.images))
return false;
this.images = this.left.images;
var mask = this.palette.length - 1; // must be power of 2
// for each image, map bytes to RGB colors
this.rgbimgs = this.images.map((im) => {
var out = new Uint32Array(im.length);
for (var i = 0; i < im.length; i++) {
out[i] = this.palette[im[i] & mask];
}
return out;
});
return true;
}
updateRefs() {
var newpalette;
if (this.context != null) {
this.paloptions = this.context.getPalettes(this.ncolors);
if (this.paloptions && this.paloptions.length > 0) {
newpalette = this.paloptions[this.palindex].palette;
}
}
if (newpalette == null) {
if (this.ncolors <= 2)
newpalette = new Uint32Array([0xff000000, 0xffffffff]);
else
newpalette = new Uint32Array([0xff000000, 0xffff00ff, 0xffffff00, 0xffffffff]); // TODO: more palettes
}
if (equalArrays(this.palette, newpalette))
return false;
this.palette = newpalette;
return true;
}
}
exports.Palettizer = Palettizer;
function dedupPalette(cols) {
var dup = new Map();
var res = new Uint32Array(cols.length);
var ndups = 0;
for (var i = 0; i < cols.length; i++) {
var n = cols[i];
while (dup[n]) {
n ^= ++ndups;
}
res[i] = n;
dup[n] = 1;
}
return res;
}
class PaletteFormatToRGB extends PixNode {
constructor(palfmt) {
super();
this.palfmt = palfmt;
}
updateLeft() {
//TODO
return true;
}
updateRight() {
if (equalArrays(this.words, this.left.words))
return false;
this.words = this.left.words;
this.palette = dedupPalette(convertPaletteFormat(this.words, this.palfmt));
this.layout = PREDEF_LAYOUTS[this.palfmt.layout];
this.rgbimgs = [];
this.palette.forEach((rgba) => {
this.rgbimgs.push(new Uint32Array([rgba]));
});
return true;
}
getAllColors() {
var arr = [];
for (var i = 0; i < getPaletteLength(this.palfmt); i++)
arr.push(i);
return convertPaletteFormat(arr, this.palfmt);
}
}
exports.PaletteFormatToRGB = PaletteFormatToRGB;
class Compositor extends PixNode {
constructor(context) {
super();
this.tileindex = 0;
this.context = context;
}
updateRefs() {
var oldtilemap = this.tilemap;
if (this.context != null) {
this.tileoptions = this.context.getTilemaps(256);
if (this.tileoptions && this.tileoptions.length > 0) {
this.tilemap = this.tileoptions[this.tileindex].images;
}
}
return !equalNestedArrays(oldtilemap, this.tilemap);
}
}
exports.Compositor = Compositor;
class MetaspriteCompositor extends Compositor {
constructor(context, metadefs) {
super(context);
this.metadefs = metadefs;
}
updateLeft() {
// TODO
return false;
}
updateRight() {
this.updateRefs();
this.width = 16; // TODO
this.height = 16; // TODO
var idata = new Uint8Array(this.width * this.height);
this.images = [idata];
this.metadefs.forEach((meta) => {
// TODO
});
return true;
}
}
exports.MetaspriteCompositor = MetaspriteCompositor;
class NESNametableConverter extends Compositor {
constructor(context) {
super(context);
}
updateLeft() {
// TODO
return false;
}
updateRight() {
if (!this.updateRefs() && equalArrays(this.words, this.left.words))
return false;
this.words = this.left.words;
this.cols = 32;
this.rows = 30;
this.width = this.cols * 8;
this.height = this.rows * 8;
this.baseofs = 0;
var idata = new Uint8Array(this.width * this.height);
this.images = [idata];
var a = 0;
var attraddr;
for (var row = 0; row < this.rows; row++) {
for (var col = 0; col < this.cols; col++) {
var name = this.words[this.baseofs + a];
if (typeof name === 'undefined')
throw Error("No name for address " + this.baseofs + " + " + a);
var t = this.tilemap[name];
if (!t)
throw Error("No tilemap found for tile index " + name);
attraddr = (a & 0x2c00) | 0x3c0 | (a & 0x0C00) | ((a >> 4) & 0x38) | ((a >> 2) & 0x07);
var attr = this.words[attraddr];
var tag = name ^ (attr << 9) ^ 0x80000000;
var i = row * this.cols * 8 * 8 + col * 8;
var j = 0;
var attrshift = (col & 2) + ((a & 0x40) >> 4);
var coloradd = ((attr >> attrshift) & 3) << 2;
for (var y = 0; y < 8; y++) {
for (var x = 0; x < 8; x++) {
var color = t[j++];
if (color)
color += coloradd;
idata[i++] = color;
}
i += this.cols * 8 - 8;
}
a++;
}
}
// TODO
return true;
}
}
exports.NESNametableConverter = NESNametableConverter;
///// UI CONTROLS
class ImageChooser {
recreate(parentdiv, onclick) {
var agrid = $('<div class="asset_grid"/>'); // grid (or 1) of preview images
parentdiv.empty().append(agrid);
var cscale = Math.max(2, Math.ceil(16 / this.width)); // TODO
var imgsperline = this.width <= 8 ? 16 : 8; // TODO
var span = null;
this.rgbimgs.forEach((imdata, i) => {
var viewer = new Viewer();
viewer.width = this.width;
viewer.height = this.height;
viewer.recreate();
viewer.canvas.style.width = (viewer.width * cscale) + 'px'; // TODO
viewer.canvas.title = '$' + (0, util_1.hex)(i);
viewer.updateImage(imdata);
$(viewer.canvas).addClass('asset_cell');
$(viewer.canvas).click((e) => {
onclick(i, viewer);
});
if (!span) {
span = $('<span/>');
agrid.append(span);
}
span.append(viewer.canvas);
var brk = (i % imgsperline) == imgsperline - 1;
if (brk) {
agrid.append($("<br/>"));
span = null;
}
});
}
}
exports.ImageChooser = ImageChooser;
function newDiv(parent, cls) {
var div = $(document.createElement("div"));
if (parent)
div.appendTo(parent);
if (cls)
div.addClass(cls);
return div;
}
class CharmapEditor extends PixNode {
constructor(context, parentdiv, fmt) {
super();
this.context = context;
this.parentdiv = parentdiv;
this.fmt = fmt;
}
updateLeft() {
return true;
}
updateRight() {
if (equalNestedArrays(this.rgbimgs, this.left.rgbimgs))
return false;
this.rgbimgs = this.left.rgbimgs;
var adual = newDiv(this.parentdiv.empty(), "asset_dual"); // contains grid and editor
var agrid = newDiv(adual);
var aeditor = newDiv(adual, "asset_editor").hide(); // contains editor, when selected
// add image chooser grid
var chooser = this.chooser = new ImageChooser();
chooser.rgbimgs = this.rgbimgs;
chooser.width = this.fmt.w || 1;
chooser.height = this.fmt.h || 1;
chooser.recreate(agrid, (index, viewer) => {
var yscale = Math.ceil(256 / this.fmt.w); // TODO: variable scale?
var xscale = yscale * (this.fmt.aspect || 1.0);
var editview = this.createEditor(aeditor, viewer, xscale, yscale);
this.context.setCurrentEditor(aeditor, $(viewer.canvas), this);
this.rgbimgs[index] = viewer.rgbdata;
});
// add palette selector
// TODO: only view when editing?
var palizer = this.left;
if (palizer instanceof Palettizer && palizer.paloptions.length > 1) {
var palselect = $(document.createElement('select'));
palizer.paloptions.forEach((palopt, i) => {
// TODO: full identifier
var sel = $(document.createElement('option')).text(palopt.name).val(i).appendTo(palselect);
if (i == palizer.palindex)
sel.attr('selected', 'selected');
});
palselect.appendTo(agrid).change((e) => {
var index = $(e.target).val();
palizer.palindex = index;
palizer.refreshRight();
});
}
return true;
}
createEditor(aeditor, viewer, xscale, yscale) {
var im = new PixEditor();
im.createWith(viewer);
im.updateImage();
var w = viewer.width * xscale;
var h = viewer.height * yscale;
while (w > 500 || h > 500) {
w /= 2;
h /= 2;
}
im.canvas.style.width = w + 'px'; // TODO
im.canvas.style.height = h + 'px'; // TODO
im.makeEditable(this, aeditor, this.left.palette);
return im;
}
}
exports.CharmapEditor = CharmapEditor;
class MapEditor extends PixNode {
constructor(context, parentdiv, fmt) {
super();
this.context = context;
this.parentdiv = parentdiv;
this.fmt = fmt;
}
updateLeft() {
return true;
}
updateRight() {
if (equalNestedArrays(this.rgbimgs, this.left.rgbimgs))
return false;
this.rgbimgs = this.left.rgbimgs;
var adual = newDiv(this.parentdiv.empty(), "asset_dual"); // contains grid and editor
var agrid = newDiv(adual);
var aeditor = newDiv(adual, "asset_editor").hide(); // contains editor, when selected
// add image chooser grid
var viewer = new Viewer();
viewer.width = this.fmt.w;
viewer.height = this.fmt.h;
viewer.recreate();
viewer.updateImage(this.rgbimgs[0]);
agrid.append(viewer.canvas);
return true;
}
}
exports.MapEditor = MapEditor;
class Viewer {
recreate() {
this.canvas = this.newCanvas();
this.imagedata = this.ctx.createImageData(this.width, this.height);
this.rgbdata = new Uint32Array(this.imagedata.data.buffer);
this.peerviewers = [this];
}
createWith(pv) {
this.width = pv.width;
this.height = pv.height;
this.imagedata = pv.imagedata;
this.rgbdata = pv.rgbdata;
this.canvas = this.newCanvas();
this.peerviewers = [this, pv];
}
newCanvas() {
var c = document.createElement('canvas');
c.width = this.width;
c.height = this.height;
//if (fmt.xform) c.style.transform = fmt.xform;
c.classList.add("pixels");
c.classList.add("pixelated");
this.ctx = c.getContext('2d');
return c;
}
updateImage(imdata) {
if (imdata) {
this.rgbdata.set(imdata);
}
for (let v of this.peerviewers) {
v.ctx.putImageData(this.imagedata, 0, 0);
}
}
}
exports.Viewer = Viewer;
class PixEditor extends Viewer {
constructor() {
super(...arguments);
this.curpalcol = -1;
this.offscreen = new Map();
}
getPositionFromEvent(e) {
var x = Math.floor(e.offsetX * this.width / $(this.canvas).width());
var y = Math.floor(e.offsetY * this.height / $(this.canvas).height());
return { x: x, y: y };
}
setPaletteColor(col) {
col &= this.palette.length - 1;
if (this.curpalcol != col) {
if (this.curpalcol >= 0)
this.palbtns[this.curpalcol].removeClass('selected');
this.curpalcol = col;
this.currgba = this.palette[col & this.palette.length - 1];
this.palbtns[col].addClass('selected');
}
}
makeEditable(leftnode, aeditor, palette) {
this.left = leftnode;
this.palette = palette;
var dragcol;
var dragging = false;
var pxls = $(this.canvas);
pxls.mousedown((e) => {
var pos = this.getPositionFromEvent(e);
dragcol = this.getPixel(pos.x, pos.y) == this.currgba ? this.palette[0] : this.currgba;
this.setPixel(pos.x, pos.y, this.currgba);
dragging = true;
$(document).mouseup((e) => {
$(document).off('mouseup');
var pos = this.getPositionFromEvent(e);
this.setPixel(pos.x, pos.y, dragcol);
dragging = false;
this.commit();
});
})
.mousemove((e) => {
var pos = this.getPositionFromEvent(e);
if (dragging) {
this.setPixel(pos.x, pos.y, dragcol);
}
});
aeditor.empty();
this.createToolbarButtons(aeditor[0]);
aeditor.append(this.canvas);
aeditor.append(this.createPaletteButtons());
this.setPaletteColor(1);
}
getPixel(x, y) {
x = Math.round(x);
y = Math.round(y);
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
return this.offscreen[x + ',' + y] | this.palette[0];
}
else {
var ofs = x + y * this.width;
return this.rgbdata[ofs];
}
}
setPixel(x, y, rgba) {
x = Math.round(x);
y = Math.round(y);
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
this.offscreen[x + ',' + y] = rgba;
}
else {
var ofs = x + y * this.width;
var oldrgba = this.rgbdata[ofs];
if (oldrgba != rgba) {
this.rgbdata[ofs] = rgba;
this.updateImage();
}
}
}
createPaletteButtons() {
this.palbtns = [];
var span = newDiv(null, "asset_toolbar");
for (var i = 0; i < this.palette.length; i++) {
var btn = $(document.createElement('button')).addClass('palbtn');
var rgb = this.palette[i] & 0xffffff;
var color = "#" + (0, util_1.hex)((0, util_1.rgb2bgr)(rgb), 6);
btn.click(this.setPaletteColor.bind(this, i));
btn.css('backgroundColor', color).text(i.toString(16));
btn.css('color', (rgb & 0x008000) ? 'black' : 'white');
span.append(btn);
this.palbtns.push(btn);
}
return span;
}
createToolbarButtons(parent) {
var toolbar = new toolbar_1.Toolbar(parent, null);
toolbar.add('ctrl+shift+h', 'Flip X', 'glyphicon-resize-horizontal', this.flipX.bind(this));
toolbar.add('ctrl+shift+v', 'Flip Y', 'glyphicon-resize-vertical', this.flipY.bind(this));
toolbar.add('ctrl+shift+9', 'Rotate', 'glyphicon-repeat', this.rotate90.bind(this));
toolbar.add('ctrl+shift+left', 'Move Left', 'glyphicon-arrow-left', this.translate.bind(this, 1, 0));
toolbar.add('ctrl+shift+right', 'Move Right', 'glyphicon-arrow-right', this.translate.bind(this, -1, 0));
toolbar.add('ctrl+shift+up', 'Move Up', 'glyphicon-arrow-up', this.translate.bind(this, 0, 1));
toolbar.add('ctrl+shift+down', 'Move Down', 'glyphicon-arrow-down', this.translate.bind(this, 0, -1));
// TODO: destroy toolbar?
}
commit() {
this.updateImage();
try {
this.left.refreshLeft();
}
catch (e) {
console.log(e);
alert(`Could not update source code. ${e}`);
}
}
remapPixels(mapfn) {
var i = 0;
var pixels = new Uint32Array(this.rgbdata.length);
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
pixels[i] = mapfn(x, y);
i++;
}
}
this.rgbdata.set(pixels);
this.commit();
}
rotate(deg) {
var s1 = Math.sin(deg * Math.PI / 180);
var c1 = Math.cos(deg * Math.PI / 180);
this.remapPixels((x, y) => {
var xx = x + 0.5 - this.width / 2.0;
var yy = y + 0.5 - this.height / 2.0;
var xx2 = xx * c1 - yy * s1 + this.width / 2.0 - 0.5;
var yy2 = yy * c1 + xx * s1 + this.height / 2.0 - 0.5;
return this.getPixel(xx2, yy2);
});
}
rotate90() {
this.rotate(90);
}
flipX() {
this.remapPixels((x, y) => {
return this.getPixel(this.width - 1 - x, y);
});
}
flipY() {
this.remapPixels((x, y) => {
return this.getPixel(x, this.height - 1 - y);
});
}
translate(dx, dy) {
this.remapPixels((x, y) => {
return this.getPixel(x + dx, y + dy);
});
}
}
// TODO: not yet used
class TwoWayPixelConverter {
constructor(width, height, bpp, colpp) {
this.w = width;
this.h = height;
this.words = new Uint8Array(width * height);
this.bitoffsets = new Uint32Array(width * height);
this.coloffsets = new Uint32Array(width * height);
}
setPixel(x, y, col, bitofs, colofs) {
var ofs = x + y * this.w;
this.words[ofs] = col;
this.bitoffsets[ofs] = bitofs;
this.coloffsets[ofs] = colofs;
}
}
class TwoWayMapper extends PixNode {
constructor(pc) {
super();
this.pc = pc;
}
updateLeft() {
//if (equalNestedArrays(this.images, this.right.images)) return false;
this.images = this.right.images;
this.words = this.pc.imageToWords(this.images[0]);
return true;
}
updateRight() {
if (equalArrays(this.words, this.left.words))
return false;
// convert each word array to images
this.words = this.left.words;
this.images = this.pc.wordsToImage(this.words);
return true;
}
}
exports.TwoWayMapper = TwoWayMapper;
//# sourceMappingURL=pixeleditor.js.map