error when using WebGLRenderer() with jsdom - node.js

I'm trying to render a cube on server side, and was able to do so using the CanvasRenderer, but i want to be able to render it with WebGLRenderer, which should produce better results. i've narrowed it down to this code snippet:
var jsdom = require('jsdom')
, document = jsdom.jsdom('<!doctype html><html><head></head><body></body></html>')
, window = document.createWindow()
, THREE = require('three');
document.onload = docOnLoad();
function docOnLoad()
{
console.log("docOnLoad called.");
global.document = document;
global.window = window;
//renderer = new THREE.CanvasRenderer(); //works
renderer = new THREE.WebGLRenderer();
renderer.setSize(400, 400);
document.body.appendChild(renderer.domElement);
}
When using the WebGLRenderer, i got:
_glExtensionTextureFloat = _gl.getExtension( 'OES_texture_float');
^
TypeError: Cannot call method 'getExtension' of undefined
at initGL (C:\programming\nodejs\node_modules\three\three.js:25870:34)
when trying to debug three.js code, console.log(_gl) indeed shows that _gl is undefined, and the source of it is line #25856, where:
_gl = _canvas.getContext( 'webgl', attributes ) || _canvas.getContext( 'experimental-webgl', attributes );
now, console.log() of _canvas shows: [Canvas 0x0]
Has anyone encountered something similar?

Usually happens when you already obtained a 2D context from _canvas. Both 2D context and gl cannot be obtained from the same canvas. If this is not the case, can you confirm if you are able to successfully run any other WebGL example independently (from node.js) ?

Related

Problem with canvas using vega with nodejs (server side only)

