Is there a way to draw hundreds of points faster (p5.js) - javascript

I am making a program to test out my attempt at a Perlin Noise generating algorithm. The Perlin noise itself seems fine, however I've found that drawing that noise on the canvas is very slow. This is probably because for every single point in the canvas I have to call the stroke() function to change the color of the next pixel, then draw that pixel. This is done over a 400*400 pixel canvas, so I am changing the color with stroke() 160,000 times and calling point() 160,000 times.
This takes some time to do. I was wondering if there is any way of making this faster. Perhaps if I could turn the Perlin noise into an image, then load that image instead of drawing all 160,000 points individually?
The code of my draw loop is below
function draw() {
background(220);
strokeWeight(1);
for(var row = 0; row < height; row ++)
{
for(var column = 0; column < width; column ++)
{
//takes a noise value from the myNoise array whose elements have a range of [-1,1] and turns it into a value from [0,256], and makes that the color of the next point
stroke((myNoise[row][column]+1)*128);
point(column,row)
}
}
noLoop();
}
Edit: I used the following code to create and load an image. Credit to Samathingamajig for the tip.
function draw() {
background(220);
img = createImage(width,height);
img.loadPixels();
for(var row = 0; row < height; row ++)
{
for(var column = 0; column < width; column ++)
{
//takes a noise value from the myNoise array whose elements have a range of [-1,1] and turns it into a value from [0,256], and makes that the color of the next pixel in the image
img.set(row,column,color((myNoise[row][column]+1)*128))
}
}
img.updatePixels();
image(img,0,0)
noLoop();
}
Also Samathingamajig pointed out that 400*400 is 160,000, not 1,600, which I have changed above.
My original code took about 4 seconds to run the draw loop. This new version takes about 0.75 seconds.
I also tested using the createGraphics() method as suggested by rednoyz. This was not as fast as using the image methods because it still requires me to call stroke() 160,000 times.
Both of these solutions gave me an image that I could very quickly draw, however createImage() allowed me to create the image in much less time than createGraphics() did.

Just to add a bit of nuance to the existing suggestion:
use pixels[] instead of set(x, y, color): it's less intuitive to think of a 1D index that takes into account [r,g,b,a,...] pixels order, and (pixelDensity on retina displays), but it is faster.
The documentation mentions:
Setting the color of a single pixel with set(x, y) is easy, but not as
fast as putting the data directly into pixels[]. Setting the pixels[]
values directly may be complicated when working with a retina display,
but will perform better when lots of pixels need to be set directly on
every loop.
In your case that would roughly look like this:
img.loadPixels();
let numPixels = width * height;
for(let pixelIndex = 0; pixelIndex < numPixels; pixelIndex ++)
{
//takes a noise value from the myNoise array whose elements have a range of [-1,1] and turns it into a value from [0,256], and makes that the color of the next pixel in the image
// index of red channel for the current pixel
// pixels = [r0, g0, b0, a0, r1, g1, b1, a1, ...]
let redIndex = pixelIndex * 4;
// convert 1D array index to 2D array indices
let column = pixelIndex % width;
let row = floor(pixelIndex / width);
// get perlin noise value
let grey = (myNoise[row][column]+1) * 128;
// apply grey value to R,G,B channels (and 255 to alpha)
img.pixels[redIndex] = grey; // R
img.pixels[redIndex + 1] = grey; // G
img.pixels[redIndex + 2] = grey; // B
img.pixels[redIndex + 3] = 255; // A
}
img.updatePixels();
(also looping once instead of nested looping will help).
Regarding point(), it might use something like beginShape(POINTS);vertex(x,y);endShape(); behind the scenes which means something like this would be slightly more efficient:
let numPixels = width * height;
beginShape(POINTS);
for(let pixelIndex = 0; pixelIndex < numPixels; pixelIndex ++)
{
//takes a noise value from the myNoise array whose elements have a range of [-1,1] and turns it into a value from [0,256], and makes that the color of the next pixel in the image
// convert 1D array index to 2D array indices
let column = pixelIndex % width;
let row = floor(pixelIndex / width);
// get perlin noise value
stroke(color((myNoise[row][column]+1) * 128));
// apply grey value to R,G,B channels (and 255 to alpha)
vertex(column, row);
}
endShape();
That being said, it might not work as intended:
AFAIK this won't work with createCanvas(400, 400, WEBGL) as currently you can't set an independent stroke to each vertex in a shape.
With the typical Canvas 2D renderer this may still be very slow to render this many vertices using beginShape()/endShape()
Although a more advanced topic, another option that should be fast is shader(). (Might find some perlin noise inspiration on shadertoy.com btw).
p5.js is great to learn with but it's main goal is not to have the most performant canvas renderers. If shaders are a bit too complex at this stage, but you're comfortable with javascript in general, you can look at other libraries such as pixi.js (maybe pixi particle-emitter could be handy ?)

