136

Drawing a line on the HTML5 canvas is quite straightforward using the context.moveTo() and context.lineTo() functions.

I'm not quite sure if it's possible to draw a dot i.e. color a single pixel. The lineTo function wont draw a single pixel line (obviously).

Is there a method to do this?

Nakilon
  • 34,866
  • 14
  • 107
  • 142
infiniteloop
  • 2,112
  • 2
  • 18
  • 21

6 Answers6

186

For performance reasons, don't draw a circle if you can avoid it. Just draw a rectangle with a width and height of one:

ctx.fillRect(10,10,1,1); // fill in the pixel at (10,10)
Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
  • 1
    It actually seems to try to fill in a pixel at 10.5,10.5 I had to offset both x and y by .5 to get it to line up to some lines that were drawn. – RustyH Dec 08 '22 at 22:16
  • you can't get something to be smaller that it's smaller unit. in this case 1 pixel. So it's either 10 or 11. – Toto Briac Dec 09 '22 at 23:06
178

If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing.

var canvas = document.getElementById("myCanvas");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext("2d");
var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);

// That's how you define the value of a pixel
function drawPixel (x, y, r, g, b, a) {
    var index = (x + y * canvasWidth) * 4;
    
    canvasData.data[index + 0] = r;
    canvasData.data[index + 1] = g;
    canvasData.data[index + 2] = b;
    canvasData.data[index + 3] = a;
}

// That's how you update the canvas, so that your
// modification are taken in consideration
function updateCanvas() {
    ctx.putImageData(canvasData, 0, 0);
}

Then, you can use it in this way :

drawPixel(1, 1, 255, 0, 0, 255);
drawPixel(1, 2, 255, 0, 0, 255);
drawPixel(1, 3, 255, 0, 0, 255);
updateCanvas();

For more information, you can take a look at this Mozilla blog post : http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/

Carson
  • 6,105
  • 2
  • 37
  • 45
HoLyVieR
  • 10,985
  • 5
  • 42
  • 67
  • Why does this code not work if you put it in the section surrounded in tags. That is it works if I put it in the body but I like to have all my script code in the section of my HTML. – Doug Hauf Feb 20 '14 at 18:59
  • 11
    @DougHauf Make sure it's executing after the page load/domcontentready otherwise the canvas element won't be defined yet. – HoLyVieR Feb 20 '14 at 20:06
  • 4
    Conversely, if you are drawing a few pixels, this is slower. :( – Nitzan Wilnai Jul 23 '15 at 20:12
  • 1
    It's genius, well done! – Hendry Aug 04 '15 at 21:21
  • I think it would be more efficient and intuitive to pass the whole color as a single hex digit/color constant than three color values. – Dmytro Jul 13 '16 at 00:47
  • If you're in a tight loop, you can probably inline this code for a performance boost? I didn't test, but just a guess. – jocull Jul 21 '16 at 15:25
  • @Doug Hauf A naïve way to ensure the HTML is loaded before your JavaScript is run, is to simpy have the script-element below the body-element, or even below the html-element. That way you don't have to mess with .onload functions. – Kebman Apr 11 '17 at 16:56
  • This answer provides a demo comparing `getImageData` `putImageData` and `FillRect` https://stackoverflow.com/a/51231100/459102 – Aaron Hudon Sep 09 '19 at 17:39
49

It seems strange, but nonetheless HTML5 supports drawing lines, circles, rectangles and many other basic shapes, it does not have anything suitable for drawing the basic point. The only way to do so is to simulate a point with whatever you have.

So basically there are 3 possible solutions:

  • draw point as a line
  • draw point as a polygon
  • draw point as a circle

Each of them has their drawbacks.


Line

function point(x, y, canvas){
  canvas.beginPath();
  canvas.moveTo(x, y);
  canvas.lineTo(x+1, y+1);
  canvas.stroke();
}

Keep in mind that we are drawing to South-East direction, and if this is the edge, there can be a problem. But you can also draw in any other direction.


Rectangle

function point(x, y, canvas){
  canvas.strokeRect(x,y,1,1);
}

or in a faster way using fillRect because render engine will just fill one pixel.

function point(x, y, canvas){
  canvas.fillRect(x,y,1,1);
}

Circle

One of the problems with circles is that it is harder for an engine to render them

function point(x, y, canvas){
  canvas.beginPath();
  canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
  canvas.stroke();
}

the same idea as with rectangle you can achieve with fill.

function point(x, y, canvas){
  canvas.beginPath();
  canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
  canvas.fill();
}

Problems with all these solutions:

  • it is hard to keep track of all the points you are going to draw.
  • when you zoom in, it looks ugly

If you are wondering, what is the best way to draw a point, I would go with filled rectangle. You can see my jsperf here with comparison tests

Tim Nguyen
  • 1,163
  • 10
  • 21
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
  • 2
    Thanks for acknowledging that it is strange. I think this madness started with OpenGL and textures where the concept of pixel was thrown out the window, and nothing made sense anymore. Drawing 1x1 rectangles and having to specify width, height when wanting to fill a pixel is strange and unnatural. – Dmytro Jul 13 '16 at 00:43
  • it's not if you're used to OpenGL :) – Fattie Jan 25 '21 at 12:43
  • Nice answer. Canvas tips are hard to come by. – UpTheCreek Dec 10 '21 at 12:25
  • canvas.beginPath(); throws error. beginPath is a function of context: var context = canvas.getContext('2d'); – JRichardsz Jan 04 '22 at 15:54
7

The above claim that "If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing" seems to be quite wrong - at least with Chrome 31.0.1650.57 m or depending on your definition of "lot of pixel". I would have preferred to comment directly to the respective post - but unfortunately I don't have enough stackoverflow points yet:

I think that I am drawing "a lot of pixels" and therefore I first followed the respective advice for good measure I later changed my implementation to a simple ctx.fillRect(..) for each drawn point, see http://www.wothke.ch/webgl_orbittrap/Orbittrap.htm

Interestingly it turns out the silly ctx.fillRect() implementation in my example is actually at least twice as fast as the ImageData based double buffering approach.

At least for my scenario it seems that the built-in ctx.getImageData/ctx.putImageData is in fact unbelievably SLOW. (It would be interesting to know the percentage of pixels that need to be touched before an ImageData based approach might take the lead..)

Conclusion: If you need to optimize performance you have to profile YOUR code and act on YOUR findings..

wothke
  • 130
  • 1
  • 8
  • 3
    I'm curious to know in what situation was fillRect better and getImageData slow. If you take this JSPerf : http://jsperf.com/canvas-pixel-painting for example, getImageData/putImageData is better by a far margin. – HoLyVieR Aug 25 '14 at 04:59
  • 1
    Perhaps the pixels are drawn using getImageData/putImageData for each pixel? That might explain it. – Kwebble Jun 05 '15 at 13:15
6

In my Firefox this trick works:

function SetPixel(canvas, x, y)
{
  canvas.beginPath();
  canvas.moveTo(x, y);
  canvas.lineTo(x+0.4, y+0.4);
  canvas.stroke();
}

Small offset is not visible on screen, but forces rendering engine to actually draw a point.

milet
  • 81
  • 1
  • 3
4

This should do the job

//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");

//draw a dot
ctx.beginPath();
ctx.arc(20, 20, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
Omar Wagih
  • 8,504
  • 7
  • 59
  • 75