Skip to content Skip to sidebar Skip to footer

Strange Results When Pre-rendering Vs Rendering In Real-time Canvas

I have a method which renders a matrix of rectangles within a Canvas before the next repaint using requestAnimationFrame. I'm trying to achieve maximum performances. My first appro

Solution 1:

The different results may be caused by different implementations of the API, or maybe even different settings that you have set-up on your browser, which make one prefer GPU acceleration over CPU computation, or one handles better grapich memory than the other or what else.

But anyway, if I understand you code correctly, you can get better than these two options.

I can think of two main ways, that you'll have to test.

The first one is to render one big rect the size of the whole matrix in one color, then loop over all the cells the other color, and compose them in a single sub path, so that you call fill() only at the end of this sub-path composition, once.

Finally you'd draw the grid over all this (grid which could be either a simple cross pattern, or pre-rendered on an offscreen canvas, or once again a single sub-path).

const W = 50;
const H = 50;
const cellSize = 10;
const grid_color = 'black';
var grid_mode = 'inline';


const ctx = canvas.getContext('2d');
const matrix = [];

canvas.width = W * cellSize;
canvas.height = H * cellSize;

for (let i = 0; i < H; i++) {
  let row = [];
  matrix.push(row);
  for (let j = 0; j < W; j++) {
    row.push(Math.random() > 0.5 ? 0 : 1);
  }
}

const grid_pattern = generateGridPattern();
const grid_img = generateGridImage();

draw();

functiondraw() {
  shuffle();

  // first draw all our green rects ;)
  ctx.fillStyle = 'green';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // now draw all the red ones
  ctx.fillStyle = 'red';
  ctx.beginPath(); // single sub-path declarationfor (let i = 0; i < H; i++) {
    for (let j = 0; j < W; j++) {
      // only if a red cellif (matrix[i][j])
        ctx.rect(i * cellSize, j * cellSize, cellSize, cellSize);
    }
  }
  ctx.fill(); // single fill operationdrawGrid();

  requestAnimationFrame(draw);

}

functionshuffle() {
  let r = Math.floor(Math.random() * H);
  for (let i = r; i < r + Math.floor(Math.random() * (H - r)); i++) {
    let r = Math.floor(Math.random() * W);
    for (let j = r; j < r + Math.floor(Math.random() * (W - r)); j++) {
      matrix[i][j] = +!matrix[i][j];
    }
  }
}

functiondrawGrid() {
  if (grid_mode === 'pattern') {
    ctx.fillStyle = grid_pattern;
    ctx.beginPath();
    ctx.rect(0, 0, canvas.width, canvas.height);
    ctx.translate(-cellSize / 2, -cellSize / 2);
    ctx.fill();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
  } elseif (grid_mode === 'image') {
    ctx.drawImage(grid_img, 0, 0);
  } else {
    ctx.strokeStyle = grid_color;
    ctx.beginPath();
    for (let i = 0; i <= cellSize * H; i += cellSize) {
      ctx.moveTo(0, i);
      ctx.lineTo(cellSize * W, i);
      for (let j = 0; j <= cellSize * W; j += cellSize) {
        ctx.moveTo(j, 0);
        ctx.lineTo(j, cellSize * H);
      }
    }
    ctx.stroke();
  }
}

functiongenerateGridPattern() {
  const ctx = Object.assign(
    document.createElement('canvas'), {
      width: cellSize,
      height: cellSize
    }
  ).getContext('2d');
  // make a cross
  ctx.beginPath();
  ctx.moveTo(cellSize / 2, 0);
  ctx.lineTo(cellSize / 2, cellSize);
  ctx.moveTo(0, cellSize / 2);
  ctx.lineTo(cellSize, cellSize / 2);

  ctx.strokeStyle = grid_color;
  ctx.lineWidth = 2;
  ctx.stroke();

  return ctx.createPattern(ctx.canvas, 'repeat');
}

functiongenerateGridImage() {
  grid_mode = 'inline';
  drawGrid();
  const buf = canvas.cloneNode(true);
  buf.getContext('2d').drawImage(canvas, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  return buf;
}
field.onchange = e => {
  grid_mode = document.querySelector('input:checked').value;
}
<fieldsetid="field"><legend>Draw grid using:</legend><label><inputname="grid"type="radio"value="inline"checked>inline</label><label><inputname="grid"type="radio"value="pattern">pattern</label><label><inputname="grid"type="radio"value="image">image</label></fieldset><canvasid="canvas"></canvas>

An other entirely different approach you could take, would be to manipulate an ImageData directly. Set it the size of your matrix (cellSize would be 1), put it on your canvas, and then finally just redraw it scaled up, and draw the grid over.

ctx.putImageData(smallImageData, 0,0);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(ctx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height);
drawgrid();

const W = 50;
const H = 50;
const cellSize = 10;
const grid_color = 'black';

canvas.width = W * cellSize;
canvas.height = H * cellSize;

const ctx = canvas.getContext('2d');
// we'll do the matrix operations directly on an imageDataconst imgData = ctx.createImageData(W, H);
const matrix = newUint32Array(imgData.data.buffer);

const red = 0xFF0000FF;
const green = 0xFF008000;

for (let i = 0; i < H*W; i++) {
    matrix[i] = (Math.random() > 0.5 ? green : red);
}

prepareGrid();
ctx.imageSmoothingEnabled = false;
draw();

functiondraw() {
  shuffle();
  // put our update ImageData
  ctx.putImageData(imgData, 0, 0);
  // scale its result
  ctx.drawImage(ctx.canvas,
    0,0,W,H,
    0,0,canvas.width,canvas.height
  );
  // draw the grid which is already drawn in memory
  ctx.stroke();
  requestAnimationFrame(draw);
}
functionshuffle() {
  // here 'matrix' is actually the data of our ImageData// beware it is a 1D array, so we need to normalize the coordslet r = Math.floor(Math.random() * H);
  for (let i = r; i < r + Math.floor(Math.random() * (H - r)); i++) {
    let r = Math.floor(Math.random() * W);
    for (let j = r; j < r + Math.floor(Math.random() * (W - r)); j++) {
      matrix[i*W + j] = matrix[i*W + j] === red ? green : red;
    }
  }
}
functionprepareGrid() {
  // we draw it only once in memory// 'draw()' will then just have to call ctx.stroke()
    ctx.strokeStyle = grid_color;
    ctx.beginPath();
    for (let i = 0; i <= cellSize * H; i += cellSize) {
      ctx.moveTo(0, i);
      ctx.lineTo(cellSize * W, i);
      for (let j = 0; j <= cellSize * W; j += cellSize) {
        ctx.moveTo(j, 0);
        ctx.lineTo(j, cellSize * H);
      }
    }
}
<canvasid="canvas"></canvas>

Post a Comment for "Strange Results When Pre-rendering Vs Rendering In Real-time Canvas"