🔥 removing graphfx.app code and unused libs
This commit is contained in:
5160
package-lock.json
generated
5160
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -1,41 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "graphfx",
|
"name": "graphfx",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"description": "Graph image processing pipeline",
|
"description": "Graph image processing pipeline",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"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",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"keywords": ["image processing", "image", "filters"],
|
"keywords": [
|
||||||
|
"image processing",
|
||||||
|
"image",
|
||||||
|
"filters"
|
||||||
|
],
|
||||||
"author": "standa.fifik@gmail.com",
|
"author": "standa.fifik@gmail.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.2.2",
|
"@babel/core": "^7.2.2",
|
||||||
"@babel/preset-env": "^7.2.3",
|
"@babel/preset-env": "^7.2.3",
|
||||||
"@types/jest": "^23.3.10",
|
"@types/jest": "^23.3.10",
|
||||||
"@types/webpack": "^4.4.22",
|
|
||||||
"babel-jest": "^23.6.0",
|
"babel-jest": "^23.6.0",
|
||||||
"babel-loader": "^8.0.4",
|
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"canvas": "^2.2.0",
|
"jest": "^23.6.0"
|
||||||
"css-loader": "^1.0.1",
|
|
||||||
"file-loader": "^2.0.0",
|
|
||||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
|
||||||
"html-webpack-plugin": "^3.2.0",
|
|
||||||
"jest": "^23.6.0",
|
|
||||||
"vue-loader": "^15.4.2",
|
|
||||||
"vue-style-loader": "^4.1.2",
|
|
||||||
"vue-template-compiler": "^2.5.17",
|
|
||||||
"webpack": "^4.28.3",
|
|
||||||
"webpack-cli": "^3.1.2",
|
|
||||||
"webpack-dev-server": "^3.1.14"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"interactjs": "^1.3.4",
|
"uuid": "^3.3.2"
|
||||||
"lodash": "^4.17.11",
|
|
||||||
"uuid": "^3.3.2",
|
|
||||||
"vue": "^2.5.21",
|
|
||||||
"webgl-utils": "^1.0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
src/graph.js
Normal file
83
src/graph.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import * as nodes from './nodes';
|
||||||
|
|
||||||
|
|
||||||
|
export default class Graph {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.nodes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return this.nodes.map(({node, x, y}) => ({
|
||||||
|
node: node.serialize(),
|
||||||
|
x, y,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
static deserialize(nodes) {
|
||||||
|
const graph = new Graph();
|
||||||
|
graph.nodes = createNodes(nodes);
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
findIOByLabel(label) {
|
||||||
|
const results = [];
|
||||||
|
const isMatch = label instanceof RegExp ? (val) => label.test(val) : (val) => val === label;
|
||||||
|
for (let {node} of this.nodes) {
|
||||||
|
for (let name of Object.keys(node.in.variables)) {
|
||||||
|
if (isMatch(node.in[name].label)) {
|
||||||
|
results.push(node.in[name])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let name of Object.keys(node.out.variables)) {
|
||||||
|
if (isMatch(node.out[name].label)) {
|
||||||
|
results.push(node.out[name])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const createNode = ({name, options, id}) => {
|
||||||
|
try {
|
||||||
|
const node = new nodes[name](options);
|
||||||
|
node.deserialize({id, options});
|
||||||
|
return node;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to create node', name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createNodes = (nodes) => {
|
||||||
|
nodes = nodes.map(({node, x, y}) => {
|
||||||
|
const nodeInstance = createNode(node);
|
||||||
|
return ({
|
||||||
|
node: nodeInstance,
|
||||||
|
x, y,
|
||||||
|
connect(outputs) {
|
||||||
|
try {
|
||||||
|
nodeInstance.reconnect(node, outputs);
|
||||||
|
} catch(err) {
|
||||||
|
console.error('Can\'t recoonect', nodeInstance, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).filter(({node}) => node);
|
||||||
|
|
||||||
|
const outputsById = nodes
|
||||||
|
.map(({node}) => Object.values(node.out.__values))
|
||||||
|
.reduce((acc, nxt) => acc.concat(nxt), [])
|
||||||
|
.reduce((acc, nxt) => {
|
||||||
|
acc[nxt.id] = nxt;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return nodes.map(({node, x, y, connect}) => {
|
||||||
|
connect(outputsById);
|
||||||
|
return {node,x,y};
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export {default as Graph} from './graph'
|
||||||
|
export {default as Node} from './nodes/Node';
|
||||||
33
src/nodes/Canvas2d/Fill.js
Normal file
33
src/nodes/Canvas2d/Fill.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
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);
|
||||||
|
this.__out.image.value = await createImageBitmap(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,16 @@ export default class Resize extends Canvas2d {
|
|||||||
min: 1,
|
min: 1,
|
||||||
default: 50,
|
default: 50,
|
||||||
},
|
},
|
||||||
|
fontStyle: {
|
||||||
|
type: 'String',
|
||||||
|
default: 'normal',
|
||||||
|
enum: [
|
||||||
|
'normal',
|
||||||
|
'bold',
|
||||||
|
'italic',
|
||||||
|
'bold italic',
|
||||||
|
]
|
||||||
|
},
|
||||||
font: {
|
font: {
|
||||||
type: 'String',
|
type: 'String',
|
||||||
default: 'Arial',
|
default: 'Arial',
|
||||||
@@ -33,17 +43,32 @@ export default class Resize extends Canvas2d {
|
|||||||
color: {
|
color: {
|
||||||
type: 'Color',
|
type: 'Color',
|
||||||
default: '#FFFFFF'
|
default: '#FFFFFF'
|
||||||
|
},
|
||||||
|
textAlign: {
|
||||||
|
type: 'String',
|
||||||
|
default: 'center',
|
||||||
|
enum: [
|
||||||
|
'left',
|
||||||
|
'center',
|
||||||
|
'right',
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}, {});
|
}, {});
|
||||||
|
this.__update();
|
||||||
}
|
}
|
||||||
|
|
||||||
async render({font, fontSize, text, color, width, height}, canvas, ctx) {
|
async render({font, fontSize, text, color, width, height, textAlign, fontStyle}, canvas, ctx) {
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
ctx.font = `${fontSize}px ${font}`;
|
ctx.font = `${fontStyle} ${fontSize}px ${font}`;
|
||||||
ctx.fillStyle = color;
|
ctx.fillStyle = color;
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = textAlign;
|
||||||
ctx.fillText(text, canvas.width/2, canvas.height/2);
|
const x = textAlign === 'center' ? canvas.width /2 :
|
||||||
|
textAlign === 'left' ? 0 : canvas.width;
|
||||||
|
console.log(text.split('\\n'));
|
||||||
|
text.split('\\n').forEach((line, index) => {
|
||||||
|
ctx.fillText(line, x, fontSize * (1 + index));
|
||||||
|
});
|
||||||
this.__out.image.value = await createImageBitmap(canvas);
|
this.__out.image.value = await createImageBitmap(canvas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,7 @@ export default class Node {
|
|||||||
name: this.name,
|
name: this.name,
|
||||||
options: {
|
options: {
|
||||||
in: this.in.serialize(),
|
in: this.in.serialize(),
|
||||||
|
out: this.out.serialize(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,11 +53,12 @@ export default class Node {
|
|||||||
this.in[name].connect(outputs[output]);
|
this.in[name].connect(outputs[output]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let name of Object.keys(options.in)) {
|
for (let name of Object.keys(options.out)) {
|
||||||
const {value} = options.in[name];
|
this.out[name].deserialize(options.out[name]);
|
||||||
if (value) {
|
|
||||||
this.in[name].deserialize(value);
|
|
||||||
}
|
}
|
||||||
|
for (let name of Object.keys(options.in)) {
|
||||||
|
console.log(options.in[name]);
|
||||||
|
this.in[name].deserialize(options.in[name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
58
src/nodes/WebGL/Tint.js
Normal file
58
src/nodes/WebGL/Tint.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import WebGL from './WebGl';
|
||||||
|
|
||||||
|
|
||||||
|
export default class Sepia extends WebGL {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('Tint', {
|
||||||
|
amount: {
|
||||||
|
type: 'Number',
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
type: 'Color',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get frag() {
|
||||||
|
return `
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
// our texture
|
||||||
|
uniform sampler2D u_image;
|
||||||
|
|
||||||
|
// the texCoords passed in from the vertex shader.
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
uniform float amount;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 color = texture2D(u_image, v_texCoord);
|
||||||
|
float r = color.r;
|
||||||
|
float g = color.g;
|
||||||
|
float b = color.b;
|
||||||
|
|
||||||
|
color.r = min(1.0, (r * (1.0 - (0.607 * amount))) + (g * (0.769 * amount)) + (b * (0.189 * amount)));
|
||||||
|
color.g = min(1.0, (r * 0.349 * amount) + (g * (1.0 - (0.314 * amount))) + (b * 0.168 * amount));
|
||||||
|
color.b = min(1.0, (r * 0.272 * amount) + (g * 0.534 * amount) + (b * (1.0 - (0.869 * amount))));
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
get amount() {
|
||||||
|
return this.in.amount.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {WebGLRenderingContext} gl
|
||||||
|
* @param {*} program
|
||||||
|
*/
|
||||||
|
_setParams(gl, program) {
|
||||||
|
gl.uniform1f(gl.getUniformLocation(program, "amount"), this.amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import Node from '../Node';
|
import Node from '../Node';
|
||||||
import webglUtils from '../lib/webgl-utils';
|
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
|
|
||||||
export default class WebGL extends Node {
|
export default class WebGL extends Node {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ export default class ToBlob extends Node {
|
|||||||
console.log(last(devices));
|
console.log(last(devices));
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: {
|
video: {
|
||||||
width: { min: 1024, ideal: 1280, max: 1920 },
|
width: { min: 1024, ideal: 1920, max: 1920 },
|
||||||
height: { min: 776, ideal: 720, max: 1080 },
|
height: { min: 776, ideal: 1080, max: 1080 },
|
||||||
deviceId: last(devices).deviceId
|
deviceId: last(devices).deviceId
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,23 +5,11 @@
|
|||||||
* @returns {HTMLCanvasElement}
|
* @returns {HTMLCanvasElement}
|
||||||
*/
|
*/
|
||||||
export const createCanvas = (width, height) => {
|
export const createCanvas = (width, height) => {
|
||||||
if (window.name !== 'nodejs') {
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
} else {
|
|
||||||
const {createCanvas} = require('canvas');
|
|
||||||
const canvas = createCanvas(width, height);
|
|
||||||
canvas.toBlob = (callback, type, quality) => {
|
|
||||||
canvas.toBuffer((err, data) => {
|
|
||||||
callback(new Blob([data]));
|
|
||||||
}, type)
|
|
||||||
canvas
|
|
||||||
}
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export {default as Webcam} from './Webcam';
|
export {default as Webcam} from './Webcam';
|
||||||
export {default as Resize} from './Canvas2d/Resize';
|
export {default as Resize} from './Canvas2d/Resize';
|
||||||
export {default as Compose} from './Canvas2d/Compose';
|
export {default as Compose} from './Canvas2d/Compose';
|
||||||
|
export {default as Fill} from './Canvas2d/Fill';
|
||||||
// export {default as ImageIdentity} from './Canvas2d/ImageIdentity';
|
// export {default as ImageIdentity} from './Canvas2d/ImageIdentity';
|
||||||
export {default as Text} from './Canvas2d/Text';
|
export {default as Text} from './Canvas2d/Text';
|
||||||
export {default as BrightnessContrast} from './WebGL/BrightnessContrast';
|
export {default as BrightnessContrast} from './WebGL/BrightnessContrast';
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export default class AbstractIO {
|
|||||||
this.__owner = owner
|
this.__owner = owner
|
||||||
this.__definition = definition;
|
this.__definition = definition;
|
||||||
this.__value = null;
|
this.__value = null;
|
||||||
|
this.label = null;
|
||||||
this.__listeners = [];
|
this.__listeners = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,15 +45,17 @@ export default class Input extends AbstractIO {
|
|||||||
|
|
||||||
serialize() {
|
serialize() {
|
||||||
return {
|
return {
|
||||||
|
label: this.label,
|
||||||
value: this.__output ? null : serialize(this.__value),
|
value: this.__output ? null : serialize(this.__value),
|
||||||
output: this.__output ? this.__output.id: null,
|
output: this.__output ? this.__output.id: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(value) {
|
deserialize({value, label}) {
|
||||||
const deserializedValue = deserialize(value);
|
const deserializedValue = deserialize(value);
|
||||||
if (deserializedValue) {
|
if (deserializedValue) {
|
||||||
this.value = deserializedValue;
|
this.value = deserializedValue;
|
||||||
}
|
}
|
||||||
|
this.label = label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,4 +37,15 @@ export default class Output extends AbstractIO {
|
|||||||
this.__listeners = this.__listeners
|
this.__listeners = this.__listeners
|
||||||
.filter((l) => l !== listener);
|
.filter((l) => l !== listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return {
|
||||||
|
label: this.label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize({label}) {
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,12 @@ export default class Outputs extends AbstractIOSet {
|
|||||||
return `${this.__owner.id}-out`;
|
return `${this.__owner.id}-out`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
serialize() {
|
serialize() {
|
||||||
return null;
|
const res = {};
|
||||||
|
for (let name of Object.keys(this.variables)) {
|
||||||
|
res[name] = this.__values[name].serialize();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,79 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app">
|
|
||||||
<div class="app__toolbar">
|
|
||||||
<select v-model="selectedSaveSlot">
|
|
||||||
<option
|
|
||||||
v-for="save in saveSlots"
|
|
||||||
:key="save"
|
|
||||||
:value="save"
|
|
||||||
>{{save}}</option>
|
|
||||||
</select>
|
|
||||||
<button @click="save">Save</button>
|
|
||||||
<button @click="load">Load</button>
|
|
||||||
</div>
|
|
||||||
<Graph
|
|
||||||
class="app__scene"
|
|
||||||
:graph="graph"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import Graph from './Graph.vue';
|
|
||||||
import {load, save} from '../persistent';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Graph,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
graph: [],
|
|
||||||
currentSelectedSaveSlot: localStorage.getItem('IFP__SELECTED_SAVE_SLOT') || 'FIRST',
|
|
||||||
saveSlots: [
|
|
||||||
'FIRST',
|
|
||||||
'SECOND',
|
|
||||||
'THIRD',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
selectedSaveSlot: {
|
|
||||||
get() {
|
|
||||||
return this.currentSelectedSaveSlot;
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
this.currentSelectedSaveSlot = val;
|
|
||||||
localStorage.setItem('IFP__SELECTED_SAVE_SLOT', val);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
load() {
|
|
||||||
this.graph = load(this.selectedSaveSlot);
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
save(this.selectedSaveSlot, this.graph);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.app {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app__toolbar {
|
|
||||||
padding: 10px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ul
|
|
||||||
v-if="open"
|
|
||||||
class="context-menu"
|
|
||||||
:style="{
|
|
||||||
'--left': `${currentPosition.x}px`,
|
|
||||||
'--top': `${currentPosition.y}px`,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
v-for="item in items"
|
|
||||||
:key="item.label"
|
|
||||||
class="context-menu__item"
|
|
||||||
@click="select(item)"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
position: {
|
|
||||||
type: null,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
open: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
currentPosition() {
|
|
||||||
return Object.assign({x: 0, y: 0}, this.position);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
select(item) {
|
|
||||||
this.$emit('selected', {
|
|
||||||
x: this.currentPosition.x,
|
|
||||||
y: this.currentPosition.y,
|
|
||||||
item,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.context-menu {
|
|
||||||
--left: 0;
|
|
||||||
--top: 0;
|
|
||||||
position: absolute;
|
|
||||||
left: var(--left);
|
|
||||||
top: var(--top);
|
|
||||||
background-color: white;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu__item {
|
|
||||||
border-bottom: 1px solid
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import interact from 'interactjs';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
abstract: true,
|
|
||||||
render() {
|
|
||||||
try {
|
|
||||||
return this.$slots.default[0];
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error('Exactly one child component needed.');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
const el = this.$slots.default[0].elm;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
interact(el)
|
|
||||||
.draggable({
|
|
||||||
onmove: (event) => {
|
|
||||||
this.$emit('move', event);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="graph">
|
|
||||||
<canvas
|
|
||||||
v-if="boundingRect"
|
|
||||||
class="graph__bg"
|
|
||||||
ref="bg"
|
|
||||||
:width="boundingRect.width"
|
|
||||||
:height="boundingRect.height"
|
|
||||||
@click="showContextMenu"
|
|
||||||
></canvas>
|
|
||||||
<Draggable
|
|
||||||
v-for="(node, index) in graph"
|
|
||||||
:key="index"
|
|
||||||
@move="updateNodePosition(node, $event)"
|
|
||||||
>
|
|
||||||
<Node
|
|
||||||
class="graph__node"
|
|
||||||
:style="{transform: `translate(${node.x}px, ${node.y}px)`}"
|
|
||||||
:node="node.node"
|
|
||||||
ref="node"
|
|
||||||
:selectedInput="selectedInput"
|
|
||||||
:selectedOutput="selectedOutput"
|
|
||||||
@outputSelected="selectedOutput = $event"
|
|
||||||
@inputSelected="selectedInput = $event"
|
|
||||||
@removeNode="removeNode"
|
|
||||||
/>
|
|
||||||
</Draggable>
|
|
||||||
<ContextMenu
|
|
||||||
:open="!!contextMenuPosition"
|
|
||||||
:position="contextMenuPosition"
|
|
||||||
:items="availableNodes"
|
|
||||||
@selected="addNode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import * as nodes from '../../src/nodes';
|
|
||||||
import Node from './Node.vue';
|
|
||||||
import Draggable from './Drag/Draggable.js';
|
|
||||||
import ContextMenu from './ContextMenu.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ContextMenu,
|
|
||||||
Draggable,
|
|
||||||
Node,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
graph: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
selectedOutput: null,
|
|
||||||
selectedInput: null,
|
|
||||||
boundingRect: null,
|
|
||||||
contextMenuPosition: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
selectedOutput() {
|
|
||||||
this.tryConnectIO();
|
|
||||||
},
|
|
||||||
selectedInput() {
|
|
||||||
this.tryConnectIO();
|
|
||||||
},
|
|
||||||
graph: {
|
|
||||||
deep: true,
|
|
||||||
handler() {
|
|
||||||
this.drawConnections();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showContextMenu(event) {
|
|
||||||
const {offsetX, offsetY} = event;
|
|
||||||
if (this.contextMenuPosition) {
|
|
||||||
this.contextMenuPosition = null;
|
|
||||||
} else {
|
|
||||||
this.contextMenuPosition = {
|
|
||||||
x: offsetX,
|
|
||||||
y: offsetY,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateNodePosition(node, {clientX, clientY, x0, y0}) {
|
|
||||||
const x = clientX - this.$el.offsetLeft;
|
|
||||||
const y = clientY - this.$el.offsetTop;
|
|
||||||
console.log('updateNodePosition', x, y)
|
|
||||||
node.x = x;
|
|
||||||
node.y = y;
|
|
||||||
},
|
|
||||||
tryConnectIO() {
|
|
||||||
if (this.selectedInput && this.selectedOutput) {
|
|
||||||
this.selectedInput.connect(this.selectedOutput);
|
|
||||||
this.selectedInput = null;
|
|
||||||
this.selectedOutput = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
recomputeSize() {
|
|
||||||
this.boundingRect = this.$el.getBoundingClientRect();
|
|
||||||
},
|
|
||||||
drawConnections() {
|
|
||||||
try {
|
|
||||||
const flattenObjectArray = (ary) =>
|
|
||||||
ary.reduce((acc, nxt) => Object.assign(acc, nxt), {});
|
|
||||||
const inputs = flattenObjectArray(this.$refs.node
|
|
||||||
.map((vm) => vm.inputPositions()));
|
|
||||||
const outputs = flattenObjectArray(this.$refs.node
|
|
||||||
.map((vm) => vm.outputPositions()));
|
|
||||||
|
|
||||||
const bg = this.$refs.bg;
|
|
||||||
const ctx = bg.getContext('2d');
|
|
||||||
ctx.clearRect(0, 0, bg.width, bg.height);
|
|
||||||
ctx.strokeStyle = "#eaa11a";
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
for (let input of Object.values(inputs)) {
|
|
||||||
if (!input.output) continue;
|
|
||||||
const output = outputs[input.output.id];
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(input.rect.left, input.rect.top);
|
|
||||||
const halfWay = {x: (input.rect.left + output.rect.right)/2, y: (input.rect.y + output.rect.y)/2};
|
|
||||||
ctx.bezierCurveTo(
|
|
||||||
Math.min(halfWay.x, input.rect.left - Math.max(Math.abs(halfWay.x - input.rect.left), 50)), input.rect.top,
|
|
||||||
Math.max(halfWay.x, output.rect.right + Math.max(Math.abs(halfWay.x - output.rect.right), 50)), output.rect.top,
|
|
||||||
// Math.min(halfWay.x, output.rect.right - (halfWay.x - output.rect.right)), output.rect.top,
|
|
||||||
//halfWay.x, output.rect.top,
|
|
||||||
output.rect.right, output.rect.top
|
|
||||||
);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('drawConnection', err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addNode({x, y, item: {node}}) {
|
|
||||||
this.graph.push({
|
|
||||||
x, y,
|
|
||||||
node: new node(),
|
|
||||||
});
|
|
||||||
this.contextMenuPosition = null;
|
|
||||||
},
|
|
||||||
removeNode(nodeToRemove) {
|
|
||||||
const index = this.graph.findIndex(({node}) => node === nodeToRemove);
|
|
||||||
console.log('nodeIndex', index);
|
|
||||||
this.graph.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
availableNodes() {
|
|
||||||
return Object.keys(nodes)
|
|
||||||
.map((name) => ({
|
|
||||||
label: name,
|
|
||||||
node: nodes[name],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.recomputeSize();
|
|
||||||
this.drawConnections();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.graph {
|
|
||||||
position: relative;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background: linear-gradient(#333333, #151515);
|
|
||||||
}
|
|
||||||
|
|
||||||
.graph__node {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.graph__bg {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<canvas></canvas>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export const getStream = (callback) => {
|
|
||||||
navigator.getUserMedia({video: true}, (stream) => {
|
|
||||||
console.log(stream)
|
|
||||||
/** @type {HTMLVideoElement} */
|
|
||||||
const video = document.getElementById('Input');
|
|
||||||
video.srcObject = stream;
|
|
||||||
video.play();
|
|
||||||
let lastFrameTime = null;
|
|
||||||
const feedLoop = () => {
|
|
||||||
if (lastFrameTime !== video.currentTime) {
|
|
||||||
lastFrameTime = video.currentTime;
|
|
||||||
callback(video);
|
|
||||||
|
|
||||||
}
|
|
||||||
requestAnimationFrame(feedLoop);
|
|
||||||
};
|
|
||||||
feedLoop();
|
|
||||||
}, console.error.bind(console))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getStream((video) => resize.in.image.value = video);
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mounted() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="node"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="node__title"
|
|
||||||
>{{node.name}}</div>
|
|
||||||
<div class="node__vars node__vars--input">
|
|
||||||
<div
|
|
||||||
v-for="(type, name) in node.in.variables"
|
|
||||||
:key="name"
|
|
||||||
:ref="`in-${name}`"
|
|
||||||
class="node__var node-var node-var--input"
|
|
||||||
:class="{'node-var--selected': node.in[name] === selectedInput }"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="node-var__label"
|
|
||||||
@click="$emit('inputSelected', node.in[name])"
|
|
||||||
>
|
|
||||||
{{ name }} <!--<i>{{typeName(type)}}</i> -->
|
|
||||||
<span
|
|
||||||
v-if="node.in[name].output"
|
|
||||||
@click.stop="node.in[name].disconnect()"
|
|
||||||
>×</span>
|
|
||||||
</div>
|
|
||||||
<Value
|
|
||||||
:io="node.in[name]"
|
|
||||||
direction="input"
|
|
||||||
@change="(value) => node.in[name].value = value"
|
|
||||||
class="node-var__value"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="node__vars node__vars--output">
|
|
||||||
<div
|
|
||||||
v-for="(type, name) in node.out.variables"
|
|
||||||
:key="name"
|
|
||||||
:ref="`out-${name}`"
|
|
||||||
class="node__var node-var node-var--output"
|
|
||||||
:class="{'node-var--selected': node.out[name] === selectedOutput }"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="node-var__label"
|
|
||||||
@click="$emit('outputSelected', node.out[name])"
|
|
||||||
>{{ name }} <!--<i>{{typeName(type)}}</i>--></div>
|
|
||||||
<Value
|
|
||||||
:io="node.out[name]"
|
|
||||||
direction="output"
|
|
||||||
class="node-var__value"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="node__remove"
|
|
||||||
@click="removeNode"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import Value from './Value';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Value,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
selectedInput: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
selectedOutput: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
typeName(type) {
|
|
||||||
return Array.isArray(type) ? 'Option' : type;
|
|
||||||
},
|
|
||||||
outputPositions() {
|
|
||||||
const outs = {};
|
|
||||||
for (let out in this.node.out.variables) {
|
|
||||||
outs[this.node.out[out].id] = {
|
|
||||||
rect: this.$refs[`out-${out}`][0].getBoundingClientRect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outs;
|
|
||||||
},
|
|
||||||
inputPositions() {
|
|
||||||
const ins = {};
|
|
||||||
for (let i in this.node.in.variables) {
|
|
||||||
ins[this.node.in[i].id] = {
|
|
||||||
output: this.node.in[i].output,
|
|
||||||
rect: this.$refs[`in-${i}`][0].getBoundingClientRect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ins;
|
|
||||||
},
|
|
||||||
removeNode() {
|
|
||||||
this.$emit('removeNode', this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.node-var {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
.node-var__label {
|
|
||||||
width: 5em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-var__value {
|
|
||||||
width: 5em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-var--selected {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-var--input {
|
|
||||||
border-left: 5px solid green;
|
|
||||||
padding-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-var--output {
|
|
||||||
border-right: 5px solid orange;
|
|
||||||
padding-right: 4px;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node {
|
|
||||||
margin: 5px;
|
|
||||||
padding: 5px 0;
|
|
||||||
display: grid;
|
|
||||||
flex-direction: row;
|
|
||||||
background-color: #EEE;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 1px 1px 5px rgba(0,0,0,0.35);
|
|
||||||
grid-gap: 5px;
|
|
||||||
grid-template-areas:
|
|
||||||
"title title"
|
|
||||||
"in out";
|
|
||||||
}
|
|
||||||
.node__title {
|
|
||||||
grid-area: title;
|
|
||||||
text-align: center;
|
|
||||||
border-bottom: 1px solid black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node__var {
|
|
||||||
margin-bottom: 2px
|
|
||||||
}
|
|
||||||
|
|
||||||
.node__vars--input {
|
|
||||||
grid-area: in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node__vars--output {
|
|
||||||
grid-area: out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node__remove {
|
|
||||||
position: absolute;
|
|
||||||
top: -20px;
|
|
||||||
right: -20px;
|
|
||||||
background-color: red;
|
|
||||||
color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="image-value"
|
|
||||||
@click="onClick"
|
|
||||||
@mouseenter="isPreviewVisible = true"
|
|
||||||
@mouseleave="isPreviewVisible = false"
|
|
||||||
>
|
|
||||||
<canvas class="image-value__thumb" ref="thumb" />
|
|
||||||
<canvas class="image-value__preview" ref="preview" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import {paintToCanvas, mediaSize} from '../../../src/nodes/canvas.js'
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
io: {
|
|
||||||
type: null,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: null,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
direction: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isRunning: true,
|
|
||||||
isPreviewVisible: false,
|
|
||||||
/*redrawThumb: _.throttle(() => {
|
|
||||||
console.log('redrawThum')
|
|
||||||
}, 500, {leading: true, trailing: true}),*/
|
|
||||||
redrawThumb: _.throttle(() => this.redrawCanvas(this.$refs.thumb), 500, {leading: true, trailing: true}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.redraw();
|
|
||||||
this.io.onchange(this.onChange);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
isPreviewVisible(isVisible) {
|
|
||||||
if (isVisible) {
|
|
||||||
this.redraw();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isRunning: {
|
|
||||||
handler() {
|
|
||||||
this.redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.isRunning = false;
|
|
||||||
if (this.io) {
|
|
||||||
this.io.offchange(this.onChange)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onChange() {
|
|
||||||
this.redraw();
|
|
||||||
},
|
|
||||||
redrawCanvas(canvas) {
|
|
||||||
const value = this.io.value;
|
|
||||||
/** @type {HTMLCanvasElement} */
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!value) {
|
|
||||||
canvas.width = 1;
|
|
||||||
canvas.height = 1;
|
|
||||||
ctx.clearRect(0, 0, 1, 1);
|
|
||||||
} else {
|
|
||||||
const {width, height} = mediaSize(value);
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
paintToCanvas(canvas, value, {
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width, height,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
redraw() {
|
|
||||||
if (this.isRunning) {
|
|
||||||
this.redrawThumb();
|
|
||||||
// this.redrawCanvas(this.$refs.thumb);
|
|
||||||
if (this.isPreviewVisible) {
|
|
||||||
this.redrawCanvas(this.$refs.preview)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick() {
|
|
||||||
if (this.direction === 'output') {
|
|
||||||
this.download();
|
|
||||||
} else {
|
|
||||||
this.selectFile();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectFile() {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
window.$$input = input;
|
|
||||||
input.type = 'file';
|
|
||||||
input.accept = 'image/*';
|
|
||||||
input.onchange = (event) => {
|
|
||||||
const files = event.target.files;
|
|
||||||
if (files.length) {
|
|
||||||
const fr = new FileReader()
|
|
||||||
fr.onload = (event) => {
|
|
||||||
const image = new Image();
|
|
||||||
image.onload = () => {
|
|
||||||
this.$emit('change', image);
|
|
||||||
this.onChange();
|
|
||||||
}
|
|
||||||
image.src = event.target.result;
|
|
||||||
}
|
|
||||||
fr.readAsDataURL(files[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input.click();
|
|
||||||
},
|
|
||||||
download() {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = this.$refs.preview.toDataURL();
|
|
||||||
a.download = 'sample.png';
|
|
||||||
a.click();
|
|
||||||
console.log('download');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.image-value {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.image-value__thumb, .image-value__preview {
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJ0lEQVQoU2NsaGj4z4AG6uvr0YUYGIeCwv///2N4prGxEdMzQ0AhAChTL3KV95+lAAAAAElFTkSuQmCC);
|
|
||||||
background-repeat: repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-value__thumb {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
object-fit: contain;
|
|
||||||
|
|
||||||
box-shadow: 0 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-value__preview {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 30vw;
|
|
||||||
height: 30vh;
|
|
||||||
object-fit: contain;
|
|
||||||
background-color: black;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-value__thumb:hover ~ .image-value__preview {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<template>
|
|
||||||
<select v-model="io.value">
|
|
||||||
<option
|
|
||||||
v-for="value in io.definition.enum"
|
|
||||||
:value="value"
|
|
||||||
:key="value"
|
|
||||||
>{{ value }}</option>
|
|
||||||
</select>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
type: {
|
|
||||||
type: null,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
io: {
|
|
||||||
type: null,
|
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<template>
|
|
||||||
<input
|
|
||||||
v-model="value"
|
|
||||||
:type="
|
|
||||||
type === 'Number' ? 'number' :
|
|
||||||
type === 'Color' ? 'color' :
|
|
||||||
'text'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
io: {
|
|
||||||
type: null,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
value: {
|
|
||||||
get() {
|
|
||||||
return this.io.value;
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
if (this.type === 'Number') {
|
|
||||||
this.io.value = parseFloat(val)
|
|
||||||
} else {
|
|
||||||
this.io.value = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onChange($event) {
|
|
||||||
console.log('change', $event);
|
|
||||||
this.value = $event.target.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import StringValue from './String.vue';
|
|
||||||
import ImageValue from './Image.vue';
|
|
||||||
import SelectValue from './Select.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
io: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
direction: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render(h) {
|
|
||||||
return h(
|
|
||||||
this.io.definition.enum ? SelectValue :
|
|
||||||
this.io.type === 'Image' ? ImageValue :
|
|
||||||
this.io.type === 'String' ? StringValue :
|
|
||||||
this.io.type === 'Number' ? StringValue :
|
|
||||||
this.io.type === 'Color' ? StringValue :
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
props: {
|
|
||||||
io: this.io,
|
|
||||||
value: this.io.value,
|
|
||||||
type: this.io.type,
|
|
||||||
direction: this.direction
|
|
||||||
},
|
|
||||||
on: {
|
|
||||||
change: (value) => {
|
|
||||||
this.$emit('change', value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
export default [
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
name: "Webcam"
|
|
||||||
},
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
"name": "Resize"
|
|
||||||
},
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
"name": "BrightnessContrast"
|
|
||||||
},
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
"name": "HSV"
|
|
||||||
},
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
"name": "Sepia"
|
|
||||||
},
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": {
|
|
||||||
"name": "Compose"
|
|
||||||
},
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
}
|
|
||||||
];
|
|
||||||
29
ui/graph.js
29
ui/graph.js
@@ -1,29 +0,0 @@
|
|||||||
import * as nodes from '../src/nodes';
|
|
||||||
import {sampleImage, getStream} from './helpers';
|
|
||||||
/*
|
|
||||||
const resize = new nodes.Resize({
|
|
||||||
width: 1000,
|
|
||||||
height: 1000,
|
|
||||||
method: 'cover'
|
|
||||||
});
|
|
||||||
|
|
||||||
const brightnessContrast = new nodes.BrightnessContrast({})
|
|
||||||
const hsv = new nodes.HSV({})
|
|
||||||
const sepia = new nodes.Sepia({})
|
|
||||||
|
|
||||||
const compose = new nodes.Compose({
|
|
||||||
width: 1200,
|
|
||||||
height: 1800,
|
|
||||||
});
|
|
||||||
|
|
||||||
compose.in.fgX.value = 100;
|
|
||||||
compose.in.fgY.value = 100;
|
|
||||||
compose.in.bg.value = sampleImage(1200, 1800);
|
|
||||||
|
|
||||||
brightnessContrast.in.image.connect(resize.out.image);
|
|
||||||
hsv.in.image.connect(brightnessContrast.out.image);
|
|
||||||
sepia.in.image.connect(hsv.out.image);
|
|
||||||
compose.in.fg.connect(sepia.out.image);
|
|
||||||
|
|
||||||
getStream((video) => resize.in.image.value = video);
|
|
||||||
*/
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
export const sampleImage = (width, height) => {
|
|
||||||
const c = document.createElement('canvas');
|
|
||||||
c.width = width;
|
|
||||||
c.height = height;
|
|
||||||
const ctx = c.getContext('2d');
|
|
||||||
|
|
||||||
const grd = ctx.createLinearGradient(0, 0, width, height);
|
|
||||||
grd.addColorStop(0, "black");
|
|
||||||
grd.addColorStop(1, "white");
|
|
||||||
|
|
||||||
ctx.fillStyle = grd;
|
|
||||||
ctx.fillRect(0, 0, width, height);
|
|
||||||
return c;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const getStream = (callback) => {
|
|
||||||
navigator.getUserMedia({video: true}, (stream) => {
|
|
||||||
console.log(stream)
|
|
||||||
/** @type {HTMLVideoElement} */
|
|
||||||
const video = document.getElementById('Input');
|
|
||||||
video.srcObject = stream;
|
|
||||||
video.play();
|
|
||||||
let lastFrameTime = null;
|
|
||||||
const feedLoop = () => {
|
|
||||||
if (lastFrameTime !== video.currentTime) {
|
|
||||||
lastFrameTime = video.currentTime;
|
|
||||||
callback(video);
|
|
||||||
|
|
||||||
}
|
|
||||||
requestAnimationFrame(feedLoop);
|
|
||||||
};
|
|
||||||
feedLoop();
|
|
||||||
}, console.error.bind(console))
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>UI</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<video id="Input" style="width: 100px"></video>
|
|
||||||
<div id="app"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
import App from './Components/App.vue';
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#app',
|
|
||||||
render(h) {
|
|
||||||
return h(App)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
|
|
||||||
import * as nodes from '../src/nodes';
|
|
||||||
import defaultValues from './defaultGraph';
|
|
||||||
|
|
||||||
|
|
||||||
export const createNode = ({name, options, id}) => {
|
|
||||||
try {
|
|
||||||
const node = new nodes[name](options);
|
|
||||||
node.deserialize({id, options});
|
|
||||||
return node;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to create node', name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createNodes = (nodes) => {
|
|
||||||
nodes = nodes.map(({node, x, y}) => {
|
|
||||||
const nodeInstance = createNode(node);
|
|
||||||
return ({
|
|
||||||
node: nodeInstance,
|
|
||||||
x, y,
|
|
||||||
connect(outputs) {
|
|
||||||
try {
|
|
||||||
nodeInstance.reconnect(node, outputs);
|
|
||||||
} catch(err) {
|
|
||||||
console.error('Can\'t recoonect', nodeInstance, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).filter(({node}) => node);
|
|
||||||
|
|
||||||
const outputsById = nodes
|
|
||||||
.map(({node}) => Object.values(node.out.__values))
|
|
||||||
.reduce((acc, nxt) => acc.concat(nxt), [])
|
|
||||||
.reduce((acc, nxt) => {
|
|
||||||
acc[nxt.id] = nxt;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return nodes.map(({node, x, y, connect}) => {
|
|
||||||
connect(outputsById);
|
|
||||||
return {node,x,y};
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const serializeNodes = (nodes) =>
|
|
||||||
nodes.map(({node, x, y}) => ({
|
|
||||||
node: node.serialize(),
|
|
||||||
x, y,
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
export const load = (name) => {
|
|
||||||
try {
|
|
||||||
return createNodes(JSON.parse(localStorage.getItem(`IFP_GRAPH_${name}`)) || defaultValues);
|
|
||||||
} catch (err) {
|
|
||||||
return createNodes(defaultValues);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export const save = (name, graph) => localStorage.setItem(`IFP_GRAPH_${name}`, JSON.stringify(serializeNodes(graph)));
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
|
|
||||||
|
|
||||||
function resolve(dir) {
|
|
||||||
return path.join(__dirname, '..', dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {import('webpack').Configuration}
|
|
||||||
*/
|
|
||||||
const config = {
|
|
||||||
entry: {
|
|
||||||
main: './ui/index.js',
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, 'dist'),
|
|
||||||
publicPath: '/',
|
|
||||||
filename: 'js/[name].js',
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: [
|
|
||||||
'.css',
|
|
||||||
'.js',
|
|
||||||
'.vue',
|
|
||||||
'.ts',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.vue$/,
|
|
||||||
loader: 'vue-loader',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
include: [
|
|
||||||
resolve('src'),
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
presets: [
|
|
||||||
["@babel/preset-env"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
use: [
|
|
||||||
{loader: 'vue-style-loader'},
|
|
||||||
{loader: 'css-loader'},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new VueLoaderPlugin(),
|
|
||||||
new FriendlyErrorsWebpackPlugin(),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: 'ui/index.html',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
devServer: {
|
|
||||||
contentBase: path.resolve(__dirname, 'dist/'),
|
|
||||||
disableHostCheck: true,
|
|
||||||
historyApiFallback: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
Reference in New Issue
Block a user