More types and refactoring; require @mapbox/mbtiles

This commit is contained in:
Christian Paul 2024-11-03 15:36:18 +01:00
parent c55157878f
commit 9d8a2d76f1
12 changed files with 133 additions and 107 deletions

View File

@ -7,6 +7,8 @@
TODO: params parsing and so on TODO: params parsing and so on
#*/ #*/
import process from "node:process";
import config from './src/config.ts'; import config from './src/config.ts';
import Mapscii from './src/Mapscii.ts'; import Mapscii from './src/Mapscii.ts';
import yargs from 'yargs/yargs'; import yargs from 'yargs/yargs';

View File

@ -32,18 +32,18 @@ const termReset = '\x1B[39;49m';
class BrailleBuffer { class BrailleBuffer {
private brailleMap: number[][]; private brailleMap: number[][];
private pixelBuffer: Buffer; private pixelBuffer: Buffer;
private charBuffer: unknown[] | null; private charBuffer: string[];
private foregroundBuffer: Buffer; private foregroundBuffer: Buffer;
private backgroundBuffer: Buffer; private backgroundBuffer: Buffer;
private height: number; private height: number;
private width: number; private width: number;
private globalBackground: string | null; private globalBackground: number | null;
private asciiToBraille: unknown[]; private asciiToBraille: string[];
constructor(width: number, height: number) { constructor(width: number, height: number) {
this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]]; this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]];
this.charBuffer = null; this.charBuffer = [];
this.asciiToBraille = []; this.asciiToBraille = [];
@ -61,42 +61,42 @@ class BrailleBuffer {
this.clear(); this.clear();
} }
clear() { clear(): void {
this.pixelBuffer.fill(0); this.pixelBuffer.fill(0);
this.charBuffer = []; this.charBuffer = [];
this.foregroundBuffer.fill(0); this.foregroundBuffer.fill(0);
this.backgroundBuffer.fill(0); this.backgroundBuffer.fill(0);
} }
setGlobalBackground(background: string) { setGlobalBackground(background: number): void {
this.globalBackground = background; this.globalBackground = background;
} }
setBackground(x, y, color) { setBackground(x: number, y: number, color: number): void {
if (0 <= x && x < this.width && 0 <= y && y < this.height) { if (0 <= x && x < this.width && 0 <= y && y < this.height) {
const idx = this._project(x, y); const idx = this._project(x, y);
this.backgroundBuffer[idx] = color; this.backgroundBuffer[idx] = color;
} }
} }
setPixel(x, y, color) { setPixel(x: number, y: number, color: number): void {
this._locate(x, y, (idx, mask) => { this._locate(x, y, (idx, mask) => {
this.pixelBuffer[idx] |= mask; this.pixelBuffer[idx] |= mask;
this.foregroundBuffer[idx] = color; this.foregroundBuffer[idx] = color;
}); });
} }
unsetPixel(x, y) { unsetPixel(x: number, y: number): void {
this._locate(x, y, (idx, mask) => { this._locate(x, y, (idx, mask) => {
this.pixelBuffer[idx] &= ~mask; this.pixelBuffer[idx] &= ~mask;
}); });
} }
_project(x, y) { private _project(x: number, y: number): number {
return (x>>1) + (this.width>>1)*(y>>2); return (x>>1) + (this.width>>1)*(y>>2);
} }
_locate(x, y, cb) { private _locate(x: number, y: number, cb: (idx: number, mask: number) => unknown) {
if (!((0 <= x && x < this.width) && (0 <= y && y < this.height))) { if (!((0 <= x && x < this.width) && (0 <= y && y < this.height))) {
return; return;
} }
@ -105,57 +105,64 @@ class BrailleBuffer {
return cb(idx, mask); return cb(idx, mask);
} }
_mapBraille() { private _mapBraille(): string[] {
this.asciiToBraille = [' ']; this.asciiToBraille = [' '];
const masks = []; const masks: {
char: string,
covered: number,
mask: number,
}[] = [];
for (const char in asciiMap) { for (const char in asciiMap) {
const bits = asciiMap[char]; const bits: number[] | undefined = asciiMap[char];
if (!(bits instanceof Array)) continue; if (!(bits instanceof Array)) continue;
for (const mask of bits) { for (const mask of bits) {
masks.push({ masks.push({
mask: mask, char,
char: char, covered: 0,
mask,
}); });
} }
} }
//TODO Optimize this part //TODO Optimize this part
var i, k; let i: number, k: number;
const results = []; const results: string[] = [];
for (i = k = 1; k <= 255; i = ++k) { for (i = k = 1; k <= 255; i = ++k) {
const braille = (i & 7) + ((i & 56) << 1) + ((i & 64) >> 3) + (i & 128); const braille = (i & 7) + ((i & 56) << 1) + ((i & 64) >> 3) + (i & 128);
results.push(this.asciiToBraille[i] = masks.reduce((function(best, mask) { const char = masks.reduce((best, mask) => {
const covered = utils.population(mask.mask & braille); const covered = utils.population(mask.mask & braille);
if (!best || best.covered < covered) { if (!best || best.covered < covered) {
return { return {
char: mask.char, ...mask,
covered: covered, covered,
}; };
} else { } else {
return best; return best;
} }
}), void 0).char); }).char;
this.asciiToBraille[i] = char;
results.push(char);
} }
return results; return results;
} }
_termColor(foreground, background) { private _termColor(foreground: number, background: number): string {
background |= this.globalBackground; const actualBackground = background ?? this.globalBackground;
if (foreground && background) { if (foreground && actualBackground) {
return `\x1B[38;5;${foreground};48;5;${background}m`; return `\x1B[38;5;${foreground};48;5;${actualBackground}m`;
} else if (foreground) { } else if (foreground) {
return `\x1B[49;38;5;${foreground}m`; return `\x1B[49;38;5;${foreground}m`;
} else if (background) { } else if (actualBackground) {
return `\x1B[39;48;5;${background}m`; return `\x1B[39;48;5;${actualBackground}m`;
} else { } else {
return termReset; return termReset;
} }
} }
frame() { frame(): string {
const output = []; const output: string[] = [];
let currentColor = null; let currentColor: string | null = null;
let skip = 0; let skip = 0;
for (let y = 0; y < this.height/4; y++) { for (let y = 0; y < this.height/4; y++) {
@ -182,7 +189,7 @@ class BrailleBuffer {
} else { } else {
if (!skip) { if (!skip) {
if (config.useBraille) { if (config.useBraille) {
output.push(String.fromCharCode(0x2800+this.pixelBuffer[idx])); output.push(String.fromCharCode(0x2800 + this.pixelBuffer[idx]));
} else { } else {
output.push(this.asciiToBraille[this.pixelBuffer[idx]]); output.push(this.asciiToBraille[this.pixelBuffer[idx]]);
} }
@ -193,11 +200,11 @@ class BrailleBuffer {
} }
} }
output.push(termReset+config.delimeter); output.push(termReset + config.delimeter);
return output.join(''); return output.join('');
} }
setChar(char, x, y, color) { setChar(char: string, x: number, y: number, color: number): void {
if (0 <= x && x < this.width && 0 <= y && y < this.height) { if (0 <= x && x < this.width && 0 <= y && y < this.height) {
const idx = this._project(x, y); const idx = this._project(x, y);
this.charBuffer[idx] = char; this.charBuffer[idx] = char;
@ -205,7 +212,7 @@ class BrailleBuffer {
} }
} }
writeText(text, x, y, color, center = true) { writeText(text, x, y, color, center = true): void {
if (center) { if (center) {
x -= text.length/2+1; x -= text.length/2+1;
} }

View File

@ -77,7 +77,7 @@ class Canvas {
let triangles; let triangles;
try { try {
triangles = earcut(vertices, holes); triangles = earcut(vertices, holes);
} catch (error) { } catch {
return false; return false;
} }
for (let i = 0; i < triangles.length; i += 3) { for (let i = 0; i < triangles.length; i += 3) {
@ -169,7 +169,7 @@ class Canvas {
const c = this._bresenham(pointA, pointB); const c = this._bresenham(pointA, pointB);
const points = a.concat(b).concat(c).filter((point) => { const points = a.concat(b).concat(c).filter((point) => {
var ref; let ref;
return (0 <= (ref = point.y) && ref < this.height); return (0 <= (ref = point.y) && ref < this.height);
}).sort(function(a, b) { }).sort(function(a, b) {
if (a.y === b.y) { if (a.y === b.y) {

View File

@ -7,7 +7,6 @@
*/ */
import RBush from 'rbush'; import RBush from 'rbush';
import stringWidth from 'string-width'; import stringWidth from 'string-width';
import { Feature } from './Renderer.ts';
export default class LabelBuffer { export default class LabelBuffer {
private tree: RBush; private tree: RBush;
@ -32,15 +31,16 @@ export default class LabelBuffer {
const point = this.project(x, y); const point = this.project(x, y);
if (this._hasSpace(text, point[0], point[1])) { if (this._hasSpace(text, point[0], point[1])) {
const data = this._calculateArea(text, point[0], point[1], margin); return this.tree.insert({
data.feature = feature; ...this._calculateArea(text, point[0], point[1], margin),
return this.tree.insert(data); feature,
});
} else { } else {
return false; return false;
} }
} }
featuresAt(x: number, y: number): Feature[] { featuresAt(x: number, y: number): void {
this.tree.search({minX: x, maxX: x, minY: y, maxY: y}); this.tree.search({minX: x, maxX: x, minY: y, maxY: y});
} }

View File

@ -5,6 +5,8 @@
UI and central command center UI and central command center
*/ */
import fs from 'node:fs'; import fs from 'node:fs';
import process from "node:process";
import keypress from 'keypress'; import keypress from 'keypress';
import TermMouse from 'term-mouse'; import TermMouse from 'term-mouse';
@ -20,14 +22,24 @@ class Mapscii {
private width: number | null; private width: number | null;
private height: number | null; private height: number | null;
private canvas: Canvas | null; private canvas: Canvas | null;
private mouse: any; private mouse: TermMouse | null;
private mouseDragging: boolean; private mouseDragging: {
private mousePosition: any; x: number,
y: number,
center: {
x: number,
y: number,
},
} | false;
private mousePosition: {lat: number, lon: number} | null;
private tileSource: TileSource | null; private tileSource: TileSource | null;
private renderer: Renderer | null; private renderer: Renderer | null;
private zoom: number; private zoom: number;
private minZoom: number | null; private minZoom: number | null;
private center: any; private center: {
lat: number,
lon: number,
};
constructor(options) { constructor(options) {
this.width = null; this.width = null;
@ -36,10 +48,7 @@ class Mapscii {
this.mouse = null; this.mouse = null;
this.mouseDragging = false; this.mouseDragging = false;
this.mousePosition = { this.mousePosition = null;
x: 0,
y: 0,
};
this.tileSource = null; this.tileSource = null;
this.renderer = null; this.renderer = null;
@ -78,7 +87,7 @@ class Mapscii {
} }
config.input.resume(); config.input.resume();
config.input.on('keypress', (ch, key) => this._onKey(key)); config.input.on('keypress', (_ch, key) => this._onKey(key));
} }
_initMouse() { _initMouse() {
@ -115,34 +124,34 @@ class Mapscii {
this.renderer.setSize(this.width, this.height); this.renderer.setSize(this.width, this.height);
} }
_colrow2ll(x, y) { _colrow2ll(x: number, y: number): {lat: number, lon: number} {
const projected = { const projected = {
x: (x-0.5)*2, x: (x-0.5)*2,
y: (y-0.5)*4, y: (y-0.5)*4,
}; };
const size = utils.tilesizeAtZoom(this.zoom); const size = utils.tilesizeAtZoom(this.zoom);
const [dx, dy] = [projected.x-this.width/2, projected.y-this.height/2]; const [dx, dy] = [projected.x - this.width / 2, projected.y - this.height / 2];
const z = utils.baseZoom(this.zoom); const z = utils.baseZoom(this.zoom);
const center = utils.ll2tile(this.center.lon, this.center.lat, z); const center = utils.ll2tile(this.center.lon, this.center.lat, z);
return utils.normalize(utils.tile2ll(center.x+(dx/size), center.y+(dy/size), z)); return utils.normalize(utils.tile2ll(center.x + (dx / size), center.y + (dy / size), z));
} }
_updateMousePosition(event) { _updateMousePosition(event: {x: number, y: number}): void {
this.mousePosition = this._colrow2ll(event.x, event.y); this.mousePosition = this._colrow2ll(event.x, event.y);
} }
_onClick(event) { _onClick(event) {
if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) { if (event.x < 0 || event.x > this.width / 2 || event.y < 0 || event.y > this.height / 4) {
return; return;
} }
this._updateMousePosition(event); this._updateMousePosition(event);
if (this.mouseDragging && event.button === 'left') { if (this.mouseDragging && event.button === 'left') {
this.mouseDragging = false; this.mouseDragging = false;
} else { } else if (this.mousePosition !== null) {
this.setCenter(this.mousePosition.lat, this.mousePosition.lon); this.setCenter(this.mousePosition.lat, this.mousePosition.lon);
} }
@ -181,8 +190,8 @@ class Mapscii {
this._draw(); this._draw();
} }
_onMouseMove(event) { _onMouseMove(event: {button: string, x: number, y: number}) {
if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) { if (event.x < 0 || event.x > this.width / 2 || event.y < 0 || event.y > this.height / 4) {
return; return;
} }
if (config.mouseCallback && !config.mouseCallback(event)) { if (config.mouseCallback && !config.mouseCallback(event)) {
@ -192,14 +201,14 @@ class Mapscii {
// start dragging // start dragging
if (event.button === 'left') { if (event.button === 'left') {
if (this.mouseDragging) { if (this.mouseDragging) {
const dx = (this.mouseDragging.x-event.x)*2; const dx = (this.mouseDragging.x - event.x) * 2;
const dy = (this.mouseDragging.y-event.y)*4; const dy = (this.mouseDragging.y - event.y) * 4;
const size = utils.tilesizeAtZoom(this.zoom); const size = utils.tilesizeAtZoom(this.zoom);
const newCenter = utils.tile2ll( const newCenter = utils.tile2ll(
this.mouseDragging.center.x+(dx/size), this.mouseDragging.center.x + (dx / size),
this.mouseDragging.center.y+(dy/size), this.mouseDragging.center.y + (dy / size),
utils.baseZoom(this.zoom) utils.baseZoom(this.zoom)
); );
@ -284,7 +293,7 @@ class Mapscii {
let footer = `center: ${utils.digits(this.center.lat, 3)}, ${utils.digits(this.center.lon, 3)} `; let footer = `center: ${utils.digits(this.center.lat, 3)}, ${utils.digits(this.center.lon, 3)} `;
footer += ` zoom: ${utils.digits(this.zoom, 2)} `; footer += ` zoom: ${utils.digits(this.zoom, 2)} `;
if (this.mousePosition.lat !== undefined) { if (this.mousePosition !== null) {
footer += ` mouse: ${utils.digits(this.mousePosition.lat, 3)}, ${utils.digits(this.mousePosition.lon, 3)} `; footer += ` mouse: ${utils.digits(this.mousePosition.lat, 3)}, ${utils.digits(this.mousePosition.lon, 3)} `;
} }
return footer; return footer;
@ -297,26 +306,25 @@ class Mapscii {
} }
} }
_write(output) { _write(output): void {
config.output.write(output); config.output.write(output);
} }
zoomBy(step) { zoomBy(step: number): void {
if (this.zoom+step < this.minZoom) { if (this.zoom + step < this.minZoom) {
return this.zoom = this.minZoom; this.zoom = this.minZoom;
} else if (this.zoom + step > config.maxZoom) {
this.zoom = config.maxZoom;
} else {
this.zoom += step;
} }
if (this.zoom+step > config.maxZoom) {
return this.zoom = config.maxZoom;
}
this.zoom += step;
} }
moveBy(lat, lon) { moveBy(lat: number, lon: number): void {
this.setCenter(this.center.lat+lat, this.center.lon+lon); this.setCenter(this.center.lat + lat, this.center.lon + lon);
} }
setCenter(lat, lon) { setCenter(lat: number, lon: number): void {
this.center = utils.normalize({ this.center = utils.normalize({
lon: lon, lon: lon,
lat: lat, lat: lat,

View File

@ -79,7 +79,7 @@ class Renderer {
this.canvas?.clear(); this.canvas?.clear();
try { try {
let tiles = this._visibleTiles(center, zoom); const tiles = this._visibleTiles(center, zoom);
await Promise.all(tiles.map(async(tile) => { await Promise.all(tiles.map(async(tile) => {
await this._getTile(tile); await this._getTile(tile);
this._getTileFeatures(tile, zoom); this._getTileFeatures(tile, zoom);
@ -165,7 +165,7 @@ class Renderer {
_renderTiles(tiles) { _renderTiles(tiles) {
const labels: { const labels: {
tile: Tile, tile: Tile,
feature: any, feature: Feature,
scale: unknown, scale: unknown,
}[] = []; }[] = [];
if (tiles.length === 0) return; if (tiles.length === 0) return;

View File

@ -8,6 +8,7 @@
Compiles layer filter instructions into a chain of true/false returning Compiles layer filter instructions into a chain of true/false returning
anonymous functions to improve rendering speed compared to realtime parsing. anonymous functions to improve rendering speed compared to realtime parsing.
*/ */
import type RBush from 'rbush';
import { Feature } from "./Renderer.ts"; import { Feature } from "./Renderer.ts";
@ -19,7 +20,7 @@ class Styler {
constructor(style) { constructor(style) {
this.styleById = {}; this.styleById = {};
this.styleByLayer = {}; this.styleByLayer = {};
var base, name; let base, name;
this.styleName = style.name; this.styleName = style.name;
if (style.constants) { if (style.constants) {
this._replaceConstants(style.constants, style.layers); this._replaceConstants(style.constants, style.layers);
@ -45,7 +46,7 @@ class Styler {
} }
} }
getStyleFor(layer, feature): unknown | false { getStyleFor(layer: string, feature: Feature): unknown | false {
if (!this.styleByLayer[layer]) { if (!this.styleByLayer[layer]) {
return false; return false;
} }

View File

@ -32,6 +32,7 @@ class Tile {
z: number, z: number,
}; };
public zoom: number; public zoom: number;
public data: unknown;
constructor(styler: Styler) { constructor(styler: Styler) {
this.styler = styler; this.styler = styler;

View File

@ -1,5 +1,5 @@
import { describe, expect, test } from 'jest'; import { describe, expect, test } from 'jest';
import TileSource from './TileSource.ts'; import TileSource, { Mode } from './TileSource.ts';
describe('TileSource', () => { describe('TileSource', () => {
describe('with a HTTP source', () => { describe('with a HTTP source', () => {

View File

@ -12,16 +12,18 @@ import fetch from 'node-fetch';
import envPaths from 'env-paths'; import envPaths from 'env-paths';
const paths = envPaths('mapscii'); const paths = envPaths('mapscii');
import Tile from './Tile.ts';
import config from './config.ts'; import config from './config.ts';
import Tile from './Tile.ts';
import Styler from './Styler.ts';
// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3) // https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
// To maximize MapSCIIs compatibility, MBTiles support must be manually added via // To maximize MapSCIIs compatibility, MBTiles support must be manually added via
// $> npm install -g @mapbox/mbtiles // $> npm install -g @mapbox/mbtiles
let MBTiles = null; // let MBTiles = null;
try { // try {
MBTiles = await import('@mapbox/mbtiles'); // MBTiles = await import('@mapbox/mbtiles');
} catch (err) {void 0;} // } catch {void 0;}
import MBTiles from '@mapbox/mbtiles';
export enum Mode { export enum Mode {
MBTiles = 1, MBTiles = 1,
@ -31,14 +33,14 @@ export enum Mode {
class TileSource { class TileSource {
private source: string; private source: string;
private cache: any; private cache: Record<string, unknown>;
private cacheSize: number; private cacheSize: number;
private cached: any; private cached: unknown[];
private mode: Mode | null; public mode: Mode | null;
private mbtiles: any; private mbtiles: MBTiles | null;
private styler: any; private styler: Styler;
init(source: string) { init(source: string): void {
this.source = source; this.source = source;
this.cache = {}; this.cache = {};
@ -68,9 +70,9 @@ class TileSource {
} }
} }
loadMBTiles(source) { loadMBTiles(source): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
new MBTiles(source, (err, mbtiles) => { new MBTiles(`${source}?mode=ro`, (err, mbtiles) => {
if (err) { if (err) {
reject(err); reject(err);
} }
@ -95,7 +97,7 @@ class TileSource {
} }
if (this.cached.length > this.cacheSize) { if (this.cached.length > this.cacheSize) {
const overflow = Math.abs(this.cacheSize - this.cache.length); const overflow = Math.abs(this.cacheSize - this.cached.length);
for (const tile in this.cached.splice(0, overflow)) { for (const tile in this.cached.splice(0, overflow)) {
delete this.cache[tile]; delete this.cache[tile];
} }
@ -151,7 +153,7 @@ class TileSource {
private _initPersistence() { private _initPersistence() {
try { try {
this._createFolder(paths.cache); this._createFolder(paths.cache);
} catch (error) { } catch {
config.persistDownloadedTiles = false; config.persistDownloadedTiles = false;
} }
} }
@ -166,7 +168,7 @@ class TileSource {
private _getPersited(z: number, x: number, y: number) { private _getPersited(z: number, x: number, y: number) {
try { try {
return fs.readFileSync(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`)); return fs.readFileSync(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`));
} catch (error) { } catch {
return false; return false;
} }
} }

View File

@ -1,3 +1,5 @@
import process from "node:process";
export default { export default {
language: 'en', language: 'en',

View File

@ -34,7 +34,7 @@ const utils = {
return angle * 0.017453292519943295; return angle * 0.017453292519943295;
}, },
ll2tile: (lon: number, lat: number, zoom: number) => { ll2tile: (lon: number, lat: number, zoom: number): {x: number, y: number, z: number} => {
return { return {
x: (lon+180)/360*Math.pow(2, zoom), x: (lon+180)/360*Math.pow(2, zoom),
y: (1-Math.log(Math.tan(lat*Math.PI/180)+1/Math.cos(lat*Math.PI/180))/Math.PI)/2*Math.pow(2, zoom), y: (1-Math.log(Math.tan(lat*Math.PI/180)+1/Math.cos(lat*Math.PI/180))/Math.PI)/2*Math.pow(2, zoom),
@ -42,7 +42,7 @@ const utils = {
}; };
}, },
tile2ll: (x: number, y: number, zoom: number) => { tile2ll: (x: number, y: number, zoom: number): {lat: number, lon: number} => {
const n = Math.PI - 2*Math.PI*y/Math.pow(2, zoom); const n = Math.PI - 2*Math.PI*y/Math.pow(2, zoom);
return { return {
@ -55,21 +55,24 @@ const utils = {
return (Math.cos(lat * Math.PI/180) * 2 * Math.PI * constants.RADIUS) / (256 * Math.pow(2, zoom)); return (Math.cos(lat * Math.PI/180) * 2 * Math.PI * constants.RADIUS) / (256 * Math.pow(2, zoom));
}, },
hex2rgb: (color: any): [r: number, g: number, b: number] => { hex2rgb: (color: unknown): [r: number, g: number, b: number] => {
if (typeof color !== 'string') return [255, 0, 0]; if (typeof color !== 'string') {
return [255, 0, 0];
}
if (!/^#[a-fA-F0-9]{3,6}$/.test(color)) { if (!/^#[a-fA-F0-9]{3,6}$/.test(color)) {
throw new Error(`${color} isn't a supported hex color`); throw new Error(`${color} isn't a supported hex color`);
} }
color = color.substring(1); const decimal = parseInt(color.substring(1), 16);
const decimal = parseInt(color, 16);
if (color.length === 3) { if (color.length === 4) {
const rgb = [decimal>>8, (decimal>>4)&15, decimal&15]; const rgb = [decimal>>8, (decimal>>4)&15, decimal&15];
return rgb.map((c) => { return [
return c + (c<<4); rgb[0] + (rgb[0]<<4),
}); rgb[1] + (rgb[1]<<4),
rgb[2] + (rgb[2]<<4),
];
} else { } else {
return [(decimal>>16)&255, (decimal>>8)&255, decimal&255]; return [(decimal>>16)&255, (decimal>>8)&255, decimal&255];
} }