I've tried a lot of millisecond tests, and the image approach is by FAR the best. The problem is really just the amount of pixels you're trying to process as #Samathinamajig pointed out.
testing: https://editor.p5js.org/KoderM/sketches/6XPirw_98s

Related

Slowdown in Game of Life with Objects - Why?

I am working on recreating Game of Life in different ways, taking Coding Train’s video as the starting point. What I am trying to add is ‘color inheritance’: A newborn cell ‘inheriting’ the color from the average of the neighbouring cells’ colours.
I have made two versions: The first one has a 2D grid array for cells, and each cell is an array with RGB values.
grid[i][j] = [255,0,100]
A for loop iterates through the grid array and draws boxes of the right colour on the right position on the canvas. It then checks if the cell should be alive or dead next generation. This works fine without any slowdown, no matter the size of the grid.
https://editor.p5js.org/umutreldem/sketches/1qCmEzHlY
The second version fills each element of the grid array with Cell objects. This object receives its coordinates on the grid and its size. The color is given relative to its position on the grid.
function Cell(x, y, size) {
this.x = x * size;
this.y = y * size;
this.size = size;
//...
this.r = x * 10;
this.g = y * 10;
this.b = x+y;
The Cell object has functions for determining how many alive neighbours it has, inheriting colour from neighbours, etc. It is the same process as the first version. Then in draw() the grid is looped twice: once for determining the next status of each cell, and once for changing the cells to their new state.
This version experiences massive slowdown, especially at higher resolutions.
https://editor.p5js.org/umutreldem/sketches/2skO6-2Cm
I would like to add more features to this, such as keeping count of the age of each cell (for how long it is alive,) and using making an object for each cell makes more sense to me.
With my limited experience with p5.js / JavaScript in general, I cannot understand why this is the case. The function of both versions is the same (apart from one extra loop in the second version), so why does the second version tax the computer this much?
In your second example (using objects) you draw a every square, even if it is "empty".
In your first example, you only draw a square when the cell is active, and thus save computation by not drawing black squares on a black background :)
You can simply comment out the else statement in the "object version" to get the same computational savings.
this.show = function() {
if (this.status == 1) {
fill(color(this.r, this.g, this.b));
stroke(0);
square(this.x, this.y, this.size);
}
// else {
// fill(20);
// stroke(0);
// square(this.x, this.y, this.size);
// }
}
Here is a modified version, which seems to work reasonably fast.

Extract objects of specific colors and convert to paths

