5

I've been playing with canvas element and discovered that when I attempt to draw NxN uniform solid-colored cells next to each other, in some width/height configurations, there are blurry white-ish lines between them.

For instance, this canvas is supposed to look black but contains some sort of grid which I conjecture to be a result of faulty antialiasing in the browser.

The canvas demonstrating the issue.

Suffice to say, this bug appears only in some configurations but I would like to get rid of it for good. Is there any way to circumvent this? Have you ever had problems with antialiasing in canvas?

I have made this fiddle which demonstrates the issue and allows you to play with the dimensions of the canvas and number of cells. It also contains the code I use to draw the cells, so that you can inspect it and tell me if I'm doing anything wrong.

var ctx = canvas.getContext('2d');  
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
for (var i = 0; i < numberOfCells; ++i) {
    for (var j = 0; j < numberOfCells; ++j) {
    ctx.fillStyle = '#000';
    ctx.fillRect(j * cellWidth, i * cellHeight, cellWidth, cellHeight);
  }
}

Thanks in advance,

Petr.

Petr Mánek
  • 1,046
  • 1
  • 9
  • 24

3 Answers3

4

jsFiddle : https://jsfiddle.net/ngxjnywz/2/

snippet of javascript

var cellWidth = Math.ceil(canvasWidth / numberOfCells);
var cellHeight = Math.ceil(canvasHeight / numberOfCells);

Depending on the width, height and your numberOfCells you are sometimes getting a... lets say 4.2 which is 4, however this would be displayed wrong and will allow a 1 pixel blank line to appear. So all you need to do is use the Math.ceil function and this will cause your cellWidth and cellHeight to always be the higher number and you won't get blank lines anymore

Canvas
  • 5,779
  • 9
  • 55
  • 98
  • Unfortunately rounding only works if lines are parallel to the X or Y axis, nor does it take into account any transformation that may be applied. – Blindman67 Dec 17 '15 at 12:09
  • That is really nice and easy solution. It might get a bit messy since the cells will start overlapping and that might cause the order of drawing to suddenly become relevant, but since the same ceiled 'cellWidth' will be used to offset the drawing procedure, this will be prevented. – Petr Mánek Dec 17 '15 at 12:19
  • I have just tried "ctx.fillRect(j * cellWidth + 151, i * cellHeight + 77, cellWidth, cellHeight);" and it works without a problem however I can confirm that any "transform" on the grid will reproduce lines, however if you transform anything after the grid it is fine, Take a look at my link https://jsfiddle.net/ngxjnywz/4/ – Canvas Dec 17 '15 at 12:26
2

The best solution is to add a 0.5 pixel wide stroke around all the fills, using the same style as the fill and offsetting all drawing so that you render at the center of pixels rather than the top left.

If you add scaling or translation you will have to adjust the coordinates so that you still give the centers for your drawing coordinates.

In the end you can only reduce the artifacts but for many situations you will not be able to completely remove them.

This answer shows you how to remove the artifacts for an untransformed canvas. How to fill the gaps

Community
  • 1
  • 1
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • This is looking quite complex a procedure. In my particular setup, I would have to completely rethink the math of my grid drawing. – Petr Mánek Dec 17 '15 at 12:26
  • @PetrMánek True, but in other situations, this is indeed the simplest solution - just add .5 to all the coordinates. – Tomáš Zato Dec 17 '15 at 12:27
  • @PetrMánek. The half-pixel trick works well at the cost of some redesign. Another workaround is to create "building blocks" on a second canvas and [assemble your design from those building blocks](http://stackoverflow.com/questions/29355920/canvas-polygons-not-touching-properly) – markE Dec 17 '15 at 17:41
  • Tried offsetting the drawing by 0.5px but the issue didn't go away. Although lines and texts in my canvas look much clearer now, the thin grid lines separating individual squares are still visible in some cases. And when I add 0.5px-wide stroke, everything starts to look bulky and messy. – Petr Mánek Dec 18 '15 at 22:36
  • You can try various stroke widths < 0.5 pixels. The alternative is to render to a canvas 2 or 4 times the size and then draw that canvas onto the original canvas with `ctx.drawImage(largeCanvas, 0, 0, canvas.width, canvas.height)` and `ctx.imageSmoothingEnabled; = true;` that will reduce the artifacts further. Or use `ctx,getImageData()` and render the boxes your self, pixel by pixel. If its just boxes then there would not be that much involved. – Blindman67 Dec 18 '15 at 23:14
  • I don't like the idea of guessing the stroke width, especially since my canvas adapts its dimensions to those of the window. On the other hand, the idea with the secondary canvas might just work. See the full answer: http://stackoverflow.com/a/34365436/1115613 – Petr Mánek Dec 19 '15 at 00:18
  • I had a look at your fiddle and the fact is that you can not get a perfect result if `(canvas.width % numberOfCells) !== 0` it is impossible to do as one or more cell boundaries will lay on a pixel and a pixel can not have two colours . You need to have it so that each cell is precisely lined up with pixel boundaries. – Blindman67 Dec 19 '15 at 00:50
1

After reading through and trying several approaches, I've decided to come up with my own. I've created another (virtual) canvas which had integer dimensions corresponding to the number of cells in the grid.

After drawing all the cells in there, I call context.drawImage() on the main canvas and pass the virtual canvas as an argument along with offset and scale parameters to make it fit rest of my drawing. Assuming that the browser would scale the virtual canvas's image as a whole (and not as individual cells), I was hoping to get rid of the unwanted separator lines.

In spite of my efforts, the lines are still there. Any suggestions?

Here's the fiddle demonstrating my technique: https://jsfiddle.net/ngxjnywz/5/

Petr Mánek
  • 1,046
  • 1
  • 9
  • 24
  • I've discovered the culprit. Turns out this approach works as long as I don't shift the secondary canvas by 0.5 pixel (as suggested in the other answer). After removing `context.translate(0.5, 0.5)`, everything else fell into place. – Petr Mánek Dec 19 '15 at 00:16