What's the best way to set a single pixel in an HTML5 canvas?
There are two best contenders:
Create a 1×1 image data, set the color, and
putImageData
at the location:var id = myContext.createImageData(1,1); // only do this once per page var d = id.data; // only do this once per page d[0] = r; d[1] = g; d[2] = b; d[3] = a; myContext.putImageData( id, x, y );
Use
fillRect()
to draw a pixel (there should be no aliasing issues):ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")"; ctx.fillRect( x, y, 1, 1 );
You can test the speed of these here: http://jsperf.com/setting-canvas-pixel/9 or here https://www.measurethat.net/Benchmarks/Show/1664/1
I recommend testing against browsers you care about for maximum speed. As of July 2017, fillRect()
is 5-6× faster on Firefox v54 and Chrome v59 (Win7x64).
Other, sillier alternatives are:
using
getImageData()/putImageData()
on the entire canvas; this is about 100× slower than other options.creating a custom image using a data url and using
drawImage()
to show it:var img = new Image; img.src = "data:image/png;base64," + myPNGEncoder(r,g,b,a); // Writing the PNGEncoder is left as an exercise for the reader
creating another img or canvas filled with all the pixels you want and use
drawImage()
to blit just the pixel you want across. This would probably be very fast, but has the limitation that you need to pre-calculate the pixels you need.
Note that my tests do not attempt to save and restore the canvas context fillStyle
; this would slow down the fillRect()
performance. Also note that I am not starting with a clean slate or testing the exact same set of pixels for each test.
One method that hasnt been mentioned is using getImageData and then putImageData.
This method is good for when you want to draw a lot in one go, fast.
http://next.plnkr.co/edit/mfNyalsAR2MWkccr
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
var id = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
var pixels = id.data;
var x = Math.floor(Math.random() * canvasWidth);
var y = Math.floor(Math.random() * canvasHeight);
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
var off = (y * id.width + x) * 4;
pixels[off] = r;
pixels[off + 1] = g;
pixels[off + 2] = b;
pixels[off + 3] = 255;
ctx.putImageData(id, 0, 0);
I hadn't considered fillRect()
, but the answers spurred me to benchmark it against putImage()
.
Putting 100,000 randomly coloured pixels in random locations, with Chrome 9.0.597.84 on an (old) MacBook Pro, takes less than 100ms with putImage()
, but nearly 900ms using fillRect()
. (Benchmark code at http://pastebin.com/4ijVKJcC).
If instead I choose a single colour outside of the loops and just plot that colour at random locations, putImage()
takes 59ms vs 102ms for fillRect()
.
It seems that the overhead of generating and parsing a CSS colour specification in rgb(...)
syntax is responsible for most of the difference.
Putting raw RGB values straight into an ImageData
block on the other hand requires no string handling or parsing.