How is possible this Gaussian Blur javascript algorithm work? - javascript

I found the following script to do a blur image processing to an image since javascript. I replicated the sample but I didn't understand how a little part of this script could work fine, I will show the script:
var gaussianBlur = function() {
var data = ctx.getImageData(0,0,canvas.width,canvas.height);
var px = data.data;
var tmpPx = new Uint8ClampedArray(px.length);
tmpPx.set(px);
for (var i = 0, len= px.length; i < len; i++) {
if (i % 4 === 3) {continue;}
px[i] = ( tmpPx[i]
+ (tmpPx[i - 4] || tmpPx[i])
+ (tmpPx[i + 4] || tmpPx[i])
+ (tmpPx[i - 4 * data.width] || tmpPx[i])
+ (tmpPx[i + 4 * data.width] || tmpPx[i])
+ (tmpPx[i - 4 * data.width - 4] || tmpPx[i])
+ (tmpPx[i + 4 * data.width + 4] || tmpPx[i])
+ (tmpPx[i + 4 * data.width - 4] || tmpPx[i])
+ (tmpPx[i - 4 * data.width + 4] || tmpPx[i])
)/9;
};
// data.data = px;
ctx.putImageData(data,0,0);
delete tmpPx;
btnBlur.removeAttribute('disabled');
btnBlur.textContent = 'Blur'; }
This function (gaussianBlur()) is triggered to a html button, so when it's clicked, it will process the image to get it blurred and this process will be repeated according to the number of clicks on the button. The part of the code I don't understand is when it finishes the for loop, all the math operations were saved on the px variable but in the next line code ctx.putImageData(data,0,0);, this data variable doesn't take the new changed variable px. So I wonder, how is it possible that the canvas could render with the image blurred if data variable wasn't changed its data component (because data.data = px as shown in the first lines of gaussianBlur function).
In my opinion, to understand this code, I would put a data.data = px; after the for loop, with this line code I am saying that the new px will be set on data.data variable. And with ot without this line code the algorithm works. So I am confused why it works despite data.data is not set by the new px value. Please help me to understand it.
Thanks in advance

What is happening, is that px is in reality the same object as data.data, meaning that every changes in px take effect in the object you would think is just a copy.
Look at this simple example:
// We create an object 'x' with an array of data
var x = {
data :[ 0, 1]
};
// Assign x.data value to 'p'
var p = x.data;
console.log(p); // [0, 1]
// Then we change one of the array's values to 'Xpto'
p[1] = "Xpto";
// Now look what happened to x.data
console.log(x.data); // [0, "Xpto"]
This is why your code isn't working. px is not a copy, it is a reference.

Related

Three.js update position attribute of mesh from boxGeometry

I'm implementing an interface for resizing a cube by clicking + dragging its faces.
I'd like to implement this by updating the position attribute on the buffer geometry object, and either recreate the mesh or just set the needsUpdate flag and let it update itself. Neither of these options have worked for me. My latest attempt is below.
this.boxGeo = new THREE.BoxGeometry(2,2,2)
this.boxMesh = new THREE.Mesh(this.boxGeo, this.boxMaterial)
...
// let disp = the amount we want to move the face
// vertex indices
let indices = this.planes[index].indices
// new typed array for position attribute
let positions = new Float32Array(8 * 3)
// for each of the 8 verts
for (let i=0; i < 8; i++) {
if(!indices.includes(i) || disp === 0) {
positions[i * 3] = this.boxGeo.vertices[i].x
positions[i * 3 + 1] = this.boxGeo.vertices[i].y
positions[i * 3 + 2] = this.boxGeo.vertices[i].z
} else {
// modify verts
let d = new THREE.Vector3(disp, disp, disp).multiply(plane.normal)
positions[i * 3] = this.boxGeo.vertices[i].x + d.x
positions[i * 3 + 1] = this.boxGeo.vertices[i].y + d.y
positions[i * 3 + 2] = this.boxGeo.vertices[i].z + d.z
}
}
// update geometry
this.boxMesh.geometry._bufferGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
I've tried a few other methods including more closely following the documentation here:
https://threejs.org/docs/#manual/en/introduction/How-to-update-things
Any help or advice would be much appreciated!
EDIT: Per comments below, I'm taking a closer look at my ...attribute.position.array and noticing that it looks like each face lists all its vertices so I can't access them (or set them) as above. Any advice on docs I should go read? Is there an easier way to do this?
So per #Murgen87's comment the code below works to update the position attribute. It looks like the BoxGeometry primitive does not use indexed faces though, and now I'm thinking it might just be easier to scale / translate the box.
let positions =
this.boxMesh.geometry._bufferGeometry.getAttribute('position')
// this loop doesn't pick the right positions for my use case
faces.map((f, i) => {
positions.array[f * 6 + i * 3] += displacement.x
positions.array[f * 6 + i * 3 + 1] += displacement.y
positions.array[f * 6 + i * 3 + 1] += displacement.z
})
positions.needsUpdate = true;
My last remaining question would be why can't I do:
box.geometry.vertices.multiply(displacement)
box.geometry.verticesNeedsUpdate = true
... And this just led me to answer my own question!
The easiest way to do this is:
this.boxMesh.geometry.vertices.map((v,i) => {
if(!planeObj.indices.includes(i)) return
this.boxMesh.geometry.vertices[i].add(displacement)
})
this.boxMesh.geometry.verticesNeedUpdate = true
A valid pattern for updating the position attribute is:
let positions =
this.boxMesh.geometry._bufferGeometry.getAttribute('position')
planeObj.faces.map((f, i) => {
positions.array[f * 6 + i * 3] += displacement.x
positions.array[f * 6 + i * 3 + 1] += displacement.y
positions.array[f * 6 + i * 3 + 1] += displacement.z
})
positions.needsUpdate = true
Note that the loop above does not select the proper elements in the positions.array if just shows how to update if that's what you need to do.
Thanks!

