🎉
This commit is contained in:
24
ui/Components/Graph.vue
Normal file
24
ui/Components/Graph.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<Node
|
||||
v-for="(node, index) in graph"
|
||||
:key="index"
|
||||
:node="node"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Node from './Node.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Node,
|
||||
},
|
||||
props: {
|
||||
graph: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
46
ui/Components/Node.vue
Normal file
46
ui/Components/Node.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div
|
||||
style="
|
||||
border: 2px solid black;
|
||||
margin: 5px;
|
||||
padding: 2px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
"
|
||||
>
|
||||
<div style="flex: 1">
|
||||
<strong>Inputs:</strong>
|
||||
<div v-for="(type, name) in node.in.variables" :key="name">
|
||||
<div>{{ name }} <i>{{typeName(type)}}</i></div>
|
||||
<Value :type="type" :value="node.in[name]" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1">
|
||||
<strong>Outputs:</strong>
|
||||
<div v-for="(type, name) in node.out.variables" :key="name">
|
||||
<div>{{ name }} <i>{{typeName(type)}}</i></div>
|
||||
<Value :type="type" :value="node.out[name]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Value from './Value';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Value,
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
typeName(type) {
|
||||
return Array.isArray(type) ? 'Option' : type;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
63
ui/Components/Value/Image.vue
Normal file
63
ui/Components/Value/Image.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<canvas
|
||||
style="
|
||||
width: 150px;
|
||||
box-shadow: 0 0 6px;
|
||||
"
|
||||
@click="isRunning = !isRunning"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import {paintToCanvas, mediaSize} from '../../../src/nodes/canvas.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: null,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(value) {
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isRunning: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.redraw();
|
||||
this.value.onchange(this.redraw);
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
redraw() {
|
||||
if (this.isRunning) {
|
||||
const value = this.value.value;
|
||||
/** @type {HTMLCanvasElement} */
|
||||
const canvas = this.$el;
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
23
ui/Components/Value/Select.vue
Normal file
23
ui/Components/Value/Select.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<select v-model="value.value">
|
||||
<option
|
||||
v-for="t in type"
|
||||
:value="t"
|
||||
:key="t"
|
||||
>{{ t }}</option>
|
||||
</select>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: null,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
13
ui/Components/Value/String.vue
Normal file
13
ui/Components/Value/String.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<input v-model="value.value">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: null,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
24
ui/Components/Value/index.js
Normal file
24
ui/Components/Value/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import StringValue from './String.vue';
|
||||
import ImageValue from './Image.vue';
|
||||
import SelectValue from './Select.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: null,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
return h(
|
||||
this.type === 'Image' ? ImageValue :
|
||||
this.type === 'Number' ? StringValue :
|
||||
Array.isArray(this.type) ? SelectValue :
|
||||
null,
|
||||
{props: {value: this.value, type: this.type}})
|
||||
}
|
||||
};
|
||||
36
ui/graph.js
Normal file
36
ui/graph.js
Normal file
@@ -0,0 +1,36 @@
|
||||
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);
|
||||
|
||||
export default [
|
||||
resize,
|
||||
brightnessContrast,
|
||||
hsv,
|
||||
sepia,
|
||||
compose,
|
||||
];
|
||||
35
ui/helpers.js
Normal file
35
ui/helpers.js
Normal file
@@ -0,0 +1,35 @@
|
||||
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))
|
||||
}
|
||||
13
ui/index.html
Normal file
13
ui/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!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>
|
||||
11
ui/index.js
Normal file
11
ui/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import Vue from 'vue';
|
||||
import Graph from './Components/Graph.vue';
|
||||
|
||||
import graph from './graph';
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render(h) {
|
||||
return h(Graph, {props: {graph}})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user