I have an idea for a project but I've hit a wall with the development of it. Essentially I would like to use JavaScript to analyze an image (any image), take a specific color and map it onto a HTML5 canvas as a path. All of the analyzed images will be basic shapes of different colors. This is an example of what I would like it to do.
This example is what I would like the script to do when passed the purple hex reference. Passing it the blue one would change the canvas output to just show the 2 blue SHAPES. Does anyone have any ideas on how this can be achieved without the use of a plugin? The canvas output will at some point be manipulated so each shape will need to be its own separate path (as they won't always be 4 sided shapes).
Since the question is quite broad as it stands I will not provide code, but a general approach on how to achieve this.
These are the steps that needs to be taken:
First pass: reduce the image based on a color and tolerance. If the color is absolute just iterate and create alpha channel where the pixel does not match the color. For tolerance a better approach would be to use RGB-HSL conversion, then define a radius and check if the color read is within the radius at the target color. Also consider alpha channel values.
This will leave an image with an alpha channel and only the colors that you are after.
Second pass: Run the image through a solution using Marching Squares algorithm (shameless plug: I made my own here (MIT license) inspired by this question, and it seem to be faster than the others incl. the D3 plugin - but anyone will do!). Extract the paths by iterating over the image, for each iteration remove the traced part. You do this by stroking+filling the obtained path using composition mode destination-out. Use a line width of about 3-5 specific to your scenario.
You can use Ramer-Douglas-Peucker to reduce points or leave them as they are. No point-reduction will allow for an accurate path but will also perform worse.
Third pass: Now you have path data that you can use to clip the parts from the original image. Add all the path data (use sub-paths by using moveTo() for each path), then composition mode to remove pixels you don't want. Or, if you're only after the paths: you're done!
To get color components of the pixel of coordinates (x, y) from a canvas, just do:
// assuming second canvas is same dimensions as first one
var secondCanvasId = context.createImageData(canvas.width, canvas.height);
for (var x = 0; x < canvas.width; x++) {
for (var y = 0; y < canvas.height; y++) {
var pix = context.getImageData(x, y, 1, 1).data;
var r = pix[0];
var g = pix[1];
var b = pix[2];
var a = pix[3];
// set canvas2 (x, y) pixel with this color, if it matches the choosen color
if (r === color.r && g === color.g && b === color.b && a === color.a) {
secondCanvasId.data[0] = r;
secondCanvasId.data[1] = g;
secondCanvasId.data[2] = b;
secondCanvasId.data[3] = a;
context.putImageData(secondCanvasId, x, y);
}
}
}

How to plot this type of "binary matrix" graphic (I honestly don't know if it has a name) using PHP and HTML

I'm trying to plot this type of "binary matrix" graphic:
Disregard the two colors from the sample image; I want to either color a dot blue for, let's say, "complete" values or leave it uncolored/gray for "incomplete" values as a way to track daily task completion for a certain amount of dots/days. The dots represent a day where a task was completed or not completed. Showing the full amount of dots/days gives perspective on % of completion as days go by.
I would like to use a combination of HTML/Javascript and PHP + MySQL. But the hardest part for me is figuring out a good algorithm to render this visualization. Thanks for your help.
Just treat each dot like it's a pixel. Also, imagine that the image has been rotated 90° CCW. Then, you simply draw a square that takes up less room that is allocated to it - this way, you get the separating lines.
Here'e a quick something to have a play with.
A few notes:
0) I just halved your image dimensions
1) 4 pixels and 5 pixels were chosen arbitrarily
2) I didn't bother with setting the colour of the dot - you can
easily do this.
3) I've simply treated the drawing area like a normal top-bottom
bitmap, while your image seems to show that all of the Y values will
be used before the next X value is needed. (This is like a 90° CCW
rotation).
4) I'm addressing the pixels with an X and a Y - perhaps you'd be
more interested in addressing them with a single number? If so, you
could easily write a function that would map two coords to a single
number - the pixels index, if you like.
I.e if an image is 100 x 100, there are 10,000 pixels. You could address them by specifying a number from 0 - 9,999
E.g
function 10k_to_100x100(index)
{
var x = index % 100;
var y = (index / 100).toFixed(0);
plotPixelDot(x, y);
}
X is simply the remainder when dividing by the width
Y is the whole number answer when dividing by the width
Here's a snippet you can try right here on the page:
function byId(id){return document.getElementById(id);}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
var x, y;
for (y=0; y<20; y++)
{
for (x=0; x<100; x++)
{
drawDot(x, y, 'output');
}
}
}
function drawDot(xPos, yPos, canvasId)
{
var actualX=xPos*5, actualY=yPos*5;
var ctx = byId(canvasId).getContext('2d');
ctx.fillRect(actualX, actualY, 4, 4);
}
<canvas width=558 height=122 id='output'></canvas>

