♻️ switching to typescript
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "graphfx",
|
"name": "graphfx",
|
||||||
"version": "0.0.15",
|
"version": "0.1.0",
|
||||||
"description": "Graph image processing pipeline",
|
"description": "Graph image processing pipeline",
|
||||||
"main": "src/index.js",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack-dev-server --quiet --progress --watch --mode development",
|
"dev": "webpack-dev-server --quiet --progress --watch --mode development",
|
||||||
"build": "webpack --mode production --quiet --progress",
|
"build": "webpack --mode production --quiet --progress",
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
|
type PoolCanvas<T extends (OffscreenCanvas | HTMLCanvasElement)> = T & {
|
||||||
|
acquire(): void
|
||||||
|
release(): void
|
||||||
|
}
|
||||||
|
|
||||||
class CanvasPool {
|
class CanvasPool<T extends (OffscreenCanvas | HTMLCanvasElement)> {
|
||||||
|
|
||||||
|
__ctx: any
|
||||||
|
__pool: PoolCanvas<T>[]
|
||||||
|
__pointerCounter: WeakMap<PoolCanvas<T>, number>
|
||||||
|
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
this.__ctx = ctx;
|
this.__ctx = ctx;
|
||||||
@@ -9,26 +17,28 @@ class CanvasPool {
|
|||||||
|
|
||||||
__createNewCanvas() {
|
__createNewCanvas() {
|
||||||
const canvas = (this.__ctx !== 'webgl') ? new OffscreenCanvas(1,1) : document.createElement('canvas');
|
const canvas = (this.__ctx !== 'webgl') ? new OffscreenCanvas(1,1) : document.createElement('canvas');
|
||||||
|
// @ts-ignore
|
||||||
canvas.acquire = this.acquireCanvas.bind(this, canvas);
|
canvas.acquire = this.acquireCanvas.bind(this, canvas);
|
||||||
|
// @ts-ignore
|
||||||
canvas.release = this.releaseCanvas.bind(this, canvas);
|
canvas.release = this.releaseCanvas.bind(this, canvas);
|
||||||
canvas.getContext(this.__ctx);
|
canvas.getContext(this.__ctx);
|
||||||
this.__pool.push(canvas);
|
this.__pool.push(<PoolCanvas<T>>canvas);
|
||||||
this.__pointerCounter.set(canvas, 0);
|
this.__pointerCounter.set(<PoolCanvas<T>>canvas, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
createCanvas() {
|
createCanvas():PoolCanvas<T> {
|
||||||
if (this.__pool.length === 0) {
|
if (this.__pool.length === 0) {
|
||||||
this.__createNewCanvas();
|
this.__createNewCanvas();
|
||||||
}
|
}
|
||||||
return this.__pool.pop();
|
return this.__pool.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
acquireCanvas(canvas) {
|
acquireCanvas(canvas: PoolCanvas<T>) {
|
||||||
const inUse = this.__pointerCounter.get(canvas) + 1;
|
const inUse = this.__pointerCounter.get(canvas) + 1;
|
||||||
this.__pointerCounter.set(canvas, inUse);
|
this.__pointerCounter.set(canvas, inUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseCanvas(canvas) {
|
releaseCanvas(canvas: PoolCanvas<T>) {
|
||||||
const inUse = this.__pointerCounter.get(canvas) - 1;
|
const inUse = this.__pointerCounter.get(canvas) - 1;
|
||||||
this.__pointerCounter.set(canvas, inUse);
|
this.__pointerCounter.set(canvas, inUse);
|
||||||
if (inUse === 0) {
|
if (inUse === 0) {
|
||||||
@@ -38,5 +48,5 @@ class CanvasPool {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const canvasPool2D = new CanvasPool('2d');
|
export const canvasPool2D = new CanvasPool<OffscreenCanvas>('2d');
|
||||||
export const canvasPoolWebGL = new CanvasPool('webgl');
|
export const canvasPoolWebGL = new CanvasPool<HTMLCanvasElement>('webgl');
|
||||||
@@ -1,14 +1,34 @@
|
|||||||
import * as nodes from './nodes';
|
import * as nodes from './nodes/index';
|
||||||
|
import Node from './nodes/Node'
|
||||||
import {MultiSubject} from './helpers/listener';
|
import {MultiSubject} from './helpers/listener';
|
||||||
|
|
||||||
|
interface GraphNode {
|
||||||
|
node: Node<any, any>,
|
||||||
|
x?: number,
|
||||||
|
y?: number
|
||||||
|
}
|
||||||
|
|
||||||
export default class Graph {
|
export default class Graph {
|
||||||
|
|
||||||
constructor() {
|
nodes: GraphNode[]
|
||||||
this.nodes = [];
|
__nodesActive: number
|
||||||
|
__isRunningSet: WeakSet<any>
|
||||||
|
subject: MultiSubject
|
||||||
|
|
||||||
|
constructor(nodes=[]) {
|
||||||
|
this.__isRunningSet = new WeakSet();
|
||||||
|
this.nodes = nodes;
|
||||||
this.__nodesActive = 0;
|
this.__nodesActive = 0;
|
||||||
this.__nodesActiveDebounce = null;
|
|
||||||
this.subject = new MultiSubject(['running', 'stopped']);
|
this.subject = new MultiSubject(['running', 'stopped']);
|
||||||
|
this.__setupRunningListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRunning() {
|
||||||
|
return this.__nodesActive > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isStopped() {
|
||||||
|
return this.__nodesActive === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize() {
|
serialize() {
|
||||||
@@ -18,28 +38,38 @@ export default class Graph {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deserialize(nodes) {
|
addNode(node) {
|
||||||
const graph = new Graph();
|
this.nodes.push({node});
|
||||||
const isRunningSet = new WeakSet();
|
this.__setupNodeRunningListener(node);
|
||||||
graph.nodes = await createUnconnectedNodes(nodes);
|
|
||||||
for (const {node} of graph.nodes) {
|
|
||||||
node.subject.on('running', (isRunning) => {
|
|
||||||
if (!isRunning && isRunningSet.has(node)) {
|
|
||||||
isRunningSet.delete(node);
|
|
||||||
graph.__nodesActive -= 1;
|
|
||||||
} else if (isRunning && !isRunningSet.has(node)) {
|
|
||||||
isRunningSet.add(node);
|
|
||||||
graph.__nodesActive += 1;
|
|
||||||
}
|
}
|
||||||
if (graph.__nodesActive > 0) {
|
|
||||||
graph.subject.next('running');
|
__setupNodeRunningListener(node) {
|
||||||
|
node.subject.on('running', (isRunning) => {
|
||||||
|
if (!isRunning && this.__isRunningSet.has(node)) {
|
||||||
|
this.__isRunningSet.delete(node);
|
||||||
|
this.__nodesActive -= 1;
|
||||||
|
} else if (isRunning && !this.__isRunningSet.has(node)) {
|
||||||
|
this.__isRunningSet.add(node);
|
||||||
|
this.__nodesActive += 1;
|
||||||
|
}
|
||||||
|
if (this.__nodesActive > 0) {
|
||||||
|
this.subject.next('running');
|
||||||
} else {
|
} else {
|
||||||
console.info('[Graphfx] graph stopped');
|
console.info('[Graphfx] graph stopped');
|
||||||
graph.subject.next('stopped');
|
this.subject.next('stopped');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
graph.nodes = await reconnectNodes(graph.nodes);
|
|
||||||
|
__setupRunningListeners() {
|
||||||
|
for (const {node} of this.nodes) {
|
||||||
|
this.__setupNodeRunningListener(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deserialize(nodes) {
|
||||||
|
const graph = new Graph(await createUnconnectedNodes(nodes));
|
||||||
|
await reconnectNodes(graph.nodes);
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,17 +101,20 @@ export default class Graph {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const createNode = async ({name, options, id}) => {
|
export async function createNode<T extends keyof typeof nodes>(
|
||||||
|
{name, options, id}:{name: T, options: any, id: string}):Promise<typeof nodes[T]> {
|
||||||
try {
|
try {
|
||||||
const node = new nodes[name](options);
|
const node = new nodes[name](options);
|
||||||
await node.deserialize({id, options});
|
await node.deserialize({id, options});
|
||||||
|
// @ts-ignore
|
||||||
return node;
|
return node;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to create node', name, err)
|
console.error('Failed to create node', name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createUnconnectedNodes = async (nodes) => {
|
export const createUnconnectedNodes = async (nodes):Promise<GraphNode[]> => {
|
||||||
|
// @ts-ignore
|
||||||
return (await Promise.all(nodes.map(async ({node, x, y}) => {
|
return (await Promise.all(nodes.map(async ({node, x, y}) => {
|
||||||
const nodeInstance = await createNode(node);
|
const nodeInstance = await createNode(node);
|
||||||
return ({
|
return ({
|
||||||
@@ -113,7 +146,8 @@ export const reconnectNodes = async (nodes) => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createNodes = async (nodes) => {
|
export const createNodes = async (nodes):Promise<GraphNode[]> => {
|
||||||
nodes = await createUnconnectedNodes(nodes);
|
const unconnectedNodes = await createUnconnectedNodes(nodes);
|
||||||
return reconnectNodes(nodes);
|
// @ts-ignore
|
||||||
|
return reconnectNodes(unconnectedNodes);
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,38 @@
|
|||||||
import Node from '../Node';
|
import Node from '../Node';
|
||||||
import {waitForMedia} from '../../utils';
|
import {waitForMedia, merge} from '../../utils';
|
||||||
import {canvasPool2D} from '../../canvas/CanvasPool';
|
import {canvasPool2D} from '../../canvas/CanvasPool';
|
||||||
|
import {
|
||||||
|
Variables,
|
||||||
|
ImageVar,
|
||||||
|
BooleanVar,
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
export default class Canvas2d extends Node {
|
const inputs = {
|
||||||
constructor(name, inputDefinition, outputDefiniton, options) {
|
|
||||||
super(name,
|
|
||||||
Object.assign({
|
|
||||||
image: {
|
image: {
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
},
|
} as ImageVar,
|
||||||
}, inputDefinition),
|
};
|
||||||
Object.assign({
|
|
||||||
|
const outputs = {
|
||||||
image: {
|
image: {
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
},
|
} as ImageVar,
|
||||||
width: {
|
width: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
height: {
|
height: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
}
|
} as NumberVar
|
||||||
}, outputDefiniton),
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default class Canvas2d<I extends Variables, O extends Variables> extends Node<I & (typeof inputs), O & (typeof outputs)> {
|
||||||
|
constructor(name, inputDefinition: I, outputDefiniton: O, options) {
|
||||||
|
super(name,
|
||||||
|
merge(inputs, inputDefinition),
|
||||||
|
merge(outputs, outputDefiniton),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import Canvas2d from './Canvas2d';
|
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
|
||||||
|
|
||||||
export default class Compose extends Canvas2d {
|
|
||||||
|
|
||||||
constructor(options) {
|
|
||||||
super('Compose', {
|
|
||||||
image: null,
|
|
||||||
fg: {
|
|
||||||
type: 'Image'
|
|
||||||
},
|
|
||||||
fgX: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
step: 1,
|
|
||||||
},
|
|
||||||
fgY: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
step: 1,
|
|
||||||
},
|
|
||||||
bg: {
|
|
||||||
type: 'Image',
|
|
||||||
},
|
|
||||||
bgX: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
step: 1,
|
|
||||||
},
|
|
||||||
bgY: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
step: 1,
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 100,
|
|
||||||
step: 1,
|
|
||||||
min: 1
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 100,
|
|
||||||
step: 1,
|
|
||||||
min: 1,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: 'String',
|
|
||||||
default: 'source-over',
|
|
||||||
enum: [
|
|
||||||
'source-over',
|
|
||||||
'source-in',
|
|
||||||
'source-out',
|
|
||||||
'source-atop',
|
|
||||||
'destination-over',
|
|
||||||
'destination-in',
|
|
||||||
'destination-out',
|
|
||||||
'destination-atop',
|
|
||||||
'lighter',
|
|
||||||
'copy',
|
|
||||||
'xor',
|
|
||||||
'multiply',
|
|
||||||
'screen',
|
|
||||||
'overlay',
|
|
||||||
'darken',
|
|
||||||
'lighten',
|
|
||||||
'color-dodge',
|
|
||||||
'color-burn',
|
|
||||||
'hard-light',
|
|
||||||
'soft-light',
|
|
||||||
'difference',
|
|
||||||
'exclusion',
|
|
||||||
'hue',
|
|
||||||
'saturation',
|
|
||||||
'color',
|
|
||||||
'luminosity',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
image: {
|
|
||||||
type: 'Image'
|
|
||||||
}
|
|
||||||
}, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
get width() {
|
|
||||||
return this.__in.width.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get height() {
|
|
||||||
return this.__in.height.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get fg() {
|
|
||||||
if (!this.__in.fg.value) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
const {width, height} = this.__in.fg.value;
|
|
||||||
return {
|
|
||||||
image: this.__in.fg.value,
|
|
||||||
top: this.__in.fgY.value || 0,
|
|
||||||
left: this.__in.fgX.value || 0,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get bg() {
|
|
||||||
if (!this.__in.bg.value) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
const {width, height} = this.__in.bg.value;
|
|
||||||
return {
|
|
||||||
image: this.__in.bg.value,
|
|
||||||
top: this.__in.bgY.value || 0,
|
|
||||||
left: this.__in.bgX.value || 0,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {any} values
|
|
||||||
* @param {HTMLCanvasElement} canvas
|
|
||||||
* @param {CanvasRenderingContext2D} ctx
|
|
||||||
*/
|
|
||||||
async render({width, height, mode}, canvas, ctx) {
|
|
||||||
if (!width || !height) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.bg && !this.fg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
|
|
||||||
ctx.globalCompositeOperation = 'source-over';
|
|
||||||
|
|
||||||
if (this.bg) {
|
|
||||||
paintToCanvas(canvas, this.bg.image, this.bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.globalCompositeOperation = mode;
|
|
||||||
|
|
||||||
if (this.fg) {
|
|
||||||
paintToCanvas(canvas, this.fg.image, this.fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
163
src/nodes/Canvas2d/Compose.ts
Normal file
163
src/nodes/Canvas2d/Compose.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import Canvas2d from './Canvas2d';
|
||||||
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
|
import {
|
||||||
|
ImageVar,
|
||||||
|
StringVar,
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
image: null,
|
||||||
|
fg: {
|
||||||
|
type: 'Image'
|
||||||
|
} as ImageVar,
|
||||||
|
fgX: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
step: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
fgY: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
step: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
bg: {
|
||||||
|
type: 'Image',
|
||||||
|
} as ImageVar,
|
||||||
|
bgX: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
step: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
bgY: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
step: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
width: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 100,
|
||||||
|
step: 1,
|
||||||
|
min: 1
|
||||||
|
} as NumberVar,
|
||||||
|
height: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 100,
|
||||||
|
step: 1,
|
||||||
|
min: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
mode: {
|
||||||
|
type: 'String',
|
||||||
|
default: 'source-over',
|
||||||
|
enum: [
|
||||||
|
'source-over',
|
||||||
|
'source-in',
|
||||||
|
'source-out',
|
||||||
|
'source-atop',
|
||||||
|
'destination-over',
|
||||||
|
'destination-in',
|
||||||
|
'destination-out',
|
||||||
|
'destination-atop',
|
||||||
|
'lighter',
|
||||||
|
'copy',
|
||||||
|
'xor',
|
||||||
|
'multiply',
|
||||||
|
'screen',
|
||||||
|
'overlay',
|
||||||
|
'darken',
|
||||||
|
'lighten',
|
||||||
|
'color-dodge',
|
||||||
|
'color-burn',
|
||||||
|
'hard-light',
|
||||||
|
'soft-light',
|
||||||
|
'difference',
|
||||||
|
'exclusion',
|
||||||
|
'hue',
|
||||||
|
'saturation',
|
||||||
|
'color',
|
||||||
|
'luminosity',
|
||||||
|
]
|
||||||
|
} as StringVar
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
|
image: {
|
||||||
|
type: 'Image'
|
||||||
|
} as ImageVar
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Compose extends Canvas2d<typeof inputs, typeof outputs> {
|
||||||
|
|
||||||
|
constructor(options={}) {
|
||||||
|
super('Compose', inputs, outputs, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
get width() {
|
||||||
|
return this.__in.width.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get height() {
|
||||||
|
return this.__in.height.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fg() {
|
||||||
|
if (!this.__in.fg.value) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
const {width, height} = this.__in.fg.value;
|
||||||
|
return {
|
||||||
|
image: this.__in.fg.value,
|
||||||
|
top: this.__in.fgY.value || 0,
|
||||||
|
left: this.__in.fgX.value || 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get bg() {
|
||||||
|
if (!this.__in.bg.value) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
const {width, height} = this.__in.bg.value;
|
||||||
|
return {
|
||||||
|
image: this.__in.bg.value,
|
||||||
|
top: this.__in.bgY.value || 0,
|
||||||
|
left: this.__in.bgX.value || 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} values
|
||||||
|
* @param {HTMLCanvasElement} canvas
|
||||||
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
|
*/
|
||||||
|
async render({width, height, mode}, canvas, ctx) {
|
||||||
|
if (!width || !height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.bg && !this.fg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
|
||||||
|
ctx.globalCompositeOperation = 'source-over';
|
||||||
|
|
||||||
|
if (this.bg) {
|
||||||
|
paintToCanvas(canvas, this.bg.image, this.bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.globalCompositeOperation = mode;
|
||||||
|
|
||||||
|
if (this.fg) {
|
||||||
|
paintToCanvas(canvas, this.fg.image, this.fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,35 @@
|
|||||||
import Canvas2d from './Canvas2d';
|
import Canvas2d from './Canvas2d';
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
|
import {
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
export default class EmptySpace extends Canvas2d {
|
const inputs = {};
|
||||||
|
const outputs = {
|
||||||
constructor() {
|
|
||||||
super('EmptySpace', {
|
|
||||||
}, {
|
|
||||||
w: {
|
w: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
h: {
|
h: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
top: {
|
top: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
right: {
|
right: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
bottom: {
|
bottom: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
left: {
|
left: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
}
|
} as NumberVar
|
||||||
});
|
};
|
||||||
|
|
||||||
|
export default class EmptySpace extends Canvas2d<typeof inputs, typeof outputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('EmptySpace', inputs, outputs, {});
|
||||||
this._update();
|
this._update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,22 +47,23 @@ export default class EmptySpace extends Canvas2d {
|
|||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
ctx.drawImage(image, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
const imageData = ctx.getImageData(0, 0, width, height)
|
const imageData = ctx.getImageData(0, 0, width, height)
|
||||||
for (let offset=0; offset<imageData.data.length; offset+=4) {
|
let x = 0, y = 0, a, offset;
|
||||||
const x = (offset/4) % width;
|
let length = imageData.data.length/4;
|
||||||
const y = Math.floor((offset/4) / width)
|
for (offset=0; offset<length; offset+=1) {
|
||||||
const [r,g,b,a] = imageData.data.slice(offset, offset+4);
|
x = (offset) % width;
|
||||||
if (a < 1) {
|
y = Math.floor(offset / width)
|
||||||
|
a = imageData.data[offset * 4 + 3];
|
||||||
|
if (a < 254) {
|
||||||
top = Math.min(top, y);
|
top = Math.min(top, y);
|
||||||
right = Math.max(right, x);
|
right = Math.max(right, x);
|
||||||
bottom = Math.max(bottom, y);
|
bottom = Math.max(bottom, y);
|
||||||
left = Math.min(left, x);
|
left = Math.min(left, x);
|
||||||
}
|
}
|
||||||
imageData.data[offset] = a;
|
|
||||||
imageData.data[offset + 1] = a;
|
if (x === left && y == bottom && x < right) {
|
||||||
imageData.data[offset + 2] = a;
|
offset += (right - left) -1;
|
||||||
imageData.data[offset + 3] = a;
|
}
|
||||||
}
|
}
|
||||||
ctx.putImageData(imageData, 0, 0);
|
|
||||||
this.out.top.value = top;
|
this.out.top.value = top;
|
||||||
this.out.right.value = right;
|
this.out.right.value = right;
|
||||||
this.out.bottom.value = bottom;
|
this.out.bottom.value = bottom;
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import Canvas2d from './Canvas2d';
|
|
||||||
|
|
||||||
export default class Fill extends Canvas2d {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('Fill', {
|
|
||||||
image: null,
|
|
||||||
width: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 100,
|
|
||||||
min: 1,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 100,
|
|
||||||
min: 1,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: 'Color',
|
|
||||||
default: '#FFFFFF'
|
|
||||||
}
|
|
||||||
}, {});
|
|
||||||
this._update();
|
|
||||||
}
|
|
||||||
|
|
||||||
async render({color, width, height}, canvas, ctx) {
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.fillRect(0, 0, width, height);
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
src/nodes/Canvas2d/Fill.ts
Normal file
40
src/nodes/Canvas2d/Fill.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import Canvas2d from './Canvas2d';
|
||||||
|
import {
|
||||||
|
NumberVar,
|
||||||
|
ColorVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
image: null,
|
||||||
|
width: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 100,
|
||||||
|
min: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
height: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 100,
|
||||||
|
min: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
color: {
|
||||||
|
type: 'Color',
|
||||||
|
default: '#FFFFFF'
|
||||||
|
} as ColorVar
|
||||||
|
};
|
||||||
|
const outputs = {};
|
||||||
|
|
||||||
|
export default class Fill extends Canvas2d<typeof inputs, typeof outputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('Fill', inputs, outputs);
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
async render({color, width, height}, canvas, ctx) {
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
import Canvas2d from './Canvas2d';
|
import Canvas2d from './Canvas2d';
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
|
import {
|
||||||
|
BooleanVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
export default class Flip extends Canvas2d {
|
const inputs = {
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('Flip', {
|
|
||||||
horizontal: {
|
horizontal: {
|
||||||
type: 'Boolean',
|
type: 'Boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
} as BooleanVar,
|
||||||
vertical: {
|
vertical: {
|
||||||
type: 'Boolean',
|
type: 'Boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
} as BooleanVar,
|
||||||
}, {});
|
}
|
||||||
|
|
||||||
|
const outputs = {};
|
||||||
|
|
||||||
|
export default class Flip extends Canvas2d<typeof inputs, typeof outputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('Flip', inputs, outputs, {});
|
||||||
this._update();
|
this._update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,22 +1,33 @@
|
|||||||
import Canvas2d from './Canvas2d';
|
import Canvas2d from './Canvas2d';
|
||||||
import {waitForMedia} from '../../utils';
|
import {waitForMedia} from '../../utils';
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
|
import {
|
||||||
|
ImageVar,
|
||||||
|
StringVar,
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
export default class Resize extends Canvas2d {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('Resize', {
|
const inputs = {
|
||||||
width: {
|
width: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 100,
|
default: 100,
|
||||||
min: 1,
|
min: 1,
|
||||||
},
|
} as NumberVar,
|
||||||
height: {
|
height: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 100,
|
default: 100,
|
||||||
min: 1,
|
min: 1,
|
||||||
},
|
} as NumberVar,
|
||||||
}, {});
|
};
|
||||||
|
|
||||||
|
const outputs = {};
|
||||||
|
|
||||||
|
export default class Resize extends Canvas2d<typeof inputs, typeof outputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('Resize', inputs, outputs, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import Canvas2d from './Canvas2d';
|
|
||||||
import {waitForMedia} from '../../utils';
|
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
|
||||||
|
|
||||||
const isEmpty = (x) => !x || Object.keys(x).length === 0;
|
|
||||||
|
|
||||||
export default class Resize extends Canvas2d {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('Text', {
|
|
||||||
image: null,
|
|
||||||
text: {
|
|
||||||
type: 'String',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
fontSize: {
|
|
||||||
type: 'Number',
|
|
||||||
min: 1,
|
|
||||||
default: 50,
|
|
||||||
},
|
|
||||||
fontStyle: {
|
|
||||||
type: 'String',
|
|
||||||
default: 'normal',
|
|
||||||
enum: [
|
|
||||||
'normal',
|
|
||||||
'bold',
|
|
||||||
'italic',
|
|
||||||
'bold italic',
|
|
||||||
]
|
|
||||||
},
|
|
||||||
font: {
|
|
||||||
type: 'Font', // {name, url, fontface}
|
|
||||||
default: 'Arial',
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 100,
|
|
||||||
min: 1,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 100,
|
|
||||||
min: 1,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: 'Color',
|
|
||||||
default: '#FFFFFF'
|
|
||||||
},
|
|
||||||
textAlign: {
|
|
||||||
type: 'String',
|
|
||||||
default: 'center',
|
|
||||||
enum: [
|
|
||||||
'left',
|
|
||||||
'center',
|
|
||||||
'right',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}, {});
|
|
||||||
this._update();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {{name: string, url: string, fontface: FontFace, type: 'font'}} font
|
|
||||||
*/
|
|
||||||
async loadFont(font) {
|
|
||||||
if (typeof font === 'string') {
|
|
||||||
return font;
|
|
||||||
} else if (!font) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
return font.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async render({font, fontSize, text, color, width, height, textAlign, fontStyle}, canvas, ctx) {
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
|
|
||||||
ctx.font = `${fontStyle} ${fontSize}px "${(await this.loadFont(font)) || ''}"`;
|
|
||||||
// ctx.font = `${fontStyle} ${fontSize}px "Arial"`;
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.textAlign = textAlign;
|
|
||||||
const x = textAlign === 'center' ? canvas.width /2 :
|
|
||||||
textAlign === 'left' ? 0 : canvas.width;
|
|
||||||
text.split('\\n').forEach((line, index) => {
|
|
||||||
ctx.fillText(line, x, fontSize * (1 + index));
|
|
||||||
});
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
101
src/nodes/Canvas2d/Text.ts
Normal file
101
src/nodes/Canvas2d/Text.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import Canvas2d from './Canvas2d';
|
||||||
|
import {waitForMedia} from '../../utils';
|
||||||
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
|
import {
|
||||||
|
NumberVar,
|
||||||
|
StringVar,
|
||||||
|
FontVar,
|
||||||
|
ColorVar,
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const isEmpty = (x) => !x || Object.keys(x).length === 0;
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
image: null,
|
||||||
|
text: {
|
||||||
|
type: 'String',
|
||||||
|
default: '',
|
||||||
|
} as StringVar,
|
||||||
|
fontSize: {
|
||||||
|
type: 'Number',
|
||||||
|
min: 1,
|
||||||
|
default: 50,
|
||||||
|
} as NumberVar,
|
||||||
|
fontStyle: {
|
||||||
|
type: 'String',
|
||||||
|
default: 'normal',
|
||||||
|
enum: [
|
||||||
|
'normal',
|
||||||
|
'bold',
|
||||||
|
'italic',
|
||||||
|
'bold italic',
|
||||||
|
]
|
||||||
|
} as StringVar,
|
||||||
|
font: {
|
||||||
|
type: 'Font', // {name, url, fontface}
|
||||||
|
default: 'Arial',
|
||||||
|
} as FontVar,
|
||||||
|
width: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 100,
|
||||||
|
min: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
height: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 100,
|
||||||
|
min: 1,
|
||||||
|
} as NumberVar,
|
||||||
|
color: {
|
||||||
|
type: 'Color',
|
||||||
|
default: '#FFFFFF'
|
||||||
|
} as ColorVar,
|
||||||
|
textAlign: {
|
||||||
|
type: 'String',
|
||||||
|
default: 'center',
|
||||||
|
enum: [
|
||||||
|
'left',
|
||||||
|
'center',
|
||||||
|
'right',
|
||||||
|
]
|
||||||
|
} as StringVar
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputs = {};
|
||||||
|
|
||||||
|
export default class Resize extends Canvas2d<typeof inputs, typeof outputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('Text', inputs, outputs, {});
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {{name: string, url: string, fontface: FontFace, type: 'font'}} font
|
||||||
|
*/
|
||||||
|
async loadFont(font) {
|
||||||
|
if (typeof font === 'string') {
|
||||||
|
return font;
|
||||||
|
} else if (!font) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return font.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async render({font, fontSize, text, color, width, height, textAlign, fontStyle}, canvas, ctx) {
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
|
||||||
|
ctx.font = `${fontStyle} ${fontSize}px "${(await this.loadFont(font)) || ''}"`;
|
||||||
|
// ctx.font = `${fontStyle} ${fontSize}px "Arial"`;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.textAlign = textAlign;
|
||||||
|
const x = textAlign === 'center' ? canvas.width /2 :
|
||||||
|
textAlign === 'left' ? 0 : canvas.width;
|
||||||
|
text.split('\\n').forEach((line, index) => {
|
||||||
|
ctx.fillText(line, x, fontSize * (1 + index));
|
||||||
|
});
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,32 @@
|
|||||||
import Node from './Node';
|
import Node from './Node';
|
||||||
|
import {ImageVar, BooleanVar, NumberVar} from './io/AbstractIOSet';
|
||||||
|
|
||||||
export default class Disable extends Node {
|
const inputs = {
|
||||||
constructor() {
|
|
||||||
super('Disable', {
|
|
||||||
image: {
|
image: {
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
},
|
} as ImageVar,
|
||||||
disabled: {
|
disabled: {
|
||||||
type: 'Boolean',
|
type: 'Boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
} as BooleanVar,
|
||||||
}, {
|
};
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
image: {
|
image: {
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
},
|
} as ImageVar,
|
||||||
width: {
|
width: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
height: {
|
height: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
}, {});
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default class Disable extends Node<typeof inputs, typeof outputs> {
|
||||||
|
constructor() {
|
||||||
|
super('Disable', inputs, outputs, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _update() {
|
async _update() {
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import Node from './Node';
|
|
||||||
|
|
||||||
export default class Font extends Node {
|
|
||||||
constructor() {
|
|
||||||
super('Font', {
|
|
||||||
fontSize: {
|
|
||||||
type: 'Number',
|
|
||||||
min: 1,
|
|
||||||
default: 50,
|
|
||||||
},
|
|
||||||
fontStyle: {
|
|
||||||
type: 'String',
|
|
||||||
default: 'normal',
|
|
||||||
enum: [
|
|
||||||
'normal',
|
|
||||||
'bold',
|
|
||||||
'italic',
|
|
||||||
'bold italic',
|
|
||||||
]
|
|
||||||
},
|
|
||||||
font: {
|
|
||||||
type: 'Font',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
fontSize: {
|
|
||||||
type: 'Number',
|
|
||||||
},
|
|
||||||
fontStyle: {
|
|
||||||
type: 'String'
|
|
||||||
},
|
|
||||||
font: {
|
|
||||||
type: 'Font',
|
|
||||||
},
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _update() {
|
|
||||||
this.out.font.value = this.in.font.value;
|
|
||||||
this.out.fontStyle.value = this.in.fontStyle.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
47
src/nodes/Font.ts
Normal file
47
src/nodes/Font.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import Node from './Node';
|
||||||
|
import {FontVar, StringVar, NumberVar} from './io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
fontSize: {
|
||||||
|
type: 'Number',
|
||||||
|
min: 1,
|
||||||
|
default: 50,
|
||||||
|
} as NumberVar,
|
||||||
|
fontStyle: {
|
||||||
|
type: 'String',
|
||||||
|
default: 'normal',
|
||||||
|
enum: [
|
||||||
|
'normal',
|
||||||
|
'bold',
|
||||||
|
'italic',
|
||||||
|
'bold italic',
|
||||||
|
]
|
||||||
|
} as StringVar,
|
||||||
|
font: {
|
||||||
|
type: 'Font',
|
||||||
|
} as FontVar,
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
|
fontSize: {
|
||||||
|
type: 'Number',
|
||||||
|
} as NumberVar,
|
||||||
|
fontStyle: {
|
||||||
|
type: 'String'
|
||||||
|
} as StringVar,
|
||||||
|
font: {
|
||||||
|
type: 'Font',
|
||||||
|
} as FontVar,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Font extends Node<typeof inputs, typeof outputs> {
|
||||||
|
constructor() {
|
||||||
|
super('Font', inputs, outputs, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _update() {
|
||||||
|
this.out.font.value = this.in.font.value;
|
||||||
|
this.out.fontStyle.value = this.in.fontStyle.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import Node from '../Node';
|
|
||||||
|
|
||||||
export default class NumberBinaryOperation extends Node {
|
|
||||||
constructor() {
|
|
||||||
super('NumberBinaryOperation', {
|
|
||||||
x: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
operation: {
|
|
||||||
type: 'String',
|
|
||||||
default: '+',
|
|
||||||
enum: [
|
|
||||||
'+',
|
|
||||||
'-',
|
|
||||||
'⨉',
|
|
||||||
'÷',
|
|
||||||
'mod',
|
|
||||||
'max',
|
|
||||||
'min',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
result: {
|
|
||||||
type: 'Number'
|
|
||||||
},
|
|
||||||
}, {});
|
|
||||||
this.__canvas = document.createElement('canvas');
|
|
||||||
}
|
|
||||||
|
|
||||||
async _update() {
|
|
||||||
const x = this.in.x.value;
|
|
||||||
const y = this.in.y.value;
|
|
||||||
const op = this.in.operation.value;
|
|
||||||
const value =
|
|
||||||
op === '+' ? x + y :
|
|
||||||
op === '-' ? x - y :
|
|
||||||
op === '⨉' ? x * y :
|
|
||||||
op === '÷' ?
|
|
||||||
y === 0 ? NaN :
|
|
||||||
x / y :
|
|
||||||
op === 'mod' ? x % y :
|
|
||||||
op === 'max' ? Math.max(x, y) :
|
|
||||||
op === 'min' ? Math.min(x, y) :
|
|
||||||
NaN;
|
|
||||||
|
|
||||||
if (!isNaN(value)) {
|
|
||||||
this.out.result.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
63
src/nodes/Math/NumberBinaryOperation.ts
Normal file
63
src/nodes/Math/NumberBinaryOperation.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import Node from '../Node';
|
||||||
|
import {
|
||||||
|
NumberVar,
|
||||||
|
StringVar,
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
x: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
} as NumberVar,
|
||||||
|
y: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
} as NumberVar,
|
||||||
|
operation: {
|
||||||
|
type: 'String',
|
||||||
|
default: '+',
|
||||||
|
enum: [
|
||||||
|
'+',
|
||||||
|
'-',
|
||||||
|
'⨉',
|
||||||
|
'÷',
|
||||||
|
'mod',
|
||||||
|
'max',
|
||||||
|
'min',
|
||||||
|
]
|
||||||
|
} as StringVar
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
|
result: {
|
||||||
|
type: 'Number'
|
||||||
|
} as NumberVar,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NumberBinaryOperation extends Node<typeof inputs, typeof outputs> {
|
||||||
|
constructor() {
|
||||||
|
super('NumberBinaryOperation', inputs, outputs, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _update() {
|
||||||
|
const x = this.in.x.value;
|
||||||
|
const y = this.in.y.value;
|
||||||
|
const op = this.in.operation.value;
|
||||||
|
const value =
|
||||||
|
op === '+' ? x + y :
|
||||||
|
op === '-' ? x - y :
|
||||||
|
op === '⨉' ? x * y :
|
||||||
|
op === '÷' ?
|
||||||
|
y === 0 ? NaN :
|
||||||
|
x / y :
|
||||||
|
op === 'mod' ? x % y :
|
||||||
|
op === 'max' ? Math.max(x, y) :
|
||||||
|
op === 'min' ? Math.min(x, y) :
|
||||||
|
NaN;
|
||||||
|
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
this.out.result.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import Node from '../Node';
|
|
||||||
|
|
||||||
export default class Numbers extends Node {
|
|
||||||
constructor() {
|
|
||||||
super('Numbers', {
|
|
||||||
x: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
z: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
x: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
z: {
|
|
||||||
type: 'Number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
}, {});
|
|
||||||
this.__canvas = document.createElement('canvas');
|
|
||||||
}
|
|
||||||
|
|
||||||
async _update() {
|
|
||||||
this.out.x.value = this.in.x.value;
|
|
||||||
this.out.y.value = this.in.y.value;
|
|
||||||
this.out.z.value = this.in.z.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
49
src/nodes/Math/Numbers.ts
Normal file
49
src/nodes/Math/Numbers.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import Node from '../Node';
|
||||||
|
|
||||||
|
import {
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
x: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
} as NumberVar,
|
||||||
|
y: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
} as NumberVar,
|
||||||
|
z: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
} as NumberVar,
|
||||||
|
};
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
|
x: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
} as NumberVar,
|
||||||
|
y: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
} as NumberVar,
|
||||||
|
z: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
} as NumberVar,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default class Numbers extends Node<typeof inputs, typeof outputs> {
|
||||||
|
constructor() {
|
||||||
|
super('Numbers', inputs, outputs, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _update() {
|
||||||
|
this.out.x.value = this.in.x.value;
|
||||||
|
this.out.y.value = this.in.y.value;
|
||||||
|
this.out.z.value = this.in.z.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,15 +1,27 @@
|
|||||||
import {Inputs, Outputs} from './io';
|
import {Inputs, Outputs} from './io/index';
|
||||||
import uuidv4 from 'uuid/v4';
|
import uuidv4 from 'uuid/v4';
|
||||||
|
import {Variables, InputProperties, OutputProperties} from './io/AbstractIOSet';
|
||||||
import {MultiSubject} from '../helpers/listener';
|
import {MultiSubject} from '../helpers/listener';
|
||||||
|
|
||||||
export default class Node {
|
export default class Node<I extends Variables, O extends Variables> {
|
||||||
|
|
||||||
constructor(name, inputDefinition, outputDefiniton, options) {
|
name: string
|
||||||
|
uid: string
|
||||||
|
id: string
|
||||||
|
__in: Inputs<I> & InputProperties<I>
|
||||||
|
__out: Outputs<O> & OutputProperties<O>
|
||||||
|
__scheduledUpdate: boolean
|
||||||
|
__currentUpdate: Promise<any>
|
||||||
|
__running: boolean
|
||||||
|
_stopped: boolean
|
||||||
|
subject: MultiSubject
|
||||||
|
|
||||||
|
constructor(name: string, inputDefinition: I, outputDefiniton: O, options) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.uid = uuidv4();
|
this.uid = uuidv4();
|
||||||
this.id = uuidv4();
|
this.id = uuidv4();
|
||||||
this.__in = new Inputs(inputDefinition, this);
|
this.__in = new Inputs(inputDefinition, this) as Inputs<I> & InputProperties<I>;
|
||||||
this.__out = new Outputs(outputDefiniton, this);
|
this.__out = new Outputs(outputDefiniton, this) as Outputs<O> & OutputProperties<O>;
|
||||||
this.__in.update = (name) => this.__update([name]);
|
this.__in.update = (name) => this.__update([name]);
|
||||||
this.__scheduledUpdate = false;
|
this.__scheduledUpdate = false;
|
||||||
this.__currentUpdate = Promise.resolve();
|
this.__currentUpdate = Promise.resolve();
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
import WebGL from './WebGl';
|
import WebGL from './WebGl';
|
||||||
|
import {
|
||||||
|
NumberVar,
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
export default class BrightnessContrast extends WebGL {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('BrightnessContrast', {
|
|
||||||
brightness: {
|
brightness: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
} as NumberVar,
|
||||||
contrast: {
|
contrast: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
} as NumberVar,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default class BrightnessContrast extends WebGL<typeof inputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('BrightnessContrast', inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
get frag() {
|
get frag() {
|
||||||
@@ -1,39 +1,43 @@
|
|||||||
import WebGL from './WebGl';
|
import WebGL from './WebGl';
|
||||||
|
import {
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
export default class Channels extends WebGL {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('Channels', {
|
|
||||||
red: {
|
red: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
} as NumberVar,
|
||||||
green: {
|
green: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
} as NumberVar,
|
||||||
blue: {
|
blue: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
} as NumberVar,
|
||||||
amount: {
|
amount: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0,
|
default: 0,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
}
|
} as NumberVar
|
||||||
})
|
};
|
||||||
|
|
||||||
|
export default class Channels extends WebGL<typeof inputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('Channels', inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
get frag() {
|
get frag() {
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import WebGL from './WebGl';
|
import WebGL from './WebGl';
|
||||||
|
import {
|
||||||
|
NumberVar,
|
||||||
|
ColorVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
const hexColorTOvec3 = (val) => {
|
const hexColorTOvec3 = (val) => {
|
||||||
const match = val.match(/#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/);
|
const match = val.match(/#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/);
|
||||||
@@ -9,35 +13,37 @@ const hexColorTOvec3 = (val) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class GreenScreen extends WebGL {
|
const inputs = {
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('GreenScreen', {
|
|
||||||
balance: {
|
balance: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0.5,
|
default: 0.5,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
} as NumberVar,
|
||||||
screen: {
|
screen: {
|
||||||
type: 'Color',
|
type: 'Color',
|
||||||
default: '#2CD6A4',
|
default: '#2CD6A4',
|
||||||
},
|
} as ColorVar,
|
||||||
screenWeight: {
|
screenWeight: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
} as NumberVar,
|
||||||
clipBlack: {
|
clipBlack: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
} as NumberVar,
|
||||||
clipWhite: {
|
clipWhite: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
} as NumberVar,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
export default class GreenScreen extends WebGL<typeof inputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('GreenScreen', inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
get frag() {
|
get frag() {
|
||||||
@@ -1,23 +1,28 @@
|
|||||||
import WebGL from './WebGl';
|
import WebGL from './WebGl';
|
||||||
|
import {
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
export default class HSV extends WebGL {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('HSV', {
|
|
||||||
hue: {
|
hue: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
} as NumberVar,
|
||||||
saturation: {
|
saturation: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
} as NumberVar,
|
||||||
value: {
|
value: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
default: 1,
|
||||||
}
|
} as NumberVar
|
||||||
})
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default class HSV extends WebGL<typeof inputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('HSV', inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
get frag() {
|
get frag() {
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
import WebGL from './WebGl';
|
import WebGL from './WebGl';
|
||||||
|
import {
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
export default class Sepia extends WebGL {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super('Sepia', {
|
|
||||||
amount: {
|
amount: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
} as NumberVar,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
export default class Sepia extends WebGL<typeof inputs> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('Sepia', inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
get frag() {
|
get frag() {
|
||||||
@@ -1,25 +1,43 @@
|
|||||||
import Node from '../Node';
|
import Node from '../Node';
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
|
import {merge} from '../../utils';
|
||||||
import {canvasPool2D} from '../../canvas/CanvasPool';
|
import {canvasPool2D} from '../../canvas/CanvasPool';
|
||||||
|
import {
|
||||||
|
Variables,
|
||||||
|
ImageVar,
|
||||||
|
NumberVar
|
||||||
|
} from '../io/AbstractIOSet';
|
||||||
|
|
||||||
export default class WebGL extends Node {
|
const inputs = {
|
||||||
|
|
||||||
constructor(name, inputs={}) {
|
|
||||||
super(name, Object.assign({
|
|
||||||
image: {
|
image: {
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
},
|
} as ImageVar,
|
||||||
}, inputs), {
|
};
|
||||||
|
|
||||||
|
const outputs = {
|
||||||
image: {
|
image: {
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
},
|
} as ImageVar,
|
||||||
width: {
|
width: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
},
|
} as NumberVar,
|
||||||
height: {
|
height: {
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
}
|
} as NumberVar
|
||||||
});
|
}
|
||||||
|
|
||||||
|
export default class WebGL<I extends Variables> extends Node<I & (typeof inputs), (typeof outputs)> {
|
||||||
|
|
||||||
|
canvas: HTMLCanvasElement
|
||||||
|
program: WebGLProgram
|
||||||
|
|
||||||
|
constructor(name, inputDefinition: I) {
|
||||||
|
super(
|
||||||
|
name,
|
||||||
|
merge(inputs, inputDefinition),
|
||||||
|
outputs,
|
||||||
|
{}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@@ -75,7 +93,7 @@ export default class WebGL extends Node {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
fragShader(gl) {
|
fragShader(gl: WebGLRenderingContext) {
|
||||||
var shader = gl.createShader(gl.FRAGMENT_SHADER);
|
var shader = gl.createShader(gl.FRAGMENT_SHADER);
|
||||||
gl.shaderSource(shader, this.frag);
|
gl.shaderSource(shader, this.frag);
|
||||||
gl.compileShader(shader);
|
gl.compileShader(shader);
|
||||||
@@ -87,7 +105,7 @@ export default class WebGL extends Node {
|
|||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
vertShader(gl) {
|
vertShader(gl: WebGLRenderingContext) {
|
||||||
var shader = gl.createShader(gl.VERTEX_SHADER);
|
var shader = gl.createShader(gl.VERTEX_SHADER);
|
||||||
gl.shaderSource(shader, this.vert);
|
gl.shaderSource(shader, this.vert);
|
||||||
gl.compileShader(shader);
|
gl.compileShader(shader);
|
||||||
@@ -99,11 +117,7 @@ export default class WebGL extends Node {
|
|||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
compileProgram(gl: WebGLRenderingContext) {
|
||||||
*
|
|
||||||
* @param {WebGLRenderingContext} gl
|
|
||||||
*/
|
|
||||||
compileProgram(gl) {
|
|
||||||
const program = gl.createProgram();
|
const program = gl.createProgram();
|
||||||
|
|
||||||
gl.attachShader(program, this.vertShader(gl));
|
gl.attachShader(program, this.vertShader(gl));
|
||||||
@@ -122,7 +136,7 @@ export default class WebGL extends Node {
|
|||||||
return this.in.image.value;
|
return this.in.image.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
createTexture(gl) {
|
createTexture(gl: WebGLRenderingContext) {
|
||||||
const texture = gl.createTexture();
|
const texture = gl.createTexture();
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
|
||||||
@@ -136,7 +150,7 @@ export default class WebGL extends Node {
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
_createFrameBuffer(gl, {width, height}) {
|
_createFrameBuffer(gl: WebGLRenderingContext, {width, height}) {
|
||||||
const texture = this.createTexture(gl);
|
const texture = this.createTexture(gl);
|
||||||
|
|
||||||
const frameBuffer = gl.createFramebuffer();
|
const frameBuffer = gl.createFramebuffer();
|
||||||
@@ -152,7 +166,7 @@ export default class WebGL extends Node {
|
|||||||
|
|
||||||
_passes() {
|
_passes() {
|
||||||
return [
|
return [
|
||||||
(gl, program, image) => {
|
(gl: WebGLRenderingContext, program: WebGLProgram, image) => {
|
||||||
this._setParams(gl, program);
|
this._setParams(gl, program);
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
/**
|
export const createCanvas = (width: number, height: number):HTMLCanvasElement => {
|
||||||
*
|
|
||||||
* @param {Number} width
|
|
||||||
* @param {Number} height
|
|
||||||
* @returns {HTMLCanvasElement}
|
|
||||||
*/
|
|
||||||
export const createCanvas = (width, height) => {
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
@@ -12,19 +6,11 @@ export const createCanvas = (width, height) => {
|
|||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
type Canvas = HTMLCanvasElement | OffscreenCanvas;
|
||||||
* @typedef {HTMLImageElement|HTMLVideoElement} PaintableElement
|
|
||||||
* @typedef {{top: Number, left: Number, width: Number, height: Number}} Bounds
|
|
||||||
* @typedef {function(HTMLCanvasElement, PaintableElement, Bounds): HTMLCanvasElement} PaintToCanvasFunction
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
export type Bounds = {top: number, left: number, width: number, height: number};
|
||||||
|
|
||||||
/**
|
export const mediaSize = (media: HTMLImageElement|HTMLVideoElement|{width: number, height: number}) =>
|
||||||
*
|
|
||||||
* @param {PaintableElement|{width: Number, height: Number}} media
|
|
||||||
* @returns {{width: Number, height: Number}}
|
|
||||||
*/
|
|
||||||
export const mediaSize = (media) =>
|
|
||||||
(media === null) ? {width: null, height: null} :
|
(media === null) ? {width: null, height: null} :
|
||||||
(media instanceof HTMLVideoElement) ? {width: media.videoWidth, height: media.videoHeight} :
|
(media instanceof HTMLVideoElement) ? {width: media.videoWidth, height: media.videoHeight} :
|
||||||
(media instanceof HTMLImageElement) ? {width: media.naturalWidth, height: media.naturalHeight} :
|
(media instanceof HTMLImageElement) ? {width: media.naturalWidth, height: media.naturalHeight} :
|
||||||
@@ -35,7 +21,11 @@ export const mediaSize = (media) =>
|
|||||||
* @param {PaintableElement} media
|
* @param {PaintableElement} media
|
||||||
* @param {Bounds} param2
|
* @param {Bounds} param2
|
||||||
*/
|
*/
|
||||||
export const paintToCanvas = (canvas, media, {top, left, width, height}) => {
|
export const paintToCanvas = (
|
||||||
|
canvas: Canvas,
|
||||||
|
media: CanvasImageSource,
|
||||||
|
{top, left, width, height}: Bounds
|
||||||
|
) => {
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (canvas.width > 0 && canvas.height > 0 && width > 0 && height > 0) {
|
if (canvas.width > 0 && canvas.height > 0 && width > 0 && height > 0) {
|
||||||
ctx.drawImage(media, left, top, width, height);
|
ctx.drawImage(media, left, top, width, height);
|
||||||
@@ -13,7 +13,7 @@ export {default as HSV} from './WebGL/HSV';
|
|||||||
export {default as Sepia} from './WebGL/Sepia';
|
export {default as Sepia} from './WebGL/Sepia';
|
||||||
export {default as GreenScreen} from './WebGL/Greenscreen';
|
export {default as GreenScreen} from './WebGL/Greenscreen';
|
||||||
export {default as Channels} from './WebGL/Channels';
|
export {default as Channels} from './WebGL/Channels';
|
||||||
export {default as Blur} from './WebGL/Blur';
|
// export {default as Blur} from './WebGL/Blur';
|
||||||
// Math
|
// Math
|
||||||
export {default as Numbers} from './Math/Numbers';
|
export {default as Numbers} from './Math/Numbers';
|
||||||
export {default as NumberBinaryOperation} from './Math/NumberBinaryOperation';
|
export {default as NumberBinaryOperation} from './Math/NumberBinaryOperation';
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
default as AbstractIOSet,
|
||||||
|
Variable,
|
||||||
|
VariableValueType,
|
||||||
|
} from './AbstractIOSet';
|
||||||
import {isNil} from '../../utils';
|
import {isNil} from '../../utils';
|
||||||
|
|
||||||
export const defaultConstrainSatisfied = (value) => {
|
export const defaultConstrainSatisfied = (value) => {
|
||||||
@@ -32,9 +37,16 @@ export const valueConstrainsSatisfied = (definition, value) => {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class AbstractIO {
|
export default class AbstractIO<V extends Variable> {
|
||||||
|
|
||||||
constructor(name, definition, owner) {
|
__name: string
|
||||||
|
__owner: AbstractIOSet<any>
|
||||||
|
__definition: V
|
||||||
|
__value: VariableValueType<V>
|
||||||
|
label: string
|
||||||
|
__listeners: Function[]
|
||||||
|
|
||||||
|
constructor(name, definition: V, owner: AbstractIOSet<any>) {
|
||||||
this.__name = name;
|
this.__name = name;
|
||||||
this.__owner = owner
|
this.__owner = owner
|
||||||
this.__definition = definition;
|
this.__definition = definition;
|
||||||
@@ -64,18 +76,18 @@ export default class AbstractIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this.__getValue;
|
return this.__getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
set value(val) {
|
set value(val) {
|
||||||
this.__setValue(val);
|
this.__setValue(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
__getValue() {
|
__getValue(): VariableValueType<V> {
|
||||||
return (valueConstrainsSatisfied(this.__definition, this.__value)) ? this.__value : this.__defaultValue;
|
return (valueConstrainsSatisfied(this.__definition, this.__value)) ? this.__value : this.__defaultValue as VariableValueType<V>;
|
||||||
}
|
}
|
||||||
|
|
||||||
__setValue(value) {
|
__setValue(value: VariableValueType<V>) {
|
||||||
if (valueConstrainsSatisfied(this.__definition, value)) {
|
if (valueConstrainsSatisfied(this.__definition, value)) {
|
||||||
this.__value = value;
|
this.__value = value;
|
||||||
}
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
export default class AbstractIOSet {
|
|
||||||
|
|
||||||
constructor(variables, owner) {
|
|
||||||
this.__values = {};
|
|
||||||
this.__owner = owner;
|
|
||||||
this.variables = variables;
|
|
||||||
|
|
||||||
for (let name of Object.keys(this.variables)) {
|
|
||||||
if (variables[name] === null) {
|
|
||||||
delete this.variables[name];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.__values[name] = this.__createProperty(name, variables[name]);
|
|
||||||
Object.defineProperty(this, name, {
|
|
||||||
get() {
|
|
||||||
return this.__values[name];
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
for (let name of Object.keys(this.variables)) {
|
|
||||||
yield this.__values[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__createProperty(name, definition) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
update(changed) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
122
src/nodes/io/AbstractIOSet.ts
Normal file
122
src/nodes/io/AbstractIOSet.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import Node from '../Node';
|
||||||
|
import Input from './Input';
|
||||||
|
import Output from './Output';
|
||||||
|
import AbstractIO from './AbstractIO';
|
||||||
|
|
||||||
|
type ImageType = HTMLCanvasElement
|
||||||
|
| OffscreenCanvas
|
||||||
|
| HTMLImageElement
|
||||||
|
| HTMLVideoElement
|
||||||
|
| ImageData
|
||||||
|
| ImageBitmap;
|
||||||
|
|
||||||
|
export interface NumberVar {
|
||||||
|
type: 'Number',
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
step?: number
|
||||||
|
default?: VariableValueType<NumberVar>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StringVar {
|
||||||
|
type: 'String',
|
||||||
|
default?: VariableValueType<StringVar>
|
||||||
|
enum?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BooleanVar {
|
||||||
|
type: 'Boolean',
|
||||||
|
default?: VariableValueType<BooleanVar>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColorVar {
|
||||||
|
type: 'Color',
|
||||||
|
default?: VariableValueType<ColorVar>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FontVar {
|
||||||
|
type: 'Font',
|
||||||
|
default?: VariableValueType<FontVar>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImageVar {
|
||||||
|
type: 'Image'
|
||||||
|
default?: VariableValueType<ImageVar>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Variable = NumberVar
|
||||||
|
| StringVar
|
||||||
|
| BooleanVar
|
||||||
|
| ColorVar
|
||||||
|
| FontVar
|
||||||
|
| ImageVar
|
||||||
|
|
||||||
|
export interface Variables {
|
||||||
|
[key: string]: Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VariablesType<V extends Variables> = {
|
||||||
|
[K in keyof V]: V[K] extends Variable ? V[K] : never
|
||||||
|
};
|
||||||
|
|
||||||
|
export type VariableValueType<T> = T extends NumberVar ? number :
|
||||||
|
T extends BooleanVar ? boolean :
|
||||||
|
T extends ImageVar ? ImageType :
|
||||||
|
string
|
||||||
|
|
||||||
|
export type IOProperties<V extends Variables> = {
|
||||||
|
[K in keyof V]: AbstractIO<V[K]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InputProperties<V extends Variables> = {
|
||||||
|
[K in keyof V]: Input<V[K]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OutputProperties<V extends Variables> = {
|
||||||
|
[K in keyof V]: Output<V[K]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AbstractIOSet<V extends Variables> {
|
||||||
|
|
||||||
|
__values: any
|
||||||
|
__owner: Node<any, any>
|
||||||
|
variables: V
|
||||||
|
|
||||||
|
constructor(variables: V, owner: Node<any, any>) {
|
||||||
|
this.__values = {};
|
||||||
|
this.__owner = owner;
|
||||||
|
this.variables = variables;
|
||||||
|
|
||||||
|
for (let name of Object.keys(this.variables)) {
|
||||||
|
if (variables[name] === null) {
|
||||||
|
delete this.variables[name];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.__values[name] = this.__createProperty(name, variables[name]);
|
||||||
|
Object.defineProperty(this, name, {
|
||||||
|
get() {
|
||||||
|
return this.__values[name];
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return `${this.__owner.id}-set`;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[Symbol.iterator]() {
|
||||||
|
for (let name of Object.keys(this.variables)) {
|
||||||
|
yield this.__values[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__createProperty(name, definition) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
update(changed) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
import AbstractIO from './AbstractIO';
|
import AbstractIO from './AbstractIO';
|
||||||
|
import Output from './Output';
|
||||||
|
import {
|
||||||
|
Variable,
|
||||||
|
VariableValueType,
|
||||||
|
} from './AbstractIOSet';
|
||||||
import {serialize, deserialize} from './serializer';
|
import {serialize, deserialize} from './serializer';
|
||||||
|
|
||||||
export default class Input extends AbstractIO {
|
export default class Input<V extends Variable> extends AbstractIO<V> {
|
||||||
|
|
||||||
|
__output: Output<any>
|
||||||
|
__onchangelistener: Function
|
||||||
|
|
||||||
constructor(name, definition, owner) {
|
constructor(name, definition, owner) {
|
||||||
super(name, definition, owner);
|
super(name, definition, owner);
|
||||||
@@ -13,11 +21,11 @@ export default class Input extends AbstractIO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value(): VariableValueType<V> {
|
||||||
return this.__getValue();
|
return this.__getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
set value(value) {
|
set value(value: VariableValueType<V>) {
|
||||||
this.__setValue(value);
|
this.__setValue(value);
|
||||||
this.__owner.update(this.name);
|
this.__owner.update(this.name);
|
||||||
this.__notifyListeners();
|
this.__notifyListeners();
|
||||||
@@ -27,7 +35,7 @@ export default class Input extends AbstractIO {
|
|||||||
return this.__output;
|
return this.__output;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(output) {
|
connect(output: Output<any>) {
|
||||||
if (output && output.type === this.type) {
|
if (output && output.type === this.type) {
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
this.__output = output
|
this.__output = output
|
||||||
@@ -36,7 +44,7 @@ export default class Input extends AbstractIO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect(output) {
|
disconnect(output?) {
|
||||||
if (this.__output) {
|
if (this.__output) {
|
||||||
this.__output.offchange(this.__onchangelistener);
|
this.__output.offchange(this.__onchangelistener);
|
||||||
this.__output = null;
|
this.__output = null;
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
import AbstractIOSet from './AbstractIOSet';
|
import Node from '../Node';
|
||||||
|
import {default as AbstractIOSet, Variables} from './AbstractIOSet';
|
||||||
|
|
||||||
export default class Inputs extends AbstractIOSet {
|
export default class Inputs<V extends Variables> extends AbstractIOSet<V> {
|
||||||
|
|
||||||
constructor(variables, owner) {
|
constructor(variables: V, owner: Node<any, any>) {
|
||||||
super(variables, owner);
|
super(variables, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import AbstractIO from './AbstractIO';
|
import AbstractIO from './AbstractIO';
|
||||||
|
import {
|
||||||
|
Variable,
|
||||||
|
} from './AbstractIOSet';
|
||||||
|
|
||||||
export default class Output extends AbstractIO {
|
export default class Output<V extends Variable> extends AbstractIO<V> {
|
||||||
|
|
||||||
constructor(name, definition, owner) {
|
constructor(name, definition, owner) {
|
||||||
super(name, definition, owner);
|
super(name, definition, owner);
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import Node from '../Node';
|
||||||
import Output from './Output';
|
import Output from './Output';
|
||||||
import AbstractIOSet from './AbstractIOSet';
|
import {default as AbstractIOSet, Variables} from './AbstractIOSet';
|
||||||
|
|
||||||
export default class Outputs extends AbstractIOSet {
|
export default class Outputs<V extends Variables> extends AbstractIOSet<V> {
|
||||||
constructor(variables, owner) {
|
constructor(variables: V, owner: Node<any, any>) {
|
||||||
super(variables, owner);
|
super(variables, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
13
src/utils.js
13
src/utils.js
@@ -1,13 +0,0 @@
|
|||||||
export const isNil = (val) => val === null || val === undefined;
|
|
||||||
|
|
||||||
const waitForImage = (img) => new Promise((resolve) => img.addEventListener('load', resolve, {once: true}));
|
|
||||||
|
|
||||||
export const waitForMedia = async (media) => {
|
|
||||||
if (media instanceof Image && !media.complete) {
|
|
||||||
await waitForImage(media);
|
|
||||||
}
|
|
||||||
return media;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setImmediate = (fn) => Promise.resolve().then(fn);
|
|
||||||
|
|
||||||
16
src/utils.ts
Normal file
16
src/utils.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const isNil = (val) => val === null || val === undefined;
|
||||||
|
|
||||||
|
const waitForImage = (img:HTMLImageElement): Promise<HTMLImageElement> => new Promise((resolve) => img.addEventListener('load', () => resolve(img), {once: true}));
|
||||||
|
|
||||||
|
export const waitForMedia = async <T>(media:T):Promise<T> => {
|
||||||
|
if (media instanceof Image && !media.complete) {
|
||||||
|
await waitForImage(media);
|
||||||
|
}
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setImmediate = <T extends (...args: any) => any>(fn:T):Promise<ReturnType<T>> => Promise.resolve().then(fn);
|
||||||
|
|
||||||
|
export const merge = (...objects) => {
|
||||||
|
return objects.reduce((acc, nxt) => Object.assign(acc, nxt), {});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user