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

Using p5.js in Node runtime #1

Open
dipamsen opened this issue Feb 14, 2021 · 3 comments
Open

Using p5.js in Node runtime #1

dipamsen opened this issue Feb 14, 2021 · 3 comments

Comments

@dipamsen
Copy link
Member

dipamsen commented Feb 14, 2021

TL;DR: scroll down to the bottom for the templates


After studying the source code of p5.js, this is what I have gathered so far:

These variables are available in browser window and unavailable in node

p5 Instance Mode

For making p5 work in instance mode I created this template:

// Adapted from:
// ! Heart Curve
// ! Daniel Shiffman
// https://thecodingtrain.com/CodingChallenges/134-heart-curve.html
// https://youtu.be/oUBAi9xQ2X4
// https://editor.p5js.org/codingtrain/sketches/egvieHyt0

const w = 400,
  h = 400;
setupWindow(w, h);

const p5 = require("p5");

new p5((p) => {
  const heart = [];
  let a = 0;
  p.setup = () => {
    p.createCanvas(w, h);
    while (true) {
      const r = p.height / 40;
      const x = r * 16 * p.pow(p.sin(a), 3);
      const y = -r * (13 * p.cos(a) - 5 * p.cos(2 * a) - 2 * p.cos(3 * a) - p.cos(4 * a));
      heart.push(p.createVector(x, y));
      // So that it stops
      if (a > p.TWO_PI) {
        break;
      }

      a += 0.1;
    }
    p.background(0);
    p.translate(p.width / 2, p.height / 2);

    p.stroke(255);
    p.strokeWeight(2);
    p.fill(150, 0, 100);
    p.beginShape();
    for (let v of heart) {
      p.vertex(v.x, v.y);
    }
    p.endShape();
    saveAsPNG(p, "filename");
  };
});

async function saveAsPNG(p5Inst, filename = "sketch", exit = true) {
  // --- save as png function code ---
}

function setupWindow(w = 100, h = 100) {
  // --- setup window function code ---
}

In this way, p5.js can be used in Instance Mode to work with node-canvas

Click here to see the codes for saveAsPNG and setupWindow:

saveAsPNG - Saves the canvas Image to filname using fs

async function saveAsPNG(p5Inst, filename = "sketch", exit = true) {
  const fs = require("fs");
  const buffer = p5Inst._renderer.drawingContext.canvas.toBuffer()
  await fs.promises.writeFile(filename + ".png", buffer)
  if (exit) process.exit(0)
}

setupWindow - Add necessary variables to global for p5 to run

function setupWindow(w = 100, h = 100) {
    // Import required stuff
    const { createCanvas } = require("canvas");
    const { JSDOM } = require("jsdom");
    const { performance } = require("perf_hooks");

    // All the global properties will be available to p5
    global.window = global;

    // Use JSDOM to simulate DOM methods
    const dom = new JSDOM();
    global.document = dom.window.document;

    const nodeCanvas = createCanvas(w, h);

    // p5 will use the methods on nodeCanvas context (overriding JSDOM's getContext function)
    dom.window.HTMLCanvasElement.prototype.getContext = (type) => {
    return nodeCanvas.getContext(type);
    };

    // p5 uses window.performance
    global.performance = performance;

    // window.screen is used to determine displayWidth and displayHeight
    // in this case it will be undefined
    global.screen = {};

    // p5 uses events "load" and "error"
    // Using JSDOM equivalent
    global.addEventListener = dom.window.addEventListener.bind(dom.window);
    global.removeEventListener = dom.window.removeEventListener.bind(dom.window);

    // Navigator.userAgent is used by p5 to polyfill methods in older browsers (safari)
    global.navigator = { userAgent: "node" };

    // when we declare a function in node, it doesn't pollute global scope
    // So implicitly write code so that p5 can access these functions on global/window.
    // UNCOMMENT NEXT TWO LINES WHEN USING GLOBAL MODE
    // global.setup = setup
    // global.draw = draw

}

Going further - Global Mode!

This is the template for using p5.js in node with Global Mode:

// Adapted from:
// ! Heart Curve
// ! Daniel Shiffman
// https://thecodingtrain.com/CodingChallenges/134-heart-curve.html
// https://youtu.be/oUBAi9xQ2X4
// https://editor.p5js.org/codingtrain/sketches/egvieHyt0

