scripting: use chroma-js

This commit is contained in:
Steven Hugg 2021-08-18 11:16:58 -05:00
parent 05ab17d3da
commit 9076ede5c1
7 changed files with 160 additions and 66 deletions

71
package-lock.json generated
View File

@ -8,10 +8,12 @@
"version": "3.9.0",
"license": "GPL-3.0",
"dependencies": {
"@types/chroma-js": "^2.1.3",
"@types/emscripten": "^1.39.5",
"@wasmer/wasi": "^0.12.0",
"@wasmer/wasmfs": "^0.12.0",
"binaryen": "^101.0.0",
"chroma-js": "^2.1.2",
"clipboard": "^2.0.6",
"error-stack-parser": "^2.0.6",
"fast-png": "^5.0.4",
@ -725,6 +727,11 @@
"@types/jquery": "*"
}
},
"node_modules/@types/chroma-js": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
"integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
},
"node_modules/@types/debug": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
@ -1853,6 +1860,14 @@
"fsevents": "~2.3.2"
}
},
"node_modules/chroma-js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.1.2.tgz",
"integrity": "sha512-ri/ouYDWuxfus3UcaMxC1Tfp3IE9K5iQzxc2hSxbBRVNQFut1UuGAsZmiAf2mOUubzGJwgMSv9lHg+XqLaz1QQ==",
"dependencies": {
"cross-env": "^6.0.3"
}
},
"node_modules/chromedriver": {
"version": "92.0.1",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-92.0.1.tgz",
@ -2233,11 +2248,25 @@
"buffer": "^5.1.0"
}
},
"node_modules/cross-env": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz",
"integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==",
"dependencies": {
"cross-spawn": "^7.0.0"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@ -4950,8 +4979,7 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"devOptional": true
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"node_modules/isstream": {
"version": "0.1.2",
@ -7608,7 +7636,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -8525,7 +8552,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
@ -8537,7 +8563,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -9673,7 +9698,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
@ -10682,6 +10706,11 @@
"@types/jquery": "*"
}
},
"@types/chroma-js": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
"integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
},
"@types/debug": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
@ -11599,6 +11628,14 @@
"readdirp": "~3.6.0"
}
},
"chroma-js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.1.2.tgz",
"integrity": "sha512-ri/ouYDWuxfus3UcaMxC1Tfp3IE9K5iQzxc2hSxbBRVNQFut1UuGAsZmiAf2mOUubzGJwgMSv9lHg+XqLaz1QQ==",
"requires": {
"cross-env": "^6.0.3"
}
},
"chromedriver": {
"version": "92.0.1",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-92.0.1.tgz",
@ -11907,11 +11944,18 @@
"buffer": "^5.1.0"
}
},
"cross-env": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz",
"integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==",
"requires": {
"cross-spawn": "^7.0.0"
}
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@ -13995,8 +14039,7 @@
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"devOptional": true
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"isstream": {
"version": "0.1.2",
@ -16195,8 +16238,7 @@
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
},
"path-parse": {
"version": "1.0.7",
@ -16894,7 +16936,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
@ -16902,8 +16943,7 @@
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
"shiki": {
"version": "0.9.6",
@ -17815,7 +17855,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}

View File

@ -10,10 +10,12 @@
},
"license": "GPL-3.0",
"dependencies": {
"@types/chroma-js": "^2.1.3",
"@types/emscripten": "^1.39.5",
"@wasmer/wasi": "^0.12.0",
"@wasmer/wasmfs": "^0.12.0",
"binaryen": "^101.0.0",
"chroma-js": "^2.1.2",
"clipboard": "^2.0.6",
"error-stack-parser": "^2.0.6",
"fast-png": "^5.0.4",

View File

@ -2,6 +2,7 @@
import * as basic from "./compiler";
import { EmuHalt } from "../emu";
import { SourceLocation } from "../workertypes";
import { isArray } from "../util";
function isLiteral(arg: basic.Expr): arg is basic.Literal {
return (arg as any).value != null;
@ -33,10 +34,6 @@ interface CompiledStatement {
$run?: () => void;
}
function isArray(obj) {
return obj != null && (Array.isArray(obj) || obj.BYTES_PER_ELEMENT);
}
class RNG {
next : () => number;
seed : (aa,bb,cc,dd) => void;

View File

@ -69,8 +69,9 @@ export class Environment {
this.postamble = '\n}';
}
error(varname: string, msg: string) {
console.log(varname, this.declvars[varname]);
throw new RuntimeError(this.declvars && this.declvars[varname].loc, msg);
let obj = this.declvars && this.declvars[varname];
console.log(varname, obj);
throw new RuntimeError(obj && obj.loc, msg);
}
preprocess(code: string): string {
this.declvars = {};
@ -94,7 +95,7 @@ export class Environment {
};
const result = yufka(code, options, (node, { update, source, parent }) => {
function isTopLevel() {
return parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program';
return parent() && parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program';
}
let left = node['left'];
switch (node.type) {
@ -152,7 +153,10 @@ export class Environment {
}
fullkey.push(key);
if (typeof value === 'function') {
this.error(fullkey[0], `"${prkey()}" is a function. Did you forget to pass parameters?`); // TODO? did you mean (needs to see entire expr)
if (fullkey.length == 1)
this.error(fullkey[0], `"${prkey()}" is a function. Did you forget to pass parameters?`); // TODO? did you mean (needs to see entire expr)
else
this.error(fullkey[0], `This expression may be incomplete.`); // TODO? did you mean (needs to see entire expr)
}
if (typeof value === 'symbol') {
this.error(fullkey[0], `"${prkey()}" is a Symbol, and can't be used.`) // TODO?

View File

@ -1,4 +1,5 @@
// TODO: dynamic import
import * as fastpng from 'fast-png';
import { Palette } from './color';
import * as io from './io'
@ -118,7 +119,7 @@ export class IndexedBitmap extends MappedBitmap {
initial?: Uint8Array | PixelMapFunction
) {
super(width, height, bitsPerPixel || 8, initial);
this.palette = color.palette.colors(this.bitsPerPixel);
this.palette = color.palette.colors(1 << this.bitsPerPixel);
}
getRGBAForIndex(index: number): number {
@ -172,11 +173,7 @@ export namespace png {
}
function convertIndexedToBitmap(png: fastpng.IDecodedPNG): IndexedBitmap {
var palarr = <any>png.palette as [number, number, number, number][];
var palette = new Palette(palarr.length);
for (let i = 0; i < palarr.length; i++) {
// TODO: alpha?
palette.colors[i] = color.arr2rgba(palarr[i]) | 0xff000000;
}
var palette = new Palette(palarr);
let bitmap = new IndexedBitmap(png.width, png.height, png.depth);
if (png.depth == 8) {
bitmap.pixels.set(png.data);
@ -194,12 +191,12 @@ export namespace png {
return bitmap;
}
function convertRGBAToBitmap(png: fastpng.IDecodedPNG): RGBABitmap {
let bitmap = new RGBABitmap(png.width, png.height);
let rgba = [0, 0, 0, 0];
const bitmap = new RGBABitmap(png.width, png.height);
const rgba : [number,number,number,number] = [0, 0, 0, 0];
for (let i = 0; i < bitmap.rgba.length; i++) {
for (let j = 0; j < 4; j++)
rgba[j] = png.data[i * 4 + j];
bitmap.rgba[i] = color.arr2rgba(rgba);
bitmap.rgba[i] = color.rgba(rgba);
}
// TODO: aspect etc
return bitmap;

View File

@ -1,28 +1,61 @@
import _chroma from 'chroma-js'
import { isArray } from '../../util';
export type ColorSource = number | [number,number,number] | [number,number,number,number] | string;
export class Palette {
colors: Uint32Array;
readonly colors: Uint32Array;
constructor(arg: number | number[] | Uint32Array) {
constructor(arg: number | any[] | Uint32Array) {
// TODO: more array types
if (typeof arg === 'number') {
if (!(arg >= 1 && arg <= 65536)) throw new Error('Invalid palette size ' + arg);
this.colors = new Uint32Array(arg);
} else {
} else if (arg instanceof Uint32Array) {
this.colors = new Uint32Array(arg);
}
} else if (isArray(arg)) {
this.colors = new Uint32Array(arg.map(rgb));
} else
throw new Error(`Invalid Palette constructor`)
}
get(index: number) {
return this.colors[index];
}
}
export function rgb(r: number, g: number, b: number): number {
return ((r & 0xff) << 0) | ((g & 0xff) << 8) | ((b & 0xff) << 16) | 0xff000000;
export const chroma = _chroma;
export function from(obj: ColorSource) {
return _chroma(obj as any);
}
export function arr2rgba(arr: number[] | Uint8Array): number {
let v = 0;
v |= (arr[0] & 0xff) << 0;
v |= (arr[1] & 0xff) << 8;
v |= (arr[2] & 0xff) << 16;
v |= (arr[3] & 0xff) << 24;
return v;
export function rgb(obj: ColorSource) : number;
export function rgb(r: number, g: number, b: number) : number;
export function rgb(obj: any, g?: number, b?: number) : number {
return rgba(obj, g, b, 0xff) | 0xff000000;
}
export function rgba(obj: ColorSource) : number;
export function rgba(r: number, g: number, b: number, a: number) : number;
export function rgba(obj: ColorSource, g?: number, b?: number, a?: number) : number {
if (typeof obj === 'number') {
let r = obj;
if (g != null && b != null)
return ((r & 0xff) << 0) | ((g & 0xff) << 8) | ((b & 0xff) << 16) | ((a & 0xff) << 24);
return obj;
}
if (typeof obj !== 'string' && isArray(obj) && typeof obj[0] === 'number') {
let arr = obj;
let v = 0;
v |= (arr[0] & 0xff) << 0;
v |= (arr[1] & 0xff) << 8;
v |= (arr[2] & 0xff) << 16;
v |= (arr[3] & 0xff) << 24;
return v;
}
return rgba(from(obj).rgb());
}
export function rgba2arr(v: number): number[] {
@ -34,18 +67,29 @@ export function rgba2arr(v: number): number[] {
]
}
export function rgb2arr(v: number): number[] {
return rgba2arr(v).slice(0,3);
}
type ColorGenFunc = (index: number) => number;
export namespace palette {
export function generate(bpp: number, func: (index: number) => number) {
var pal = new Palette(1 << bpp);
for (var i = 0; i < pal.colors.length; i++) {
pal.colors[i] = 0xff000000 | func(i);
export function from(obj: number | any[] | Uint32Array | ColorGenFunc, count?: number) {
if (typeof obj === 'function') {
if (!count) throw new Error(`You must also pass the number of colors to generate.`)
var pal = new Palette(count);
for (var i = 0; i < pal.colors.length; i++) {
pal.colors[i] = rgba(obj(i));
}
return pal;
} else {
return new Palette(obj);
}
return pal;
}
export function mono() {
return greys(1);
}
export function rgb2() {
function rgb2() {
return new Palette([
rgb(0, 0, 0),
rgb(0, 0, 255),
@ -53,7 +97,7 @@ export namespace palette {
rgb(0, 255, 0),
]);
}
export function rgb3() {
function rgb3() {
return new Palette([
rgb(0, 0, 0),
rgb(0, 0, 255),
@ -65,23 +109,26 @@ export namespace palette {
rgb(255, 255, 255),
]);
}
export function greys(bpp: number) {
return generate(bpp, (i) => {
let v = 255 * i / ((1 << bpp) - 1);
export function greys(count: number) {
return from((i) => {
let v = 255 * i / (count - 1);
return rgb(v,v,v);
});
}, count);
}
export function colors(bpp: number) {
switch (bpp) {
case 1: return mono();
case 2: return rgb2();
case 3: return rgb3();
default: return factors(bpp); // TODO
export function colors(count: number) {
switch (count) {
case 2: return mono();
case 4: return rgb2();
case 8: return rgb3();
default: return factors(count); // TODO
}
}
export function factors(bpp: number, mult?: number) {
export function helix(count: number) {
return new Palette(chroma.cubehelix().scale().colors(count));
}
export function factors(count: number, mult?: number) {
mult = mult || 0x031f0f;
return generate(bpp, (i) => i * mult);
return from((i) => rgb(i * mult), count);
}
// TODO: https://www.iquilezles.org/www/articles/palettes/palettes.htm
}

View File

@ -492,6 +492,14 @@ export function getRootBasePlatform(platform : string) : string {
return getRootPlatform(getBasePlatform(platform));
}
export function isArray(obj: any) : obj is ArrayLike<any> {
return obj != null && (Array.isArray(obj) || isTypedArray(obj));
}
export function isTypedArray(obj: any) : obj is ArrayLike<number> {
return obj != null && obj['BYTES_PER_ELEMENT'];
}
export function convertDataToUint8Array(data: string|Uint8Array) : Uint8Array {
return (typeof data === 'string') ? stringToByteArray(data) : data;
}