1

I am drawing onto an HTML5 canvas with stroke() and regardless of how or when I set globalAlpha, the stroke is being drawn with some measure of transparency. I'd like for the stroke to be completely opaque (globalAlpha=1). Is there somewhere else where the alpha is being set?

In this jsfiddle, I am drawing a grid of solid black lines onto a canvas. For me, the result shows dots at the intersections, confirming that the lines are partially transparent. Here's the gist of it:

context.globalAlpha=1;
context.strokeStyle="#000";
context.beginPath();
/* draw the grid */
context.stroke();
context.closePath;

The especially weird thing (to me) is that this problem was not occurring in my code before my last computer restart, so I'm guessing there was something hanging around in the cache that was keeping the alpha at my desired level.

I'm obviously missing something here... thanks for any help you can provide.

2 Answers 2

3

Real answer :

  • Each point in a canvas has its center in its (+0.5, +0.5) coordinate. So to avoid artifacts, start by translating the context by (0.5, 0.5) , then round the coordinates.

  • css scaling creates artifact, deal only with canvas width and height, unless you want to deal with hidpi devices with webGL, or render at a lower resolution with both webGL and context2D.

-> in your case, your setup code would be (with NO css width/height set ) :

( http://jsfiddle.net/gamealchemist/x9bTX/8/ )

// parameters
var canvasHorizontalRatio = 0.9;
var canvasHeight = 300;
var hCenterCanvas = true;
// setup
var canvasWidth = Math.floor(window.innerWidth * canvasHorizontalRatio);
var cnv = document.getElementById("myCanvas");
cnv.width = canvasWidth;
cnv.height = canvasHeight;
if (hCenterCanvas)
cnv.style['margin-left'] = Math.floor((window.innerWidth - canvasWidth) * 0.5) + 'px';
var ctx = cnv.getContext("2d");
ctx.translate(0.5, 0.5);
gridContext();
     

The rest of the code is the same as your original code, i just changed the size of you squares to get quite the same visual aspect.

ctx.beginPath();
for (var i=60; i<canvasHeight; i+=60) {
    ctx.moveTo(0,i);
    ctx.lineTo(canvasWidth,i);
}
for (i=60; i<canvasWidth; i+=60) {
    ctx.moveTo(i,0);
    ctx.lineTo(i,canvasHeight);
}
ctx.strokeStyle="#000";
ctx.stroke();
ctx.closePath();

With those changes we go from : enter image description here

to :

enter image description here

Edit : to ensure rounding, in fact i think most convenient is to inject the context and change moveTo, lineTo :

function gridContext() {
     var oldMoveTo = CanvasRenderingContext2D.prototype.moveTo;
     CanvasRenderingContext2D.prototype.moveTo = function (x,y) {
               x |= 0; y |= 0; 
               oldMoveTo.call(this, x, y); 
     }
     var oldLineTo = CanvasRenderingContext2D.prototype.lineTo;
     CanvasRenderingContext2D.prototype.lineTo = function (x,y) {
               x |= 0; y |= 0; 
               oldLineTo.call(this, x, y); 
     }
} 

Obviously, you must do this for all drawing functions you need.

4
  • Thank you. Using the translate() command is a lot more elegant. Is there a problem with using myCanvas.width = myCanvas.clientWidth and myCanvas.height = myCanvas.clientHeight, so the canvas size can be determined by the stylesheet? It appears to work for me, but I haven't tested it throughly.
    – TobyRush
    Commented May 12, 2014 at 16:26
  • You are welcome. Ho, yes in fact, if you assure css and canvas size to be the same in code, there should be no scaling, but most simple way to be sure is not to set css size altogether. I edited to have a cleaner code. Commented May 12, 2014 at 16:45
  • 1
    @GameAlchemist, Be sure you do the rounding that you mention in your answer. Your demo still anti-aliases if you replace the grid spacing (you use 60) with a non-integer (eg 300/18).
    – markE
    Commented May 12, 2014 at 17:29
  • @markE : Yes you are right, in case the grid size is changed to an non integer, it fails. I updated my post. Commented May 12, 2014 at 18:34
2

When drawing lines on a canvas, the line itself is exactly on the pixel grid. But because the line is one pixel wide, half of it appears in each of the pixels to either side of the grid, resulting in antialising and a line that is basically 50% transparent over two pixels.

Instead, offset your line by 0.5 pixels. This will cause it to appear exactly within the pixel.

Demo

3
  • A good start, but offsetting lines by 0.50 pixels might/might not solve the anti-aliasing effect. For example, if a vertical gridline is already aligned on a pixel boundary then adding 0.50 will actually cause antialiasing. You might expand your answer to include adjusting the gridlines into axis-aligned pixel boundaries.
    – markE
    Commented May 12, 2014 at 15:04
  • Thank you. Out of curiosity, any thoughts on why I wasn't seeing the antialiasing issue before I restarted? Is there some global browser property that might have been set inadvertently at some point?
    – TobyRush
    Commented May 12, 2014 at 15:14
  • Sorry, in my actual code I have myCanvas.width = myCanvas.clientWidth and myCanvas.height = myCanvas.clientHeight, which I left out of the jsfiddle. Is there something better that I should be doing to size my canvas appropriately?
    – TobyRush
    Commented May 12, 2014 at 15:44

Not the answer you're looking for? Browse other questions tagged or ask your own question.