From 37e3a1b061303aaf6e16da34ab0b31c7c7c9b6ca Mon Sep 17 00:00:00 2001
From: Przemek Kaminski <pk@intrepidus.pl>
Date: Thu, 13 Oct 2022 14:24:38 +0200
Subject: [PATCH] [sigmajs] screenshot WebGL implementation

---
 package.json                            |   3 +-
 src/Gargantext/Hooks/Sigmax/Sigma.js    |  13 +-
 src/external-deps/sigmajs-screenshot.js | 150 ++++++++++++++++++++++++
 yarn.lock                               |   5 +
 4 files changed, 159 insertions(+), 12 deletions(-)
 create mode 100644 src/external-deps/sigmajs-screenshot.js

diff --git a/package.json b/package.json
index 639e828bf..72278a103 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,8 @@
         "react-dom": "^17.0.2",
         "react-tooltip": "^4.2.8",
         "secp256k1": "^4.0.2",
-        "sigma": "^2.3.1"
+        "sigma": "^2.3.1",
+        "twgl.js": "^5.0.4"
     },
     "devDependencies": {
         "@babel/core": "^7.15.0",
diff --git a/src/Gargantext/Hooks/Sigmax/Sigma.js b/src/Gargantext/Hooks/Sigmax/Sigma.js
index 9e7aabaa6..bd55643c4 100644
--- a/src/Gargantext/Hooks/Sigmax/Sigma.js
+++ b/src/Gargantext/Hooks/Sigmax/Sigma.js
@@ -2,6 +2,7 @@
 
 import Graph from 'graphology';
 import Sigma from 'sigma';
+import { takeScreenshot } from '../../src/external-deps/sigmajs-screenshot.js';
 
 let sigma = Sigma.Sigma;
 console.log('imported sigma', Sigma);
@@ -192,17 +193,7 @@ function _bindMouseSelectorPlugin(left, right, sig) {
 function _on(sigma, event, handler) { sigma.on(event, handler); }
 
 function _takeScreenshot(sigma) {
-  let c = sigma.renderers[0].container;
-  let edges = c.getElementsByClassName('sigma-edges')[0];
-  let scene = c.getElementsByClassName('sigma-scene')[0];
-  // let sceneCtx = scene.getContext('2d');
-  // sceneCtx.globalAlpha = 1;
-  // sceneCtx.drawImage(edges, 0, 0);
-  // return scene.toDataURL('image/png');
-  let edgesCtx = edges.getContext('2d');
-  edgesCtx.globalAlpha = 1;
-  edgesCtx.drawImage(scene, 0, 0);
-  return edges.toDataURL('image/png');
+  return takeScreenshot(sigma);
 }
 
 function _proxySetSettings(window, sigma, settings) {
diff --git a/src/external-deps/sigmajs-screenshot.js b/src/external-deps/sigmajs-screenshot.js
new file mode 100644
index 000000000..56fc6b86e
--- /dev/null
+++ b/src/external-deps/sigmajs-screenshot.js
@@ -0,0 +1,150 @@
+import * as twgl from 'twgl.js';
+
+
+const vertexShader = `
+attribute vec2 a_position;
+attribute vec2 a_texCoord;
+
+uniform vec2 u_resolution;
+
+varying vec2 v_texCoord;
+
+void main() {
+   // convert the rectangle from pixels to 0.0 to 1.0
+   vec2 zeroToOne = a_position / u_resolution;
+
+   // convert from 0->1 to 0->2
+   vec2 zeroToTwo = zeroToOne * 2.0;
+
+   // convert from 0->2 to -1->+1 (clipspace)
+   vec2 clipSpace = zeroToTwo - 1.0;
+
+   gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
+
+   // pass the texCoord to the fragment shader
+   // The GPU will interpolate this value between points.
+   v_texCoord = a_texCoord;
+}
+`;
+
+const fragmentShader = `
+precision mediump float;
+
+    // our 2 canvases
+    uniform sampler2D u_canvas1;
+    uniform sampler2D u_canvas2;
+
+    // the texCoords passed in from the vertex shader.
+    // note: we're only using 1 set of texCoords which means
+    //   we're assuming the canvases are the same size.
+    varying vec2 v_texCoord;
+
+    void main() {
+         // Look up a pixel from first canvas
+         vec4 color1 = texture2D(u_canvas1, v_texCoord);
+
+         // Look up a pixel from second canvas
+         vec4 color2 = texture2D(u_canvas2, v_texCoord);
+
+         // return the 2 colors multiplied
+         // TODO Modify this
+         gl_FragColor = color1 * color2;
+    }
+`;
+
+
+
+function setupTexture(gl, canvas, textureUnit, program, uniformName) {
+  let tex = gl.createTexture();
+
+  updateTextureFromCanvas(gl, tex, canvas, textureUnit);
+
+  // Set the parameters so we can render any size image.
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+
+  let location = gl.getUniformLocation(program, uniformName);
+  gl.uniform1i(location, textureUnit);
+}
+
+function updateTextureFromCanvas(gl, tex, canvas, textureUnit) {
+  gl.activeTexture(gl.TEXTURE0 + textureUnit);
+  gl.bindTexture(gl.TEXTURE_2D, tex);
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
+}
+
+
+export function takeScreenshot(sigma) {
+  // https://stackoverflow.com/questions/12590685/blend-two-canvases-onto-one-with-webgl
+  let c = sigma.container;
+  let edges = c.getElementsByClassName('sigma-edges')[0];
+  let nodes = c.getElementsByClassName('sigma-nodes')[0];
+  // let sceneCtx = scene.getContext('2d');
+  // sceneCtx.globalAlpha = 1;
+  // sceneCtx.drawImage(edges, 0, 0);
+  // return scene.toDataURL('image/png');
+  let edgesCtx = twgl.getContext(edges);
+  //edgesCtx.globalAlpha = 1;
+  //edgesCtx.drawImage(nodes, 0, 0);
+
+  let gl = edgesCtx;  // TODO Create separate canvas for this
+
+  const program = twgl.createProgramFromSources(gl, [vertexShader, fragmentShader]);
+
+  gl.useProgram(program);
+
+  const positionLocation = gl.getAttribLocation(program, "a_position");
+  const texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
+
+  const texCoordBuffer = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+      0.0,  0.0,
+      1.0,  0.0,
+      0.0,  1.0,
+      0.0,  1.0,
+      1.0,  0.0,
+      1.0,  1.0]), gl.STATIC_DRAW);
+  gl.enableVertexAttribArray(texCoordLocation);
+  gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
+
+  // lookup uniforms
+  let resolutionLocation = gl.getUniformLocation(program, "u_resolution");
+
+  // set the resolution
+  gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
+
+  // Create a buffer for the position of the rectangle corners.
+  let buffer = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+  gl.enableVertexAttribArray(positionLocation);
+  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
+
+  // Set a rectangle the same size as the image.
+  setRectangle(gl, 0, 0, gl.canvas.width, gl.canvas.height);
+
+  let tex1 = setupTexture(gl, nodes, 0, program, "u_canvas1");
+  let tex2 = setupTexture(gl, edges, 1, program, "u_canvas2");
+
+  // Draw the rectangle.
+  gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+  return gl.canvas.toDataURL('image/png');
+}
+
+
+function setRectangle(gl, x, y, width, height) {
+  const x1 = x;
+  const x2 = x + width;
+  const y1 = y;
+  const y2 = y + height;
+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+    x1, y1,
+    x2, y1,
+    x1, y2,
+    x1, y2,
+    x2, y1,
+    x2, y2]), gl.STATIC_DRAW);
+}
diff --git a/yarn.lock b/yarn.lock
index 23038674e..77975bcd9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10218,6 +10218,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
   resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
   integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
 
+twgl.js@^5.0.4:
+  version "5.0.4"
+  resolved "https://registry.yarnpkg.com/twgl.js/-/twgl.js-5.0.4.tgz#4e3d54c4e8ff01af418bfbf7ce38df26595a6108"
+  integrity sha512-8U0ehLWvqOMTqMQd3xGjeQJSDwCNtoFvLg4V3Ne4QTCaPoJmOB4CSpy7MMHkMkc1qO2DMRhnV0uAgZmhO3Q/2w==
+
 type-check@~0.3.2:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
-- 
2.21.0