Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(grabCanvas): Adds grab-canvas as a plugin #612

Merged
merged 9 commits into from
Jul 1, 2021
8 changes: 7 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@
<NDIConfig />
</gl-component>
</gl-stack>

<gl-component title="Plugins" :closable="false">
<Plugins />
</gl-component>
</gl-stack>

<gl-stack title="Preview Stack">
Expand Down Expand Up @@ -101,6 +105,7 @@ import InfoView from "@/components/InfoView";
import Search from "@/components/Search";
import FrameRateDialog from "@/components/dialogs/FrameRateDialog";
import ErrorWatcher from "@/components/ErrorWatcher";
import Plugins from "@/components/Plugins";

import getNextName from "@/application/utils/get-next-name";
import constants from "@/application/constants";
Expand All @@ -125,7 +130,8 @@ export default {
ModuleInspector,
Search,
FrameRateDialog,
ErrorWatcher
ErrorWatcher,
Plugins
},

data() {
Expand Down
185 changes: 185 additions & 0 deletions src/application/plugins/grab-canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
const mappingCanvas = new OffscreenCanvas(1, 1);
mappingCanvas.title = "mappingCanvas";

let timeout = 0;
let connection = undefined;
let outputContext = null;
let reconnect = false;

const mappingContext = mappingCanvas.getContext("2d", {
// Boolean that indicates if the canvas contains an alpha channel.
// If set to false, the browser now knows that the backdrop is always opaque,
// which can speed up drawing of transparent content and images.
// (lights don't have an alpha channel, so let's drop it)
alpha: false,
desynchronized: true,
imageSmoothingEnabled: false
});

export default {
name: "Grab Canvas",
props: {
mappingWidth: {
type: "int",
default: 7,
min: 1,
max: 1024,
step: 1,
abs: true
},

mappingHeight: {
type: "int",
default: 7,
min: 1,
max: 1024,
step: 1,
abs: true
},

url: {
type: "text",
default: "ws://localhost:3006/modV"
},

reconnectAfter: {
type: "int",
default: 4000,
min: 1000,
max: 60000,
step: 1,
abs: true
},

shouldReconnect: {
type: "bool",
default: true
}
},

async init({ store, props }) {
if (!outputContext) {
outputContext = await store.dispatch("outputs/getAuxillaryOutput", {
name: "Fixture Canvas",
group: "Plugins",
canvas: mappingCanvas,
context: mappingContext,
reactToResize: false
});
}

mappingCanvas.width = props.mappingWidth;
mappingCanvas.height = props.mappingHeight;

reconnect = props.shouldReconnect;

this.setupSocket(props);
},

shutdown() {
this.stopReconnect();
this.closeConnection();
},

/**
* Create a WebSocket for example to luminave
*/
setupSocket(props) {
const { url, reconnectAfter } = props;

// Close an old connection
this.closeConnection();

// Create a new connection
connection = new WebSocket(url);

// Listen for errors (e.g. could not connect)
connection.addEventListener("error", event => {
console.error("grab-canvas: WebSocket: Error:", event);

// Reconnect is allowed
if (reconnect) {
// Reconnect after a specific amount of time
timeout = setTimeout(() => {
this.setupSocket(props);
}, reconnectAfter);
}
});

// Connection is opened
connection.addEventListener("open", () => {
console.info("grab-canvas: WebSocket: Opened");
});

connection.addEventListener("close", () => {
console.info("grab-canvas: WebSocket: Closed");
});
},

/**
* Close the WebSocket connection and stop reconnecting
*/
closeConnection() {
clearTimeout(timeout);

if (connection !== undefined) {
connection.close();
}

connection = undefined;
},

/**
* Stop reconnecting to WebSocket
*/
stopReconnect() {
reconnect = false;
clearTimeout(timeout);
},

postProcessFrame({ canvas, props }) {
mappingContext.clearRect(0, 0, canvas.width, canvas.height);
mappingContext.drawImage(
canvas,
0,
0,
canvas.width,
canvas.height,
0,
0,
props.mappingWidth,
props.mappingHeight
);

const imageData = mappingContext.getImageData(
0,
0,
props.mappingWidth,
props.mappingHeight
);
const { data } = imageData;
const arrayData = Array.from(data);
const rgbArray = arrayData.filter((value, index) => (index + 1) % 4 !== 0);

this.send(rgbArray);
},

/**
* Send data to WebSocket if connection is established
* @param {Object} data
*/
send(data) {
// Connection is established
if (connection !== undefined && connection.readyState === 1) {
const message = {
_type: "modV",
colors: data
};

const messageString = JSON.stringify(message, null, 2);

// Send JSON message
connection.send(messageString);
}
}
};
5 changes: 4 additions & 1 deletion src/application/worker/index.worker.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-env worker node */

const { default: constants } = require("../constants");

let lastKick = false;
Expand All @@ -9,6 +8,8 @@ async function start() {
const fs = require("fs");
const store = require("./store").default;
const loop = require("./loop").default;
const grabCanvasPlugin = require("../plugins/grab-canvas").default;

const { tick: frameTick } = require("./frame-counter");
const { getFeatures, setFeatures } = require("./audio-features");
// const featureAssignmentPlugin = require("../plugins/feature-assignment");
Expand Down Expand Up @@ -45,6 +46,8 @@ async function start() {
});
});

store.dispatch("plugins/add", grabCanvasPlugin);

const renderers = require.context("../renderers/", false, /\.js$/);

const rendererKeys = renderers.keys();
Expand Down
19 changes: 17 additions & 2 deletions src/application/worker/loop.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ function loop(delta, features, fftOutput) {
const preProcessFrameFunctionsLength = preProcessFrameFunctions.length;

for (let i = 0; i < preProcessFrameFunctionsLength; ++i) {
preProcessFrameFunctions[i].preProcessFrame({ features, store });
preProcessFrameFunctions[i].preProcessFrame({
features,
store,
props: preProcessFrameFunctions[i].$props
});
}

const renderersWithTick = store.getters["renderers/renderersWithTick"];
Expand Down Expand Up @@ -338,7 +342,18 @@ function loop(delta, features, fftOutput) {
}
}

// main.getImageData(0, 0, main.canvas.width, main.canvas.height);
const postProcessFrameFunctions =
store.getters["plugins/postProcessFrame"] || [];
const postProcessFrameFunctionsLength = postProcessFrameFunctions.length;

for (let i = 0; i < postProcessFrameFunctionsLength; ++i) {
postProcessFrameFunctions[i].postProcessFrame({
canvas: main.canvas,
features,
store,
props: postProcessFrameFunctions[i].$props
});
}
}

export default loop;
12 changes: 8 additions & 4 deletions src/application/worker/store/modules/outputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const actions = {
{ dispatch },
{
canvas = new OffscreenCanvas(300, 300),
context,
name,
type = "2d",
options = {},
Expand All @@ -63,13 +64,16 @@ const actions = {
options.storage = "discardable";
}

const context = canvas.getContext(type, options);
canvas.width = width;
canvas.height = height;
if (!context) {
canvas.width = width;
canvas.height = height;
}

const canvasContext = context || canvas.getContext(type, options);

const outputContext = await dispatch("addAuxillaryOutput", {
name,
context,
context: canvasContext,
reactToResize,
group,
id
Expand Down
Loading