Finding the greatest prime factor of an integer in Javascript

I am trying to write a simple program to find the greatest prime factor of an integer in JavaScript. The code I have written to do this follows:
let ans;
function factor(target, half) {
for (let i = 2; i < half; i++) {
if (target % i == 0) {
ans = target / i;
factor(ans, ans / 2);
}
}
}
factor(30, 15);
console.log(ans);
Now whether or not this code is an efficient solution to the problem or if it even works at all is beyond my issue with it: When I follow breakpoints set at each line of the factor function, I see that right after i = 2, target = 5, half = 2.5, and ans = 5, the value of target and half jump back up to 15 and 7.5 respectively. However, I do not see where in my code the values are told to change.
You're calling the function recursively, and each call to the function gets its own target, half, and i variables. In the first call to factor, target is 30 and half is 15. Then you call it again with the arguments 15 and 7.5; that inner call to factor gets its own target (15) and half (7.5), but the outer call still has its copies (30) and (15). This continues when you call factor again recursively, creating a third set, etc. When you step out of the innermost call, its variables disappear and you see the ones that are for the call that called it.
It may be clearer with a simpler example:
function countdown(value, indent) {
var twice = value * 2;
console.log(indent + "[before] value = " + value + ", twice = " + twice);
if (value > 0) {
countdown(value - 1, indent + " ");
}
console.log(indent + "[after] value = " + value + ", twice = " + twice);
}
countdown(3, "");
.as-console-wrapper {
max-height: 100% !important;
}
The output of that is:
[before] value = 3, twice = 6
[before] value = 2, twice = 4
[before] value = 1, twice = 2
[before] value = 0, twice = 0
[after] value = 0, twice = 0
[after] value = 1, twice = 2
[after] value = 2, twice = 4
[after] value = 3, twice = 6
As you can see, the values for value and twice in the outer call aren't changed by making the inner call. Each call gets its own set of variables.

returning NaN and Undefined in JavaScript

var currDice, totDice, dice, complete;
function moveIt(){
dice = Math.floor(Math.random()*6) + 1,
currDice = 40,
totDice = totDice+complete,
complete = dice * currDice
return totDice;
};
The function moveIt returns NaN.
It should return multiple of 40 till six randomly and keep the previous value.
If I remove it returns Undefined.
I know it is a scope problem. Please help.
You are using complete before initialising it! you habe to swap your lines of code:
function moveIt(beforeTotal) {
var dice = Math.floor(Math.random()*6) + 1;
var currDice = 40;
var complete = dice * currDice;
var totDice = (beforeTotal || 0)+complete;
return totDice;
};
var total = moveIt();
console.log(total);
total = moveIt(total);
console.log(total);
I try to point out how this method work.
You can call this function without an inital value. Then (beforeTotal || 0) is (undefined || 0) and will evaluate to 0, that's JS logic, and you get the result for one dice.
If you pass a value to this function it will be used to add complete to it. By passing 1000 and complete gets 120, you get 1120 out of it.
All other variables are only available in this function.
Initialy totDIce is undefined and when you add undefined to something, you get the value casted to NaN
function moveIt(){
//memoizing the mutable value as key of the function itself
moveIt.totDice = moveIt.totDice || 0;
// you might wanna wrap it with parseInt()/Math.floor()/Math.ceil()
var dice = Math.floor(Math.random()*6) + 1;
var currDice = 40;
var complete = dice * currDice;
var totDice = totDice+complete;
return moveIt.totDice ;
};
var totDice; will have undefined and that's why you get NaN
Assign totDice = 0 on the first line...
Also move complete = dice * currDice line above because complete has no value yet
var currDice, totDice = 0, dice, complete;
function moveIt(){
dice = Math.floor(Math.random()*6) + 1,
currDice = 40,
complete = dice * currDice,
totDice = totDice+complete
return totDice;
};
you can use the browser's integrated console.
In Chrome: Tools > Javascript Console. Or CTRL + SHIFT + J. There you can console.log from your code and watch it there, or you can use the Sources tab (at the top of the panel). Navigate to the file, click on it and you can put a breakpoint in the line you want by ckicking the line number.
In Firefox: Tools > development > Web console. Or CTRL + SHIFT + K. Yu can debug in a similar fashion like the aforementioned.
You can also use tools like Firebug. See https://getfirebug.com/wiki/index.php/Script_Debugging

How to detect change of variable with formula

I have this variable (levelFormula) that contains a formula and then is updated via the output of another variable
var points = 100,
levelFormula = (Math.sqrt(2 * points + 255) - 5) / 10;
In other words, if points gets updated then so does levelFormula. The question is, how do I detect when the value of levelFormula gets changed?
As mentioned in the comments,
var previousLevelFormula; // should be separated with below scope
...
var points = 100; // I guess it's from function parameter, because it will be changed so you need detection changes of levelFormula
var levelFormula = (Math.sqrt(2 * points + 255) - 5) / 10;
if (previousLevelFormula && previousLevelFormula != levelFormula) {
// it's changed
}
previousLevelFormula = levelFormula; // update previous value

End Recursion in Javascript

So I've tried to implement the floodfill algorithm in js and came up with the following:
function floodAreaFromPoint(x,y) {
if(typeof pixel[x] == "undefined") pixel[x] = [];
pixel[x][y] = 1; // 1 for alpha
if(!coordsInPixelArray(x + 1,y)) floodAreaFromPoint(x + 1,y);
if(!coordsInPixelArray(x,y + 1)) floodAreaFromPoint(x,y + 1);
if(!coordsInPixelArray(x - 1,y)) floodAreaFromPoint(x - 1,y);
if(!coordsInPixelArray(x,y - 1)) floodAreaFromPoint(x,y - 1);
}
It works kinda fine but I have some issues with filling larger areas (10000x10000) where this alogrithm results in the error "maximum call stack exceeded". I understand the meaning of this error but I have no idea how i could possibly fix this...
I am willing to replace this function with a more efficient algorithm but I think the solution to this problem could be end recursion (which I have no idea how to correctly implement in js).
Edit: The pixel array contains the pixels that should be filled. When the function is called it already holds all border pixels.
Solution:
function flood(x,y) {
var nodes = [];
nodes.push({"x":x,"y":y});
while(nodes.length > 0) {
var p = nodes[nodes.length - 1];
if(coordsInPixelArray(p.x, p.y)) {
nodes.pop();
continue;
}
if(typeof pixel[p.x] == "undefined") pixel[p.x] = [];
pixel[p.x][p.y] = 1; // 1 for alpha
if(!coordsInPixelArray(p.x + 1, p.y)) nodes.push({"x": p.x + 1,"y": p.y});
if(!coordsInPixelArray(p.x - 1, p.y)) nodes.push({"x": p.x - 1,"y": p.y});
if(!coordsInPixelArray(p.x, p.y + 1)) nodes.push({"x": p.x,"y": p.y + 1});
if(!coordsInPixelArray(p.x, p.y - 1)) nodes.push({"x": p.x,"y": p.y - 1});
}
}
The solution is pretty simple: remove the recursion. You can aswell use a stack and push the nodes to the stack instead of a recursive call. pseudocode:
stack nodes//create a new stack
add(nodes , startNode)//initialize the stack with the first node
while ! isEmpty(nodes)//still nodes available that haven't been processed
node p = peek(nodes)
if ! nodeInArray(p) OR getColor(p) == 1
//this node has already been visited or is not in the array
//continue with the next node in the stack
pop(nodes)
continue
color(p , 1)//mark the node as visited
push(nodes , node(x(p) - 1 , y(p))//add node to be processed in the future
...//push all other neighbours to the stack

Categories