user1544307
user1544307

Reputation: 121

HTML5 Canvas - Fastest way to display an array of pixel colors on the screen

Given a 40x30 array of colors what would be the fastest way to display them on the screen in a 400x300 canvas? That is, each color would require 100 pixels in the canvas element.

Using the canvas element in HTML5 I created 4 possible ways to do it. http://jsperf.com/pixel-plotting

Random pixel data is loaded into the 40x30 array. It is then displayed in a canvas of size 400x300 such that each logical pixel is represented by 100 pixels on the screen.

The fastest way (using Chrome 38.0) was to create the image data in a hidden canvas and copy it using toDataURL into the canvas that is displayed on the screen. The property imageSmoothingEnabled is set to false to stop the pixels blurring when they are scaled upwards.

Is there any other method of doing this? I tried using small canvas dimensions and just scaling it up in CSS but this led to the pixels being blurred.

I'd like to know how the test stands in other web browsers; especially mobile phone ones.

Drawing the pixel data onto the screen is quite important as it needs to happen at least 30 times a second so even a small tweak could improve efficiency a lot.

Here is the code for the four ways I tested.

Method 1

var png = document.createElement("img");
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");

var c1 = document.createElement("canvas");
c1.width = 40;
c1.height = 30;
var ctx1 = c1.getContext("2d");

var imgData = ctx1.createImageData(40, 30);
for (var i=0; i<imgData.data.length; i+=4) {
var x = (i/4)%40;
    var y = Math.floor(i/160);
    imgData.data[i] = pixelData1[x][y].r;
    imgData.data[i+1] = pixelData1[x][y].g;
    imgData.data[i+2] = pixelData1[x][y].b;
    imgData.data[i+3] = 255;
}
ctx1.putImageData(imgData, 0, 0);

png.src = c1.toDataURL("image/png");

c2.width = 400;
c2.height = 300;

ctx2.imageSmoothingEnabled = false;
ctx2.drawImage(png, 0, 0, 400, 300);

Method 2

var c = document.getElementById("myCanvas");
c.width = 400;
c.height = 300;
var ctx = c.getContext("2d");

var imgData = ctx.createImageData(400, 300);
for (var i=0; i<imgData.data.length; i+=4) {
    var x = Math.floor(((i/4)%400)/10);
    var y = Math.floor(i/16000);
    imgData.data[i] = pixelData1[x][y].r;
    imgData.data[i+1] = pixelData1[x][y].g;
    imgData.data[i+2] = pixelData1[x][y].b;
    imgData.data[i+3] = 255;
}
ctx.putImageData(imgData, 0, 0);

Method 3

var png = document.createElement("img");
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");

var c1 = document.createElement("canvas");
c1.width = 40;
c1.height = 30;
var ctx1 = c1.getContext("2d");

for (var x = 0; x < 40; x++) {
    for (var y = 0; y < 30; y++) {
        ctx1.fillStyle = pixelData2[x][y];
        ctx1.fillRect(x, y, 1, 1);
    }
}

png.src = c1.toDataURL("image/png");

c2.width = 400;
c2.height = 300;

ctx2.imageSmoothingEnabled = false;
ctx2.drawImage(png, 0, 0, 400, 300);

Method 4

var c = document.getElementById("myCanvas");
c.width = 400;
c.height = 300;
var ctx = c.getContext("2d");

for (var x=0; x<40; x++) {
    for (var y=0; y<30; y++) {
        ctx.fillStyle = pixelData2[x][y];
        ctx.fillRect(x*10, y*10, 10, 10);
    }
}

Upvotes: 4

Views: 4713

Answers (1)

user1544307
user1544307

Reputation: 121

Thanks for the response in the comments. From Ken Frystenberg's comment I updated the tests on jsperf: http://jsperf.com/pixel-plotting/5

It seems that the fastest way is to edit the image data of a canvas not visible on the screen. Then, on the second canvas's context, set the property imageSmoothingEnabled to false. Finally call drawImage passing the first canvas as the source of the image.

To support this method in most browsers the appropriate prefixes were added to the imageSmoothingEnabled property of the canvas's context.

Here is the final code:

var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");

var c1 = document.createElement("canvas");
c1.width = 40;
c1.height = 30;
var ctx1 = c1.getContext("2d");

var imgData = ctx1.createImageData(40, 30);
for (var i=0; i<imgData.data.length; i+=4) {
    var x = (i/4)%40; 
    var y = Math.floor(i/160); 
    imgData.data[i] = pixelData1[x][y].r; 
    imgData.data[i+1] = pixelData1[x][y].g; 
    imgData.data[i+2] = pixelData1[x][y].b; 
    imgData.data[i+3] = 255; 
}
ctx1.putImageData(imgData, 0, 0);

c2.width = 400;
c2.height = 300;

ctx2.mozImageSmoothingEnabled = false;
ctx2.webkitImageSmoothingEnabled = false;
ctx2.msImageSmoothingEnabled = false;
ctx2.imageSmoothingEnabled = false;
ctx2.drawImage(c1, 0, 0, 400, 300);

Upvotes: 5

Related Questions