// width and height to be passed both in createCanvas and in setupWindow
setupWindow(400, 400);
// Note that in global mode even though we don't use the p5 variable, it still needs to be imported.
const p5 = require("p5");

const heart = [];
let a = 0;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  while (true) {
    const r = height / 40;
    const x = r * 16 * pow(sin(a), 3);
    const y = -r * (13 * cos(a) - 5 * cos(2 * a) - 2 * cos(3 * a) - cos(4 * a));
    heart.push(createVector(x, y));
    // So that it stops
    if (a > TWO_PI) {
      break;
    }

    a += 0.1;
  }
  background(0);
  translate(width / 2, height / 2);

  stroke(255);
  strokeWeight(2);
  fill(150, 0, 100);
  beginShape();
  for (let v of heart) {
    vertex(v.x, v.y);
  }
  endShape();
  // in global mode, p5 is attatched to the window (global in node)
  // the third arg stands for whether to exit the process
  // in this case, if false is passed, then this sketch will run forever, until manually cancelled (because this is the draw loop)
  saveAsPNG(global, "globalimg", true);
}

In this case as well, the functions saveAsPNG and setupWindow should be appended at the end of the file, with the last two lines of setupWindow uncommented.

This is the output of the global mode code
globalimg

Use these Templates!

To Use these templates, first install these node-modules
npm i p5 canvas jsdom

@dipamsen
Copy link
Member Author

dipamsen commented Feb 14, 2021

Example Discord Bot using p5.js: DiscordP5Canvas

P.S. There is an issue with using this method of p5, whenever any kind of error is thrown, (undefined variable/invalid syntax, etc) in the sketch, only this error will show in the console:

Error: Uncaught [TypeError: Cannot read property '_ownerDocument' of undefined]
    at reportException (jsdom\lib\jsdom\living\helpers\runtime-script-errors.js:62:24)
    at innerInvokeEventListeners (jsdom\lib\jsdom\living\events\EventTarget-impl.js:333:9)
    at invokeEventListeners (jsdom\lib\jsdom\living\events\EventTarget-impl.js:274:3)
    at DocumentImpl._dispatch (jsdom\lib\jsdom\living\events\EventTarget-impl.js:221:9)
    at fireAnEvent (jsdom\lib\jsdom\living\helpers\events.js:18:36)
    at dispatchEvent (jsdom\lib\jsdom\living\nodes\Document-impl.js:475:9)
    at jsdom\lib\jsdom\living\nodes\Document-impl.js:480:11
    at new Promise (<anonymous>)
    at onLoad (jsdom\lib\jsdom\living\nodes\Document-impl.js:478:14)
    at Object.check (jsdom\lib\jsdom\browser\resources\resource-queue.js:76:23) TypeError: Cannot read property '_ownerDocument' of undefined
    at innerInvokeEventListeners (jsdom\lib\jsdom\living\events\EventTarget-impl.js:327:26)
    at invokeEventListeners (jsdom\lib\jsdom\living\events\EventTarget-impl.js:274:3)
    at EventTargetImpl._dispatch (jsdom\lib\jsdom\living\events\EventTarget-impl.js:221:9)
    at fireAnEvent (jsdom\lib\jsdom\living\helpers\events.js:18:36)
    at Document.<anonymous> (jsdom\lib\jsdom\browser\Window.js:776:9)
    at Document.callTheUserObjectsOperation (jsdom\lib\jsdom\living\generated\EventListener.js:26:30)
    at innerInvokeEventListeners (jsdom\lib\jsdom\living\events\EventTarget-impl.js:318:25)
    at invokeEventListeners (jsdom\lib\jsdom\living\events\EventTarget-impl.js:274:3)
    at DocumentImpl._dispatch (jsdom\lib\jsdom\living\events\EventTarget-impl.js:221:9)
    at fireAnEvent (jsdom\lib\jsdom\living\helpers\events.js:18:36)

I don't have any idea on what is causing this error, as I have not checked the JSDOM source code out.

@dipamsen
Copy link
Member Author

CodingTrain/Random-Whistle ported to node: here

@jkenzer
Copy link

jkenzer commented Feb 15, 2021

@dipamsen Great job! I worked on this over the weekend and got pretty close but I don't think I would have gotten there. Thanks for sharing this. I hope to use this to output p5.js sketches to pixel arrays using a raspberry pi.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants