I have been working on a project in Javascript and after a certain development stage, the code stopped working. I've narrowed the problem down to creating and indexing "multi-dimensional" arrays in Javascript. I've included code just to test creating arrays of arrays, assigning color values to the arrays, and then a test if the values can be displayed on-screen via a loop that iterates through the "multi-dimensional array".
I've tried just about every method of creating arrays of arrays in Javascript -- some code snippets from answers even here -- but nothing has worked.
function setRGB(image, width1, x1, y1, r, g, b, a) {
var t1 = y1 * width1 * 4 + x1 * 4;
image.data[t1] = r;
image.data[t1 + 1] = g;
image.data[t1 + 2] = b;
image.data[t1 + 3] = a;
}
function draw() {
var pixels = [];
for (var i = 0; i < height; ++i) {
var row = [];
for (var j = 0; j < width; ++j) {
row[j] = 0;
}
pixels[i] = row;
}
var k = 12;
var j = 29;
console.log(pixels[12][29].toString());
var canvas = document.getElementById('test');
var width = 500;
var height = 500;
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var canvasImage = ctx.getImageData(0, 0, width, height);
var testImage = ctx.createImageData(width, height);
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
pixels[y][x] = Math.round(Math.random() * 255);
}
}
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
console.log(pixels[y][x].toString());
}
}
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
setRGB(testImage, width, pixels[y][x], pixels[y][x], pixels[y][x], 255);
ctx.putImageData(testImage, 0, 0);
canvasImage = testImage;
ctx.putImageData(canvasImage, 0, 0);
}
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Array test</title>
</head>
<body onload="draw();">
<canvas id="test">Sorry, but your browser doesn't support the 'canvas' element.</canvas>
</body>
</html>
In Google Chrome, nothing is displayed on-screen and the web console shows the error :
array-plot-test-1.html:26 Uncaught TypeError: Cannot read property '29' of undefined
at draw (array-plot-test-1.html:26)
at onload (array-plot-test-1.html:60)
even though the array of arrays is indexed in its creation loop.
The first time the array is indexed outside its creation loop consists of the test code :
var k = 12;
var j = 29;
console.log(pixels[12][29].toString());
In Mozilla Firefox, I get the error :
TypeError: pixels[12] is undefined[Learn More] array-plot-test-1.html:26:3
draw file:///[censored]/[censored]/[censored]/array-plot-test-1.html:26
onload file:///[censored]/[censored]/[censored]/array-plot-test-1.html:1
I've rewritten the array test code about a dozen different times, at least, using "solutions" provided by sites such as Stack Overflow, but nothing has worked so far. I expect there is some basic error that I've made.
Curiously, if I create a "multi-dimensional array" in Javascript like so
array1 = [];
for (var i=0; i < max; ++i) {
var temp = [1, 2, 3];
array[i] = temp;
}
with a pre-defined array, then Javascript "multi-dimensional arrays" work fine, but I need arrays of arrays of arbitrary size which is why I can't use the above code snippet.
You defined width and height after using them. You need to define them before using them.
You also were not passing x and y to setRGB
I suggest you learn how to use the debugger in your browser.
Not sure why you had this
ctx.putImageData(testImage, 0, 0);
canvasImage = testImage;
ctx.putImageData(canvasImage, 0, 0)
inside your loop. You’re drawing the same thing twice plus your drawing both of those 500x500 times. You won’t see the result until your draw function exits so there’s no reason to do that inside the loop. Just moved it after the loop.
Also, put
“use strict”;
at the top of your JavaScript and never use var, use const and let as they will help you find these kinds of issues.
function setRGB(image, width1, x1, y1, r, g, b, a) {
var t1 = y1 * width1 * 4 + x1 * 4;
image.data[t1] = r;
image.data[t1 + 1] = g;
image.data[t1 + 2] = b;
image.data[t1 + 3] = a;
}
function draw() {
var width = 500;
var height = 500;
var pixels = [];
for (var i = 0; i < height; ++i) {
var row = [];
for (var j = 0; j < width; ++j) {
row[j] = 0;
}
pixels[i] = row;
}
var k = 12;
var j = 29;
console.log(pixels[12][29].toString());
var canvas = document.getElementById('test');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var testImage = ctx.createImageData(width, height);
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
pixels[y][x] = Math.round(Math.random() * 255);
}
}
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
setRGB(testImage, width, x, y, pixels[y][x], pixels[y][x], pixels[y][x], 255);
}
}
ctx.putImageData(testImage, 0, 0);
}
draw();
<canvas id="test"></canvas>
Seems the problems were caused by some dumb errors including accidentally not noticing that the width and height variables hadn't been declared yet and also my failure to pass setRGB() its x and y values.
Thanks to all who helped :)
jdb2
Related
I want to achieve something like an infinite drag like the one in Konva js Can anyone help me with this. I try varius things but non of them were ok. Im new in p5js and javascript. Please for any hints. Only this element prevents me from completing the entire project.
var grid;
var current_img;
var BgCat1 = [];
var layerOne;
let show_grid = false;
There may be a more elegant solution, but here I draw an extra cell on each side of the grid to handle the wraparound, so a 12x12 grid with 10x10 visible. See it run here: https://editor.p5js.org/rednoyz/full/uJCADfZXv
let dim = 10, sz;
let xoff = 0, yoff = 0;
function setup() {
createCanvas(400, 400);
sz = width/ dim;
}
function mouseDragged() {
xoff += mouseX - pmouseX;
yoff += mouseY - pmouseY;
}
function draw() {
background(255);
for (let i = 0; i < dim+2; i++) {
for (let j = 0; j < dim+2; j++) {
let x = ((xoff + j * sz) % (width+sz)) - sz;
if (x < -sz) x += width+sz;
let y = ((yoff + i * sz) % (height+sz)) - sz;
if (y < -sz) y += height+sz;
rect(x, y, sz, sz);
text(i * 10 + j, x + sz/2, y + sz/2);
}
}
}
I'm working with a 1D pixel RGBA array that looks like this:
pixelArray =[0,0,0,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255];
What this pixelArray corresponds to when drawn is 2 black pixels and 4 white pixels:
BB
WW
WW
My goal is to rotate the order of the pixels inside the array so the picture when drawn will look like
BWW or WWB
BWW WWB
Which means I need to convert the pixelArray to
rotatedPixelArray = [0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255]
The above example is just that, an example. The actual rgba could represent any image and could have a length of 1 million +.
I've tried various algorithms like this, and converting to a 2d array and then rotating and flattening (which does work) but i'd like to avoid this because speed/memory is an issue.
Maybe this could be helpful in p5.js for 90 degrees rotation (left and right) (the previous suggested solution seems to have a little 'asymmetric' error):
function rotateRight(img){
var w = img.width;
var h = img.height;
var index, indexr;
var img2 = createImage(w, h);
img.loadPixels();
img2.loadPixels();
indexr = 0;
for (let x = 0; x < w; x++) {
for(let y = h - 1; y >= 0; y--) {
index = (x+y*w)*4;
img2.pixels[indexr] = img.pixels[index];
img2.pixels[indexr + 1] = img.pixels[index+1];
img2.pixels[indexr + 2] = img.pixels[index+2];
img2.pixels[indexr + 3] = img.pixels[index+3];
indexr += 4;
}
}
img.updatePixels();
img2.updatePixels();
return img2;
}
function rotateLeft(img){
var w = img.width;
var h = img.height;
var index, indexr;
var img2 = createImage(w, h);
img.loadPixels();
img2.loadPixels();
indexr = 0;
for (let x = w - 1; x >= 0; x--) {
for(let y = 0; y < h; y++) {
index = (x+y*w)*4;
img2.pixels[indexr] = img.pixels[index];
img2.pixels[indexr + 1] = img.pixels[index+1];
img2.pixels[indexr + 2] = img.pixels[index+2];
img2.pixels[indexr + 3] = img.pixels[index+3];
indexr += 4;
}
}
img.updatePixels();
img2.updatePixels();
return img2;
}
So I figured it out, in my case it needed to be rotated left or right. The code I used is as followed:
function rotatePixelArray(pixelArray,w,h) {
var rotatedArray = [];
for (var x=0;x<w;x++) {
for(var y=0;y<h;y++) {
index = (x+y*w)*4;
rotatedArray.push(pixelArray[index]);
rotatedArray.push(pixelArray[index+1]);
rotatedArray.push(pixelArray[index+2]);
rotatedArray.push(pixelArray[index+3]);
}
}
return rotatedArray;
}
To rotate it back you can pass in switched w,h variables.
I'm trying to get the pixel data from an image to calculate the image's average brightness. I have no problem accessing the data with img.loadPixels(); but for some reason some elements of the pixel array are undefined, which breaks my computation.
Here is the code for sketch.js:
var img;
var brightness;
var inc;
var gotBright = false;
function preload(){
img = loadImage('assets/2.png');
}
function setup(){
createCanvas(500, 500);
background(0);
}
function draw(){
img.resize(10, 10);
image(img, 0, 0, img.width, img.height);
brightness = 0;
inc = 0;
if (gotBright == false) {
img.loadPixels();
loadPixels();
for (var i = 0; i < img.width; i++) {
for (var j = 0; j < img.height; j++) {
var index = (i * j * img.width) * 4;
var r = img.pixels[index + 0];
var g = img.pixels[index + 1];
var b = img.pixels[index + 2];
brightness += (r + g + b) / 3;
inc ++;
console.log(inc, r , g, b);
}
}
gotBright = true;
brightness = brightness/inc;
}
}
brightness should be equal to some number between 0 and 255, but now it's NaN...
If you have recommendations on other methods to calculate average brightness of an image, I am happy to hear them :)
Thanks!
I think your index calculation is wrong. Shouldn't it be:
var index = (i + j * img.width) * 4;
Bauer's answer is on the right track. You need to make sure your pixel index is correct. The best way to do that is to get out a piece of graph paper and draw out some examples.
Or you could just use the get() function that takes an x and a y parameter. From the reference:
var myImage;
var c;
function preload() {
myImage = loadImage("assets/rockies.jpg");
}
function setup() {
background(myImage);
noStroke();
c = myImage.get(60, 90);
fill(c);
rect(25, 25, 50, 50);
}
I tried to look most of the suggested search results concerning the error message I receive, but unfortunately none is talking about a case that is similar to mine. So I believe this is not a duplicate.
(In particular I don't use jQuery, I don't want to use it. Besides, the accepted answer given is correct and it does not involve jQuery, it elaborates the understanding of hoisting in JavaScript.)
I wonder Why the code below (2nd code snippet) does not work? I can't figure that out.
This code works. I instantiate Cell objects outside the drawGrid function.
'use strict';
var dim = 20;
var side_length = 25;
var canvas = document.getElementById('world');
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
drawGrid(ctx);
}
var Cell = function(x, y, alive, context) {
this.x = x;
this.y = y;
this.alive = alive;
this.ctx = context;
};
Cell.prototype.draw = function() {
if (this.alive === true) {
this.ctx.beginPath();
this.ctx.arc(this.x + side_length / 2, this.y + side_length / 2, 10, 0, 2 * Math.PI);
this.ctx.fill();
}
};
for (var i = 0; i < dim; i++) {
for (var j = 0; j < dim; j++) {
var x = i * canvas.width / dim,
y = j * canvas.height / dim;
new Cell(x, y, true, ctx).draw();
}
}
function drawGrid(ctx) {
for (var i = 0; i < dim; i++) {
for (var j = 0; j < dim; j++) {
var x = i * canvas.width / dim,
y = j * canvas.height / dim;
ctx.strokeRect(x, y, side_length, side_length);
}
}
}
<canvas id='world'></canvas>
This code does not work. I instantiate the Cell objects inside the drawGrid function.
'use strict';
var dim = 20;
var side_length = 25;
var canvas = document.getElementById('world');
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
drawGrid(ctx);
}
var Cell = function(x, y, alive, context) {
this.x = x;
this.y = y;
this.alive = alive;
this.ctx = context;
};
Cell.prototype.draw = function() {
if (this.alive === true) {
this.ctx.beginPath();
this.ctx.arc(this.x + side_length / 2, this.y + side_length / 2, 10, 0, 2 * Math.PI);
this.ctx.fill();
}
};
function drawGrid(ctx) {
for (var i = 0; i < dim; i++) {
for (var j = 0; j < dim; j++) {
var x = i * canvas.width / dim,
y = j * canvas.height / dim;
ctx.strokeRect(x, y, side_length, side_length);
new Cell(x, y, true, ctx).draw();
}
}
}
<canvas id='world'></canvas>
The problem is here:
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
drawGrid(ctx);
}
var Cell = function(x, y, alive, context) {
You're assigning Cell after you call drawGrid. So, inside drawGrid, Cell is undefined.
Simple fix 1, use a standard function declaration to have the assignment hoisted along the variable declaration :
function Cell(x, y, alive, context) {
Simple (more readable) fix 2: just move the call to drawGrid at the end.
I saw some user index a pixel in [Image Data] array, with the following two methods:
for(var i = 0; i < imageData.length; i+=4) {
data[i] = r;
data[i+1] = g;
data[i+2] = b;
data[i+3] = a;
}
or with this method.
for(var x = 0; w < canvas.width; x++) {
for(var y = 0; h < canvas.height; y++) {
var index = (x + y*canvas.width)*4;
}
}
So, I want to know is there any difference between the two. Also, if the both are same, then which is fastest.
It all depends of your needs :
• If you need to iterate through all pixel linearly, just do :
var pixelCount=data.length, i=0;
while (pixelCount--) {
data[i++] = r;
data[i++] = g;
data[i++] = b;
data[i++] = a;
}
• If you iterate though all pixels, but need the (x,y) of each points to perform some computations do:
var index=0, canvasWidth = canvas.width, canvasHeight = canvas.height ;
for(var y = 0; h < canvasHeight; y++) {
for(var x = 0; w < canvasWidth ; x++) {
data[index++] = /* depends on x, y */;
data[index++] = /* depends on x, y */;
data[index++] = /* depends on x, y */;
data[index++] = /* depends on x, y */;
}
}
(it's especially important to cache canvas.width/height to avoid DOM access within a loop).
• If you iterate through a rectangle within the data, then you can't avoid computing the index, which you can speed up a bit by using bit shifting :
var startX = ?? , startY = ???, endX = ???, endY = ??? ;
var canvasWidth = canvas.width;
var index=0;
for(var y = startY; y <= endY; y++) {
for(var x = startX; x <= endX ; x++) {
index = ( y * canvasWidth + x ) << 2 ;
data[index] = ... ;
data[index+1] = ... ;
data[index+2] = ... ;
data[index+3] = ... ;
}
}
Both of these methods will produce the same results. I assume that the index placement is the same in both images once they are calculated. The only thing that changes is the order that pixels are changed in.
For speed, the second one is likely to be slower. First of all, this is because of caching speed, programs can access data in similar array locations faster than continuously accessing locations across the array. Additionally, the compiler has to do a few multiplication operations and an addition in order to recalculate the index, instead of just an addition. For faster speed on the second one, try switching the x and y for loops, or multiply x by canvas.height instead of y by canvas.width.
The previous answer work but I think I can provide a single loop answer.
Imagine we have to pick the pixels in a square image of 100x100:
const size = 100;
const pixels = size * size;
for( let index = 0; index < pixels; index++ ){
const id = ~~index;
const x = id % size;
const y = ~~(id / size);
console.log("x:", x, "y:", y);
}
Open your dev-tools to see the full console output.