How to rotate an array of canvas rectangles

I'm creating a Pentomino puzzle game for a final project in a class I'm taking. I've created all dozen of the required puzzle pieces and can drag those around here. And I've tried this code to rotate the array (without using canvas.rotate() & located at the very bottom of the fiddle), it basically swaps the X & Y coordinates when drawing the new piece:
var newPiece = targetPiece;
pieces.splice(pieces.indexOf(targetPiece), 1);
targetPiece = null;
console.log(newPiece);
var geometry = [];
for (var i = 0; i < newPiece.geometry.length; i++) {
geometry.push([newPiece.geometry[i][3], newPiece.geometry[i][0]]);
}
var offset = [newPiece.offset[1], newPiece.offset[0]];
console.log(geometry);
console.log(offset);
newPiece.geometry = geometry;
newPiece.position = geometry;
newPiece.offset = offset;
pieces.push(newPiece);
console.log(pieces);
for (var j = 0; j < pieces.length; j++) {
draw(pieces[j]);
}
This doesn't work properly, but has promise.
In this fiddle, I've isolated the problem down to a single piece and tried to use canvas.rotate() to rotate the array by double clicking, but what's actually happening is it's rotating each piece of the array (I think), which results in nothing happening because each block of the array is just a 50x50 rectangle and when you rotate a square, it still looks just like a square.
function doubleClickListener(e) {
var br = canvas.getBoundingClientRect();
mouse_x = (e.clientX - br.left) * (canvas.width / br.width);
mouse_y = (e.clientY - br.top) * (canvas.height / br.height);
var pieceToggle = false;
for (var i = 0; i < pieces.length; i++) {
if (onTarget(pieces[i], mouse_x, mouse_y)) {
targetPiece = pieces[i];
rotate(targetPiece);
}
}
}
function rotate() {
targetPiece.rotationIndex = targetPiece.rotationIndex === 0 ?
1 : targetPiece.rotationIndex === 1 ?
2 : targetPiece.rotationIndex === 2 ?
3 : 0;
for (var j = 0; j < pieces.length; j++) {
draw(pieces[j]);
}
}
Just FYI, I've tried creating the puzzle pieces as individual polygons, but could not figure out how to capture it with a mousedown event and move it with mousemove, so I abandoned it for the canvas rectangle arrays which were relatively simple to grab & move.
There's a brute force solution to this, and a total rewrite solution, both of which I'd rather avoid (I'm up against a deadline-ish). The brute force solution is to create geometry for all possible pieces (rotations & mirroring), which requires 63 separate geometry variants for the 12 pieces and management of those states. The rewrite would be to use fabric.js (which I'll probably do after class is over because I want to have a fully functional puzzle).
What I'd like to be able to do is rotate the array of five blocks with a double click (don't care which way it goes as long as it's sequential 90° rotations).
Approaching a usable puzzle:
With lots of help from #absolom, here's what I have, you can drag with a mouse click & drag, rotate a piece by double clicking it, and mirror a piece by right clicking it (well, mostly, it won't actually rotate until you next move the piece, I'm working on that). The Z-order of the pieces are manipulated so that the piece you're working with is always on top (it has to be the last one in the array to appear on top of all the other pieces):
Pentominoes II
The final solution
I've just handed the game in for grading, thanks for all the help! There was a lot more tweaking to be done, and there are still some things I'd change if I rewrite it, but I'm pretty happy with the result.
Pentominoes Final
Quick & Dirty:
The quick & dirty solution is when 2+ pieces are assembled you create a single image of them (using an in-memory canvas). That way you can move / rotate the 2-piece-as-1-image as a single entity.
More Proper:
If the 2+ piece assembly must later be disassembled, then you will need the more proper way of maintaining transformation state per piece. That more proper way is to assign a transformation matrix to each piece.
Stackoverflow contributor Ken Fyrstenberg (K3N) has coded a nice script which allows you to track individual polygons (eg your rects) using transformation matrices: https://github.com/epistemex/transformation-matrix-js
Does this code do what you need? The rotate method looks like this now:
function rotate(piece) {
for (i = 0; i < piece.geometry.length; i++) {
var x = piece.geometry[i][0];
var y = piece.geometry[i][2];
piece.geometry[i][0] = -y;
piece.geometry[i][3] = x;
}
drawAll();
}
I simplified how your geometry and positioning was handled too. It's not perfect, but it can gives you some hints on how to handle your issues.
Please note that this solution works because each piece is composed of blocks with the same color and your rotations are 90 degrees. I only move the blocks around to simulate the rotation but nothing is rotated per-se. If you build your pieces differently or if you need to rotate at different angles, then you would need to go with another approach like transformation matrices.
UPDATE
Here is a better solution: fiddle