I've been working for a few weeks now on a Discord bot that basically compiles stats on the server and deduces patterns. In order to improve it, I wanted to make it generate graphs as PNGs in order to send them back to the user - in short, no DOM.
In order to achieve this, I'm currenlty using vega (version 5.10.1 - latest) and node-canvas (version 2.6.1 - latest), with nodejs v12.16.1.
I've been scouring the web for help on vega usage, and found a couple contradicting sources. I've been using the example code provided here :
https://vega.github.io/vega/usage/
The thing is that I keep getting this error :
TypeError: Cannot read property 'getContext' of null
message:"Cannot read property 'getContext' of null"
stack:"TypeError: Cannot read property 'getContext' of null
at resize (e:\DEV\GIT REPOS\GITHUB\PERSO\JS\test-StatBot\node_modules\vega-scenegraph\build\vega-scenegraph.js:3665:28)
at CanvasRenderer.prototype$6.resize (e:\DEV\GIT REPOS\GITHUB\PERSO\JS\test-StatBot\node_modules\vega-scenegraph\build\vega-scenegraph.js:3714:5)
at CanvasRenderer.prototype$4.initialize (e:\DEV\GIT REPOS\GITHUB\PERSO\JS\test-StatBot\node_modules\vega-scenegraph\build\vega-scenegraph.js:3294:17)
at CanvasRenderer.prototype$6.initialize (e:\DEV\GIT REPOS\GITHUB\PERSO\JS\test-StatBot\node_modules\vega-scenegraph\build\vega-scenegraph.js:3709:28)
at initializeRenderer (e:\DEV\GIT REPOS\GITHUB\PERSO\JS\test-StatBot\node_modules\vega-view\build\vega-view.js:657:8)
at renderHeadless (e:\DEV\GIT REPOS\GITHUB\PERSO\JS\test-StatBot\node_modules\vega-view\build\vega-view.js:780:12)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async View.renderToCanvas [as toCanvas] (e:\DEV\GIT REPOS\GITHUB\P...
Here is the code which is giving me trouble :
// Imports
const vega = require('vega');
// Render image from given graph spec (statsObject)
async function graphToImage (statsObject) {
graphObject = new vega.View(vega.parse(statsObject), { renderer: 'none'});
const pngName = generateFileName(10);
removeExistingFile(pngName);
graphObject.toCanvas().then(canvas => {
console.log('Writing PNG to file...');
writeFile(`../../../../generated/${pngName}.png`, canvas.toBuffer());
}).catch(err => {
console.log("Error writing PNG to file:");
console.error(err);
});
return pngName;
}
I don't really know how canvas or vega work, and so I have no idea what could be causing this issue and how to fix it... However, the problem seems to be located inside of the toCanvas() method. Any help is much appreciated !
Thanks in advance !
// Using view.toSVG() along with the npm package sharp worked well for me
const view = new vega.View(vega.parse(templateObject), {renderer: 'none'});
view.toSVG().then(async function (svg) {
await sharp(Buffer.from(svg))
.toFormat('png')
.toFile('fileName.png')
}).catch(function(err) {
console.error(err);
});
Edit : I managed to fix my issue, and I am posting the anwser here for future notice :
I succeeded to actually generate a graph picture by rendering the View object straight to an SVG string, by using view.toSVG() instead of the buggy view.toCanvas(), which worked great.
Then, all that was left to do was to convert the obtained SVG string into a PNG file, and that was it.
Here is the updated, working code :
// Imports
const vega = require('vega');
// Render image from given graph object
async function graphToImage (statsObject) {
// Generate a new 10-char hex string
const pngName = generateHexStringName(10);
// Remove any existing file with the same name in order to prevent confusion
removeExistingFile(pngName);
var view = new vega.View(vega.parse(statsObject), {renderer: 'none'});
// Generate an SVG string
view.toSVG().then(async function (svg) {
// Working SVG string
console.log(svg);
// Process obtained SVG string, e. g. write it to PNG file
}).catch(function(err) {
console.error(err);
});
// Return the name of the generated PNG file
return pngName;
}

Can't change leaflet setView params when using leaflet-headless

I need to generate a map on the server side using Nodejs and then create an image of that map. I'm using leaflet-headless to create the map and generate the image.
This is the code:
const L = require('leaflet-headless');
const document = global.document;
let createMap = (lanLat) => {
const element = document.createElement('div');
element.id = 'map-leaflet-image';
document.body.appendChild(element);
const filename = path.join(__dirname, '/leaflet-image.png');
const map = L.map(element.id).setView([0, 0], 3);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
map.saveImage(filename, () => {
console.log('done');
})
};
This works and the image is saved but when I change the setView() parameters to setView([0,0], 1)(zoom out) I receive an error message:
return prev.apply(ctx, arguments);
Error: Image given has not completed loading
at Error (native)
at CanvasRenderingContext2D.ctx.(anonymous function) [as drawImage]
Any thoughts?
If this might interest someone, the problem was in the map.save() function which uses the leaflet-image lib.
This happened due to a weirdly specific scenario where a marker with certain coordinates, when added to the map with any other marker(?!), caused the error. I removed that marker and it worked.

Present canvas interface in shared code on both server and client

I am trying to write shared code (that runs on both server and client) that uses an HTML canvas.
On the client, this should work perfectly fine. On the server, Node doesn't have a canvas (or a DOM), so I'd like to use the node-canvas plugin: https://github.com/Automattic/node-canvas.
However, I can't work out a way to access it that doesn't make webpack try to bundle node-canvas into my client-side code (which crashes webpack). Is there any way of loading node-canvas in such a way that I can reference it with the same code I'll use in the browser and without making webpack crash horribly?
My current effort, which did not work:
canvas.server.js
import Canvas from 'canvas';
const createCanvas = (width, height) => new Canvas(width, height);
export default createCanvas;
canvas.client.js
const createCanvas = (width, height) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
};
export default createCanvas;
canvas.js
let createCanvas;
if (typeof document === 'undefined') {
// SERVER/node
createCanvas = require('./canvas.server.js');
} else {
// BROWSER
createCanvas = require('./canvas.client.js');
}
export default createCanvas;
in use:
import createCanvas from './canvas';
const canvasElement = createCanvas(width, height);
const ctx = canvasElement.getContext('2d');
Unfortunately, webpack still bundles in node-canvas.
Did you try requiring node-canvas only when the code is running in node?
If you do not actually call the require in front-end code, webpack will not bundle it. This means calling the actual require inside aforementioned conditional statement and not at the top of your file. This is important. Also, verify that you did not put node-canvas as an entry point in webpack.
Example:
// let's assume there is `isNode` variable as described in linked answer
let canvas;
if (isNode) {
const Canvas = require('canvas');
canvas = new Canvas(x, y);
else {
canvas = document.getElementById('canvas');
}
// get canvas context and draw on it
Edit after OP provided code example:
I've reproduced the exact structure in this WebpackBin to prove this should be working as explained above. After all, this is a feature of common.js (and other module loaders) and is therefore supported in webpack.
My changes to the code:
Replaced the code in canvas.client.js and canvas.server.js with console.log of what would be called at that line
Fixed wrong use of require with export default (should be require(...).default). Irrelevant, but had to do it to make the code work.
Removed the canvasElement.getContex call. Would not work in the example code and is irrelevant anyway, so there is no point of mocking it.

Dynamic require in Nodejs

I'm requiring a library in NodeJS which has a self-invoking function, that results an error because it looks for an object which is not initialized at that moment .
I want to dynamically require this library when that object is initialized.
Is there any way to dynamically require/ load a library ?
This is the part of library required :
https://github.com/sakren/node-google-maps/blob/develop/lib/Google.js#L5
Actually I want to require when the window object is present (client-side rendering).
So something like this :
'use strict';
var React = require('react');
var Map = require('./map.jsx');
var Common = require('../common/common');
var MapStatic = require('./map-static.jsx');
exports.type = function() {
return 'map';
};
exports.jsx = function(data) {
if (Common.isServerSide()) {
return (<MapStatic data={data}/>);
} else {
return (
<Map data={data}/>
);
}
};
exports.transform = require('./map-transform.js');
The reason the code looks weired is that I'm using react.
In nodeJS require can be used anywhere at anytime whithout much limitations AFAIK.
Which error is thrown once you require at runtime ?
In your else branch.
Try the following.
requires = {}
function getter(key) {
if(!requires[key]){
requires[key] = require(key)
}
return requires[key]
}

PIXI BitmapText bug ("can't read property 'size' of undefined")

I can't even instantiate a BitmapText object from either pixi.js or pixi.dev.js without encountering the error
pixi.dev.js:3688 Uncaught TypeError: Cannot read property 'size' of undefined
My implementation is their trivial example
text = new PIXI.BitmapText('test', {font:'Arial'});
I've traced the error exactly to the attempted instantiation above.
Here is the relevant snippet from the pixi.dev.js source code (with the error occurring on the last line).
PIXI.BitmapText.prototype.updateText = function()
{
var data = PIXI.BitmapText.fonts[this.fontName];
var pos = new PIXI.Point();
var prevCharCode = null;
var chars = [];
var maxLineWidth = 0;
var lineWidths = [];
var line = 0;
var scale = this.fontSize / data.size;
...
Is this a bug in the library, or am I doing something wrong?
BitmapText requires you to load a font first. You can create a font using the BMFont software from angelcode: http://www.angelcode.com/products/bmfont/
When you export your font from BMFont, you end up with a png image that looks like a Texture Atlas, plus an xml file.
You can then load your BitmapFont like this:
var loader = new PIXI.AssetLoader(["MyBitmapFont.xml"]);
loader.onComplete = function()
{
stage.addChild( new PIXI.BitmapText("SomeText", { font: "35px MyBitmapFont", align: "left"}));
};
loader.load();
PIXI Example 10 is a more complete example:
https://github.com/GoodBoyDigital/pixi.js/tree/master/examples/example%2010%20-%20Text
You can use the example font, "desyrel" to try it out for yourself.

Resources