Currently in the page we have a mesh loaded with TreeJS and displayed in a canvas:
How could we get the color of the point where we click on?
I have tried as it has been suggested here: Getting the color value of a pixel on click of a mesh with three.js , to create a canvas with a 2d context on top of the one using webgl context.
The problem is that when we convert the model to PNG, the image is white:
Our img 2d src:
If we click on it:
So then the console logs that the color is: 0 0 0 0
Also I will show the code where we generate the webgl canvas and the 2d canvas:
// this class handles the load and the canva for a nrrd
// Using programming based on prototype: https://javascript.info/class
// This class should be improved:
// - Canvas Width and height
InitCanvas = function (IdDiv, Filename) {
this.IdDiv = IdDiv;
this.Filename = Filename
}
InitCanvas.prototype = {
constructor: InitCanvas,
init: function () {
this.container = document.getElementById(this.IdDiv);
// this should be changed.
this.container.innerHeight = 600;
this.container.innerWidth = 800;
//These statements should be changed to improve the image position
this.camera = new THREE.PerspectiveCamera(60, this.container.innerWidth / this.container.innerHeight, 0.01, 1e10);
this.camera.position.z = 300;
let scene = new THREE.Scene();
scene.add(this.camera);
// light
let dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(200, 200, 1000).normalize();
this.camera.add(dirLight);
this.camera.add(dirLight.target);
// read file
let loader = new THREE.NRRDLoader();
loader.load(this.Filename, function (volume) {
//z plane
let sliceZ = volume.extractSlice('z', Math.floor(volume.RASDimensions[2] / 4));
this.container.innerWidth = sliceZ.iLength;
this.container.innerHeight = sliceZ.jLength;
sliceZ.mesh.material.color.setRGB(0,1,1);
console.log('Our slice is: ', sliceZ);
scene.add(sliceZ.mesh);
}.bind(this));
this.scene = scene;
var canvas2d = document.createElement('canvas');
canvas2d.id = "canvas2D";
canvas2d.style.width = `${this.container.innerWidth}px`;
canvas2d.style.height = `${this.container.innerHeight}px`;
canvas2d.style.zIndex = 8;
this.container.appendChild(canvas2d);
// renderer
this.renderer = new THREE.WebGLRenderer({alpha: true});
this.renderer.setPixelRatio(this.container.devicePixelRatio);
this.renderer.setSize(this.container.innerWidth, this.container.innerHeight);
// add canvas in container
this.container.appendChild(this.renderer.domElement);
},
animate: function () {
this.renderer.render(this.scene, this.camera);
}
}
Also we use it in the logic.js
if (!Detector.webgl) Detector.addGetWebGLMessage();
// global variables for this scripts
let OriginalImg,
SegmentImg,
myFileReader;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var mousePressed = false;
var clickCount = 0;
var allText;
init();
animate();
// initilize the page
function init() {
readTextFile("columna01-es-latin1.txt");
let originalImgPath = getParameterByName('originalImgPath');
let filename = originalImgPath || "models/nrrd/columna02.nrrd"; // change your nrrd file
let idDiv = 'original';
OriginalImg = new InitCanvas(idDiv, filename);
OriginalImg.init();
console.log(OriginalImg);
let segmentedImgPath = getParameterByName('segmentedImgPath');
filename = segmentedImgPath || "models/nrrd/columnasegmentado02.nrrd"; // change your nrrd file
idDiv = 'segment';
SegmentImg = new InitCanvas(idDiv, filename);
SegmentImg.init();
let canvas2D = document.getElementById('canvas2D');
let ctx2D = canvas2D.getContext('2d');
var img2D = new Image();
img2D.src = OriginalImg.renderer.domElement.toDataURL("img/png");
console.log('Our img 2d source is:::: ',img2D.src);
img2D.addEventListener("load", function () {
ctx2D.clearRect(0, 0, canvas2D.width, canvas2D.height);
ctx2D.drawImage(img2D, 0, 0);
// from here, get your pixel data
var imgData = ctx2D.getImageData(10, 10, 1, 1);
red = imgData.data[0];
green = imgData.data[1];
blue = imgData.data[2];
alpha = imgData.data[3];
console.log(red + " " + green + " " + blue + " " + alpha);
});
}
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
let originalCanvas = document.getElementById('original');
originalCanvas.addEventListener('mousedown', onDocumentMouseDown, false);
originalCanvas.addEventListener('mouseup', onDocumentMouseUp, false);
function onDocumentMouseDown(event) {
mousePressed = true;
clickCount++;
let realClickedCanvasX = event.offsetX;
let realClickedCanvasY = event.offsetY;
mouse.x = ( ( event.clientX - OriginalImg.renderer.domElement.offsetLeft ) / OriginalImg.renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = -( ( event.clientY - OriginalImg.renderer.domElement.offsetTop ) / OriginalImg.renderer.domElement.clientHeight ) * 2 + 1
console.log('Mouse x position is: ', realClickedCanvasX, 'the click number was: ', clickCount);
console.log('Mouse x position is: ', realClickedCanvasY, 'the click number was: ', clickCount);
//console.log('Mouse x position is: ', mouse.x, 'the click number was: ', clickCount);
//console.log('Mouse Y position is: ', mouse.y);
raycaster.setFromCamera(mouse.clone(), OriginalImg.camera);
var objects = raycaster.intersectObjects(OriginalImg.scene.children);
console.log(objects);
}
function onDocumentMouseUp(event) {
mousePressed = false
}
function animate() {
requestAnimationFrame(animate);
OriginalImg.animate();
SegmentImg.animate();
}
I have also read:
- ThreeJS How to pick the intersection point color of an object
- Get pixel color from an image
- How to check if a specific pixel of an image is transparent?
- Get a pixel from HTML Canvas?
- https://johnresig.com/blog/ocr-and-neural-nets-in-javascript/
- https://dev.opera.com/articles/html5-canvas-basics/#pixelbasedmanipulation
- Get the mouse coordinates when clicking on canvas
- HTML5 Dynamically create Canvas
- Canvas width and height in HTML5
- Set Canvas size using javascript
Thank you for your help, in advance.
EDIT:
Thank you @TheJim01 for your help.
I have tried your suggestion, and with the following code:
Into OnDocumentMouseDown function:
var target = OriginalImg.renderer.getRenderTarget();
console.log('Our target which should be a WebGLRenderTarget is: ');
console.log(target);
var outputBuffer = new Uint8Array( OriginalImg.renderer.width * OriginalImg.renderer.height * 4 );
OriginalImg.renderer.readRenderTargetPixels ( target, 0, 0, OriginalImg.renderer.width, OriginalImg.renderer.height, outputBuffer );
var pixelIndex = ((realClickedCanvasX * OriginalImg.renderer.width) + realClickedCanvasY) * 4;
var color = {
r: outputBuffer[pixelIndex + 0],
g: outputBuffer[pixelIndex + 1],
b: outputBuffer[pixelIndex + 2],
a: outputBuffer[pixelIndex + 3]
};
console.log('Color of clicked pixel is: ');
console.log(color);
We see in the web console:
Our target which should be a WebGLRenderTarget is:
null
THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.
Color of clicked pixel is:
Object { r: undefined, g: undefined, b: undefined, a: undefined }
I tried to understand what happened and to solve this I read:
And I added a WebGlRenderTarget as:
... more code...
this.renderTarget = new THREE.WebGLRenderTarget(this.renderer.domElement.clientWidth, this.renderer.domElement.clientHeight);
this.renderer.render(this.scene, this.camera, this.renderTarget);
... more code...
And now we see in our web console:
Error: WebGL warning: clear: Framebuffer not complete. (status: 0x8cd6) COLOR_ATTACHMENT0 has no width or height
three.js:21370:4
Error: WebGL warning: clear: Framebuffer must be complete.
three.js:21370:4
Error: WebGL warning: clear: Framebuffer not complete. (status: 0x8cd6) COLOR_ATTACHMENT0 has no width or height
three.js:21370:4
Error: WebGL warning: clear: Framebuffer must be complete.
three.js:21370:4
...
I have tried to look for it and I have seen this issue in GitHub:
https://github.com/pixijs/pixi.js/issues/3767
However I do not see it could be related.
EDIT2:
Thank you @TheJim01 for your help, I appreciate it!! With your help and code the Render Target issue looks like it is solved. In fact I see a strange warning in the web console:
Error: WebGL warning: Exceeded 16 live WebGL contexts for this principal, losing the least recently used one.
After searching for this warning, I saw this great topic: Error: WebGL: Exceeded 16 live WebGL contexts for this principal, losing the least recently used one
And after closing the tab, and reloading the project it has disappeared.
In addition, the colors we obtain are undefined, I will show a console log:
Our OriginalImg where we want to get the color is:
Object { IdDiv: "original", Filename: "models/nrrd/columna02.nrrd", container: div#original.column, camera: {…}, scene: {…}, renderer: {…} }
Error: WebGL warning: Exceeded 16 live WebGL contexts for this principal, losing the least recently used one.
Error: WebGL warning: Exceeded 16 live WebGL contexts for this principal, losing the least recently used one.
The target being used in this click is:
Object { uuid: "8EAEFD8F-5C55-4973-84BC-471D43C006F8", width: undefined, height: undefined, scissor: {…}, scissorTest: false, viewport: {…}, texture: {…}, depthBuffer: true, stencilBuffer: true, depthTexture: null }
Our target which should be a WebGLRenderTarget is:
onMouseDownLogic.js:23:5
Object { uuid: "8EAEFD8F-5C55-4973-84BC-471D43C006F8", width: 800, height: 600, scissor: {…}, scissorTest: false, viewport: {…}, texture: {…}, depthBuffer: true, stencilBuffer: true, depthTexture: null, … }
onMouseDownLogic.js:24:5
Color of clicked pixel is:
onMouseDownLogic.js:39:5
{…}
a: undefined
b: undefined
g: undefined
r: undefined
__proto__: Object { … }
To help better understand what I am writing about I refactored the code to better explain and write it.
We have logic.js which aims is to initialize canvas and animate them:
if (!Detector.webgl) Detector.addGetWebGLMessage();
// global variables for this scripts
let OriginalImg,
SegmentImg;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var allText;
init();
animate();
// initilize the page
function init() {
readTextFile("columna01-es-latin1.txt");
OriginalImg = initCanvasOfOriginalImg();
initCanvasOfSegmentedImg();
}
let originalCanvas = document.getElementById('original');
originalCanvas.addEventListener('mousedown', onDocumentMouseDown, false);
// re-usable objects defined outside your mouse event
console.log('Our OriginalImg where we want to get the color is: ');
console.log(OriginalImg);
var size = OriginalImg.renderer.getSize();
var reusableTarget = new THREE.WebGLRenderTarget(OriginalImg.renderer.width, OriginalImg.renderer.height);
function onDocumentMouseDown(event) {
let {realClickedCanvasX, realClickedCanvasY} = calculateClickedPointWindowCoordinates(event);
let OriginalImgRenderer = calculateNormalizedClickedPointCoordinates(event);
var target = OriginalImgRenderer.getRenderTarget() || reusableTarget;
console.log('The target being used in this click is: ');
console.log(target);
size = OriginalImgRenderer.getSize();
target.setSize(size.width, size.height);
OriginalImgRenderer.render( OriginalImg.scene, OriginalImg.camera, target );
calculateClickedPointColor(OriginalImgRenderer, realClickedCanvasX, realClickedCanvasY);
projectRayAndGetIntersectedObject();
}
function animate() {
requestAnimationFrame(animate);
OriginalImg.animate();
SegmentImg.animate();
}
Then we have InitCanvas.js which is the Prototype which joins the div, with the canvas and the model being loaded:
// this class handles the load and the canva for a nrrd
// Using programming based on prototype: https://javascript.info/class
// This class should be improved:
// - Canvas Width and height
InitCanvas = function (IdDiv, Filename) {
this.IdDiv = IdDiv;
this.Filename = Filename
}
InitCanvas.prototype = {
constructor: InitCanvas,
init: function () {
this.container = document.getElementById(this.IdDiv);
// this should be changed.
this.container.innerHeight = 600;
this.container.innerWidth = 800;
//These statenments should be changed to improve the image position
this.camera = new THREE.PerspectiveCamera(60, this.container.innerWidth / this.container.innerHeight, 0.01, 1e10);
this.camera.position.z = 300;
let scene = new THREE.Scene();
scene.add(this.camera);
// light
let dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(200, 200, 1000).normalize();
this.camera.add(dirLight);
this.camera.add(dirLight.target);
// read file
let loader = new THREE.NRRDLoader();
loader.load(this.Filename, function (volume) {
//z plane
let sliceZ = volume.extractSlice('z', Math.floor(volume.RASDimensions[2] / 4));
this.container.innerWidth = sliceZ.iLength;
this.container.innerHeight = sliceZ.jLength;
sliceZ.mesh.material.color.setRGB(0, 1, 1);
// console.log('Our slice is: ', sliceZ);
scene.add(sliceZ.mesh);
}.bind(this));
this.scene = scene;
// renderer
this.renderer = new THREE.WebGLRenderer({alpha: true});
this.renderer.setPixelRatio(this.container.devicePixelRatio);
this.renderer.setSize(this.container.innerWidth, this.container.innerHeight);
// add canvas in container
this.container.appendChild(this.renderer.domElement);
},
animate: function () {
this.renderer.render(this.scene, this.camera);
}
}
Plus we have the functions being used when we click the Mouse, into OnDocumentMouseDown():
let projectRayAndGetIntersectedObject = function () {
raycaster.setFromCamera(mouse.clone(), OriginalImg.camera);
var objects = raycaster.intersectObjects(OriginalImg.scene.children);
console.log(objects);
};
let calculateClickedPointWindowCoordinates = function (event) {
let realClickedCanvasX = event.offsetX;
let realClickedCanvasY = event.offsetY;
return {realClickedCanvasX, realClickedCanvasY};
};
let calculateNormalizedClickedPointCoordinates = function (event) {
let OriginalImgRenderer = OriginalImg.renderer;
mouse.x = ( ( event.clientX - OriginalImgRenderer.domElement.offsetLeft ) / OriginalImgRenderer.domElement.clientWidth ) * 2 - 1;
mouse.y = -( ( event.clientY - OriginalImgRenderer.domElement.offsetTop ) / OriginalImgRenderer.domElement.clientHeight ) * 2 + 1
return OriginalImgRenderer;
};
let calculateClickedPointColor = function (OriginalImgRenderer, realClickedCanvasX, realClickedCanvasY) {
var target = OriginalImgRenderer.getRenderTarget();
console.log('Our target which should be a WebGLRenderTarget is: ');
console.log(target);
var outputBuffer = new Uint8Array(OriginalImgRenderer.width * OriginalImgRenderer.height * 4);
OriginalImgRenderer.readRenderTargetPixels(target, 0, 0, OriginalImgRenderer.width, OriginalImgRenderer.height, outputBuffer);
var pixelIndex = ((realClickedCanvasX * OriginalImgRenderer.width) + realClickedCanvasY) * 4;
var color = {
r: outputBuffer[pixelIndex + 0],
g: outputBuffer[pixelIndex + 1],
b: outputBuffer[pixelIndex + 2],
a: outputBuffer[pixelIndex + 3]
};
console.log('Color of clicked pixel is: ');
console.log(color);
};
I have no clue about how could we fix this issue.
EDIT:3
Thank you @TheJim01 your example code solved the problem, here I show the one I am using:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="chrome=1,IE=edge">
<title>TEST</title>
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script src="js/NRRDLoader.js"></script>
<script src="js/Volume.js"></script>
<script src="js/VolumeSlice.js"></script>
</head>
<body>
<canvas id="view" width="500" height="500" style="border: 1px black solid;"></canvas>
<script type="text/javascript">
/**********************/
/* Initialization */
/**********************/
let realClickedCanvasX, realClickedCanvasY, clickCount=0;
var pixelBuffer = new Uint8Array(500 * 500 * 4);
function testPixelBuffer() {
var x, y, r, g, b, a, i1, i2;
i1 = realClickedCanvasX * size.width;
i2 = i1 + realClickedCanvasY;
i2 *= 4;
r = pixelBuffer[i2 + 0];
g = pixelBuffer[i2 + 1];
b = pixelBuffer[i2 + 2];
a = pixelBuffer[i2 + 3];
console.log(i2, r, g, b, a);
}
document.getElementById('view').addEventListener('mousedown', onDocumentMouseDown, false);
function onDocumentMouseDown(event) {
clickCount++;
realClickedCanvasX = event.offsetX;
realClickedCanvasY = event.offsetY;
console.log('You have clicked on: ' + realClickedCanvasY
+ ' , ' + realClickedCanvasY +
' it is the click number: ' +
clickCount);
testPixelBuffer();
}
var renderer = new THREE.WebGLRenderer({
canvas: document.getElementById("view"),
antialias: true,
alpha: true
});
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.z = 50;
camera.add(new THREE.PointLight(0xffffff, 1, Infinity));
scene.add(camera);
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.addEventListener("change", render);
/**********************/
/* Populate the Scene */
/**********************/
let loader = new THREE.NRRDLoader();
loader.load('models/nrrd/columnasegmentado01.nrrd', function (volume) {
//z plane
let sliceZ = volume.extractSlice('z', Math.floor(volume.RASDimensions[2] / 4));
scene.add(sliceZ.mesh);
});
/**********************/
/* Render Function */
/**********************/
var externalTarget = new THREE.WebGLRenderTarget();
var size = renderer.getSize();
externalTarget.setSize(size.width, size.height);
function render() {
renderer.render(scene, camera);
renderer.render(scene, camera, externalTarget);
}
render();
/**********************/
/* Animation Loop */
/**********************/
function animate() {
requestAnimationFrame(animate);
renderer.readRenderTargetPixels(externalTarget, 0, 0, size.width, size.height, pixelBuffer);
controls.update();
}
animate();
</script>
</body>
</html>
I have clicked on the following zones:
And the result were:
You have clicked on: 81 , 81 it is the click number: 1
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 194324 0 0 0 255
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 94 , 94 it is the click number: 2
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 440376 23 23 23 255
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 150 , 150 it is the click number: 3
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 246600 23 23 23 255
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 195 , 195 it is the click number: 4
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 122780 0 0 0 0
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 210 , 210 it is the click number: 5
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 12840 0 0 0 0
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 310 , 310 it is the click number: 6
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 69240 0 0 0 0
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 271 , 271 it is the click number: 7
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 255084 255 255 255 255
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 360 , 360 it is the click number: 8
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 495440 23 23 23 255
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 318 , 318 it is the click number: 9
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 511272 23 23 23 255
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 291 , 291 it is the click number: 10
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 685164 92 92 92 255
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 283 , 283 it is the click number: 11
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 963132 0 0 0 0
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 180 , 180 it is the click number: 12
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 960720 0 0 0 0
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 174 , 174 it is the click number: 13
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 864696 0 0 0 0
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:51 You have clicked on: 216 , 216 it is the click number: 14
index.html?_ijt=uvc1m089ec37ho5kobp4qir885:40 510864 46 46 46 255
I wonder why regions 2, 3 are logged as being with the same color!
I also wonder why zones 4,5,6 are logged as they are of equal color!.
I do not fully understand the following code in your example:
function testPixelBuffer() {
var x, y, r, g, b, a, i1, i2;
i1 = realClickedCanvasX * size.width;
i2 = i1 + realClickedCanvasY;
i2 *= 4;
Could you help me please?
Thank you in advance!