How does CHIP 8 graphics rendered on screen?

Opcode DXYN:
Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels. Each row of 8 pixels is read as bit-coded (with the most significant bit of each byte displayed on the left) starting from memory location I; I value doesn't change after the execution of this instruction. As described above, VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn, and to 0 if that doesn't happen.
Basically I have an array called graphics which is a double array constructed out of 64 rows of new arrays per each with 32 columns.
//Creating new double arrays for storing graphics data
graphics = new Array(GFX_WIDTH);
for(var i = 0; i < graphics .length; i++){
graphics [i] = new Array(GFX_HEIGHT);
for(var j = 0; j < graphics [i].length; j++){
graphics [i][j] = 0;
}
}
and inside these arrays, I am storing graphics data as described above. My question is, do I just have to draw a square when an array element is 1 and empty that space when it's 0? According to a blog article on CHIP8, there is an extra array for font-set but what's the usage of it?
The blog article I have mentioned above
http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/
Thank you.
First of all, note that the pixels are bitpacked - each byte will contain 8 pixels of sprite data. In other words, the byte 0xAA is a single-pixel tall sprite with the first, third, fifth and seventh pixel set.
When drawing your sprite, you need to loop through each bit. If the bit is set, you flip the corresponding bit in your display - 0 becomes 1 and 1 becomes 0. This can, for example, be done by applying the XOR (^) operation to your display byte.
Note, however, that VF must be set to 1 if any pixels go from 1 to 0 in the process, and 0 if no pixels are unset - so you also need to do this. I find it is easiest to read if you have an if that checks whether or not to flip a bit, and then take care of both flipping and VF-updating inside that if.
For reference, my own Delphi implementation of this opcode looks like this:
procedure TChip8CPU.OpcodeD(inst: TInstruction);
var
X, Y, cX, cY, data: byte;
begin
Reg[$F] := 0;
for Y := 0 to inst.NibbleArg - 1 do begin
cY := (Reg[inst.Y] + Y) mod HEIGHT;
data := Memory[AddressI + Y];
for X := 0 to 7 do begin
if (data and ($80 shr X)) <> 0 then begin
cX := (Reg[inst.X] + X) mod WIDTH;
if Display[cY, cX] = 1 then Reg[$F] := 1;
Display[cY, cX] := Display[cY, cX] xor 1;
end;
end;
end;
end;
Note that for sprites which go outside the screen boundaries, they wrap around (this is done by the mod WIDTH/mod HEIGHT parts). This is mentioned in Cowgod's Chip-8 Technical Reference.

Categories