0

I want to randomly generate a color for each pixel in the canvas however my loop seems to crash the browser and I cannot figure out why?

I have tried reducing the loop to a static number (X = 10 x Y=100) and it seems to work. However it takes a few seconds for the result to be shown on the screen (despite my tests showing a run time of 10ms)

I am new to javascript/html so this could be an obvious solution, however any help is greatly appreciated.

//"use strict";

    // SELECT THE CANVAS ELEMENT FROM THE HTML PAGE AND NAME IT 'WORLD'
    let world = document.querySelector("canvas");


    // SET THE CANVAS HEIGHT/WIDTH TO THE WINDOW INNER X/Y
    world.width = window.innerWidth;
    world.height = window.innerHeight;

    let context = world.getContext("2d"); 


    // GET PERFORMANCE TEST VALUE BEFORE LOOP
    let t0 = performance.now();

    let x=0;
    let y=0;

    // LOOP THROUGH THE CANVAS STARTING AT FIRST PIXEL OF TOP ROW MOVING TO THE LAST PIXEL OF THE TOP ROW AND FILL, THEN MOVE TO THE NEXT LINE AND REPEAT FILL FOR EACH ROW UNTIL CANVAS IS COLORED

    for (y=0; y < world.height; y++)
    {
     //TODO: ADD RANDOM RGB COLOR TO STROKE
     context.lineTo(x,y);
     context.stroke();

     for (x=0; x < 10; x++){
      //TODO: ADD RANDOM RGB COLOR TO STROKE
      context.lineTo(x,y);
      context.stroke();
     }
    }

    // GET PERFORMANCE TEST VALUE AFTER LOOP
    let t1 = performance.now();

    // LOG THE TOTAL MILLISECONDS OF THE LOOP
    console.log("Total Time" + (t1-t0) + " milliseconds");

    // GENERATE A RANDOM NUMBER BASED ON THE WINDOW INNER WIDTH
    function getRandomX(){
     return Math.random() * window.innerWidth;
    }

    // GENERATE A RANDOM NUMBER BASED ON THE WINDOW INNER HEIGHT
    function getRandomY(){
      return Math.random() * window.innerHeight;
    }

    // GENERATE A RANDOM NUMBER BETWEEN 0 - 255
    function getRandomRGB(){
      return Math.Random() * 255;
    }
<canvas></canvas>

Does not load, crashes browser window

shrys
  • 5,860
  • 2
  • 21
  • 36

1 Answers1

1

You’re redrawing the path every time you add a line to it. That’s quadratic and not necessary. (Quadratic is a problem when you have 20,000+ lines.)

Draw once:

let x = 0;

for (let y = 0; y < world.height; y++) {
    context.lineTo(x, y);

    for (x = 0; x < 10; x++) {
        context.lineTo(x, y);
    }
}

context.stroke();

And when you want to draw in multiple colours, start a new path every for each line:

let x = 0;

for (let y = 0; y < world.height; y++) {
    //TODO: ADD RANDOM RGB COLOR TO STROKE
    context.lineTo(x, y);
    context.stroke();
    context.beginPath();
    context.moveTo(x, y);

    for (x = 0; x < 10; x++) {
        //TODO: ADD RANDOM RGB COLOR TO STROKE
        context.lineTo(x, y);
        context.stroke();
        context.beginPath();
        context.moveTo(x, y);
    }
}

fillRect seems like a better choice for a canvas method to draw a pixel, though:

const getRandomColor = () =>
    '#' + (Math.random() * 0x1000000 >>> 0).toString(16);

const world = document.getElementById('canvas');
const context = world.getContext('2d');

const start = performance.now();

for (let y = 0; y < world.height; y++) {
    for (let x = 0; x < world.width; x++) {
        context.fillStyle = getRandomColor();
        context.fillRect(x, y, 1, 1);
    }
}

console.log(performance.now() - start);
<canvas id="canvas"></canvas>

And finally, putting image data probably gives the best performance.

const world = document.getElementById('canvas');
const context = world.getContext('2d');

const start = performance.now();

const {width, height} = world;
const random32 = new Uint32Array(width * height);

for (let i = 0; i < random32.length; i++) {
    random32[i] = Math.random() * 0x100000000 >>> 0;
}

const randomRGBA = new Uint8ClampedArray(random32.buffer);

for (let i = 3; i < randomRGBA.length; i += 4) {
    randomRGBA[i] = 255;
}

const imageData = new ImageData(randomRGBA, width, height);
context.putImageData(imageData, 0, 0);

console.log(performance.now() - start);
<canvas id="canvas"></canvas>

(Fun fact: crypto.getRandomValues is faster than this in practice, but not a good choice.)

Ry-
  • 218,210
  • 55
  • 464
  • 476
  • Can you explain why you choose '0x100000000' in the following code 'random32[i] = Math.random() * 0x100000000 >>> 0;'. Why not '0x100' or '0x999' is there any particular reason? –  Jun 27 '19 at 07:35
  • @minlopalis: Change it to 0x100 and see what happens. :) It’s randomizing an array of 32-bit values (each representing an entire RGBA pixel), so the random numbers need to go from 0 to 2^32-1. Multiplying `Math.random()` by `0x100000000` gives you that range. – Ry- Jun 27 '19 at 07:50